Factuarea APIDevelopers

Document presentations

Choose the PDF template of an invoice, quote, pro-forma or delivery note, read what each document retained, revise it after issuing, and recover from ETag, CAS and precondition errors.

The presentation of a document is how its PDF looks: which template, which brand, which paper format. Presentation never changes the fiscal content of the document. It follows the document through three moments:

  1. While it is a draft you choose a template (or let it be inherited). Nothing is frozen yet.
  2. When it is issued or delivered Factuarea retains the presentation that was used. From then on the original cannot be edited.
  3. After that you can append a presentation revision: a new retained presentation derived from the previous one, with a reason. The history is append-only.

This guide covers the four resources that expose it — invoices, quotes, proformas and delivery_notes — the template of a series, the precondition that delivery operations ask for, and how to recover from every conflict. To build the templates themselves see Document templates, and to render a PDF before committing to anything see Template previews.

The operations

Each resource exposes the same four operations. {id} is the document UUID and the resource is invoices, quotes, proformas or delivery-notes. 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.

OperationPurposeScope
GET /v1/companies/{company}/{resource}/{id}/presentationRead the current state. Returns an ETag.{resource}:read
PATCH /v1/companies/{company}/{resource}/{id}/presentationChoose or clear the template of a draft.{resource}:write
GET /v1/companies/{company}/{resource}/{id}/presentation-historyList the retained presentations, paginated with page and per_page (default 25, up to 100).{resource}:read
POST /v1/companies/{company}/{resource}/{id}/presentation-revisionsAppend a revision to a document that is no longer a draft.{resource}:write
GET /v1/companies/{company}/series/{series}/pdf-templateRead the template preference of a series. Returns an ETag.series:read
PATCH /v1/companies/{company}/series/{series}/pdf-templateSet or clear it.series:write

{resource}:read and {resource}:write are the scopes the document already had: invoices:write for invoices, delivery_notes:write for delivery notes, and so on. Presentation does not add scopes to the document operations. The preview that feeds a revision also needs pdfs:read and pdf_templates:read. The MCP server exposes the same operations as get_invoice_presentation, assign_invoice_presentation, list_invoice_presentations and revise_invoice_presentation (and their quote, proforma and delivery_note twins), with the scopes above.

Writes accept an Idempotency-Key like any other v1 write; see Idempotency. None of these operations generates a PDF: they move metadata and consume no PDF budget.

Writes on the presentation resources (PATCH and presentation-revisions) answer 422 pdf_presentation_not_ready while the revisioned presentation is not active for your company. The state tells you beforehand: render_activation is legacy and can_select and can_revise are false. Reads keep working, and so does the PDF you already download.

Read the state

curl -i https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197c4a0-6b1e-7d52-8f3a-2c9d4e1b7a60/presentation \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"
HTTP/1.1 200 OK
ETag: "9f2c1b7e0a4d5c8e3b6a1f0d2c9e4b7a5d8c3f6e1a0b9d2c7e4f5a8b1c3d6e90"
{
  "data": {
    "id": null,
    "manual_template_id": null,
    "selection_version": 1,
    "pointer_version": 1,
    "binding_version": 1,
    "render_activation": "revisioned",
    "can_select": true,
    "can_revise": false,
    "capture_matches_source": null,
    "selection": {
      "document_type": "invoice",
      "document_id": "0197c4a0-6b1e-7d52-8f3a-2c9d4e1b7a60",
      "origin": "series",
      "template_id": "0197c4a1-02aa-7c31-9e5b-6d2f8a4c1b73",
      "revision_id": "0197c4a1-02ab-7e08-8a14-3b7c9d5e2f40",
      "presentation_id": null,
      "selection_version": 1,
      "base_preset": "modern",
      "layout_version": "1",
      "locale": "es",
      "selection_mode": "inherit",
      "purpose": "complete",
      "format": "a4",
      "contact_id": null,
      "commercial_identity_id": null,
      "commercial_identity_revision_id": null,
      "commercial_identity_version": null,
      "preview_context_fingerprint": "5d8c3f6e1a0b9d2c7e4f5a8b1c3d6e909f2c1b7e0a4d5c8e3b6a1f0d2c9e4b7a",
      "warnings": []
    },
    "captured_presentation": null,
    "selection_candidate": null,
    "selection_availability": { "selectable": true, "reason": null, "requires_confirmation": false },
    "warnings": [],
    "legal_source_changed": false
  }
}

The state has exactly fifteen fields. The ones you act on:

