We are in official beta.

Cross-Format

Search/replace across formats, image replacement, and HTTP proxy

9 endpoints in this category. All require X-API-Key header.


Search & Replace Text

POST /api/SearchReplaceText 1 token

Find and replace text across document formats (DOCX, XLSX, TXT). Auto-detects format.

Parameters
NameTypeRequiredDescription
file string required Base64-encoded file
replacements array required Array of {find, replace} or object {find: replace}
format string optional docx, xlsx, txt (auto-detect if omitted)
Request Example
JSON
{"file": "<base64-file>", "replacements": [{"find": "old", "replace": "new"}]}
Response Example
JSON
{"file": "UEsDBBQAAAA...", "replacementCount": 3, "format": "docx"}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SearchReplaceText" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"file": "<base64-file>", "replacements": [{"find": "old", "replace": "new"}]}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""file"": ""<base64-file>"", ""replacements"": [{""find"": ""old"", ""replace"": ""new""}]}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SearchReplaceText", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SearchReplaceText"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"file": "<base64-file>", "replacements": [{"find": "old", "replace": "new"}]}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SearchReplaceText
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "file": "\u003Cbase64-file\u003E",
│      "replacements": [
│        {
│          "find": "old",
│          "replace": "new"
│        }
│      ]
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SearchReplaceText"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Replace Text with Image

POST /api/ReplaceTextWithImage 2 tokens

Replace a placeholder tag in a DOCX with an inline image.

Parameters
NameTypeRequiredDescription
file string required Base64-encoded DOCX
placeholder string required Text to find e.g. {logo}
image string required Base64-encoded image
width number optional Width in cm (default: 5)
height number optional Height in cm (default: 5)
Request Example
JSON
{"file": "<base64-docx>", "placeholder": "{logo}", "image": "<base64-image>", "width": 5, "height": 3}
Response Example
JSON
{"file": "UEsDBBQAAAA..."}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/ReplaceTextWithImage" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"file": "<base64-docx>", "placeholder": "{logo}", "image": "<base64-image>", "width": 5, "height": 3}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""file"": ""<base64-docx>"", ""placeholder"": ""{logo}"", ""image"": ""<base64-image>"", ""width"": 5, ""height"": 3}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/ReplaceTextWithImage", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/ReplaceTextWithImage"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"file": "<base64-docx>", "placeholder": "{logo}", "image": "<base64-image>", "width": 5, "height": 3}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/ReplaceTextWithImage
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "file": "\u003Cbase64-docx\u003E",
│      "placeholder": "{logo}",
│      "image": "\u003Cbase64-image\u003E",
│      "width": 5,
│      "height": 3
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/ReplaceTextWithImage"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

HTTP Request

POST /api/HttpRequest 1 token

