How to abstract a contract with bem Render
Jul 23, 2026

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.

Antonio Bustamante
Antonio Bustamante
Jul 23, 2026·2 min read·

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

bash
1source 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
1{
2 "type": "object",
3 "description": "Extract the amended terms from a credit-agreement amendment. Copy every figure verbatim exactly as printed; do not normalize or correct numbers.",
4 "properties": {
5 "amendmentTitle": { "type": "string" },
6 "effectiveDate": { "type": "string", "description": "Verbatim." },
7 "parentBorrower": { "type": "string" },
8 "administrativeAgent": { "type": "string" },
9 "additionalLender": { "type": "string" },
10 "newAggregateRevolvingCommitments": {
11 "type": "string",
12 "description": "New aggregate commitment amount, verbatim exactly as printed."
13 }
14 },
15 "required": ["amendmentTitle", "parentBorrower"]
16}

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:

bash
1CREDIT FACILITY SUMMARY
2Borrower: {{ borrower }} | Agent: {{ agent }}
3
4{{p facility_terms }}
5
6Items Flagged for Human Review
7{{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
1{
2 "borrower": "DIGITALOCEAN, LLC",
3 "agent": "Morgan Stanley Senior Funding, Inc.",
4 "facility_terms": [
5 { "table": {
6 "styleId": "TableGrid",
7 "rows": [
8 ["Term", "Amended value", "Source"],
9 ["New Aggregate Revolving Commitments", "$412,500,00.00 (as filed)", "Amendment No. 1"],
10 ["New Lender", "Goldman Sachs Bank USA ($40,000,000)", "Amendment No. 1"]
11 ]
12 }}
13 ],
14 "review_flags": [
15 { "list": { "kind": "bullet", "items": [
16 { "contents": [{ "paragraph": {
17 "text": "The aggregate commitment is captured verbatim as \"$412,500,00.00\", exactly as filed. It appears malformed. Confirm against the Commitment Schedule."
18 }}]}
19 ]}}
20 ]
21}

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
1body = {
2 "callReferenceID": "facility-summary-001",
3 "input": { "singleFile": { "inputType": "pdf", "inputContent": "<base64 of the amendment>" } }
4}
5# 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)

Antonio Bustamante

Written by

Antonio Bustamante

Jul 23, 2026

CTA accent 1CTA accent 2

Ready to see it in action?

Talk to our team to walk through how Bem can work inside your stack.

Talk to the team
How to abstract a contract with bem Render | bem