FieldMeaning
selectionWhat the document would use now, and why: origin is manual, series, contact_default, document_type_default, company_default or legacy (see precedence). selection_mode is manual only when you chose a template on the document.
selection_versionCAS counter of the manual choice. Starts at 1. Send it back as expected_version (or expected_selection_version) when you change the template.
captured_presentationThe retained presentation that is current, or null while nothing was captured (a draft). Its id is the parent_id of a revision.
pointer_versionCAS counter of the retained history. It is the expected_version of a revision.
can_select, can_reviseWhether PATCH or a revision is allowed right now. They are the contract: do not infer them from the document status.
selection_availabilityselectable is false with a reason (for example template_archived) when the inherited or chosen template can no longer be used.
warningsNotices such as pdf_series_template_unavailable: the series template cannot be used and the next level applied. Each has requires_confirmation.
legal_source_changedtrue when the legal data of the issuer or recipient changed after the capture, so a revision with source_policy: "current_legal" would differ.
capture_matches_sourcenull for a draft; otherwise whether the retained presentation still reflects the document.

A document that was never touched has a virtual state: id is null and the versions are 1. Reading never creates anything.

Choose a template on a draft

A draft takes its template in two ways. Both use the same selector, pdf_template_id:

You sendResult
Nothing (omit pdf_template_id)On create the template is inherited; on update the current choice is kept.
"pdf_template_id": nullOn create, inherit. On update or PATCH, clear the manual choice and go back to inheriting.
"pdf_template_id": "<uuid>"Manual choice: that template wins over every inherited level.

pdf_template_uuid is the spelling of the internal application and the public API does not accept it: it only matters when revisioned presentation is enabled for the company, where sending it, or both selectors, answers 422; otherwise it is ignored.

On create (POST /v1/companies/{company}/invoices, /quotes, /proformas, /delivery-notes, and the corrective operations of invoices) send pdf_template_id. There is no version to compare yet, so expected_selection_version is rejected when POST /v1/companies/{company}/invoices creates an invoice and on the corrective operations. On /quotes, /proformas and /delivery-notes it only matters when the POST resolves an existing external_id (an upsert): then it is the selection CAS and travels with pdf_template_id.

On update (PATCH /v1/companies/{company}/invoices/{id} and its siblings) the template and its version travel together: pdf_template_id and expected_selection_version must be both present or both absent. Missing one answers 422 on expected_selection_version.

curl -X PATCH https://api.factuarea.com/v1/companies/$COMPANY_ID/quotes/0197c4b2-91de-7a40-b6c3-5e8f1d2a9c04 \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "pdf_template_id": "0197c4a1-02aa-7c31-9e5b-6d2f8a4c1b73",
    "expected_selection_version": 1
  }'

With the presentation resource you change only the template, without touching the rest of the document. The body has one required key, template_id (explicit null clears it), and a precondition: expected_version in the body, an If-Match header with the ETag you read, or both.

curl -X PATCH https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197c4a0-6b1e-7d52-8f3a-2c9d4e1b7a60/presentation \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'If-Match: "9f2c1b7e0a4d5c8e3b6a1f0d2c9e4b7a5d8c3f6e1a0b9d2c7e4f5a8b1c3d6e90"' \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "0197c4a1-02aa-7c31-9e5b-6d2f8a4c1b73",
    "expected_version": 1
  }'

It answers 200 with the new state and its new ETag. Choosing the template that is already chosen changes nothing and does not move the version. The template must be one of your own, published, not archived, and compatible with the document (an invoice template for an invoice, with a format that fits the document purpose); otherwise nothing is saved. A document that is no longer a draft answers 422 pdf_presentation_invalid with param: "presentation": from then on use a revision.

Precedence and series

A document with no manual choice is resolved down a fixed ladder, and the first level that has an available template wins:

  1. Manual selection on the document. It is exclusive: if that template stops being available you get an explicit error, it does not fall through.
  2. Template of the series the document is numbered in.
  3. Preference of the customer (contact) for that document type and purpose.
  4. Default of the company for that document type and purpose.
  5. Default of the company.
  6. The existing PDF settings of the company (legacy), which always exist.

An inherited level that cannot be used (archived template, plan without the feature) is skipped and reported as a warning in warnings, so the document still renders. The customer and company levels are configured with the library operations of Document templates.

The template of a series

A series stores an optional template and a version of its own, independent of the series data:

curl https://api.factuarea.com/v1/companies/$COMPANY_ID/series/0197c4c3-17f0-7b92-a1d4-8e6b3c5f2a18/pdf-template \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"
{
  "data": {
    "id": "0197c4c3-17f0-7b92-a1d4-8e6b3c5f2a18",
    "object": "series_pdf_template",
    "pdf_template_id": null,
    "version": 1,
    "document_type": "invoice",
    "purpose": "complete",
    "is_series_archived": false,
    "template_state": null,
    "supported_formats": ["a4", "ticket_80", "ticket_58"],
    "availability": { "selectable": true, "reason": null },
    "warnings": []
  }
}

PATCH it with pdf_template_id (omit it to keep, null to inherit, a UUID to assign) and the precondition, expected_version and/or If-Match:

curl -X PATCH https://api.factuarea.com/v1/companies/$COMPANY_ID/series/0197c4c3-17f0-7b92-a1d4-8e6b3c5f2a18/pdf-template \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "pdf_template_id": "0197c4a1-02aa-7c31-9e5b-6d2f8a4c1b73",
    "expected_version": 1
  }'
  • Assigning a template never changes the numbering mask, the counter or any document already issued. The preference is only a default for new documents.
  • It also travels in the series write itself: POST /v1/companies/{company}/series accepts pdf_template_id, and PATCH /v1/companies/{company}/series/{series} accepts pdf_template_id with pdf_template_expected_version (required whenever the selector is present, even if it is null or does not change). The series response returns pdf_template_id and pdf_template_version. See Series.
  • Invoices, quotes, pro-formas and delivery notes can use a template only if it is compatible with the purpose of the series. Quotes, pro-formas and delivery notes take A4 and the complete purpose.
  • If the saved template stops being selectable (the plan changed, the template was archived) the preference is kept, the read reports availability.selectable: false and the warning inherited_candidate_unavailable, and new documents fall to the next level. Clearing it is always allowed.
  • Editing the series template needs the current authority of the issuer on series: an administrator or a user without the series-settings restriction. Otherwise 403 series_template_access_denied.

The delivery precondition

The moment a document leaves the draft stage its presentation is retained, so the operations that issue or deliver it ask you to confirm what you saw: the precondition. When the revisioned presentation is active for your company and the request carries no precondition, the operation answers 428 pdf_presentation_precondition_required and nothing happens.

What it contains

Seven fields, all required together (two of them nullable but never omitted):

FieldWhere the value comes from
expected_selection_versionselection_version of the presentation state.
expected_source_tokensource_token of the preview artifact.
expected_preview_context_fingerprintexpected_preview_context_fingerprint of the preview artifact.
expected_template_revision_idtemplate_revision_id of the preview artifact (null when no library template applies).
expected_commercial_identity_revision_idcommercial_identity_revision_id of the preview artifact (null when there is no brand).
purposeThe purpose you previewed: for invoices complete, simplified, corrective or simplified_corrective; complete for the other documents.
formata4, ticket_80 or ticket_58; only a4 for quotes, pro-formas and delivery notes.

Get the artifact with a preview of the saved draft: POST /v1/companies/{company}/pdf-template-previews with source: { "kind": "persisted_draft", "document_id": "<uuid>" }. The preview does not freeze anything; it shows what the document would be right now and returns the values to confirm. If the draft changes afterwards (lines, customer, series, template), the values are stale and the next step fails on purpose.

Where it goes

OperationShape
POST /v1/companies/{company}/invoices/{id}/issue, POST /v1/companies/{company}/invoices/{id}/sendThe seven fields flat, at the top level of the body.
POST /v1/companies/{company}/quotes/{id}/send, .../accept; POST /v1/companies/{company}/proformas/{id}/send, .../accept; POST /v1/companies/{company}/delivery-notes/{id}/sendAn object presentation_precondition with the seven fields.
POST /v1/companies/{company}/invoices/bulk-send, .../bulk-status; bulk-send and bulk-status of quotes, proformas and delivery notespresentation_preconditions: a map from each document UUID that is in ids to its own precondition. Source tokens cannot be shared by two documents.
curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/quotes/0197c4b2-91de-7a40-b6c3-5e8f1d2a9c04/send \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "compras@cliente.example",
    "presentation_precondition": {
      "expected_selection_version": 2,
      "expected_source_token": "3b6a1f0d2c9e4b7a5d8c3f6e1a0b9d2c7e4f5a8b1c3d6e909f2c1b7e0a4d5c8e",
      "expected_preview_context_fingerprint": "5d8c3f6e1a0b9d2c7e4f5a8b1c3d6e909f2c1b7e0a4d5c8e3b6a1f0d2c9e4b7a",
      "expected_template_revision_id": "0197c4a1-02ab-7e08-8a14-3b7c9d5e2f40",
      "expected_commercial_identity_revision_id": null,
      "purpose": "complete",
      "format": "a4"
    }
  }'