Make an HTTP request to an external URL. Blocks private/internal IPs and never follows a redirect to one. Text responses come back in body; binary responses (PDF, images, archives) come back as contentBase64 with contentType and no body (#1485). Send a binary body with contentBase64 instead of body (#1487); the Content-Type header you set wins, otherwise application/octet-stream. Bodies are capped at 5 MB in both directions.

Parameters
NameTypeRequiredDescription
url string required Target URL
method string optional GET, POST, PUT, DELETE, PATCH (default: GET)
headers object optional Request headers
body string optional Request body as text (use contentBase64 for binary)
contentBase64 string optional Request body as base64 for binary payloads; decoded to exact bytes, 5 MB decoded cap, invalid base64 is a 400 and nothing is sent; null means absent (#3068)
timeout number optional Timeout in ms (max 30000) (default: 30000)
Request Example
JSON
{"url": "https://httpbin.org/get", "method": "GET"}
Response Example
JSON
{"statusCode": 200, "headers": {"content-type": "application/json"}, "body": "...", "contentType": "application/pdf (binary responses only)", "contentBase64": "... (binary responses only, body absent)"}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/HttpRequest" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://httpbin.org/get", "method": "GET"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""url"": ""https://httpbin.org/get"", ""method"": ""GET""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/HttpRequest", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/HttpRequest"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"url": "https://httpbin.org/get", "method": "GET"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/HttpRequest
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "url": "https://httpbin.org/get",
│      "method": "GET"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/HttpRequest"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

REST Request

POST /api/RestRequest 1 token

Call any REST API through a saved connection — the credential lives in the connection, never in the request. Source and destination: GET to read, POST/PUT/PATCH to write. Supports bearer, basic, API-key header, and OAuth2 client-credentials. Text responses (by media type, case-insensitive) come back in body with any echo of the connection's credential redacted; binary responses come back as contentBase64 with contentType and no body (#3060); a 204 has neither. A binary response whose bytes contain the connection's own credential is refused as a 502 CREDENTIAL_ECHO_REFUSED at 0 tokens, with statusCode and contentType but no bytes and no headers (#3068). An invalid contentBase64 on a write is a 400 at 0 tokens and nothing is sent; contentBase64: null means absent, the same as leaving it out (#3068). Bodies are capped at 5 MB in both directions.

PAGINATION (#1496) IS OPT-IN AND OFF BY DEFAULT — with no "paginate" block the behavior is exactly one request and one response, and no "pagination" field comes back. With it, the response instead carries items (or pages) plus pagination: { mode, pagesRequested, pagesFetched, itemCount, bytesFetched, elapsedMs, stopReason }. stopReason is always present and is one of exhausted, cap, bytes, time, loop, refused, httpError or transport — a truncated result that LOOKS complete is worse than an error, so branch on that field rather than on the count. N pages is still ONE token: the aggregate byte ceiling equals the single-response ceiling, so a paginated call cannot return more data than a single call already could, and pagination.pagesRequested reports exactly how many outbound calls your one billed call made. Every page URL is re-checked against the SSRF guard and pinned to the connection's origin, because a next-link is remote-controlled input — an API answering with a link to the instance metadata endpoint, or to another host, terminates the walk with stopReason "refused" and returns the pages already collected. CONDITIONAL UPDATE (#1596) IS OPT-IN — with a "conditional" block, a PATCH, PUT or DELETE first reads the record through the same connection, takes its version, and sends the write with that version in If-Match, so a change made after the read is never silently overwritten. The answer is the write's own, plus conditional: { action, readStatusCode, readUrl, requests }. action is "write"; "create" when the read answered 404 and ifMissing is "create" (the body went to createPath, and a PUT create also carries If-None-Match: *); or "none" when the read was refused, in which case its answer is the result and nothing was written. A 412 means the record changed after it was read: it comes back with ok false and is never retried. A record with no readable version is a 502 CONDITIONAL_ETAG_MISSING at no charge with nothing written, and an If-Match in headers next to the block is a 400 — the version is never guessed and never *. The read and the write are one call and one charge. THROTTLING (#1594): an answer of 429 or 503 is sent again, at most 3 attempts of each request, after the wait its Retry-After asks for (seconds or a date) or 1 then 2 seconds without one, never more than 10 seconds of waiting per call and never past your timeout. A read is always eligible, and so is a paginated POST search (cursorIn body); any other write (POST, PUT, PATCH, DELETE) only when it carries an Idempotency-Key header, so an unkeyed write is never sent twice. A conditional update's read is a read, and its write or create follows the write rule and is sent again with the same version; a 412 is never sent again. When that happened the response carries retries: { attempts, waitedMs, stopReason?, stopDetail?, retryAfterMs? } (pagination.retries on a walk, where attempts is the outbound-call count, re-sent pages included); stopReason is cap, wait, time or unkeyedWrite when a 429 or 503 is what came back. A Retry-After longer than the wait allowed is not shortened: the 429 comes back with retryAfterMs set, so you can wait and call again. A re-send costs nothing extra.
Parameters
NameTypeRequiredDescription
connectionId string required A registry connection of type "rest"
path string optional Path appended under the connection's baseUrl (or an absolute URL on the same origin)
method string optional GET, POST, PUT, PATCH, DELETE, HEAD (default: GET)
query object optional Query string values; an array value repeats the key
headers object optional Extra request headers. Authorization is owned by the connection and cannot be overridden
body string optional Request body — an object is sent as JSON. Provide this OR contentBase64
contentBase64 string optional Binary request body as base64 (max 5MB decoded). Provide this OR body; null means absent
timeout number optional Timeout in ms (min 5000, max 30000; values outside that range are clamped) (default: 30000)
paginate object optional Follow the API's own paging and return the whole walk in one call. GET only. { mode: "link" | "cursor" | "nextUrl" | "offset" | "page" | "queryTemplate", itemsPath?, maxPages? (default 10, max 50), maxBytes? (default and max 5MB aggregate), maxMs? (default 60000, max 120000) } plus per-mode keys: cursor takes cursorPath, cursorParam and optional hasMorePath; nextUrl takes nextUrlPath; offset takes limitParam, offsetParam, pageSize and optional startOffset; page takes pageParam and optional pageSizeParam with pageSize, startPage and totalPagesPath; queryTemplate takes queryParam, pageSize, itemsPath and optional startOffset, and the value of that query parameter carries {offset} and {limit} once each, which every page fills in (QuickBooks Online: query = SELECT * FROM Vendor STARTPOSITION {offset} MAXRESULTS {limit}); link needs none. Omit the block entirely for the single-request behavior
conditional object optional Guard a PATCH, PUT or DELETE against lost updates in one call: read the record first, take its version (ETag), and send the write only against that version. {} reads the same path, takes "@odata.etag" from the body and sends it as If-Match (Business Central, Dataverse, Microsoft Graph). Options: readPath?, etagFrom? ("body" | "header"), etagPath? (default "@odata.etag"), etagHeader? (default "ETag"), header? (default "If-Match"), ifMissing? ("fail" | "create"), and for "create" createPath plus createMethod? ("POST" | "PUT"). Omit the block for a single request
Request Example
JSON
{"connectionId": "conn_1a2b3c…", "path": "orders", "method": "GET", "query": {"limit": 10}}
Response Example
JSON
{"success": true, "statusCode": 200, "ok": true, "headers": {"content-type": "application/json"}, "contentType": "application/json", "body": "...", "url": "https://api.example.com/v1/orders", "timing": {"total": 512}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/RestRequest" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_1a2b3c…", "path": "orders", "method": "GET", "query": {"limit": 10}}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_1a2b3c…"", ""path"": ""orders"", ""method"": ""GET"", ""query"": {""limit"": 10}}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/RestRequest", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/RestRequest"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_1a2b3c…", "path": "orders", "method": "GET", "query": {"limit": 10}}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/RestRequest
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_1a2b3c\u2026",
│      "path": "orders",
│      "method": "GET",
│      "query": {
│        "limit": 10
│      }
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/RestRequest"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Compose Fill

POST /api/ComposeFill 1 token

Fill a docx/pdf/xlsx/pptx/html template in one call — inline (base64 + kind) or by saved templateId. Returns the filled document plus a dataHash for dedupe.

Saved templates built with the Template Generator are HTML and support repeating sections plus contract-declared formatting (currency/date/number). Add outputFormat:"pdf" to get a rendered PDF. No contract field is mandatory — a key left out of the payload renders blank and is reported in unresolvedFields. CODED VALUES: send the code your system stores ("needs_replacement", a Dataverse optionset integer) — never pre-format it. A saved template decodes it from the code/label list held in its contract. An inline template has no stored contract, so put the list in the payload: {"data": {"filter_condition": "needs_replacement", "__choices": {"filter_condition": {"needs_replacement": "Needs replacement (part on order)"}}}}. __choices is read at the top level only, is never rendered or reported as a field, and accepts [{value,label}], [{value,text}] or a compact {code: label} map. A code with no entry is de-slugged ("Needs Replacement") rather than printed raw, so adding an option upstream degrades readably instead of breaking a running pipeline. PDF OUTPUT LIMITS: with outputFormat "pdf" the filled HTML is printed to PDF, and a document that would print to more than 1,000 pages, take more than 60 seconds to load, lay out and print, or need more than 1 GB of memory answers 413 PDF_RENDER_TOO_LARGE at 0 tokens, with reason (pages, time or memory) and maxPages, maxSeconds and maxMemoryMB. It is never answered with filled HTML instead. For a Word (docx) template: A template that cannot be filled answers 422 at 0 tokens: DOCX_TEMPLATE_INVALID with reason markup (the Word markup is not well-formed) or tags (a tag or loop is unclosed, unopened or cannot expand), or DOCX_TEMPLATE_DATA_INVALID with reason data (a value holds characters a Word document cannot contain, or a raw tag's value is not text). The body lists up to 10 problems (problem, tag cut to 64 characters, closingTag, part, offset) plus problemCount. A file that is not a Word document answers 400 UNREADABLE_DOCUMENT with reason archive or package; this used to be 400 INVALID_INPUT. See Troubleshooting.
Parameters
NameTypeRequiredDescription
template string optional Base64 template (one-off). Provide this OR templateId
templateId string optional Saved template id — a Template Generator (HTML) template, or a PDF AcroForm template. Provide this OR template
kind string optional docx, pdf, xlsx, pptx, html (required for inline) (one of: docx, pdf, xlsx, pptx, html)
data object required Merge data: {key: value}. May carry a reserved "__choices" block mapping a field (or a repeating-section column) to its code/label list — see notes
outputFormat string optional native or pdf (pdf only for html kind, Linux) (default: native) (one of: native, pdf)
pageSize string optional Named paper size — see the paper-size table on the API reference overview. Case- and punctuation-insensitive; aliases such as "US Letter" are accepted. Via raw JSON a custom size object is also accepted: {"width": n, "height": n, "units": "pt|px|in|mm|cm"} (units default pt; 1-14400pt / 200in per side). An unknown size, unit, or orientation returns 400 — nothing silently falls back. Applies to PDF output (outputFormat "pdf" or an HTML saved template rendered to PDF); legacy key paperSize is still accepted (default: Letter) (one of: Letter, Legal, Tabloid, Ledger, Executive, Statement, Folio, A0, A1, A2, A3, A4, A5, A6, B4, B5)
orientation string optional Page orientation. "landscape" puts the long edge horizontal. Omit to keep the size's own orientation (portrait for every named size except Ledger) (one of: portrait, landscape)
flatten boolean optional Flatten PDF form fields (default: false)
filename string optional Output filename (no extension) (default: result)
Request Example
JSON
{"template": "<base64-docx>", "kind": "docx", "data": {"name": "Jamie"}}
Response Example
JSON
{"success": true, "kind": "docx", "file": "UEsDBBQAAAA...", "contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "ext": "docx", "filename": "result.docx", "dataHash": "<sha256>"}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/ComposeFill" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"template": "<base64-docx>", "kind": "docx", "data": {"name": "Jamie"}}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""template"": ""<base64-docx>"", ""kind"": ""docx"", ""data"": {""name"": ""Jamie""}}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/ComposeFill", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/ComposeFill"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"template": "<base64-docx>", "kind": "docx", "data": {"name": "Jamie"}}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/ComposeFill
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "template": "\u003Cbase64-docx\u003E",
│      "kind": "docx",
│      "data": {
│        "name": "Jamie"
│      }
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/ComposeFill"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Run Workflow

POST /api/RunWorkflow 1 token

Runs another of your own published workflows as a step: the payload you give it becomes that workflow's $input, it runs to completion, and what it produced comes back as finalResult. The workflow must be published and must be yours. Workflows can nest up to three deep, and a workflow can never run itself, directly or through another one.

Billed 1 token for this step plus whatever the workflow's own steps cost, all on your account, and totalTokens is that whole amount. Before the workflow starts, its cost is held against your balance, so a workflow you cannot afford is refused with 402 and none of it runs; if it stops part-way, the steps that finished are billed and the rest is returned. Called directly with your API key, X-Tokens-Used is the 1-token step and X-Workflow-Tokens-Used is what the workflow's steps cost; the body's billing field says the same. A slug that does not exist, is not published, or is not yours answers 404 WORKFLOW_NOT_FOUND, the same answer for all three. A workflow already running in the chain answers 422 WORKFLOW_CYCLE, and a fourth level of nesting answers 422 WORKFLOW_DEPTH. "wait": false answers 400 WAIT_NOT_SUPPORTED.
Parameters
NameTypeRequiredDescription
slug string required The published workflow to run. It must be one of yours
payload object optional Becomes the workflow's $input. Defaults to an empty object
wait boolean optional Must be true or left out: the workflow always finishes before this step answers (default: true)
Request Example
JSON
{"slug": "send-invoice", "payload": {"invoiceId": "INV-1001", "email": "ap@example.com"}}
Response Example
JSON
{"success": true, "slug": "send-invoice", "finalResult": {"invoiceId": "INV-1001", "sent": true}, "totalTokens": 4, "stepCount": 2, "timing": {"total": 812, "workflow": 790}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/RunWorkflow" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"slug": "send-invoice", "payload": {"invoiceId": "INV-1001", "email": "ap@example.com"}}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""slug"": ""send-invoice"", ""payload"": {""invoiceId"": ""INV-1001"", ""email"": ""ap@example.com""}}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/RunWorkflow", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/RunWorkflow"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"slug": "send-invoice", "payload": {"invoiceId": "INV-1001", "email": "ap@example.com"}}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/RunWorkflow
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "slug": "send-invoice",
│      "payload": {
│        "invoiceId": "INV-1001",
│        "email": "ap@example.com"
│      }
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/RunWorkflow"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Analyze Template Data

POST /api/AnalyzeTemplateData 1 token

Flatten a sample JSON or XML payload into a field contract: dot-paths, inferred datatypes, sample values, and arrays promoted to repeating tables. No AI, no document required.

Datatypes are inferred from the value plus the key name (a number under a key like total/amount/price becomes currency). XML lists are only detected as repeating tables when the sample contains two or more of the same element — include at least two rows. Inference never guesses that a value is a coded choice — "rooftop_unit" and a part number are indistinguishable from the value alone, and a wrong guess would rewrite real data. Supply the list via dataDefinition, choices, or a __choices block inside the sample itself, and those fields come back as dataType "choice" with an options table that the fill engine uses for every later ComposeFill.
Parameters
NameTypeRequiredDescription
data object optional Sample payload as JSON. Provide this OR dataXml
dataXml string optional Sample payload as XML. Provide this OR data
maxDepth number optional Maximum nesting depth to map (default: 6)
dataDefinition object optional The source system's form/optionset definition, pasted whole (SurveyJS or Dynamics Field Service shape). Any question with a choices list becomes a choice field so the document shows the label, not the code
choices object optional Explicit code/label lists: {fieldName: [{value, label}]}. Overrides dataDefinition and the payload's own __choices block
Request Example
JSON
{"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}]}}
Response Example
JSON
{"success": true, "source": "json", "fields": [{"name": "invoice.number", "label": "Number", "mode": "dynamic", "dataType": "string", "sample": "INV-1001"}, {"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", "dataType": "boolean"}, {"name": "customer.email", "dataType": "email"}], "tables": [{"name": "lines", "label": "Lines", "columns": [{"name": "description", "dataType": "string"}, {"name": "qty", "dataType": "number"}, {"name": "amount", "dataType": "currency"}], "sampleRowCount": 2}], "inputContract": {"required": [], "optional": ["invoice.number", "invoice.issuedOn", "invoice.total", "invoice.paid", "customer.name", "customer.email"], "tables": [{"name": "lines", "columns": ["description", "qty", "amount"]}]}, "warnings": []}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/AnalyzeTemplateData" \
  -H "X-API-Key: df_your_api_key_here" \
  -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}]}}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""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}]}}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/AnalyzeTemplateData", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/AnalyzeTemplateData"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"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}]}}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/AnalyzeTemplateData
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "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
│          }
│        ]
│      }
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/AnalyzeTemplateData"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Generate Template from Data

