REST API

Document requests API

API v120 September 2026·10 min read

Full reference for Doclift's Document Requests API: synchronous and asynchronous generation, the payload, statuses, validation errors and limits.

The document request object

  • id: unique identifier
  • tag: free-form string you send, echoed back
  • type: synchrone or asynchrone (spelled as shown, not the English "synchronous"/"asynchronous"); the value you send at creation, echoed back unchanged
  • sandbox_mode: set once at creation from the calling key's environment
  • status: in_progress, success, or error (the request's overall outcome)
  • documents_generations: present on the list endpoint, on the show endpoint, and on a synchronous create response; absent from an asynchronous create response, since nothing has rendered yet when it answers; each entry has id, tag, generated_at, created_at, generation_status (created, in_progress, success, or error), and file (filename, url, size)

template_id accepts a custom, fillable_form, or workflow template, as long as it is published. The payload shape is identical for all three. Workflow templates carry one additional contract on top of everything below. See Workflow templates.


Synchronous or asynchronous

type: "synchrone" renders exactly one document and answers 200 OK with that document already attached: id, tag, type, timestamp, and documents_generations. There is nothing else to implement: no webhook to expose, no URL to configure, no second code path. This is the mode to pick when a person is waiting for their document on screen.

type: "asynchrone" accepts one or more entries in document_generations, queues the lot, and answers immediately with id, tag, type, sandbox_mode, and status: "in_progress": no documents_generations key at all, since nothing has rendered yet. The finished documents arrive later as a webhook. See Webhooks. This is the batch mode, and the only one that accepts several documents in a single request.

Asynchronous generation requires a webhook URL configured on the calling external application; without one, the request is refused before anything is created:

422 Unprocessable Content · application/json
{
  "error": "Please add a webhook URL to this external application to use asynchronous generation. You can add one from your Doclift.io account."
}

One document per synchronous request

A synchronous request carrying more than one entry in document_generations is refused with 422. The documents of one request render one after another: a batch would keep the caller waiting for the sum of its renders. For several documents, use asynchronous.

How long to wait

A synchronous answer arrives within seconds in the vast majority of cases. The API imposes no maximum, but your own network often does: many connections are cut once they have stayed silent for about a minute, and neither you nor Doclift can do anything about it.

A request whose answer never reached you is not lost. See When the answer does not reach you.

Sandbox forces priority to low

priority (critical, default, or low, default critical) sets how this request ranks against others waiting to generate. In sandbox mode it is always forced to low, whatever value you send.


Create a document request

POST/api/v1/document_requests

Asynchronously

cURL
curl https://app.doclift.io/api/v1/document_requests \
  --request POST \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "document_request": {
      "type": "asynchrone",
      "document_generations": [
        {
          "template_id": 100013,
          "tag": "Invoice#42",
          "variables": {
            "client_name": "John Doe"
          }
        }
      ],
      "tag": "Batch#1"
    }
  }'
200 OK · application/json
{
  "id": 100004,
  "tag": "Batch#1",
  "type": "asynchrone",
  "sandbox_mode": false,
  "status": "in_progress"
}

Synchronously

cURL
curl https://app.doclift.io/api/v1/document_requests \
  --request POST \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "document_request": {
      "type": "synchrone",
      "document_generations": [
        {
          "template_id": 100013,
          "tag": "Invoice#42",
          "variables": {
            "client_name": "John Doe"
          }
        }
      ],
      "tag": "Invoice#42"
    }
  }'
200 OK · application/json
{
  "id": 100005,
  "tag": "Invoice#42",
  "type": "synchrone",
  "timestamp": "2026-09-20T15:23:24+02:00",
  "documents_generations": [
    {
      "id": 100231,
      "tag": "Invoice#42",
      "generated_at": "2026-09-20T15:23:24+02:00",
      "created_at": "2026-09-20T15:23:23+02:00",
      "generation_status": "success",
      "file": {
        "filename": "Invoice-1615737313.pdf",
        "url": "https://doclift-production.s3.eu-west-1.amazonaws.com/d207f8fe.pdf?[...]",
        "size": 189241
      }
    }
  ]
}

The file URL is valid for 2 hours. After that, read the request back through View a document request to get a fresh one.

The fields