With the revisioned presentation not active, those fields are ignored, and so are pdf_template_id and expected_selection_version on create, update and duplicate: the request behaves exactly as it did before the feature and never answers pdf_presentation_not_ready because of them. Once it is active they are validated.

Accepting a quote or a pro-forma. The presentation_precondition object is required only for the first capture: when the document has no retained capture yet (it was never shared) or its draft was edited or re-templated after it was captured. Without it the request answers 428 pdf_presentation_precondition_required before anything is written. A document that already has a retained capture (for a pro-forma, one sent as a draft) is accepted without the object and that capture follows the number; cite it with the optional expected_presentation_uuid, which answers 409 pdf_presentation_conflict if it is no longer the current one. Accepting a draft numbers it and captures what was reviewed.

The conversion operations (POST /v1/companies/{company}/quotes/{quote}/convert, POST /v1/companies/{company}/proformas/{proforma}/convert, POST /v1/companies/{company}/delivery-notes/{delivery_note}/convert) take no precondition (nor does POST /v1/companies/{company}/invoices/{invoice}/duplicate): sending one answers 422 when the revisioned presentation is active and is ignored otherwise. The invoice they create is a new document whose template is resolved by precedence. The acceptance of a quote or a pro-forma and the send of a delivery note can also name the presentation the customer received with expected_presentation_uuid (the id of the retained presentation); the delivery-note send also takes expected_publication_id, the publication the customer received. In the delivery-note send body, template_id is the legacy email template and has nothing to do with pdf_template_id.

In a batch

A bulk operation does not fail as a whole. Each document without its entry (or with a stale one) is reported in failures[] with its own error_code, here pdf_presentation_precondition_required or pdf_presentation_context_changed, and the others go ahead. See Bulk operations.

Revise after issuing

Once a document is issued, delivered or accepted, its retained presentation cannot be edited. To change how the PDF looks you append a revision. It copies the previous retained presentation and changes only what you ask:

  • The template (template_id): omit it to keep the parent's, null to inherit, or a UUID.
  • Optionally purpose and format. The language is always the parent's.
  • source_policy: captured keeps the legal data exactly as retained; current_legal refreshes the issuer and recipient identification fields from the current data. The state's legal_source_changed tells you when that matters.

A revision never reissues the document, never changes numbers, amounts or the VeriFactu record, and never deletes a previous presentation.

Because it creates retained evidence, it requires proof that you looked at the result. Request a preview with source.kind: "presentation_revision", the same document_id, parent_id, template_id, source_policy and expected_version, and use the artifact it returns:

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197c4a0-6b1e-7d52-8f3a-2c9d4e1b7a60/presentation-revisions \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "parent_id": "0197c4d4-3a21-7f16-9b8e-1c5d7a2e4f90",
    "expected_version": 1,
    "template_id": "0197c4a1-02aa-7c31-9e5b-6d2f8a4c1b73",
    "source_policy": "captured",
    "reason": "El cliente pide la plantilla corporativa para su archivo.",
    "expected_source_token": "3b6a1f0d2c9e4b7a5d8c3f6e1a0b9d2c7e4f5a8b1c3d6e909f2c1b7e0a4d5c8e",
    "expected_preview_context_fingerprint": "5d8c3f6e1a0b9d2c7e4f5a8b1c3d6e909f2c1b7e0a4d5c8e3b6a1f0d2c9e4b7a",
    "expected_template_revision_id": "0197c4a1-02ab-7e08-8a14-3b7c9d5e2f40",
    "expected_commercial_identity_revision_id": null
  }'
FieldNotes
parent_idcaptured_presentation.id of the state: the revision you start from.
expected_versionpointer_version of the state. It is the CAS of the history.
reasonRequired, 1 to 500 characters. It is stored with the revision.
expected_source_token, expected_preview_context_fingerprintFrom the preview artifact (source_token, expected_preview_context_fingerprint). Exactly 64 lowercase hexadecimal characters.
expected_template_revision_id, expected_commercial_identity_revision_idFrom the preview artifact. Both are sent together with the template, or omitted together; expected_template_revision_id must be null when template_id is null.

It answers 201 with the new retained presentation and the refreshed state:

{
  "data": {
    "presentation": {
      "id": "0197c4d5-8e02-7a63-8c1f-4b9d2e6a3f15",
      "number": 2,
      "parent_id": "0197c4d4-3a21-7f16-9b8e-1c5d7a2e4f90",
      "kind": "derived",
      "derivation_kind": "visual_revision",
      "created_at": "2026-10-14T09:42:17.000000Z",
      "reason": "El cliente pide la plantilla corporativa para su archivo.",
      "template_id": "0197c4a1-02aa-7c31-9e5b-6d2f8a4c1b73",
      "revision_id": "0197c4a1-02ab-7e08-8a14-3b7c9d5e2f40",
      "origin": "manual",
      "locale": "es",
      "purpose": "complete",
      "format": "a4",
      "source_kind": "captured",
      "availability": "captured",
      "unavailable_reason": null
    },
    "state": { "id": "0197c4d5-8e02-7a63-8c1f-4b9d2e6a3f15", "pointer_version": 2 }
  }
}

(state is the full object of the state; it is abridged here.)

The history lists the same summaries, with meta.total:

curl "https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197c4a0-6b1e-7d52-8f3a-2c9d4e1b7a60/presentation-history?per_page=10" \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"

kind is original, derived or legacy_observed (a document that predates retained presentations: observed, never invented). derivation_kind says how a derived one was made: visual_revision, legal_snapshot_refresh, legal_qr or signature_capture. availability is captured while the snapshot is readable; if the signature data of a delivery note was erased for privacy, the metadata stays and availability is unavailable with unavailable_reason: "privacy_erased". Revising from that presentation answers 410 pdf_signature_personal_data_forgotten.

A revision is evidence about the presentation; it does not mean the PDF bytes already exist. Downloading and rendering documents is covered in Template previews.

ETag, If-Match and recovery

Presentation uses three guards. They answer different questions, so a client should know which one failed:

GuardSent asFails withMeaningWhat to do
Representation tagIf-Match header412 pdf_presentation_etag_mismatch (series: series_template_precondition_failed)The resource you read is not the current one.GET again, look at what changed, and retry with the new ETag.
Version (CAS)expected_version or expected_selection_version409 pdf_presentation_conflict (series: series_template_version_conflict)Someone changed the choice or the history after you read it.GET again, reapply your intent on the fresh version.
Contextexpected_* of the precondition or revision409 pdf_presentation_context_changedThe document, template or brand changed after the preview.Request a new preview and confirm again.
Missingnothing428 pdf_presentation_precondition_required (series: series_template_precondition_required)A guard is mandatory and you sent none.Send the version, the ETag or the precondition.

Rules that apply to every PATCH:

  • The tag is the strong ETag of that same URL (GET/PATCH of .../presentation or .../pdf-template), never the tag of the document, a PDF or another resource. A weak validator (W/"...") never matches.
  • When you send both, the tag is evaluated first (412), then the version (409).
  • The revision operation does not accept If-Match; its guard is expected_version in the body (422 if you send the header).
  • Errors are the usual envelope. Branch on code; param points at the guard (if_match, expected_version, expected_preview_context_fingerprint). The series conflict adds details.current_version and details.recovery: "reload_or_keep_local".
{
  "error": {
    "type": "conflict_error",
    "code": "pdf_presentation_conflict",
    "message": "La presentación documental ha cambiado. Actualiza los datos antes de volver a guardar.",
    "param": "expected_version",
    "request_id": "req_01JBVH7K9Y4N3CDQ2EHJB1AGSV",
    "doc_url": "https://docs.factuarea.com/guides/errors#pdf_presentation_conflict"
  }
}

Errors

HTTPcodeWhen
404pdf_presentation_not_foundparent_id is not a presentation of that document.
404invoice_not_found, quote_not_found, proforma_not_found, delivery_note_not_found, series_not_foundThe document or series does not exist or belongs to another company.
410pdf_signature_personal_data_forgottenThe presentation lost its personal data (privacy erasure); it cannot be revised.
412pdf_presentation_etag_mismatchIf-Match does not match the current representation.
409pdf_presentation_conflictexpected_version is stale.
409pdf_presentation_context_changedThe preview context no longer matches.
409pdf_legal_source_changedThe legal data of the document changed after the presentation you were about to deliver. Revise with source_policy: "current_legal".
422pdf_presentation_invalidInvalid or unknown key (param), a document that cannot take this action (param: "presentation"), or a malformed If-Match.
422pdf_presentation_not_readyThe revisioned presentation is not active for the company.
428pdf_presentation_precondition_requiredNo expected_version, If-Match or precondition where one is mandatory.
403insufficient_scopeThe key lacks the scope of the table above.
403series_template_access_deniedThe issuer has no authority on series for this operation.

On this page

Need a hand?Contact support