# Introduction (/docs)
> DocDealer turns typed templates into Word and PDF documents, over a REST API, an embedded editor, or MCP.
DocDealer turns **typed templates** into Word and PDF documents. A template is a schema: every field
has a type and validation rules, and that one schema drives the UI form, the AI generation and the
API. Anything the app can do, an HTTP call can do.
It is deliberately not a general-purpose agent. It makes one scoped AI call per document section,
with the prompt assembled by the server — roughly 50× cheaper and 20× faster than an exploration
loop over the same task. [How generation works](/docs/concepts/generation) explains why.
## Three ways in [#three-ways-in]
You own the UI entirely. Create documents, push form values, generate, export.
You own the surrounding app; DocDealer renders the collaborative editor in an iframe.
An AI agent drives DocDealer directly as a set of typed tools.
## Authentication in one line [#authentication-in-one-line]
Every call carries an organization-scoped API key as a bearer token. The key already identifies the
organization, so there is no tenant header to set.
```bash title="curl"
curl https://thedocdealer.com/api/templates \
-H "Authorization: Bearer dd_live_xxxxxxxxxxxx"
```
Keys are minted at `/admin/api-keys` and shown once. What a key may do is set by its
[scopes and template allowlist](/docs/get-started/authentication).
## Where to go next [#where-to-go-next]
From an API key to a generated `.docx` in a single request.
Templates, rules, generation and versioning — the model behind the API.
Task-shaped walkthroughs: lifecycle, attachments, embedding, agents.
Every public endpoint, generated from the OpenAPI spec.
## Machine-readable [#machine-readable]
The OpenAPI 3.1 document is public and needs no credentials, so you can point a generator at it:
```bash title="curl"
curl https://thedocdealer.com/api/openapi.public.json
```
It is a curated view — admin-only and internal endpoints are left out. The MCP equivalent is at
`/api/mcp/openapi.public.json`. Every docs page is also available as raw markdown by appending `.md`
to its URL, and the whole corpus is at [/llms-full.txt](/llms-full.txt).
# How generation works (/docs/concepts/generation)
> One scoped call per section, not an agent loop. This is why it is fast and cheap.
DocDealer is not a general-purpose agent pointed at a document. It makes **one scoped AI call per
section**, with the prompt assembled by the server before the model is ever invoked.
That single design choice is where the cost and latency difference comes from.
## Why this is cheaper than an agent [#why-this-is-cheaper-than-an-agent]
| | DocDealer | A general-purpose agent |
| ------------------ | ----------------------------------- | ------------------------------ |
| Calls per document | One per section | Typically 30–100 tool calls |
| Model | `google/gemini-2.5-flash` | Usually a frontier-class model |
| Context | Preassembled, scoped to the section | Discovered by exploration |
| Wall clock | 3–15 s, streamed | 1–5 minutes |
The agent burns tokens on scratch reasoning, JSON repair and tool discovery — work that never
reaches the document. DocDealer does that assembly deterministically in server code, so every token
spent is a token of prose.
Roughly **50× cheaper and 20× faster** for the same task. Not because the model is better, but
because the loop is absent.
## What keeps it cheap [#what-keeps-it-cheap]
The universal drafting instructions are a byte-identical constant sent as the `system` message; the
volatile part — section context, form data, current date — goes in the `user` message. Gemini 2.5
implicitly caches the stable prefix, so every section after the first in a run reuses it at a lower
rate.
This is why the system prompt contains no interpolation: a single templated value would break the
cache for the whole run.
For templates that declare relations, a section's prompt includes only the fields belonging to that
section's related objects — not the whole form. Attachment analyses are stripped of validator
metadata before being included.
Facts derived once during a run — a computed total, a resolved address — are written to the
document's memory and reused by later sections instead of being recomputed. That is what the
`update_memory` tool is for.
Form and document validation cache their result against a hash of the input and dedupe in-flight
calls. Re-validating unchanged data does not reach the model at all.
Reasoning is left on for drafting and validation, where it measurably improves output. It is
disabled for pure extraction (attachment analysis) and for single trivial decisions, where it only
consumes the output budget.
## Tools available during a section [#tools-available-during-a-section]
Generation is not pure text completion — a section can call:
## The call types [#the-call-types]
Every AI call is typed, logged with its cost, and billed. The types you can trigger over the API:
| Type | What it does | Relative cost |
| ------------------ | ---------------------------------------------------------------- | ------------------- |
| `generate_section` | Drafts one section; can call tools. | High |
| `analyze_file` | OCR, transcription and structured extraction from an attachment. | High |
| `analyze_form` | Validates form entries against the template's validation rules. | Medium, hash-cached |
| `analyze_document` | Validates the drafted document. | Medium, hash-cached |
| `generate_title` | A short document title from form data. | Low |
The two that spend meaningfully are `generate_section` and `analyze_file`. Their API scopes
(`documents:generate`, `attachments:analyze`) exist precisely so you can mint a key that cannot
spend — see [Authentication](/docs/get-started/authentication).
## Determinism [#determinism]
Generation is not deterministic, and the docs will not pretend otherwise. What *is* deterministic is
everything around it: which sections apply, which fields are required, what the prompt contains, and
what counts as valid. That is the point of putting the schema and the rules in the template rather
than in the prompt.
For text that must be word-for-word identical every time, use a
[static rule](/docs/concepts/rules) — the example content is emitted verbatim with no model call.
# Rules (/docs/concepts/rules)
> Four rule kinds turn one template into every valid variant of a document.
A template without rules produces one document shape. Rules are what let a single template cover
every legitimate variant — one signatory or four, a company instead of a person, a mortgage clause
that only applies when there is a mortgage.
There are four kinds, and they act at different levels.
Target a field or an object, and hide or disable it when conditions match.
Two effects: `hide` removes it, `disable` shows it greyed out. Prefer `disable` when the reader
benefits from knowing the field exists but does not apply.
Modes are `always`, `show_if` and `hide_if` — with `always`, the rule needs no conditions.
Target a field, an object entry, or the whole form, and carry a **rule written in prose**:
"the tax ID must match the signatory's country", "the completion date cannot precede the deed date".
These are evaluated by AI, not by a regex, which is what lets them express relationships between
values rather than shapes of single values. That evaluation is the `analyze_form` call, and it is
hash-cached — re-validating unchanged form data costs nothing.
Target a section and produce one of four results when conditions match:
| Result | Meaning |
| ----------- | -------------------------------------------- |
| `mandatory` | Must be generated. |
| `optional` | May be generated; skipped without complaint. |
| `disable` | Present but not generated. |
| `hide` | Removed from the document entirely. |
This is how one template covers documents of genuinely different length.
A `` inside a ``. When its conditions match, that case is emitted
as-is with no AI call at all.
Use it for boilerplate that must be word-for-word — statutory recitals, fixed clauses. It is both
cheaper and safer than asking a model to reproduce fixed text.
Write a `` on the case as well. Staticness is decided per *document*: if no static
rule in the section matches, the whole section falls through to AI generation and every case is
offered to the model by its description.
## Conditions [#conditions]
Every rule kind shares the same condition vocabulary.
Operators:
| Group | Operators |
| ---------- | --------------------------------------------------------- |
| Equality | `equals`, `not_equals` |
| Substring | `contains`, `not_contains` |
| Ordering | `greater_than`, `less_than` |
| Presence | `is_empty`, `is_not_empty` |
| Group size | `object_count_gte`, `object_count_lte`, `object_count_eq` |
Conditions in a rule combine with a single `logicOperator` — `and` (all must match) or `or` (any).
There is no nesting; if you need it, that is usually a sign the template wants splitting.
In the template body the same operators are spelled shorter — `is`, `is-not`, `contains`,
`not-contains`, `gt`, `lt`, `empty`, `not-empty`, and `gte` / `lte` / `eq` for the group-size
ones — and `logicOperator` is written as `match="all"`, whose absence means "any".
The `path` field is the interesting one. It lets a rule branch on something a model *extracted*
from an uploaded file — "show the non-resident clause when the analysed ID's country is not ES" —
so document structure can follow the contents of an attachment.
## Where rules live [#where-rules-live]
**Next to what they govern, and nowhere else.** A section rule is a `` child of its
``; a static rule is a `` inside the `` it pins; field and object rules sit
inline on the field or object in the form schema. There is no separate `rules` blob — frontmatter
carrying one is rejected on import.
That is deliberate: a rule and the thing it describes used to live in different halves of the
file, and the two disagreeing is how a hard-coded price once shipped inside a live deed.
All of them are frozen into a [template version](/docs/concepts/versioning) on publish, so a
document's behaviour cannot change under it.
The [templates page](/docs/concepts/templates#the-authoring-format-a-catalogue-of-examples) has a
worked example of every rule kind and every operator.
# Templates and the form schema (/docs/concepts/templates)
> A template is a schema, not a document. Understanding it is most of understanding DocDealer.
A template is not a Word file with holes in it. It is a **schema** with three parts, and the same
schema drives the UI form, the AI generation and the API surface — which is why any client produces
the same validated document.
## The three parts [#the-three-parts]
## Objects and fields [#objects-and-fields]
`formSchema` holds **objects** and **fields**. An object is a repeating group — "the parties to this
deed", "the properties being transferred" — and a field is a single value inside it.
That structure is exactly why `formData` keys look the way they do:
```json title="formData"
{
"comparecientes._count": 2,
"comparecientes.0.nombre": "Ana Torres",
"comparecientes.1.nombre": "Luis Gil"
}
```
`objectKey._count` says how many entries the repeating group has; `objectKey.index.fieldKey`
addresses one value inside one entry. There is no nesting beyond that — the shape is deliberately
flat so it survives a JSON round-trip through any client.
Read the real keys rather than guessing: `GET /api/templates/{templateId}` returns `formSchema`
with `objects` and `fields`, and each field's `fieldKey` (falling back to its `id`) is what goes
in the key.
## Field types [#field-types]
| Type | Value shape |
| ----------------- | --------------------------------------------------------------------------- |
| `string` | Text. |
| `number` | Numeric. |
| `date` | ISO date string. |
| `checkbox` | Boolean. |
| `selector` | One of a declared set of allowed values. |
| `file_attachment` | An object wrapper — an uploaded file, optionally with an AI analysis of it. |
| `geoaddress` | An object wrapper — a structured address with coordinates. |
| `connector_tool` | An object wrapper — a value fetched from an organization connector. |
The first five are scalars: pass the value directly. The last three are **object wrappers**, because
one field holds several derived values at once — an analysed ID document yields a name, a number and
a date, and a geocoded address yields road, house number, postcode, city and coordinates.
## Sections and example content [#sections-and-example-content]
The template's `content` is divided into sections, and each section carries **example content**: real
prose from real documents of that kind. Generation shows the model the examples plus the relevant
form values and asks for one section. It is imitation against a known shape, not open-ended writing —
which is what makes it fast and cheap. See [Generation](/docs/concepts/generation).
A section can also be marked to **skip AI entirely** and emit its example content verbatim when
conditions hold. That is a `` inside the case, covered in
[Rules](/docs/concepts/rules).
## Response language [#response-language]
Every AI prompt appends a "respond in X" directive taken from the **template**, never from the
reader's UI language. The chain is `template.locale` → the organization's default locale → English.
So a Spanish template generates Spanish prose for an English-speaking operator, which is almost
always what you want for a legal document.
## The authoring format: a catalogue of examples [#the-authoring-format-a-catalogue-of-examples]
Everything above describes the schema. This is what it looks like on disk.
A template serializes to **one `.html` file**: YAML frontmatter carrying the metadata and the
form schema, then the HTML body carrying the sections — each with its own ``,
its `` rules, an optional `` and its `` reference examples. There is no
separate rules block: a rule is a child of the thing it describes.
Each snippet below is a literal extract from a template that parses and validates — a test asserts
both, so an example here cannot document syntax the validator would reject.
## What to read next [#what-to-read-next]
Visibility, validation, section behaviour and static content.
What actually happens on a generate call, and why it is cheap.
Why documents pin a version, and what publishing changes.
# Versioning (/docs/concepts/versioning)
> Templates change; documents do not change under you. How publishing and pinning work.
A template is edited continuously. A document, once created, must keep behaving the way it did when
it was created — otherwise a deed drafted last quarter would re-render differently today.
DocDealer resolves that with **immutable versions and pinning**.
## Publishing creates a frozen snapshot [#publishing-creates-a-frozen-snapshot]
A `TemplateVersion` is a lossless copy of everything that defines behaviour at publish time: content,
form schema, rules, the three sets of AI instructions, the word-format binding, category, locale,
tags, and the editability flag. Versions auto-increment per template.
Nothing about a published version can change afterwards. That is the guarantee documents rely on.
## Documents pin a version [#documents-pin-a-version]
Creating a document without `templateVersionId` pins it to the template's **latest published**
version. A template with no published version returns `404` — there is nothing to pin to.
This is why editing a template does not disturb documents in flight. Publishing does not either:
existing documents stay on the version they were created against until you deliberately move them.
## Unpublished changes [#unpublished-changes]
The API exposes an `unpublishedChanges` flag on a template. It is **derived on read**, by hashing the
template's current editable surface and comparing it with the latest published version's stored hash.
No flag is stored on the row, which means it cannot drift — there is no bookkeeping step to forget.
## Changelogs [#changelogs]
On publish, the body-content differences against the previous published version are summarised into
a changelog automatically. It describes what changed in the prose, which is the part a reviewer
actually needs to see; schema and rule changes are visible in the diff of the snapshot itself.
## Edit history is separate [#edit-history-is-separate]
Publishing is deliberate. **History is automatic** — a separate, time-based record captured as the
collaborative editor saves, whether or not anything is published.
Consecutive edits by the same actor within about five minutes coalesce into one entry, so the
timeline reads as editing sessions rather than keystrokes. Each entry records which areas changed —
content, form, rules or metadata — and who changed them: a human editor, or an agent
together with the admin who approved it.
The two mechanisms answer different questions:
| | Versions | History |
| ---------- | ---------------------------- | ----------------------------- |
| Created by | Publishing, deliberately | The save loop, automatically |
| Purpose | What documents are pinned to | What happened, and who did it |
| Retention | Kept | Latest \~200 per template |
Both can be restored from, and both carry a snapshot of the collaborative editor state, so a restore
is lossless rather than a re-paste of text.
## What this means for an integration [#what-this-means-for-an-integration]
* **Pin explicitly if you care.** Pass `templateVersionId` when creating a document if it must be
built against a known version rather than whatever is latest.
* **Expect `404` on an unpublished template.** Publishing is the act that makes a template usable.
* **Treat `unpublishedChanges` as advisory.** It tells you a template has drifted from its last
publish, which is useful to surface to an author, not something to gate an API call on.
# Authentication (/docs/get-started/authentication)
> API keys, scopes, and the per-template allowlist.
Every API call carries an organization-scoped API key as a bearer token. Keys belong to an
organization, never to a person, so nothing you do with a key depends on a user still being
employed.
## Using a key [#using-a-key]
```bash title="curl"
curl https://thedocdealer.com/api/templates \
-H "Authorization: Bearer dd_live_xxxxxxxxxxxx"
```
The key carries its own organization, so there is no tenant header to send. The prefix marks the
environment: `dd_live_`, `dd_staging_` or `dd_dev_`.
## Creating a key [#creating-a-key]
An organization admin creates keys at `/admin/api-keys`: name it, tick the scopes it needs, and
optionally restrict it to specific templates.
**The plaintext is shown exactly once**, in the creation dialog. Only a SHA-256 hash is stored, so
it cannot be recovered — if you lose it, revoke the key and make a new one. Store it the way you
would store a password.
Revoking (or toggling a key off) takes effect immediately: calls start returning `401`.
## Scopes [#scopes]
A key may only do what its scopes allow. Two of them spend money — `documents:generate` and
`attachments:analyze` both run AI calls, so leave them off a key that only needs to read.
| Scope | Allows |
| --------------------- | -------------------------------------------------------------------- |
| `documents:read` | List and read documents, and read a document's context. |
| `documents:write` | Create, update and delete documents. |
| `documents:generate` | Spend AI credit: generate sections, titles, and validation. |
| `templates:read` | List templates and read their form schemas. |
| `templates:write` | Reserved. Template authoring is not reachable with an API key today. |
| `attachments:read` | List, read and download attachments and their analyses. |
| `attachments:write` | Upload and delete attachments; edit analyses. |
| `attachments:analyze` | Spend AI credit: run OCR / extraction over an attachment. |
| `mcp:tools` | Call MCP tools over the JSON-RPC endpoint. |
Two caveats worth knowing rather than discovering. `POST /api/documents/generate` and `POST
/api/documents/export` currently check only that your key can reach the document — they do not
require `documents:generate`. And `templates:write` is declared but unenforced, because template
authoring lives on admin routes an API key cannot reach.
## Template allowlist [#template-allowlist]
Scopes say *what* a key can do; the allowlist says *which templates* it may do it to. Both must
pass.
* **Empty** (the default) means no restriction — every template in the organization.
* **Non-empty** restricts the key to those template ids. Documents built from any other template
return `403`, and template listings hide them.
A restricted key also cannot reach documents with no template at all, since there is no id to
match.
## What API keys cannot do [#what-api-keys-cannot-do]
* **Manage API keys, or reach any admin route.** Those authorize against a real user's organization
role, and a key has no user.
## Listing documents with a key [#listing-documents-with-a-key]
`GET /api/documents` is organization-scoped for an API key, not user-scoped: it returns every
document in the key's organization, newest first, including ones created through the UI by a
person. A key has no user, so "my documents" is not a question it can answer.
A key with a [template allowlist](#template-allowlist) sees only documents built from those
templates, matching what it would be allowed to fetch.
## Auth errors [#auth-errors]
| Status | Means |
| ------ | --------------------------------------------------------------------------------------------------- |
| `401` | Missing, malformed, disabled, deleted or expired key. |
| `403` | Valid key, but a missing scope or a template outside its allowlist. |
| `404` | The resource does not exist — or belongs to another organization, reported the same way on purpose. |
Error bodies are always `{ "error": "…" }`.
## Other credentials [#other-credentials]
Two flows use short-lived tokens instead of your key, so you never have to hand it to a browser or
an agent: [embed sessions](/docs/guides/embedding) for the iframed editor, and [MCP OAuth](/docs/guides/mcp) for
agents that authenticate as a person.
# Quickstart (/docs/get-started/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 [#before-you-start]
Mint an API key at `/admin/api-keys` (see [Authentication](/docs/get-started/authentication)). The plaintext is
shown once. Give it at least `documents:write`.
## Generate in one call [#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.
```bash title="curl"
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.notario": "Salvador Farres Ripoll"
},
"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 [#getting-formdata-right]
This is where integrations usually stumble. Keys are flat and dotted:
```json title="formData shape"
{
"comparecientes._count": 2,
"comparecientes.0.nombre": "Ana Torres",
"comparecientes.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.
An unrecognised key fails the whole request, and the error names the valid patterns — worth
reading rather than guessing.
## Full example [#full-example]
Same flow in three languages — pick one.
```python title="Python (stdlib only)"
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"])
```
```ts title="TypeScript"
const API = 'https://thedocdealer.com'
const KEY = process.env.DOCDEALER_API_KEY!
async function call(path: string, body?: unknown): Promise {
const res = await fetch(`${API}${path}`, {
method: body ? 'POST' : 'GET',
headers: {
Authorization: `Bearer ${KEY}`,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body ? JSON.stringify(body) : undefined,
})
if (!res.ok) throw new Error(`${path} -> ${res.status}: ${await res.text()}`)
return res.json() as Promise
}
const { templates } = await call<{ templates: { id: string }[] }>('/api/templates')
const result = await call<{ documentId: string; blob: string; filename: string }>(
'/api/documents/generate',
{
templateId: templates[0].id,
title: 'Poder — Ana Torres',
formData: { 'intro._count': 1, 'intro.0.fecha': '2026-08-24' },
validateForm: true,
strict: true,
}
)
await Bun.write(result.filename, Buffer.from(result.blob, 'base64'))
```
```bash title="Bash (curl + jq)"
#!/usr/bin/env bash
set -euo pipefail
API="https://thedocdealer.com"
KEY="${DOCDEALER_API_KEY:?set DOCDEALER_API_KEY}"
auth=(-H "Authorization: Bearer $KEY" -H 'Accept: application/json')
# 1. Find a template.
template_id=$(curl -fsSL "${auth[@]}" "$API/api/templates" | jq -r '.templates[0].id')
# 2. Read its form schema to learn the field keys.
curl -fsSL "${auth[@]}" "$API/api/templates/$template_id" \
| jq '.formSchema | {objects: [.objects[].objectKey], fields: [.fields[].fieldKey]}'
# 3. Generate. One call creates the document, fills it, and renders the DOCX.
payload=$(jq -n --arg templateId "$template_id" '{
templateId: $templateId,
title: "Poder — Ana Torres",
formData: { "intro._count": 1, "intro.0.fecha": "2026-08-24" },
validateForm: true,
strict: true
}')
curl -fsSL "${auth[@]}" -H 'Content-Type: application/json' -d "$payload" \
"$API/api/documents/generate" \
| jq -r '.blob' | base64 --decode > document.docx
echo "wrote document.docx"
```
## Flags worth knowing [#flags-worth-knowing]
* `validateForm` — runs form validation alongside generation and returns `warnings.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. Requires `templateSource` rather than
a stored template.
* `generateTitle` — defaults to true only when you omit `title`.
## Next [#next]
Need attachments, OCR, or step-by-step control instead of one shot? See
[Documents](/docs/guides/documents). Want your users editing in your own app? See
[Embedding](/docs/guides/embedding).
# Attachments and OCR (/docs/guides/attachments)
> Upload a file, let DocDealer extract structured data from it, and feed that into the form.
This is the flow that makes DocDealer worth wiring into a backend rather than filling a form by
hand: upload an ID, a deed, a land-registry extract, and have its contents become form values.
## Upload [#upload]
Multipart, with `file`, `fileName` and `documentId`. Max 50 MB.
```bash title="curl"
curl -X POST https://thedocdealer.com/api/attachments \
-H "Authorization: Bearer $DOCDEALER_API_KEY" \
-F "file=@dni.pdf" \
-F "fileName=dni.pdf" \
-F "documentId="
```
PDFs, images (JPEG/PNG/GIF/WebP/TIFF), Office documents, plain text and CSV are accepted. DOCX is
converted to PDF server-side. Identical files are de-duplicated by hash, so re-uploading the same
document costs nothing and returns the existing attachment.
## Analyse [#analyse]
Analysis is asynchronous: start it, then poll.
It targets a **specific `file_attachment` field**, and that is not incidental — the field's schema is
what tells the model which shape to extract. Analysing "a PDF" in the abstract would give you prose;
analysing it *as* the `dni` field of the `comparecientes` object gives you the name, number and
expiry the template actually needs.
```bash title="curl"
# Start — returns { "id": "", ... }
curl -X POST https://thedocdealer.com/api/ai/analyze/attachment \
-H "Authorization: Bearer $DOCDEALER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"documentId": "",
"attachmentIds": [""],
"fieldId": "field_dni",
"entryIndex": 0
}'
# Poll — status: 0 pending, 1 success, 2 error
curl "https://thedocdealer.com/api/documents//attachment-analyses/" \
-H "Authorization: Bearer $DOCDEALER_API_KEY"
```
Requires the `attachments:analyze` scope, which spends AI credit. For API-key callers, results are
auto-accepted regardless of what you pass for `autoAccept` — there is no human in the loop to review
them.
## Reference the result from the form [#reference-the-result-from-the-form]
Analyses are referenced, not copied:
```json title="formData"
{
"comparecientes._count": 1,
"comparecientes.0.dni": { "type": "attachment_analysis", "$ref": "" }
}
```
The reference is what lets a [rule](/docs/concepts/rules) branch on something the model extracted —
`path: "address.postcode"` on a condition reads into the analysis result, so document structure can
follow the contents of an uploaded file.
## The upload → analyse flow is mandatory [#the-upload--analyse-flow-is-mandatory]
There is **no shortcut for callers who already hold the extracted data.** A
`file_attachment` field accepts exactly one value shape — a reference to a real analysis:
```json title="the only accepted shape"
{ "type": "attachment_analysis", "$ref": "" }
```
Anything else on such a field is rejected with `403` and this message:
```text
Field "comparecientes.0.dni" is a file attachment — do not set it directly and never
inline extracted data. Call create_attachment_upload_url, upload the file from disk,
then analyze_attachments, which fills this field from the real extraction.
```
In particular, inlining `{ "type": "attachment_analysis", "data": { "analysisResult": … } }`
does **not** work, even though it looks like it should. The server requires a non-empty
`$ref` to an analysis it created, so provenance always traces back to a real uploaded file
and a real extraction.
So a migration of existing records still has to push the source files through
`POST /api/attachments` and `POST /api/ai/analyze/attachment`. That costs an `analyze_file`
call per document, which is the expensive call type — budget for it rather than being
surprised by it.
## Cost [#cost]
`analyze_file` is one of the two expensive call types — it is a vision call over a whole document.
Two things follow:
* **De-duplication is free money.** The same file uploaded twice is analysed once.
* **Re-triggering is not cached.** Asking for analysis again means you want a fresh result, so it
runs again. Do not poll by re-posting.
Mint a key without `attachments:analyze` for any integration that only needs to read or attach files.
See [Authentication](/docs/get-started/authentication).
# Document lifecycle (/docs/guides/documents)
> Step-by-step control over create, generate and export, for when one-shot generation isn't enough.
Step-by-step control over the lifecycle, for when [one-shot generation](/docs/get-started/quickstart)
isn't enough — typically because you need to attach files and let DocDealer extract data from them
first.
The attachment leg is covered in [Attachments and OCR](/docs/guides/attachments). If you have no
attachments, skip to [one-shot generation](/docs/get-started/quickstart) — it does the create and the
generate together.
## Create a document [#create-a-document]
```bash title="curl"
curl -X POST https://thedocdealer.com/api/documents \
-H "Authorization: Bearer $DOCDEALER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"templateId": "", "title": "Poder — Ana Torres", "type": "api"}'
```
Returns `{ "documentId": "", "success": true }`. Omitting `templateVersionId` pins the document
to the template's latest published version, and a template with no published version returns `404` —
see [Versioning](/docs/concepts/versioning).
**Keep that `documentId`.** `GET /api/documents` returns an empty array for API-key callers — it
lists the calling *user's* documents and a key has no user — so there is no way to list it back
later.
## Generate [#generate]
`POST /api/documents/generate` with a `documentId` (rather than a `templateId`) pushes your
`formData`, generates every applicable section, and renders the DOCX. See
[the quickstart](/docs/get-started/quickstart#getting-formdata-right) for the key format, and
[Generation](/docs/concepts/generation) for what happens per section.
Which sections are "applicable" is decided by the template's
[section visibility rules](/docs/concepts/rules), not by you.
## Export [#export]
`POST /api/documents/export` re-renders stored content in another format. It takes `documentId` and
the document `content` HTML you want rendered, plus a `type`:
| `type` | Returns |
| --------- | ----------------------------------------------------------------------- |
| `doc` | Default. A `.docx` byte stream. |
| `pdf` | A `.pdf` byte stream. |
| `all` | A ZIP with both plus every referenced attachment. Capped at 60 seconds. |
| `preview` | JSON `{ previewUrl, filename }` for an Office-Online viewer, not bytes. |
## Discovering the form schema [#discovering-the-form-schema]
Two ways in, depending on whether you want the shape or the state.
`GET /api/templates/{templateId}` returns the template including `formSchema`, which holds `objects`
(repeating groups) and `fields`. Each field's `type` tells you what a value looks like — see
[Templates and the form schema](/docs/concepts/templates).
Use this when you are building a static integration against a known template.
`GET /api/documents/{documentId}/context` returns the schema **together with** the document's current
values, warnings, and the list of sections that will be generated.
Use this when you are building a UI over a live document, or when the applicable sections depend on
values you have already pushed.
# Embedding the editor (/docs/guides/embedding)
> Put the collaborative editor in your own app with an iframe.
Render DocDealer's collaborative editor inside your own product. Your users never see a DocDealer
login, and your API key never reaches their browser.
## How it works [#how-it-works]
Your backend trades its API key for an **embed token**: short-lived, scoped to one document, and
safe to put in an iframe URL.
A cross-site iframe sends no cookies, so the token in the URL *is* the credential. That is also why
you must mint it server-side: anything the browser can read, your users can read.
## Mint a session [#mint-a-session]
```bash title="curl"
# Your backend, holding your API key.
curl -X POST https://thedocdealer.com/api/embed/sessions \
-H "Authorization: Bearer $DOCDEALER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"documentId": "",
"displayName": "Ana Torres",
"externalUserId": "user-1042",
"parentOrigin": "https://app.example.com"
}'
# → { "token": "...", "url": "https://thedocdealer.com/embed/?embed_token=...",
# "expiresAt": "2026-08-24T12:34:56.000Z" }
```
Then drop the returned `url` straight into an iframe:
```html title="your page"
```
## Keeping the session alive [#keeping-the-session-alive]
Tokens last 15 minutes by default, and the iframe renews itself at half-life without any help from
you. Each renewal re-checks your API key server-side, which is what makes revocation take effect.
It cannot always renew itself, though: a laptop that slept past the expiry, a reload of a stale
`src`, or a session that has run for 12 hours. Then it asks your page, because only your backend can
mint a token. Implement the other half of that exchange and sessions survive indefinitely:
```js title="your page"
// Your page. The iframe renews itself while it can; when it can't,
// it asks you — because only your backend holds the API key.
window.addEventListener('message', async event => {
if (event.origin !== 'https://thedocdealer.com') return
const msg = event.data
if (msg?.source !== 'docdealer-embed' || msg.type !== 'token-request') return
// Your own session check belongs here. Returning nothing ends the
// embedded session, which is what you want when your user signs out.
if (!currentUser) return
const res = await fetch('/api/embed-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ documentId: msg.documentId, displayName: currentUser.name }),
})
const { token } = await res.json()
iframe.contentWindow.postMessage(
{ source: 'docdealer-embed', v: 1, type: 'token', token },
'https://thedocdealer.com'
)
})
```
Note the origin check on the way in and the explicit target origin on the way out. Both matter:
without them any page could hand your iframe a token, or read yours.
**Declining to answer is a feature.** It is how you end an embedded session when your own user signs
out.
## Matching your theme [#matching-your-theme]
Pass `theme` when you mint the session and the editor paints light or dark from the first frame — no
flash of the wrong palette:
```json title="POST /api/embed/sessions"
{ "documentId": "", "displayName": "Ana Torres", "theme": "light" }
```
If your users can toggle the theme without a reload, send it over the same bridge and the iframe
follows immediately:
```js title="your page"
iframe.contentWindow.postMessage(
{ source: 'docdealer-embed', v: 1, type: 'theme', theme: 'dark' },
'https://thedocdealer.com'
)
```
While embedded, the editor takes the theme only from you — it neither reads nor writes the visitor's
own stored DocDealer preference, so it can never drift out of sync with your page.
## What an embed session can do [#what-an-embed-session-can-do]
Everything a DocDealer user can do **to that one document**. The embedded editor is the same
component the app itself renders, so your users get the form, the collaborative document,
attachments with OCR, section generation and export.
The boundary is the document, not the feature set:
* **One document.** Every other id is denied — including documents in the same organization, and
including on the endpoints that take the id in the request body rather than the path.
* **No creating or listing.** It cannot create documents, list your documents, or list your
templates.
* **No administration.** No API-key management, no admin endpoints, and it cannot mint another
session.
It **can** spend AI credit on that document: generating sections and analysing attachments are AI
calls, and they are what the editor is for. So treat an embed token like a logged-in session scoped
to one document — short-lived, and worth revoking if it leaks. Disabling the API key that minted it
kills every live session within one token lifetime.
One deliberate exception to the single-document rule: fields backed by an **organization connector**
can read and invoke that connector, since those fields are unusable otherwise.
## Practical notes [#practical-notes]
* **Content Security Policy.** Your page needs `frame-src https://thedocdealer.com`. If you serve
over HTTP in development, note that `upgrade-insecure-requests` will break the frame.
* **An invalid token renders an error, not a redirect.** Redirecting a cross-site iframe to a login
page is a dead end, so a bad token returns `401` and an expired one shows a recoverable state
instead.
* **Your users appear as themselves.** The `displayName` you sign shows up on cursors and in the
document's edit history.
A complete worked example — backend, page, and both halves of the bridge — lives in the repository:
# MCP (/docs/guides/mcp)
> Connect an AI agent over Model Context Protocol — API key or OAuth.
DocDealer speaks Model Context Protocol, so an AI agent can author templates and produce documents
directly. One endpoint, two ways to authenticate, and the tool catalogue below.
## The endpoint [#the-endpoint]
Everything goes through a single JSON-RPC endpoint:
```text title="endpoint"
POST https://thedocdealer.com/api/mcp
```
It is **stateless**: no session id, no resumability, no server-initiated streams. `GET` and `DELETE`
return `405` by design.
## Authenticating [#authenticating]
Simplest, and right for a service you control. Mint a key with the `mcp:tools` scope (see
[Authentication](/docs/get-started/authentication)) and send it as a bearer.
```json title="client config"
{
"mcpServers": {
"docdealer": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://thedocdealer.com/api/mcp",
"--header",
"Authorization: Bearer dd_live_xxxxxxxxxxxx"
]
}
}
}
```
Or call it directly:
```bash title="curl"
curl -X POST https://thedocdealer.com/api/mcp \
-H "Authorization: Bearer $DOCDEALER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
Missing the scope returns HTTP `403` with a JSON-RPC error body (code `-32001`), not the usual
`{ "error": … }` shape.
Use this when the agent should act as a *person*, with that person's organization membership — an
MCP client in someone's editor, for instance. No pre-shared secret is needed: clients register
themselves.
Discovery follows the standards, so a compliant client needs only the base URL:
```text title="discovery"
GET /.well-known/oauth-protected-resource (RFC 9728)
GET /.well-known/oauth-authorization-server (RFC 8414)
```
Then the usual three legs:
```text title="endpoints"
POST /api/mcp/oauth/register dynamic client registration (RFC 7591)
GET /api/mcp/oauth/authorize authorization code + PKCE challenge
POST /api/mcp/oauth/token exchange code (or refresh token) for an access token
```
**PKCE is mandatory and only `S256` is accepted** — sending `code_challenge_method=plain` is
rejected at `/authorize`. Access tokens last one hour; refresh tokens rotate on every use.
Add `?organization_id=…` to the authorize request to pick which organization the session binds to,
if the user belongs to several.
## Tools [#tools]
One resource is exposed: `docs://docdealer`, a static overview of what the server does and which
tool to call for what. Everything dynamic is a tool — most MCP clients never fetch a resource
unless the user attaches it, so anything an agent must be able to reach on its own is a tool.
### Skills [#skills]
Two tools return the workflow you need, so an agent pays only for the one it is doing:
* **`get_document_generation_skill({ templateId? })`** — the generation workflow and the form-data
format. Pass `templateId` and it also returns that template's form checklist, attachment and
address fields, section ids, and any `agentInstructions` its author wrote — replacing a separate
`get_template` call.
* **`get_template_authoring_skill()`** — the authoring workflow and the complete template file
format.
`get_instructions` is the entry point: a short overview that routes to one of the two.
`whoami` answers the other orientation question — who this credential belongs to, which
organization is active, and which operations the session may actually perform. Its `capabilities`
block is computed by calling the same checks the tools enforce, so it cannot disagree with them,
and it saves an agent from discovering its own permissions by failing a write.
Two more tools support authoring: `get_template_examples` returns one validated snippet per
construct of the template format, and `list_section_tools` lists the tokens a section's `tools`
attribute may name — including this organization's connector tools, which cannot be guessed.
Clients also receive an `instructions` string at `initialize`, built per caller, naming the document
types that credential can see. That reaches the model's system prompt without a tool call, so an
agent recognises a request for one of your document types without asking first.
## How authorization differs from REST [#how-authorization-differs-from-rest]
Worth understanding before you mint a key for an agent, because it is not what the REST rules would
lead you to expect.
`mcp:tools` is what admits a key to the MCP endpoint at all — without it, every call is rejected
with a `403` before any tool runs. It is necessary, and it grants nothing on its own: every tool
then checks its own scope, the same one the equivalent REST route asserts.
The organization boundary, the [template allowlist](/docs/get-started/authentication#template-allowlist),
and one scope per tool:
| Scope | Tools |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `documents:read` | `get_document`, `get_document_context`, `export_document`, `get_form_value`, `get_document_generation_skill`, `get_document_url` |
| `documents:write` | `create_document`, `update_document_form` |
| `documents:generate` | `generate_section`, `generate_all_sections`, `generate_document`, `validate_document` |
| `attachments:read` | `list_attachments` |
| `attachments:write` | `create_attachment_upload_url`, `upload_attachment` |
| `attachments:analyze` | `analyze_attachments` |
| `templates:read` | `export_template`, `validate_template`, version reads |
| `templates:write` | `create_template`, `update_template_*`, `import_template`, `create_template_version`, `get_template_authoring_skill` |
`whoami`, `get_instructions`, `get_template_examples`, `list_section_tools` and
`extract_file_text` check no scope. The first four are reference about the server and the format
rather than access to anything; `extract_file_text` needs only a resolvable organization, because
its pdf/image path spends a metered AI call that has to be attributable.
Note that generation and analysis sit behind `documents:generate` and `attachments:analyze` because
they are **metered AI usage billed to your organization** — not merely because they write.
Human (OAuth) sessions carry no scopes, so they are gated on role instead: template authoring
requires organization admin or superuser.
The allowlist remains your sharpest instrument for narrowing an agent to specific templates.
## Tool reference [#tool-reference]
Every tool's input schema is published in the MCP OpenAPI document, which needs no credentials:
```bash title="curl"
curl https://thedocdealer.com/api/mcp/openapi.public.json
```
One caveat if you read it in a viewer: the `/tools/{tool_name}` paths are a **reference only and
are not routable**. There is no HTTP endpoint per tool — a "try it" button against those paths
returns `404`. Use the body shown as the `arguments` of a `tools/call` request to `POST /api/mcp`.
# API reference (/docs/reference)
> Every public endpoint, generated from the OpenAPI spec.
Every endpoint an integrator can call, generated from the same OpenAPI 3.1 document the server
publishes at `/api/openapi.public.json`. Pages are grouped by resource — expand this section in the
sidebar to browse them.
The document is a **curated view**: over half of the internal spec's operations are `/api/admin/*`,
which no integrator can call, so publishing their shapes would describe the internals without
helping anyone. It needs no credentials, so you can point a code generator straight at it:
```bash title="curl"
curl https://thedocdealer.com/api/openapi.public.json
```
Every request needs an `Authorization: Bearer ` header — see
[Authentication](/docs/get-started/authentication) for scopes and the template allowlist.
# MCP tools (/docs/reference/mcp-tools)
> The full tool catalogue exposed over Model Context Protocol.
Every tool the MCP endpoint exposes, derived from the published MCP spec. For how to connect and
authenticate, see [Agents over MCP](/docs/guides/mcp).
## Input schemas [#input-schemas]
Each tool's input schema is published in the MCP OpenAPI document, which needs no credentials:
```bash title="curl"
curl https://thedocdealer.com/api/mcp/openapi.public.json
```
The `/tools/{tool_name}` paths in that document are a **reference only and are not routable**.
There is no HTTP endpoint per tool — a "try it" button against those paths returns `404`. Use the
body shown as the `arguments` of a `tools/call` request to `POST /api/mcp`.
# List attachments for a document (/docs/reference/attachments/get)
> List attachments for a document - GET /attachments. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Upload attachment (/docs/reference/attachments/post)
> Upload attachment - POST /attachments. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List user documents (/docs/reference/documents/get)
> List user documents - GET /documents. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create document (/docs/reference/documents/post)
> Create document - POST /documents. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List accessible templates (/docs/reference/templates/get)
> List accessible templates - GET /templates. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Cancel section generation (/docs/reference/ai/cancel-section/post)
> Cancel section generation - POST /ai/cancel-section. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Delete attachment (/docs/reference/attachments/attachmentid/delete)
> Delete attachment - DELETE /attachments/{attachmentId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get attachment (/docs/reference/attachments/attachmentid/get)
> Get attachment - GET /attachments/{attachmentId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Delete document (/docs/reference/documents/documentid/delete)
> Delete document - DELETE /documents/{documentId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get document by ID (/docs/reference/documents/documentid/get)
> Get document by ID - GET /documents/{documentId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Partial update document (/docs/reference/documents/documentid/patch)
> Partial update document - PATCH /documents/{documentId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Full update document (/docs/reference/documents/documentid/put)
> Full update document - PUT /documents/{documentId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Export document as DOCX/PDF/ZIP (/docs/reference/documents/export/post)
> Export document as DOCX/PDF/ZIP - POST /documents/export. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# One-shot document generation (/docs/reference/documents/generate/post)
> One-shot document generation - POST /documents/generate. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Mint an embed session for one document (/docs/reference/embed/sessions/post)
> Mint an embed session for one document - POST /embed/sessions. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Geocode address search (/docs/reference/geocode/search/get)
> Geocode address search - GET /geocode/search. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get template by ID (/docs/reference/templates/templateid/get)
> Get template by ID - GET /templates/{templateId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Trigger AI analysis on document attachments (/docs/reference/ai/analyze/attachment/post)
> Trigger AI analysis on document attachments - POST /ai/analyze/attachment. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Validate document content (/docs/reference/ai/analyze/document/post)
> Validate document content - POST /ai/analyze/document. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Validate form data (/docs/reference/ai/analyze/form/post)
> Validate form data - POST /ai/analyze/form. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Generate document section(s) (/docs/reference/ai/generate/section/post)
> Generate document section(s) - POST /ai/generate/section. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Generate document title (/docs/reference/ai/generate/title/post)
> Generate document title - POST /ai/generate/title. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get presigned download URL (/docs/reference/attachments/attachmentid/download/get)
> Get presigned download URL - GET /attachments/{attachmentId}/download. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Upload an attachment via a short-lived token (multipart, no auth header) (/docs/reference/attachments/upload/token/post)
> Upload an attachment via a short-lived token (multipart, no auth header) - POST /attachments/upload/{token}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List attachment analyses for document (/docs/reference/documents/documentid/attachment-analyses/get)
> List attachment analyses for document - GET /documents/{documentId}/attachment-analyses. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get document context for AI (/docs/reference/documents/documentid/context/get)
> Get document context for AI - GET /documents/{documentId}/context. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Resolve a filled placeholder to a form/memory proposal (/docs/reference/documents/documentid/resolve-placeholder/post)
> Resolve a filled placeholder to a form/memory proposal - POST /documents/{documentId}/resolve-placeholder. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Refresh the current embed session (/docs/reference/embed/sessions/refresh/post)
> Authenticated with the embed token itself. Re-issues the same payload with a new expiry; the session start (`sst`) is preserved so the absolute cap keeps…
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# List template versions (/docs/reference/templates/templateid/versions/get)
> List template versions - GET /templates/{templateId}/versions. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Convert a reference document to text via a short-lived token (multipart, no auth header) (/docs/reference/templates/convert-reference/token/post)
> Convert a reference document to text via a short-lived token (multipart, no auth header) - POST /templates/convert-reference/{token}. DocDealer REST API…
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Delete attachment analysis for document (/docs/reference/documents/documentid/attachment-analyses/analysisid/delete)
> Delete attachment analysis for document - DELETE /documents/{documentId}/attachment-analyses/{analysisId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Get attachment analysis for document (/docs/reference/documents/documentid/attachment-analyses/analysisid/get)
> Get attachment analysis for document - GET /documents/{documentId}/attachment-analyses/{analysisId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Update attachment analysis for document (/docs/reference/documents/documentid/attachment-analyses/analysisid/patch)
> Update attachment analysis for document - PATCH /documents/{documentId}/attachment-analyses/{analysisId}. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Public proxy for Office Online preview (token-authed) (/docs/reference/documents/preview/token/file-docx/get)
> Public proxy for Office Online preview (token-authed) - GET /documents/preview/{token}/file.docx. DocDealer REST API reference.
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}