POST /api/GenerateTemplateFromData 5 tokens

Turn a sample JSON or XML payload into a ready-to-edit HTML document template with placeholders and repeating sections. AI drafts the layout; falls back to a standard layout if AI is unavailable.

Generated HTML is sanitized before it is returned or stored — scripts, event handlers, external stylesheets/images and unsafe URL schemes are removed. Repeating-section markers are emitted wrapped in HTML comments so they survive inside <table> elements. Save the result with POST /api/manage/templates/{clientId}/compose, then fill it repeatedly with ComposeFill using the returned templateId — you pay this 5-token authoring cost once, and 1 token per document thereafter. Code/label lists supplied here (dataDefinition, choices, or a __choices block in the sample) are stored on the saved template's contract, so every later fill decodes the same way no matter which system posts the data.
Parameters
NameTypeRequiredDescription
data object optional Sample payload as JSON. Provide this OR dataXml
dataXml string optional Sample payload as XML. Provide this OR data
documentType string optional Hint for the layout, e.g. invoice, purchase order, statement
title string optional Document title
pageSize string optional Named paper size for the template's @page rule — any name from the paper-size table (no custom dimensions here; an unrecognized name falls back to Letter) (default: Letter) (one of: Letter, Legal, Tabloid, Ledger, Executive, Statement, Folio, A0, A1, A2, A3, A4, A5, A6, B4, B5)
extraFields array optional Custom fields to include: [{name, dataType, mode, value}]
useAi boolean optional Set false to skip AI and use the standard layout (default: true)
dataDefinition object optional The source system's form/optionset definition, pasted whole (SurveyJS or Dynamics Field Service shape). Questions with a choices list become choice fields, and the drafter is shown the decoded label rather than the code
choices object optional Explicit code/label lists: {fieldName: [{value, label}]}. Overrides dataDefinition and the payload's own __choices block
background object optional Page background: {image (data: or https:), color, opacity 0-1, fit: cover|contain|tile|stretch, showThrough: true to keep content elements' own background colors from hiding it}. When the template html already carries a background, omitted keys keep the designed values — opacity does not reset to 1
Request Example
JSON
{"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", "title": "Invoice", "pageSize": "Letter", "extraFields": [{"name": "invoice.dueDate", "dataType": "date"}, {"name": "company.tagline", "mode": "static", "value": "Precision document automation"}]}
Response Example
JSON
{"success": true, "source": "xml", "engine": "ai", "html": "<!DOCTYPE html><html><head><style>@page{size:Letter;margin:18mm}...</style></head><body><h1>Invoice {{number}}</h1><table><tbody><!--{{#lines.line}}--><tr><td>{{description}}</td><td>{{qty}}</td><td>{{amount}}</td></tr><!--{{/lines.line}}--></tbody></table></body></html>", "fields": [], "tables": [], "inputContract": {"required": [], "optional": ["invoice.dueDate", "number", "issuedOn", "total"], "tables": [{"name": "lines.line", "columns": ["description", "qty", "amount"]}], "example": {}}, "placeholders": {"used": ["number", "total"], "unknown": [], "unused": []}, "warnings": []}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/GenerateTemplateFromData" \
  -H "X-API-Key: df_your_api_key_here" \
  -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", "title": "Invoice", "pageSize": "Letter", "extraFields": [{"name": "invoice.dueDate", "dataType": "date"}, {"name": "company.tagline", "mode": "static", "value": "Precision document automation"}]}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""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"", ""title"": ""Invoice"", ""pageSize"": ""Letter"", ""extraFields"": [{""name"": ""invoice.dueDate"", ""dataType"": ""date""}, {""name"": ""company.tagline"", ""mode"": ""static"", ""value"": ""Precision document automation""}]}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/GenerateTemplateFromData", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/GenerateTemplateFromData"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"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", "title": "Invoice", "pageSize": "Letter", "extraFields": [{"name": "invoice.dueDate", "dataType": "date"}, {"name": "company.tagline", "mode": "static", "value": "Precision document automation"}]}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/GenerateTemplateFromData
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "dataXml": "\u003Cinvoice\u003E\u003Cnumber\u003EINV-1001\u003C/number\u003E\u003CissuedOn\u003E2026-07-28\u003C/issuedOn\u003E\u003Ctotal\u003E1240.50\u003C/total\u003E\u003Clines\u003E\u003Cline\u003E\u003Cdescription\u003EConsulting\u003C/description\u003E\u003Cqty\u003E10\u003C/qty\u003E\u003Camount\u003E100.00\u003C/amount\u003E\u003C/line\u003E\u003Cline\u003E\u003Cdescription\u003ELicense\u003C/description\u003E\u003Cqty\u003E1\u003C/qty\u003E\u003Camount\u003E240.50\u003C/amount\u003E\u003C/line\u003E\u003C/lines\u003E\u003C/invoice\u003E",
│      "documentType": "invoice",
│      "title": "Invoice",
│      "pageSize": "Letter",
│      "extraFields": [
│        {
│          "name": "invoice.dueDate",
│          "dataType": "date"
│        },
│        {
│          "name": "company.tagline",
│          "mode": "static",
│          "value": "Precision document automation"
│        }
│      ]
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/GenerateTemplateFromData"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Import HTML Template

