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 template | Template Generator template | |
|---|---|---|
| Direction | Document → data | Data → document |
| You start with | A PDF you upload | A JSON or XML sample |
| You define | Regions drawn on the page | A field contract |
| You get back | Extracted values | A rendered document |
| Used for | Reading invoices you receive | Producing invoices you send |
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": []
}
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}}.
{{#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"
}'
<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:
| Kind | Behavior | Who 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"
}
}
| Field | Accepted values |
|---|---|
image | A data: image URI or an https: URL. Anything else is rejected. |
color | Hex, rgb()/rgba(), hsl()/hsla(), or a CSS color name. |
opacity | 0–1, clamped. Applies to the background only. |
fit | cover (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
bodybackground, 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.
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:
| Datatype | You send | Document shows |
|---|---|---|
currency | 1240.5 | $1,240.50 |
date | "2026-07-28" | Jul 28, 2026 |
boolean | false | No |
number | 1500 | 1,500 |
"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
| Status | Code | Meaning |
|---|---|---|
| 400 | MISSING_REQUIRED_FIELDS | A required contract field was absent. The response lists them in missingFields. |
| 400 | UNSUPPORTED_SAVED_KIND | You passed kind as something other than html for a generated template. |
| 400 | NO_TEMPLATE_BODY | The template record exists but has no stored body — re-save it from the designer. |
| 404 | NOT_FOUND | No 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'])}.
{ "invoice.number": "INV-1002" } resolves exactly like the nested form. You don't have to reshape your payload.
What It Costs
| Call | Tokens | How often |
|---|---|---|
AnalyzeTemplateData | 1 | While designing. No AI call. |
GenerateTemplateFromData | 5 | Once per template you author. |
ComposeFill | 1 | Per 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.