FieldTypeRequiredNotes
typestringyessynchrone or asynchrone
prioritystringnocritical, default, or low; forced to low in sandbox
tagstringnodefault ""; the only identifier you choose yourself, and the one you can look a request up by
document_generationsarrayyesexactly one entry for synchrone, one or more for asynchrone
document_generations[].template_idintegeryesmust be published
document_generations[].tagstringnodefault ""
document_generations[].variablesobject or nullnoflat key/value map (see Templates)

There is no idempotency key: submitting the same body twice creates two independent document requests, each reporting its own status.


Validation errors

Each of the following is checked in order; the first failure short-circuits the rest and answers before any document is generated.

FailureStatusNotes
type422 "The requested generation type is invalid. Accepted values: synchrone / asynchrone"
priority422 {"error": "..."}, names the accepted values
A synchronous request carrying more than one entry422 "You can only generate a single document with this route."
webhook_url422 see Synchronous or asynchronous
document_generations422 "The following required parameters are missing: document_generations"
template_id422 "The following required parameters are missing: template_id"
template_id422 names the id; a wrong id and someone else's id answer identically
content422
Rendered content exceeds the organization's size limit422 see Limits
variables422
document_request400 {"error": "..."}, names the missing parameter when applicable

Missing/invalid/disabled API key answers 403 on every variant, as elsewhere in the API. See Response codes.

The messages above are the English ones, served when you send Accept-Language: en. Without that header the API answers in French. See Language of the answers.

Variable validation errors

On a fillable_form template only, each provided value is checked against the auto-detected allowed_values of its variable (see Fillable forms). A value outside the allowed list answers 422 naming every offending field:

422 Unprocessable Content · application/json
{
  "error": "Les valeurs fournies pour certaines variables ne sont pas valides (status: 'bad_value' (valeurs autorisées : approved, rejected)).",
  "invalid_variables": [
    { "field": "status", "value": "bad_value", "allowed_values": ["approved", "rejected"] }
  ]
}

custom and workflow templates are not checked against allowed_values at generation time by this step.

If generation fails for any other reason once the payload is otherwise valid, you observe it through GET /api/v1/document_requests/:id's generation_status/ generation_error, or through the webhook payload's generation_status: "error".


When the answer does not reach you

This section concerns the synchronous mode only: asynchronously, the answer is immediate and the result arrives by webhook.

Three situations leave you without a usable answer. They are all handled the same way, and this is the rule to remember: the tag you sent lets you find the request again.

cURL
curl "https://app.doclift.io/api/v1/document_requests?tag=Invoice%2342" \
  --header "X-Api-Key: <your-api-key>"
SituationWhat you getWhat to do
Your connection drops before the answernothing: a network error from your own clientlook the request up by its tag before replaying it
429 {"error": "…"} with a Retry-After headerwait the stated delay and replay
502 {"code": "outcome_unknown", "tag": …, "retrieve": …}the request may well have landed: check by its tag before replaying
504 {"code": "generation_pending", "id": …, "tag": …, "retrieve": …}generation is still running; read the request back at the given address

Do not replay without checking

There is no idempotency key. A replayed request creates a second, independent one, and bills you a second document. A 502 and a dropped connection both mean "I do not know", not "it failed".

Too many generations pending

An organization may only have so many synchronous requests waiting to be processed at once. Beyond that, further ones are refused straight away rather than left to wait:

429 Too Many Requests · Retry-After: 1
{
  "error": "Too many synchronous generations are already pending for your organization. Try again shortly."
}

A slot frees as soon as a generation finishes, rather than at the end of a time window: a Retry-After of one second is enough in almost every case. This ceiling does not apply to asynchronous generation. See Limits.


Workflow templates

Workflow templates are generated through this same endpoint. There is no separate workflow generation route. Workflows are a private beta, available on request by writing to [email protected]; see Workflows for the full picture.

On top of everything above, a workflow's variables payload carries a required-variable contract that no other category enforces:

Workflow document_generations entry
{
  "template_id": 100050,
  "variables": {
    "investor_type": "natural",
    "country": "FR",
    "investments": [{ "product": "SCPI", "amount": "10 000 €" }]
  },
  "tag": ""
}
FailureTrigger
Missing required variablea variable declared required has no key at all in the payload
Malformed collectiona collection-type variable's value is not an array of flat objects
Non-scalar valuea non-collection variable received an array or object
Oversized collectiona collection carries more than 200 rows
Missing required row fielda required field of a collection row has no key in that row
Excludes every sectionthe payload's conditions leave nothing to render
Prints nothingsections survive the conditions but assemble to blank content

