Factuarea APIDevelopers

PDF previews and artifacts

Preview a template before using it, prepare and render the exact PDF of a document, download it with ETag and 304, and read pdf_output_state with a correct retry loop.

Every PDF the API produces from the template library is an artifact: an exact, immutable PDF with its own identifier, state and checksum. Two kinds exist.

purposeWhat it isLifetime
documentThe original PDF of a document: it is rendered from the presentation the document was captured with, and its bytes never change.Kept with the document.
previewA draft rendering to check how a template, a brand or a document will look. It is never the official PDF of anything.Expires 24 hours after it is created (expires_at).

Four groups of operations cover the flow. Library management, defaults and precedence are in the PDF template library guide.

OperationScopesIdempotency-Key
POST /v1/companies/{company}/pdf-template-previewspdfs:read and pdf_templates:readRequired
POST /v1/companies/{company}/pdf-artifacts (prepare)pdfs:readRequired
POST /v1/companies/{company}/pdf-artifacts/{artifact}/renderpdfs:readRequired
GET /v1/companies/{company}/pdf-artifacts/{artifact} (metadata)pdfs:readNot applicable
GET /v1/companies/{company}/pdf-artifacts/{artifact}/downloadpdfs:readNot applicable
GET /v1/companies/{company}/invoices/{invoice}/pdf and its quote, proforma and delivery note equivalentspdfs:readNot applicable

All the routes in this guide hang off /v1/companies/{company}/…: {company} is the id (UUID v7) of the tax ID you operate on, and in the curl examples it is $COMPANY_ID. Reach a tax ID explains how to resolve it.

Artifacts need the admin or user role and the module section of the document family (invoices, quotes, proformas or delivery_notes). Preview artifacts also follow the pdf_templates module and the pdf_templates:read scope, so reading one needs that scope too. Generating a PDF consumes the same render budget as the other PDF endpoints: new work can return 429 pdf_generation_budget_exceeded with Retry-After, while replays and already rendered artifacts cost nothing more (see rate limits).

Preview a template

POST /v1/companies/{company}/pdf-template-previews renders a PDF without saving anything: it does not publish a template, change a default or touch a document. The body has five required fields and a closed source:

FieldValue
document_typeinvoice, quote, proforma or delivery-note.
purposeInvoices: complete, simplified, corrective, simplified_corrective. Other types: complete.
formata4, ticket_80 or ticket_58 (tickets for invoices only).
localees, en or ca.
sourceWhere the data comes from, see below.

Depending on the source, three more fields describe the design to render: base_preset, layout_version (always "1") and configuration (the complete normalized configuration, exactly as a template revision returns it).

source.kindData it rendersVisual block
exampleSample data: scenario is standard, long, simplified, corrective, simplified_corrective, f3 or unvalued_delivery_note. Optional commercial_identity: { "mode": "inherit" } (default), { "mode": "exact_revision", "identity_id", "revision_id" } or { "mode": "draft", "configuration": { ... } } with the six brand keys and, optionally, logo_dark_asset_id, brand_image_dark_asset_id, dark_background_fallback and brand_size.All three required.
unsaved_formThe form object you send (series, contact, document, recipient, lines, payment terms...), nothing persisted.All three required.
persisted_draftAn existing draft document: { "kind": "persisted_draft", "document_id" }.All three or none.
presentation_revisionAn issued document and its retained presentation: document_id, parent_id, template_id (null to inherit), source_policy (captured or current_legal) and expected_version. Accepts a format other than the retained one to ask for another paper of the same issued document (receipt or A4), without creating a revision.All three or none.
invoice_substitutionA detached preview of the invoice that would substitute simplified invoices: document_id (null for a new one), source_invoice_uuids and line_uuids (same length and order, up to 100), client_uuid, notes, prepared_date and template_id (null to inherit). Only with document_type: invoice, purpose: complete and format: a4; nothing is saved or substituted.None: omit all three.

When a persisted source omits the visual block, the server previews the template that would apply today. Sending only one or two of the three fields is a 422 pdf_presentation_invalid, with the missing one in param. The draft brand selector needs the admin role and assets you already uploaded.

For document_type: invoice, source.form.document.exemption_reason is an optional header exemption cause (catalogue in GET /v1/tax-catalog): its legal mention opens the automatic mentions of the preview, as when the invoice is saved. Omit it or send null for no cause. An unknown cause, or one sent on another document type, is rejected.

A good way to preview a design you are editing is to reuse the revision you read:

