Quickstart
Generate a Word document from a DocDealer template in a single API call, with curl, Python or TypeScript.
One request creates a document from a template, fills the form, generates every section, and
returns a .docx. Use it when you want a finished document and don't need to hold state in
between.
Before you start
Mint an API key at /admin/api-keys (see Authentication). The plaintext is
shown once. Give it documents:write and documents:generate. Also grant templates:read to look up the template and its form schema.
Generate in one call
POST /api/documents/generate is the whole flow. Pass a templateId and it creates the document
for you; pass a documentId instead to reuse one you already have.
curl -X POST https://thedocdealer.com/api/documents/generate \
-H "Authorization: Bearer $DOCDEALER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "2359ce30-66f2-4f02-be82-d2231a2a5ec3",
"title": "Poder — Ana Torres",
"formData": {
"intro._count": 1,
"intro.0.fecha": "2026-08-24",
"intro.0.responsable": "Equipo Acme"
},
"validateForm": true,
"strict": true
}'The response is { documentId, blob, filename, warnings }, where blob is the DOCX
base64-encoded. Ask for "returnHtml": true and you get html instead, with no blob.
Getting formData right
This is where integrations usually stumble. Keys are flat and dotted:
{
"contactos._count": 2,
"contactos.0.nombre": "Ana Torres",
"contactos.1.nombre": "Luis Gil"
}The pattern is objectKey.entryIndex.fieldKey, plus one objectKey._count per repeating object
saying how many entries it has. Read the keys from a template's formSchema:
GET /api/templates/{templateId} returns objects and fields, and each field's fieldKey
(falling back to its id) is what goes in the key. Object and field ids are not accepted as
aliases.
Values are typed by field:
| Field type | Value |
|---|---|
string | String |
number | Number (250000) |
date | ISO date "2026-03-13" |
selector | One of the field's options (an array when multiple) |
checkbox | true / false |
geoaddress | { "type": "geocode_result", "data": { … } } |
file_attachment | { "type": "attachment_analysis", "$ref": "<analysisId>" } from an analysis |
connector_tool | { "type": "connector_tool_result", "connectorId", "toolName", "params", "selected" } |
The contract is strict: an unrecognised key, or a value that does not fit its field type, fails
the whole request, and the error names the key and lists the valid ones — worth reading rather
than guessing. Writing an entry beyond an object's maximumCount is an error too.
Full example
Same flow in three languages — pick one.
import base64, json, os, urllib.request
API = "https://thedocdealer.com"
KEY = os.environ["DOCDEALER_API_KEY"]
def call(path, payload=None):
data = json.dumps(payload).encode() if payload is not None else None
req = urllib.request.Request(
f"{API}{path}",
data=data,
method="POST" if data else "GET",
headers={
"Authorization": f"Bearer {KEY}",
"Accept": "application/json",
**({"Content-Type": "application/json"} if data else {}),
},
)
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
# 1. Find a template.
template = call("/api/templates")["templates"][0]
# 2. Read its form schema to learn the field keys.
schema = call(f"/api/templates/{template['id']}")["formSchema"]
# 3. Generate. One call creates the document, fills it, and renders the DOCX.
result = call("/api/documents/generate", {
"templateId": template["id"],
"title": "Poder — Ana Torres",
"formData": {"intro._count": 1, "intro.0.fecha": "2026-08-24"},
"validateForm": True,
"strict": True,
})
with open(result["filename"], "wb") as f:
f.write(base64.b64decode(result["blob"]))
print("wrote", result["filename"], "document", result["documentId"])Flags worth knowing
validateForm— runs form validation alongside generation and returnswarnings.form. Off by default.strict— fail the whole request if any phase fails, instead of returning a partial document. Off by default, except for ephemeral runs.ephemeral— run entirely in memory with no document row. RequirestemplateSourcerather than a stored template.generateTitle— defaults to true only when you omittitle.
Next
Need attachments, OCR, or step-by-step control instead of one shot? See Documents. Want your users editing in your own app? See Embedding.