A key present with an empty string or null counts as answered. Only the absence of the key triggers the missing-variable check. All applicable failures across the whole batch are reported together in one 422 {"error": "..."}, naming every affected template and variable at once (useful when a batch mixes several workflow generations). allowed_values is not checked for workflow variables at generation time.


List document requests

GET/api/v1/document_requests
cURL
curl https://app.doclift.io/api/v1/document_requests \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
[
  {
    "id": 100004,
    "tag": "Batch#1",
    "type": "asynchrone",
    "external_application": {
      "id": 100000,
      "name": "Production key",
      "environment": "sandbox",
      "active": true
    },
    "documents_generations": [
      {
        "id": 100230,
        "tag": "Invoice#42",
        "generated_at": "2024-01-15T10:35:02+01:00",
        "created_at": "2024-01-15T10:35:00+01:00",
        "generation_status": "success",
        "file": {
          "filename": "Invoice-1615737313.pdf",
          "url": "https://doclift.s3.eu-west-1.amazonaws.com/d207f8fe.pdf?[...]",
          "size": 40290
        }
      }
    ]
  }
]

Lists your organization's document requests, newest first. This follows from the organization-wide association, so requests created through other external applications under the same organization appear too, not only the calling key's. Paginated at 30 per page; see Pagination.

Filter by tag

The tag parameter narrows the list to requests carrying exactly that tag. It is the only identifier you choose yourself and know in advance, which makes it the one you use to find a request whose answer never reached you.

cURL
curl "https://app.doclift.io/api/v1/document_requests?tag=Invoice%2342" \
  --header "X-Api-Key: <your-api-key>"

The match is exact, not partial. An unknown tag answers 200 with an empty array, never 404. Nothing requires a tag to be unique: if you have reused one, the list carries every request sharing it.

StatusBodyWhen
200 arrayalways, including an empty account
403 {"error": "..."}missing, unknown, or disabled API key

View a document request

GET/api/v1/document_requests/:id
cURL
curl https://app.doclift.io/api/v1/document_requests/100004 \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "id": 100004,
  "tag": "Batch#1",
  "type": "asynchrone",
  "external_application": {
    "id": 100000,
    "name": "Production key",
    "environment": "sandbox",
    "active": true
  },
  "documents_generations": [
    {
      "id": 100230,
      "tag": "Invoice#42",
      "generated_at": "2024-01-15T10:35:02+01:00",
      "created_at": "2024-01-15T10:35:00+01:00",
      "generation_status": "success",
      "generation_duration": 1721,
      "generation_error": null,
      "sent_payload": { "client_name": "John Doe" },
      "file": {
        "filename": "Invoice-1615737313.pdf",
        "url": "https://doclift.s3.eu-west-1.amazonaws.com/d207f8fe.pdf?[...]",
        "size": 40290
      },
      "template": {
        "id": 100013,
        "title": "Invoice",
        "description": "A template for invoices"
      }
    }
  ]
}

This is the richer view: sent_payload (exactly what you sent), generation_duration, generation_error, and the source template are only present here, never in the list or in the create response. File URLs are valid for 20 hours in this view, against 2 hours everywhere else.

StatusBodyWhen
200 document request, show viewbelongs to your organization
404 {"error": "Record not found"}unknown id or another organization's
403 {"error": "..."}missing, unknown, or disabled API key

Limits

LimitValueScope
Rendered content size800 ,000 characters by default, configurable per organizationcustom and workflow templates; fillable_form is never measured
Collection rows (workflow)200 per collection variable, per generation
Documents per request1 for synchrone, unlimited for asynchronesee Synchronous or asynchronous
Synchronous requests pending at onceper organization, adjustable on requestbeyond it: 429 with Retry-After
Webhook delivery retriesconfigurable per organizationsee Webhooks

There is no request-rate limiting on this API: nothing counts your requests per minute or per day. The only ceiling is how many synchronous requests your organization may have waiting to be processed at once, described above. It frees at the pace of the generations that finish, and does not apply to asynchronous generation. See Environments.