curl -s https://api.factuarea.com/v1/companies/$COMPANY_ID/pdf-templates/0199f3a2-7c4e-7b1a-9d3e-5a2c8f1b6e40 \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  | jq '{
      document_type: "invoice", purpose: "complete", format: "a4", locale: "en",
      base_preset: .current_revision.base_preset,
      layout_version: .current_revision.layout_version,
      configuration: .current_revision.configuration,
      source: { kind: "example", scenario: "standard" }
    }' > preview.json

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/pdf-template-previews \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data-binary @preview.json

The answer is the artifact, wrapped in data. It is 200 when the PDF is ready and 202 while it is prepared, queued or processing; in the second case poll metadata_url as described in the retry loop (abbreviated):

{
  "data": {
    "id": "0199f405-91ab-7c42-8d6e-2a9f3b5c7e10",
    "purpose": "preview",
    "state": "ready",
    "source_kind": "example",
    "context_authority": "illustrative",
    "sha256": "7a265f5f003866f84e23c7e237301879b7d4a4ecc0ab80d2041a3c980b12310a",
    "size_bytes": 48210,
    "expires_at": "2026-10-15T09:20:00.000000Z",
    "geometry": { "format": "a4", "orientation": "portrait", "width_mm": 210, "height_mm": 297 },
    "metadata_url": "https://api.factuarea.com/v1/companies/0193f1a0-5c2e-7b41-8d06-9a1e2f3c4d5e/pdf-artifacts/0199f405-91ab-7c42-8d6e-2a9f3b5c7e10",
    "download_url": "https://api.factuarea.com/v1/companies/0193f1a0-5c2e-7b41-8d06-9a1e2f3c4d5e/pdf-artifacts/0199f405-91ab-7c42-8d6e-2a9f3b5c7e10/download",
    "retry_after": null,
    "error": null,
    "unavailable_reason": null
  }
}

The artifact also reports the effective_font and warnings of the rendering, context_fingerprint, artifact_fingerprint and the tokens described next.

Authority: what a preview can authorize

context_authority tells how far you can trust the preview.

context_authorityOriginUse
illustrativeexample, unsaved_form, or a persisted source with a different visual block.Look only. It has no document_id, source_token or selection_version.
delivery_preconditionpersisted_draft previewing the template that applies.It returns source_token, selection_version, template_revision_id, commercial_identity_revision_id and expected_preview_context_fingerprint: the values of the presentation precondition of the issue, send and accept operations. See sending documents.
revision_preconditionpresentation_revision.The same values, valid only for an explicit revision of that document. They never authorize issuing, sending, accepting or signing.
substitution_preconditioninvoice_substitution.It returns the same values as the other preconditions, specific to the substitution of that invoice. No v1 operation consumes them (POST /v1/companies/{company}/invoices/substitute-simplified takes no precondition), and they never authorize issuing, sending, accepting or signing.
capturedThe original document artifacts.The retained PDF of a captured document.

A preview is never converted into the official PDF. If the source, the selection, the brand, the configuration or the rendering environment changes after you previewed, the precondition no longer matches and the action answers 409; preview again.

Previews and the company switch

New previews and new document artifacts need revisioned output to be active for your company: the platform switch on and your company's initialization finished (the read-only render_activation of the defaults reports it). Otherwise the call returns 422 pdf_presentation_not_ready and creates nothing. Nothing else is lost: retrying later works, and reads and the classic rendering keep working.

Prepare, render and read a document PDF

The official artifact of a document is created from its retained presentation: the id of the presentation block that the document carries (see Document presentations), together with its document type, purpose and paper format.

Prepare. POST /v1/companies/{company}/pdf-artifacts with document_type, document_id, presentation_id, purpose, format and variant. It creates or finds the artifact for exactly that presentation, so repeating the call returns the same one.

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/pdf-artifacts \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "document_type": "invoice",
    "document_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a42",
    "presentation_id": "0199f401-33de-7a8c-9e15-b7c4d2a60f38",
    "purpose": "complete",
    "format": "a4",
    "variant": "official"
  }'

Render. POST /v1/companies/{company}/pdf-artifacts/{artifact}/render with { "mode": "sync" } to wait for the PDF in the request, or { "mode": "async" } to queue it and poll. Rendering a ready artifact does not render again or charge again.

Read the state. GET /v1/companies/{company}/pdf-artifacts/{artifact} always answers 200 with the metadata, whatever the state. These reads accept no query string, no body and no If-Match.

Download. GET /v1/companies/{company}/pdf-artifacts/{artifact}/download returns the bytes once the state is ready.