POST /api/ImportHtmlTemplate 1 token

Bring your own HTML. Send a document you already have plus the JSON payload it was produced from, and get it back as a fill template: {{placeholders}} where the values were, a repeating section around the repeated block, and a field contract. Fill it repeatedly with ComposeFill.

A PLACEHOLDER IS ONLY WRITTEN WHERE THE FILL REPRODUCES THE ORIGINAL TEXT EXACTLY. Candidate placements are generated by running the renderer's own formatting over the value, so a figure the document printed in a form the renderer cannot regenerate — a truncated $562 where the renderer rounds to $563 — is left as literal text and reported in unbound, rather than silently changing a number on your document. verification.identical says whether the round trip matched, text node for text node. READ THE REPORT, NOT JUST THE TEMPLATE. unbound names values that are nowhere in the document or cannot round-trip; ambiguous names values that appear only inside a longer sentence and are too short to place without guessing; residualLiterals names values that WERE bound somewhere and still sit as literal text somewhere else — those spots will not follow the next payload and are the ones to mark by hand. Each unbound and ambiguous entry also carries forms: the exact texts the fill can print for that value, each with the field type and format that prints it. An unbound number the document printed cut short or rounded the other way also carries lossy: printed (what the page says, $562), wouldPrint (what the fill would say, $563) and the format, so a review step can show exactly what binding it by hand would change. The authored document is untrusted and is sanitized the same way a generated template is: script, iframe/object/embed, form controls, every on* handler, external stylesheets and unsafe URL schemes are removed, and remote url() is scrubbed out of <style>. That matters because the filled HTML is a document you publish, and a published page runs whatever script it carries. An interactive page loses its behavior here — the layout and the data survive, the scripting does not. Nothing here fetches a URL. Paste or upload the markup.
Parameters
NameTypeRequiredDescription
html string optional The authored document markup. Provide this OR htmlBase64. This is markup, never a URL — nothing here fetches
htmlBase64 string optional The same document, base64-encoded, for callers that would rather not escape it into JSON
data object required The payload the document was produced from. It is what the import matches the document's text against, and it becomes the field contract
sanitize boolean optional Remove script, event handlers, embedded frames and unsafe URL schemes from the authored document (default: true)
stripExternalReferences boolean optional Also remove https: images and other external references, which sanitizing keeps by default (default: false)
annotateForDesigner boolean optional Stamp the data-tg-* designer attributes a generated template carries, so the import is manageable in the Template Generator (default: false)
returnFilled boolean optional Return the template filled with the payload you supplied, as proof the fill reproduces the original (default: true)
Request Example
JSON
{"html": "<h1>Cedar Ridge Manufacturing</h1><p>Columbus, OH</p><table><tbody><tr><td>Marcus Handley</td><td>$525.00</td></tr><tr><td>Alina Petrova</td><td>$525.00</td></tr></tbody></table>", "data": {"employer": "Cedar Ridge Manufacturing", "city_state": "Columbus, OH", "roster": [{"name": "Marcus Handley", "allowance": 525.0}, {"name": "Alina Petrova", "allowance": 525.0}]}}
Response Example
JSON
{"success": true, "template": "<h1>{{employer}}</h1><p>{{city_state}}</p><table><tbody><!--{{#roster}}--><tr><td>{{name}}</td><td>{{allowance}}</td></tr><!--{{/roster}}--></tbody></table>", "bindings": [{"path": "employer", "occurrences": 1, "renderedAs": "Cedar Ridge Manufacturing", "dataType": "string"}], "tables": [{"path": "roster", "marked": true, "rowsCollapsed": 2, "columns": [{"name": "name", "dataType": "string"}, {"name": "allowance", "dataType": "currency", "format": {"currency": "USD", "decimals": 2}}]}], "unbound": [], "ambiguous": [], "residualLiterals": [], "sanitized": {"removed": [], "externalReferences": []}, "verification": {"identical": true, "substitutions": 6, "missing": []}, "filled": "<h1>Cedar Ridge Manufacturing</h1>..."}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/ImportHtmlTemplate" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Cedar Ridge Manufacturing</h1><p>Columbus, OH</p><table><tbody><tr><td>Marcus Handley</td><td>$525.00</td></tr><tr><td>Alina Petrova</td><td>$525.00</td></tr></tbody></table>", "data": {"employer": "Cedar Ridge Manufacturing", "city_state": "Columbus, OH", "roster": [{"name": "Marcus Handley", "allowance": 525.0}, {"name": "Alina Petrova", "allowance": 525.0}]}}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""html"": ""<h1>Cedar Ridge Manufacturing</h1><p>Columbus, OH</p><table><tbody><tr><td>Marcus Handley</td><td>$525.00</td></tr><tr><td>Alina Petrova</td><td>$525.00</td></tr></tbody></table>"", ""data"": {""employer"": ""Cedar Ridge Manufacturing"", ""city_state"": ""Columbus, OH"", ""roster"": [{""name"": ""Marcus Handley"", ""allowance"": 525.0}, {""name"": ""Alina Petrova"", ""allowance"": 525.0}]}}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/ImportHtmlTemplate", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/ImportHtmlTemplate"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"html": "<h1>Cedar Ridge Manufacturing</h1><p>Columbus, OH</p><table><tbody><tr><td>Marcus Handley</td><td>$525.00</td></tr><tr><td>Alina Petrova</td><td>$525.00</td></tr></tbody></table>", "data": {"employer": "Cedar Ridge Manufacturing", "city_state": "Columbus, OH", "roster": [{"name": "Marcus Handley", "allowance": 525.0}, {"name": "Alina Petrova", "allowance": 525.0}]}}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/ImportHtmlTemplate
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "html": "\u003Ch1\u003ECedar Ridge Manufacturing\u003C/h1\u003E\u003Cp\u003EColumbus, OH\u003C/p\u003E\u003Ctable\u003E\u003Ctbody\u003E\u003Ctr\u003E\u003Ctd\u003EMarcus Handley\u003C/td\u003E\u003Ctd\u003E$525.00\u003C/td\u003E\u003C/tr\u003E\u003Ctr\u003E\u003Ctd\u003EAlina Petrova\u003C/td\u003E\u003Ctd\u003E$525.00\u003C/td\u003E\u003C/tr\u003E\u003C/tbody\u003E\u003C/table\u003E",
│      "data": {
│        "employer": "Cedar Ridge Manufacturing",
│        "city_state": "Columbus, OH",
│        "roster": [
│          {
│            "name": "Marcus Handley",
│            "allowance": 525.0
│          },
│          {
│            "name": "Alina Petrova",
│            "allowance": 525.0
│          }
│        ]
│      }
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/ImportHtmlTemplate"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed