Skip to content

Document API

The document API fills a template with data you send as JSON (from an ERP, a web form, a script) with no record behind it. You get a link to the finished document back, in the template’s output format or the one you ask for.

It works with every kind of template. The template’s own data source is simply not used: the JSON you send is the record.

Send an API key in the X-Webhook-API-Key header. Two kinds of key work:

  • The template’s own key: on the Template tab, under Document API (the same key as the HTTP trigger). It opens that one template.
  • A company API key: created by an admin in the Company Portal (in a Lark company, a Lark app admin), under Company → API keys. It opens every template of your company. It is shown once when it is created; store it like a password. Approval templates have no key of their own, so use a company key for them.

Revoking a company key, or resetting a template’s key, stops it working at once.

POST https://api.automadoc.cc/api/v1/templates/{templateId}/documents

The template id is in the endpoint shown on the template’s Document API card.

Field Required What it does
data yes The record: its keys are the placeholders. A list of objects makes one document per item; for a template that makes one document from many records, it makes one document from all of them (see Several documents at once).
format no "pdf", "png" (the first page as an image) or "native" (the template’s own file, e.g. .docx). Defaults to the template’s setting.
filename no A filename pattern for this request, with {Field} placeholders, in place of the template’s.
recordId no What {Record_ID} prints, for a single document. Defaults to the document’s id.
mode no "separate", "list", "continuous" or "labels": what the template makes, sent as a check. Left out, the template decides (see Several documents at once).

Add ?wait=20 (up to 25 seconds) to hold the response until the document is ready, so one request is enough. Without it, the response comes at once and you poll the status URL.

Workflow tools that follow the 202 + Location polling convention (Power Automate, Logic Apps and similar) can add ?async=true instead: while the document is being made, the 202 response carries a Location header to poll and a Retry-After header (5 seconds). Polling that location answers 202 until the document is completed (200) or failed (422, with "code": "DOCUMENT_FAILED" and the reason in error). A document that has already failed when the request is answered gets that 422 straight away. The status endpoint takes ?async=true too. async follows one document, so it cannot be used with a list in data for a template that makes a document per item (INVALID_ASYNC). The Power Automate connector uses it.

Response: 202 Accepted while the document is being made, 200 OK when wait saw it finish:

{
"generationId": "5d2f0c1e-6a1b-4c7e-9f0a-2b8c3d4e5f60",
"templateId": "9b1c…",
"status": "processing",
"statusUrl": "https://api.automadoc.cc/api/v1/documents/5d2f0c1e-6a1b-4c7e-9f0a-2b8c3d4e5f60",
"createdAt": 1791878400000
}
GET https://api.automadoc.cc/api/v1/documents/{generationId}

Use the same key as for the request. status is processing, completed or failed:

{
"generationId": "5d2f0c1e-6a1b-4c7e-9f0a-2b8c3d4e5f60",
"status": "completed",
"filename": "Invoice-INV-2026-001.pdf",
"downloadUrl": "https://api.automadoc.cc/api/generations/…/download?expires=…&signature=…",
"downloadUrlExpiresAt": 1792137600000
}

The downloadUrl needs no key. It stays valid as long as your company keeps generated documents (set by your plan), or 30 days if they are kept indefinitely, and stops working when the document is deleted. Each read of the status gives a fresh link. A failed document has an error with the reason. A completed document may have warnings: what was left out of it, such as a picture whose URL could not be fetched, named by its field and the URL’s host. Placeholders your data had no value for are listed together in one line (“3 fields had no value in the data: …”) after the other warnings.

This endpoint also reports documents started by the HTTP trigger: its response lists their generationIds.

A key only sees documents of the templates it opens. Documents generated by hand in the extension are not visible through the API.

GET https://api.automadoc.cc/api/v1/documents/{generationId}/file

The finished file itself, with the same key, for a tool that takes a file rather than a link. It answers 409 with DOCUMENT_NOT_READY while the document is being made, 422 with DOCUMENT_FAILED and the reason in error if it failed, and 404 with DOCUMENT_FILE_MISSING once your company’s retention has removed the file. Content-Type and Content-Disposition (the file name) describe it.

GET https://api.automadoc.cc/api/v1/templates
GET https://api.automadoc.cc/api/v1/templates/{templateId}

The first lists every template of your company: id, name, source (base or approval), templateFormat, mode (what a request makes, as in Several documents at once), outputFormat and ready (false while a template has no file, or has errors to fix). It needs a company API key; a template’s own key is refused with COMPANY_KEY_REQUIRED.

The second describes one template (either key works) and adds dataSchema, a JSON schema of the data it takes (one property per placeholder: text, a number or a picture URL, a yes/no for a condition, a list for a loop), sampleData, and documentsUrl. Workflow tools use it to show one input per placeholder.

The full description of these routes is an OpenAPI document at https://api.automadoc.cc/api/v1/openapi.json.

Names. Each key is turned into a placeholder name exactly the way a Base field’s name is: spaces, dots and other symbols become _, letters and digits of any language are kept, and case matters.

Key you send Placeholder it fills
"Customer" {Customer}
"Invoice No." {Invoice_No_}
"Unit price" {Unit_price}
"客户名称" {客户名称}

Two keys of the same object that become the same name, such as "Unit price" and "Unit-price", are refused with DATA_NAME_COLLISION rather than one silently winning.

Values.

  • Text and numbers print as sent. Numbers stay numbers, so filters such as number, currency and sum work on them.
  • true / false work with conditions: {#if Paid}…{/if}.
  • null and an empty list print nothing, and count as empty in {#if}.
  • An object fills dotted placeholders: {"Customer": {"Name": "Acme"}} fills {Customer.Name}.
  • A list of objects is a loop: {"Items": [{"Product": "Pen", "Qty": 2}]} fills {#Items}{Product} × {Qty}{/Items}.
  • A list of plain values prints joined with commas: ["red", "blue"] prints red, blue.
  • Pictures: where the template has a picture placeholder (a picture whose Alt Text is {Logo}, also inside a loop row), send an https:// URL, or a list of URLs for {Photos[1]}, {Photos[2]}. The URL must be reachable from the internet. Anywhere else a URL is just text.
  • Attachments: {ATTACH:Field} adds the files of a Base attachment field or an approval form only. The document API cannot send files for it: it adds nothing, and URLs sent for it print as text.
  • {QR:Field} and {BARCODE:Field} print a code of the text you send.

Record_ID, Template_Name and Print_Time are set by AutomaDoc; a key of that name is replaced (the response warns you).

For an approval template, the variables listed on its page are the names to send. Its default filename names the approval request, which JSON does not have, so documents made through the API are named {Template_Name}-{Record_ID} unless you send filename.

Send data as a list of up to 50 objects to make one document per item. The response lists them:

{ "documents": [ { "generationId": "…", "status": "processing", "statusUrl": "…" }, … ] }

If any item is refused, nothing is made. recordId cannot be used with a list.

If the template is set to make one document from many records (a list, a page per record, or labels), a list in data makes one document instead: each item is a record of its {#Records} loop, a page or a label, in the order sent. Up to 500 items; a single object makes a one-item document. The response is that one document, with recordCount:

{ "generationId": "…", "status": "processing", "statusUrl": "…", "recordCount": 120 }
  • {Record_ID} prints each item’s position, 1 to the number of items.
  • The filename pattern reads the first item, plus {RecordCount} and {Date}. Without one, the document is named {Template_Name}-{Date}.
  • "format": "png" makes a PDF: a picture would hold the first page only.
  • It counts as one document. Against the company’s 600 documents a minute it counts one per item for a page per record, and one per ten items for a list or labels.
  • It cannot be sent for signature (ESIGN_COMBINED).

The template’s Records per document setting decides. Send mode: "separate" for one document per item, or "list", "continuous" (a page per record) or "labels". A template set otherwise refuses the request with MODE_MISMATCH, rather than return a different number of files than your script expects.

Terminal window
curl -X POST 'https://api.automadoc.cc/api/v1/templates/TEMPLATE_ID/documents?wait=20' \
-H 'X-Webhook-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"data": {
"Invoice No.": "INV-2026-001",
"Customer": { "Name": "Acme Corp", "Address": "1 Main St" },
"Items": [
{ "Product": "Consulting", "Qty": 10, "Price": 120 },
{ "Product": "Travel", "Qty": 1, "Price": 340 }
],
"Logo": "https://example.com/logo.png"
},
"format": "pdf"
}'

The template’s Document API card copies a request like this one, built from the template’s own placeholders.

const API = 'https://api.automadoc.cc'
const headers = { 'X-Webhook-API-Key': process.env.AUTOMADOC_KEY, 'Content-Type': 'application/json' }
async function generate(templateId, data) {
const res = await fetch(`${API}/api/v1/templates/${templateId}/documents?wait=20`, {
method: 'POST',
headers,
body: JSON.stringify({ data, format: 'pdf' }),
})
let doc = await res.json()
if (!res.ok) throw new Error(`${doc.code}: ${doc.error}`)
// Still processing after the wait: poll the status URL.
while (doc.status === 'processing') {
await new Promise((r) => setTimeout(r, 2000))
doc = await (await fetch(doc.statusUrl, { headers })).json()
}
if (doc.status === 'failed') throw new Error(doc.error)
return doc.downloadUrl
}

Add a sign block to send the document out for signature as soon as it is generated. It costs nothing beyond the document.

sign works on a Lark Base template: a Word template with signature fields (see E-signature) that makes one document per record, with data sent as one object. Every other template refuses it with SIGN_BASE_TEMPLATES_ONLY: Excel, SharePoint list, Dataverse, Google Sheets and API-only templates, and Lark Approval templates, which are signed in the approval itself. Only a Lark account has Lark Base templates, so in a Microsoft, Google or email account no template takes sign.

{
"data": { "Customer": "Acme Corp" },
"sign": {
"signers": {
"Customer": { "name": "Budi Santoso", "contact": "budi@acme.example" },
"Supplier": { "larkUserId": "ou_1234…" }
},
"order": "sequential"
}
}
Field What it does
signers One signer per signature role of the template, by role name. Every role needs one: there is no record to read a signer from. Someone outside your company is { "name", "contact"? }: a name, and optionally a contact note kept with the request, such as an email address (nothing is sent to it). A colleague is { "larkUserId" }: their Lark open_id, for a person of your Lark company who uses AutomaDoc.
order "parallel" (everyone at once) or "sequential" (in the template’s order). Defaults to the template’s setting.
generatePasswords A generated password for each outside signer. Default true.
locale Language of the bot messages and the audit page, e.g. "en".

The request starts when the document is ready. Colleagues get a message from the AutomaDoc bot in Lark. Outside signers’ links, and their generated passwords, are returned once, by the first GET /api/v1/documents/{generationId} after the request has started:

{
"status": "completed",
"signing": {
"status": "started",
"requestId": "…",
"links": [{ "role": "Customer", "displayName": "Budi Santoso", "url": "https://…/sign/…", "password": "K7mQ…" }]
}
}

Send them on yourself, the password separately if you can. Later reads show the same entries with url and password set to null and "linksRedacted": true. If a link is lost, issue a new one from the Signatures page. signing.status is queued until the document is done, briefly starting while the request opens, then started or failed (with an error code).

sign is refused with a list in data (SIGN_SINGLE_DOCUMENT) and on a template that makes one document from many records (ESIGN_COMBINED).

If the template has a webhook callback, it is called for API documents too, with the download link. This needs Pro (or a trial) or a Business seat, as for every automated document.

Every refusal has a code and an English error:

Status code Why
400 INVALID_JSON, DATA_REQUIRED, DATA_NOT_OBJECT The body is not JSON, has no data, or data is not an object or a list of objects
400 DATA_NAME_COLLISION, DATA_INVALID_NAME Two keys fill the same placeholder; a key has no letter or digit
400 DATA_MIXED_ARRAY A list mixes objects and plain values
400 DATA_TOO_DEEP, DATA_ARRAY_TOO_LONG, DATA_TOO_MANY_KEYS, DATA_STRING_TOO_LONG Over a limit
400 IMAGE_URL_NOT_ALLOWED, TOO_MANY_IMAGES A picture URL is not a public http(s) address; too many pictures
400 BATCH_EMPTY, BATCH_TOO_LARGE An empty list; more than 50 items (500 for a template that makes one document from many records)
400 INVALID_FORMAT, INVALID_FILENAME, INVALID_RECORD_ID, INVALID_WAIT, INVALID_MODE, INVALID_ASYNC An option is not valid
400 TEMPLATE_VALIDATION_ERRORS The template file has errors to fix first (listed in validationErrors)
400 MODE_MISMATCH mode is not what the template makes
401 API_KEY_MISSING No X-Webhook-API-Key header
403 API_KEY_INVALID The key is wrong, revoked, or does not open this template. It is also the answer for a template id that does not exist
403 QUOTA_EXCEEDED No documents left this month; buy a pack or a seat
403 TEMPLATE_DISABLED The template is above your plan’s template limit
403 COMPANY_KEY_REQUIRED Listing templates needs a company API key, not a template’s own key
404 TEMPLATE_NOT_FOUND, DOCUMENT_NOT_FOUND No such template or document for this key
404 DOCUMENT_FILE_MISSING The document’s file has been removed by your company’s retention
409 TEMPLATE_FILE_MISSING, TEMPLATE_NO_OWNER The template has no file yet; it has no owner (open and save it once, or use a company key)
409 DOCUMENT_NOT_READY Downloading the file of a document still being made
413 BODY_TOO_LARGE The body is over 1 MB
422 DOCUMENT_FAILED Downloading the file of a document that failed, or a request or status read with async=true for one; error says why
429 RATE_LIMITED More than 60 requests a minute for one template, 600 documents a minute for one company, 60 status reads or 60 downloads a minute for one document, or 60 template lists a minute for one company. Documents from the HTTP trigger count toward the company’s 600; its requests have their own 300 a minute per template
400 SIGN_INVALID, SIGN_SINGLE_DOCUMENT, SIGN_BASE_TEMPLATES_ONLY The sign block is not valid, data is a list, or the template is not a Lark Base template, so its documents cannot be sent for signature
400, 409 ESIGN_… Refused by e-signature: ESIGN_ROLES_MISMATCH (a role without a signer, or an unknown one), ESIGN_SIGNER_FIELD (a signer read from a field), ESIGN_INVALID_SIGNER (a larkUserId that is not a user of your company), ESIGN_NO_MASTER (the template has no signature fields), ESIGN_COMBINED (the template makes one document from many records), ESIGN_RECORD_UNREADABLE (the template has no owner to send the request as)
502 WORKER_UNAVAILABLE The document service could not be reached; try again shortly
503 TEMPLATE_FORMAT_UNAVAILABLE The template is an Excel or PowerPoint file the document service cannot open yet; try again later
503 COMBINED_UNAVAILABLE One document from many records cannot be made right now; try again later
503 ATTACHMENTS_UNAVAILABLE The template makes one document from many records and adds attachments to the PDF, which cannot be done right now; try again later
Request body 1 MB
Documents per request 50, or 500 items in one document from many records
Nesting of objects and lists 8 levels
Items in a list 1,000
Keys in an object 500
Characters in a value 100,000
Pictures per document 50; 500 across all the items of one document from many records
wait 25 seconds
Requests per template 60 a minute; the HTTP trigger has its own 300 a minute
Status reads per document 60 a minute; file downloads have their own 60
Template list 60 a minute per company; one template’s description 60 a minute
Documents per company 600 a minute, the document API and the HTTP trigger together