prepare and render answer 200 with the artifact when it is already ready, and 202 while it is prepared, queued or processing. state is one of prepared, queued, processing, ready, failed, unavailable and corrupt. A failed artifact carries error with the code (pdf_generation_failed, pdf_engine_profile_unavailable, pdf_image_resource_unavailable or pdf_payload_resource_unavailable), a retryable flag and a correlation_id to give to support. retry_after is the number of seconds to wait while it is pending.

Original and derived PDFs

  • The official variant is the original: the PDF of the presentation captured with the document, with an immutable checksum. It is the one pdf_output_state reports. Changing a template, a default or a brand later never alters it.
  • A different look for an already issued document is a new presentation: you revise the document explicitly, which creates a derived presentation with its own artifact. The original stays readable.
  • The alternative variant is another artifact of the same presentation in an authorized paper format. It never replaces the original, never changes what the document publishes and is not the one pdf_output_state reports. A different style always needs a new presentation.
  • If the bytes of an original are lost or fail their checksum, you get 500 pdf_original_unavailable or 500 pdf_original_corrupt. Nothing is silently regenerated in its place.
  • A derived presentation (a signature, the legal QR or refreshed legal data) keeps the layout of its original. If the current release no longer serves that layout, preparing it answers 500 pdf_original_layout_unavailable: the original is intact and nothing is prepared. Contact support; an explicit, authorized revision creates another artifact without replacing the original.

How downloads answer

StatusWhenWhat to do
200The PDF is ready. application/pdf, Content-Disposition: attachment, Cache-Control: private, no-store and a strong ETag: the SHA-256 of the bytes between quotes.Store it; keep the ETag.
304You sent If-None-Match with the current ETag. It is decided after authorization, privacy and a check of the stored bytes.Use your copy.
202GET /v1/companies/{company}/{documents}/{id}/pdf only: the PDF is still being generated, including a render that failed but can be retried, which this request claimed again (a retryable failure is never answered with the earlier error). JSON data with pdf_url and pdf_output_state, and a Retry-After header.Wait and repeat the same request.
503GET .../download of an artifact that is not ready: pdf_artifact_not_ready with Retry-After. The metadata still answers 200. The same 503 comes from a preview, prepare, render, GET /v1/companies/{company}/{documents}/{id}/pdf or GET /v1/companies/{company}/invoices/{invoice}/pdf-link while another request is still preparing that same PDF; that request created nothing.Wait Retry-After seconds and repeat the same request.
503pdf_generation_failed: the direct document endpoints could not produce the PDF; the document and its data are intact.Wait and repeat the same request.
410pdf_signature_personal_data_forgotten: the signature data of the document was erased. Terminal.Do not retry; the artifact has no download any more.
404pdf_artifact_not_found: unknown, from another company, or an expired preview.Create a new preview.
500pdf_original_unavailable, pdf_original_corrupt, pdf_original_layout_unavailable, or a render that failed terminally: pdf_engine_profile_unavailable, pdf_payload_resource_unavailable, pdf_image_resource_unavailable. Nothing is enqueued.Contact support with request_id.

Document-level GET /v1/companies/{company}/invoices/{invoice}/pdf, GET /v1/companies/{company}/quotes/{quote}/pdf, GET /v1/companies/{company}/proformas/{proforma}/pdf and GET /v1/companies/{company}/delivery-notes/{delivery_note}/pdf return the same 200 with the bytes, the 304 with If-None-Match and the 202 above. They accept download=1 for an attachment disposition, and invoices also format. See the reference of invoices, quotes, proformas and delivery notes. When the original cannot be served they answer an error instead of a 202: the statuses above apply, plus 409 when the retained presentation cannot be delivered as requested: pdf_presentation_conflict (it changed since you read it), pdf_presentation_context_changed (for example, the requested format differs from the retained one) and, for invoices, pdf_legal_source_changed (revise it with source_policy: "current_legal"). Read the document presentation again before requesting the PDF. Before a document has a retained presentation, or while your company still renders with the classic pipeline, the PDF is served as before.

The pdf_output_state block

Invoices, quotes, proformas and delivery notes carry a read-only pdf_output_state object that summarizes their original PDF. It never prepares or renders anything.

