Skip to content

How to abstract a contract with bem Render

Extract the terms from a dense agreement, render a clean source-attributed summary, and keep every value traceable. Grounded in DigitalOcean's real credit-agreement amendment.


Contract and facility abstraction is one of the most common document jobs in the enterprise and one of the least automated. A legal-ops analyst reads a 30-page agreement or amendment and produces a one-page summary of the terms that matter. A treasury team does the same for every credit-facility amendment. It is high-volume, high-stakes, and today it is mostly done by hand, because the input is a dense legal PDF, not a clean record.

This is the exact shape bem was built for: unstructured document in, verified structured document out. Here is how to build it with two functions, Extract and Render, with bem.

First page of DigitalOcean's real Amendment No. 1 to Credit Agreement, the public SEC exhibit used as the input

The real input: DigitalOcean's Amendment No. 1 to Credit Agreement, a public SEC exhibit. Dense, defined-term-heavy legalese. This is what an analyst is handed.

The pattern

Shell
source contract (PDF) -> Extract (structured terms + source pages) -> Render (.docx summary)

Extract pulls the terms out of the legalese. Render places them into a template you control. Because both run in one workflow, the summary is built from the source document in a single call, and every value can be tied back to the page it came from.

1. Define what a summary needs

Write an Extract schema for the terms your summary must carry. Keep the instruction explicit about copying figures verbatim. This matters more than it looks: legal documents contain malformed and unusual figures, and you want the model to report what is there, not a tidied-up guess.

JSON
{
"type": "object",
"description": "Extract the amended terms from a credit-agreement amendment. Copy every figure verbatim exactly as printed; do not normalize or correct numbers.",
"properties": {
"amendmentTitle": { "type": "string" },
"effectiveDate": { "type": "string", "description": "Verbatim." },
"parentBorrower": { "type": "string" },
"administrativeAgent": { "type": "string" },
"additionalLender": { "type": "string" },
"newAggregateRevolvingCommitments": {
"type": "string",
"description": "New aggregate commitment amount, verbatim exactly as printed."
}
},
"required": ["amendmentTitle", "parentBorrower"]
}

Create it as an Extract function with POST /v3/functions and "type": "extract".

2. Author the summary template

Make a normal .docx in Word or Google Docs. Mark inline values with {{ key }} and blocks with {{p key }}. A facility summary usually wants a title, a couple of inline fields, a terms table, and a review section:

Shell
CREDIT FACILITY SUMMARY
Borrower: {{ borrower }} | Agent: {{ agent }}
{{p facility_terms }}
Items Flagged for Human Review
{{p review_flags }}

Upload it when you create the Render function. bem reads the file and derives the contract, the placeholder set and the style catalog, so you never hand-maintain a schema in a proprietary syntax.

3. Compose the values

The table and the review list are block primitives. A table is rows of cells with a template style; a list is items you compose in code from the extracted fields:

JSON
{
"borrower": "DIGITALOCEAN, LLC",
"agent": "Morgan Stanley Senior Funding, Inc.",
"facility_terms": [
{ "table": {
"styleId": "TableGrid",
"rows": [
["Term", "Amended value", "Source"],
["New Aggregate Revolving Commitments", "$412,500,00.00 (as filed)", "Amendment No. 1"],
["New Lender", "Goldman Sachs Bank USA ($40,000,000)", "Amendment No. 1"]
]
}}
],
"review_flags": [
{ "list": { "kind": "bullet", "items": [
{ "contents": [{ "paragraph": {
"text": "The aggregate commitment is captured verbatim as \"$412,500,00.00\", exactly as filed. It appears malformed. Confirm against the Commitment Schedule."
}}]}
]}}
]
}

That flag is not a contrivance. When we ran this on DigitalOcean's real amendment, the filed document contained a malformed commitment figure. bem captured it exactly as written rather than silently normalizing it, and the summary surfaced it for a human to confirm. That is the behavior you want in front of a credit committee.

4. Run it

Send the source document through the workflow. Render returns a finished .docx:

Python
body = {
"callReferenceID": "facility-summary-001",
"input": { "singleFile": { "inputType": "pdf", "inputContent": "<base64 of the amendment>" } }
}
# POST /v3/workflows/<name>/call?wait=true

The response carries a short-lived download URL for the finished document.

What you get that a merge tool cannot give you

  • The summary is built from the source, not from a clean record you had to assemble first.
  • Every value is anchored to its source page, so the abstract is auditable.
  • Render refuses to guess. A missing or mismatched field is rejected before output, and ambiguous source values are flagged, not buried.

Contract abstraction stops being a person reading a PDF and retyping it, and becomes a workflow you call once per document.


The build above runs on the public bem API. [Start here.](https://app.bem.ai/auth/sign-up)