Template Generator from JSON or XML

Paste a sample of the data your workflow already produces. Get back a document template that renders it — designed by AI, refined by you, then filled on every run with a single API call.

Two Directions, Two Kinds of Template

DocButterfly has templates that run in opposite directions. It's worth being clear which one you want:

Document AI templateTemplate Generator template
DirectionDocument → dataData → document
You start withA PDF you uploadA JSON or XML sample
You defineRegions drawn on the pageA field contract
You get backExtracted valuesA rendered document
Used forReading invoices you receiveProducing invoices you send
No source document is needed here. If you already have a payload — from Dataverse, SQL, an ERP export, a Power Automate flow — that is all it takes to produce a template.

Step 1 — Analyze Your Sample Data

AnalyzeTemplateData flattens your payload into a field contract: every value gets a dot-path, an inferred datatype, and a sample. Arrays become repeating tables. This step makes no AI call and costs 1 token.

curl -X POST https://your-app.azurewebsites.net/api/AnalyzeTemplateData \
  -H "X-API-Key: df_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "invoice":  { "number": "INV-1001", "issuedOn": "2026-07-28", "total": 1240.50, "paid": false },
      "customer": { "name": "Acme Ltd", "email": "ap@acme.com" },
      "lines": [
        { "description": "Consulting", "qty": 10, "amount": 100.00 },
        { "description": "License",    "qty": 1,  "amount": 240.50 }
      ]
    }
  }'

The response tells you what was understood:

{
  "success": true,
  "source": "json",
  "fields": [
    { "name": "invoice.number",   "label": "Number",    "dataType": "string" },
    { "name": "invoice.issuedOn", "label": "Issued On", "dataType": "date",     "format": { "dateStyle": "medium" } },
    { "name": "invoice.total",    "label": "Total",     "dataType": "currency", "format": { "currency": "USD", "decimals": 2 } },
    { "name": "invoice.paid",     "label": "Paid",      "dataType": "boolean" },
    { "name": "customer.email",   "label": "Email",     "dataType": "email" }
  ],
  "tables": [
    { "name": "lines", "label": "Lines", "sampleRowCount": 2,
      "columns": [
        { "name": "description", "dataType": "string" },
        { "name": "qty",         "dataType": "number" },
        { "name": "amount",      "dataType": "currency" }
      ] }
  ],
  "warnings": []
}
Why total became currency and qty did not. Datatypes come from the value and the key name. A number under a key like total, amount, price, or balance is treated as money; anything else stays a plain number. You can override any of it in the designer.

Step 2 — Generate the Template

GenerateTemplateFromData takes the same payload and returns a complete HTML template. Costs 5 tokens, once, per template you author.

curl -X POST https://your-app.azurewebsites.net/api/GenerateTemplateFromData \
  -H "X-API-Key: df_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "data": { "...same payload as above..." },
    "documentType": "invoice",
    "title": "Invoice",
    "pageSize": "Letter"
  }'

The generated template uses two placeholder forms:

  • {{invoice.number}} — a single value, HTML-escaped and formatted per its datatype.
  • <!--{{#lines}}--> … <!--{{/lines}}--> — a repeating section. Inside it, columns are referenced by bare name: {{description}}.
Repeating markers are wrapped in HTML comments on purpose. A bare {{#lines}} placed directly inside a <tbody> is text in a table context, and every HTML parser relocates it outside the table — which silently drops every line item. Comments are preserved in place. Keep the comment wrappers if you hand-edit the HTML.

Generated markup is sanitized before it is returned or stored: scripts, event handlers, external stylesheets, remote images, and unsafe URL schemes are stripped. Images may only be data: URIs or https: URLs — headless rendering has no network access, so a remote reference would render as a broken image in your production PDF.

The Same Thing in XML

Send dataXml instead of data. Everything downstream is identical.

curl -X POST https://your-app.azurewebsites.net/api/GenerateTemplateFromData \
  -H "X-API-Key: df_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "dataXml": "<invoice><number>INV-1001</number><issuedOn>2026-07-28</issuedOn><total>1240.50</total><lines><line><description>Consulting</description><qty>10</qty><amount>100.00</amount></line><line><description>License</description><qty>1</qty><amount>240.50</amount></line></lines></invoice>",
    "documentType": "invoice"
  }'
Include at least two rows of any repeating element. XML gives no schema, so <lines><line/></lines> with a single <line> is indistinguishable from a plain object. With one row it becomes a group of fields; with two or more it is correctly detected as a repeating table. If your sample only has one, add a second — or mark it as a table in the designer.

Adding Fields That Aren't in Your Sample

You are not limited to what the sample contained. Two kinds of field can be added:

KindBehaviorWho supplies the value
Static Baked into the template. Renders identically on every document. The template. Caller data of the same name is ignored.
Dynamic A name you choose plus a datatype. Becomes part of the template's declared input contract. You, in your JSON or XML payload.

Supported datatypes: string, multiline, number, currency, date, datetime, boolean, email, phone, url, image, richtext, table.

{
  "data": { "...your sample..." },
  "extraFields": [
    { "name": "invoice.dueDate",   "dataType": "date", "required": true },
    { "name": "company.tagline",   "mode": "static", "value": "Precision document automation" },
    { "name": "invoice.poNumber",  "dataType": "string" }
  ]
}

This is the round trip that matters: invoice.dueDate did not exist in your sample data. You added it in the designer, so it now appears in the template's input contract — and from the next fill onward you include it in your payload:

{
  "templateId": "8f3c...",
  "data": {
    "invoice": { "number": "INV-1002", "issuedOn": "2026-08-01", "dueDate": "2026-08-31", "total": 500.00 }
  }
}

The template drives the data shape, not only the other way round. Ask any template what it expects at any time:

GET /api/manage/templates/{clientId}/{templateId}/contract

Backgrounds, Branding and Opacity

A template can carry a page background — an image, a color, or both — with adjustable opacity. Typical uses are a letterhead, a watermark, or a faint brand pattern behind the content.

{
  "data": { "...your sample..." },
  "background": {
    "image":   "data:image/png;base64,iVBORw0KGgo...",
    "color":   "#f7f9fc",
    "opacity": 0.15,
    "fit":     "cover"
  }
}
FieldAccepted values
imageA data: image URI or an https: URL. Anything else is rejected.
colorHex, rgb()/rgba(), hsl()/hsla(), or a CSS color name.
opacity01, clamped. Applies to the background only.
fitcover (default), contain, tile, stretch.

Three details worth knowing, because each one is a silent failure if you build this yourself:

  • Opacity fades the background, not your text. The background is a separate layer behind the content rather than a body background, so a 15% watermark leaves the text fully opaque and readable.
  • It repeats on every page. The layer is fixed-position, which Chromium re-paints on each printed page — a five-page statement keeps its letterhead throughout.
  • Backgrounds print. The layer sets print-color-adjust: exact; without it Chromium discards background colors and images when producing a PDF, and you get a blank white page with no error.
Background images are embedded in the template, so rendering needs no outbound network call. That is what makes them work in headless rendering, where an external image URL would silently come out blank.

Use the Template

Save the designed template, then fill it with ComposeFill by templateId — 1 token per document, no AI cost. This is the call your workflow makes on every run.

curl -X POST https://your-app.azurewebsites.net/api/ComposeFill \
  -H "X-API-Key: df_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "8f3c1e42-...",
    "outputFormat": "pdf",
    "filename": "invoice-1002",
    "data": {
      "invoice":  { "number": "INV-1002", "issuedOn": "2026-08-01", "dueDate": "2026-08-31", "total": 500.00, "paid": true },
      "customer": { "name": "Beta Co", "email": "ap@beta.co" },
      "lines": [ { "description": "Support", "qty": 2, "amount": 250.00 } ]
    }
  }'
{
  "success": true,
  "kind": "pdf",
  "file": "JVBERi0xLjcKJ...",
  "contentType": "application/pdf",
  "filename": "invoice-1002.pdf",
  "tokensReplaced": 14,
  "dataHash": "<sha256>"
}

Omit outputFormat to get filled HTML back instead of a PDF. Identical repeat calls are served from the fill cache.

Formatting is applied for you

Values are rendered according to the datatype declared in the contract, so you send raw data and the document shows presentation:

DatatypeYou sendDocument shows
currency1240.5$1,240.50
date"2026-07-28"Jul 28, 2026
booleanfalseNo
number15001,500
A value that isn't genuinely numeric — "n/a", "TBD" — is passed through unchanged rather than being coerced to $0.00. A wrong number on an invoice reads as authoritative, so we never invent one.
Errors you should expect
StatusCodeMeaning
400MISSING_REQUIRED_FIELDSA required contract field was absent. The response lists them in missingFields.
400UNSUPPORTED_SAVED_KINDYou passed kind as something other than html for a generated template.
400NO_TEMPLATE_BODYThe template record exists but has no stored body — re-save it from the designer.
404NOT_FOUNDNo template with that id for this client.

Power Automate / Logic Apps

Authoring happens once in the portal. Your flow only ever makes the fill call, so there is a single HTTP action to configure:

{
  "method": "POST",
  "uri": "https://your-app.azurewebsites.net/api/ComposeFill",
  "headers": {
    "X-API-Key": "df_your_key",
    "Content-Type": "application/json"
  },
  "body": {
    "templateId": "8f3c1e42-...",
    "outputFormat": "pdf",
    "filename": "invoice-@{triggerBody()?['invoiceNumber']}",
    "data": {
      "invoice": {
        "number":   "@{triggerBody()?['invoiceNumber']}",
        "issuedOn": "@{triggerBody()?['issuedOn']}",
        "total":     @{triggerBody()?['total']}
      },
      "lines": "@{triggerBody()?['lineItems']}"
    }
  }
}

Then add Create file (SharePoint or OneDrive) with content @{base64ToBinary(body('HTTP')?['file'])}.

Flat dotted keys work too. Power Automate often produces flattened objects, so { "invoice.number": "INV-1002" } resolves exactly like the nested form. You don't have to reshape your payload.

What It Costs

CallTokensHow often
AnalyzeTemplateData1While designing. No AI call.
GenerateTemplateFromData5Once per template you author.
ComposeFill1Per document, forever after.

The AI cost is a one-time authoring cost, not a per-document cost. A template generated once for 5 tokens then produces documents at 1 token each.