{
  "state": "processing",
  "presentation_id": "0199f401-33de-7a8c-9e15-b7c4d2a60f38",
  "artifact_id": "0199f405-91ab-7c42-8d6e-2a9f3b5c7e10",
  "format": "a4",
  "variant": "official",
  "unavailable_reason": null,
  "error": null,
  "retry_after": 1,
  "download_url": null
}
FieldMeaning
stateprepared, queued and processing (pending), ready, failed, unavailable, corrupt, or null when the document has no captured original yet.
presentation_id, artifact_idThe retained presentation and its original artifact; artifact_id is what you pass to /v1/companies/{company}/pdf-artifacts/{artifact}.
format, variantPaper format and official.
unavailable_reasonprivacy_erased (state unavailable, the original is gone for good) or original_corrupt (state corrupt).
error{ code, retryable, correlation_id } for failed, corrupt and erased originals.
retry_afterSeconds to wait while pending, otherwise null.
download_urlOnly when ready: the relative v1 URL /v1/companies/{company}/pdf-artifacts/{id}/download, never a storage path. Resolve it against the API host, or build the call from artifact_id.

All the keys are always present; a document without a captured original has them all null. A privacy erasure shows as state: unavailable with error.code: pdf_signature_personal_data_forgotten, no retry_after and no download_url.

A correct retry loop

Poll the cheap read, wait what the server asks, bound the attempts, and stop on terminal answers. Do not regenerate to "speed it up".

async function downloadOriginal(companyId, artifactId, key) {
  const base = 'https://api.factuarea.com/v1/companies/' + companyId + '/pdf-artifacts/' + artifactId;
  const headers = { Authorization: 'Bearer ' + key };

  for (let attempt = 1; attempt <= 12; attempt++) {
    const res = await fetch(base + '/download', { headers });

    if (res.status === 200) return Buffer.from(await res.arrayBuffer());
    if (res.status === 503) {
      const wait = Number(res.headers.get('Retry-After')) || 2 * attempt;
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    const { error } = await res.json();
    // 410 is terminal, 500 needs support, 404 means it expired or never existed.
    throw new Error(error.code + ' (' + res.status + ') ' + error.request_id);
  }
  throw new Error('The PDF is still not ready; check the artifact metadata later.');
}
  • Respect Retry-After and the retry_after field. They are seconds. Add a small increase between attempts and a cap on the number of attempts.
  • Poll, do not re-create. Repeat the same GET. Calling prepare or render on every poll only adds noise: they already return the same artifact.
  • Retry a failure on purpose. When state is failed and error.retryable is true, call render again with a new Idempotency-Key (the same key would replay the stored failure). If retryable is false, change the cause first: the asset, the amount of data or the configuration.
  • A new key is a new intention. Reuse the same key only to retry the same request after a timeout.
  • Stop on 410. It will not change.
  • Cache with the ETag. Send If-None-Match to get a free 304.

Idempotency

preview, prepare and render need Idempotency-Key. For these three the fingerprint is computed on the raw body, so the same key with a body that differs by a single byte (key order or whitespace included) returns 422 idempotency_key_reused. A replay returns the stored response, with Idempotent-Replayed: true, before any work and even if the live data changed since; the authorization of the caller is still checked first. Two keys with equivalent bodies produce the same artifact. See Idempotency.

Errors

StatusCodeMeaning
403pdf_template_access_deniedThe role is not allowed, or the module section of the document type is not available to you.
404pdf_artifact_not_found, pdf_presentation_not_foundMissing, from another company or expired.
409pdf_presentation_conflict, pdf_presentation_context_changedThe selection, source, brand or environment changed after the preview. Preview again.
409pdf_legal_source_changedThe legal data of an issued invoice changed since it was captured; the original stays readable and the new delivery is blocked until you revise explicitly.
412 / 428pdf_presentation_etag_mismatch, pdf_presentation_precondition_requiredPresentation preconditions on the operations that use them.
410pdf_signature_personal_data_forgottenTerminal erasure.
422pdf_presentation_invalidA field is invalid. param has the path, such as source.kind.
422pdf_presentation_not_readyRevisioned output is not active for your company yet.
422idempotency_key_requiredThe key is missing.
429pdf_generation_budget_exceededRender budget; wait Retry-After.
500pdf_original_unavailable, pdf_original_corrupt, pdf_original_layout_unavailable, pdf_engine_profile_unavailable, pdf_image_resource_unavailable, pdf_payload_resource_unavailableThe original, its layout or the rendering resources are not available. The last two are not retryable.
503pdf_artifact_not_readyNot ready yet; wait Retry-After.
503pdf_generation_failedThe direct document PDF endpoints could not produce the PDF. The document is intact; repeat later.

Branch on code, not on message. See the error model.

MCP

The five artifact operations are the tools preview_pdf_template, prepare_pdf_artifact, render_pdf_artifact, get_pdf_artifact and read_pdf_artifact, which returns an authorized download link instead of the bytes. They use the same scopes, accept idempotency_key and follow the same states. See MCP tools.

On this page

Need a hand?Contact support