---
title: Document generation
description: "A template plus a record becomes a document: generation is queued, status is polled, and a download is a short-lived signed URL that cannot be recalled."
---

Every endpoint here needs the `documents` module. Generation is **asynchronous** — this is the one module in the API where a write does not finish the work.

## Storage has to be configured first

Before anything else: a workspace whose document storage is not set up refuses generation with `document_storage_unconfigured`. That is an administrator's setting, not something a client can arrange, so handle it as a prerequisite rather than a transient failure.

## A template binds to an object

[`POST /document-templates`](/reference/documents/post-api-v1-document-templates) takes a `name`, the `objectId` its documents are rendered from, a `layout`, and a `version`.

**A template may only name fields that object has.** A layout referring to a field that does not exist answers `template_field_unavailable`, at creation rather than at render time, so a broken template cannot be saved and discovered later.

## Generating is a queue, not a response

[`POST /documents`](/reference/documents/post-api-v1-documents) takes `templateId`, `recordId` and the record's `revision`. It **queues** the work and answers immediately.

The `revision` pins which version of the record is rendered, the same way an approval pins what its approvers read. A document therefore shows the record as it was when generation was asked for, not as it is when the file is opened.

`template_revision_mismatch` means the template changed between reading it and asking for the document. Re-read the template and submit again.

## Polling for the result

[`GET /documents/{id}`](/reference/documents/get-api-v1-documents-by-id) answers a `status` of `pending`, `ready` or `failed`, with an `inputHash` and — once there is output — an `outputHash`.

**Poll until `ready` or `failed`.** There is no callback and no streaming; `pending` means keep waiting. The two hashes are how a client can tell a re-render from the same inputs apart from a new one.

## Retrying and downloading

[`POST /documents/{id}/retry`](/reference/documents/post-api-v1-documents-by-id-retry) is for `failed` documents only. A `pending` or `ready` one is refused with `document_not_failed`, so **a retry cannot reset work in progress or discard a finished file**.

[`POST /documents/{id}/download`](/reference/documents/post-api-v1-documents-by-id-download) answers a **short-lived signed URL**. Two things follow from that:

- A document that is not `ready` has nothing to sign, and answers `document_not_ready`.
- **The link cannot be recalled.** Once issued it works until it expires, whoever holds it. Treat it as a bearer credential for that one file: do not log it, do not put it in a redirect a third party can read, and issue it at the moment the user asks rather than ahead of time.

It is a `POST` rather than a `GET` because issuing a credential is an action, not a read.

## One scope covers four endpoints

`documents.read` lists templates and `documents.manage` creates them, but **generating, reading status, retrying and downloading all need `documents.generate`**. There is no narrower scope for fetching a finished file, so a token that may download may also generate.
