Vistes prèvies i artefactes PDF
Previsualitza una plantilla abans d’usar-la, prepara i renderitza el PDF exacte d’un document, descarrega’l amb ETag i 304, i llegeix pdf_output_state amb un bucle de reintent correcte.
Tot PDF que l’API genera a partir de la biblioteca de plantilles és un artefacte: un PDF exacte i immutable amb el seu propi identificador, el seu estat i la seva suma de comprovació. Hi ha dos tipus.
purpose | Què és | Vida útil |
|---|---|---|
document | El PDF original d’un document: es genera a partir de la presentació amb què es va capturar el document i els seus bytes no canvien mai. | Es conserva amb el document. |
preview | Un esborrany per comprovar com quedarà una plantilla, una marca o un document. No és mai el PDF oficial de res. | Caduca 24 hores després de crear-se (expires_at). |
Quatre grups d’operacions cobreixen el flux. La gestió de la biblioteca, els valors per defecte i la precedència són a la guia de la biblioteca de plantilles PDF.
| Operació | Scopes | Idempotency-Key |
|---|---|---|
POST /v1/companies/{company}/pdf-template-previews | pdfs:read i pdf_templates:read | Obligatòria |
POST /v1/companies/{company}/pdf-artifacts (preparar) | pdfs:read | Obligatòria |
POST /v1/companies/{company}/pdf-artifacts/{artifact}/render | pdfs:read | Obligatòria |
GET /v1/companies/{company}/pdf-artifacts/{artifact} (metadades) | pdfs:read | No s’aplica |
GET /v1/companies/{company}/pdf-artifacts/{artifact}/download | pdfs:read | No s’aplica |
GET /v1/companies/{company}/invoices/{invoice}/pdf i els seus equivalents de pressupost, proforma i albarà | pdfs:read | No s’aplica |
Totes les rutes d’aquesta guia pengen de /v1/companies/{company}/…: {company} és l’id (UUID v7)
del NIF amb què operes, i als exemples curl és $COMPANY_ID.
Arribar a un NIF explica com resoldre’l.
Els artefactes necessiten el rol admin o user i la secció de mòdul de la família del document
(invoices, quotes, proformas o delivery_notes). Els artefactes de vista prèvia segueixen a
més el mòdul pdf_templates i el scope pdf_templates:read, així que llegir-ne un també exigeix
aquest scope. Generar un PDF consumeix el mateix pressupost de renderització que els altres
endpoints de PDF: el treball nou pot retornar 429 pdf_generation_budget_exceeded amb
Retry-After, mentre que les repeticions i els artefactes ja renderitzats no costen res més
(vegeu els límits de peticions).
Previsualitzar una plantilla
POST /v1/companies/{company}/pdf-template-previews genera un PDF sense desar res: no publica cap plantilla, no canvia
cap valor per defecte ni toca cap document. El cos té cinc camps obligatoris i un source tancat:
| Camp | Valor |
|---|---|
document_type | invoice, quote, proforma o delivery-note. |
purpose | Factures: complete, simplified, corrective, simplified_corrective. Altres tipus: complete. |
format | a4, ticket_80 o ticket_58 (tiquets només per a factures). |
locale | es, en o ca. |
source | D’on surten les dades, vegeu a sota. |
Segons l’origen, altres tres camps descriuen el disseny que es genera: base_preset,
layout_version (sempre "1") i configuration (la configuració normalitzada completa, tal com la
retorna una revisió de plantilla).
source.kind | Dades que genera | Bloc visual |
|---|---|---|
example | Dades d’exemple: scenario és standard, long, simplified, corrective, simplified_corrective, f3 o unvalued_delivery_note. commercial_identity és opcional: { "mode": "inherit" } (per defecte), { "mode": "exact_revision", "identity_id", "revision_id" } o { "mode": "draft", "configuration": { ... } } amb les sis claus de marca i, opcionalment, logo_dark_asset_id, brand_image_dark_asset_id, dark_background_fallback i brand_size. | Els tres, obligatoris. |
unsaved_form | L’objecte form que envies (sèrie, contacte, document, destinatari, línies, condicions de pagament...), sense desar res. | Els tres, obligatoris. |
persisted_draft | Un esborrany de document existent: { "kind": "persisted_draft", "document_id" }. | Els tres o cap. |
presentation_revision | Un document emès i la seva presentació retinguda: document_id, parent_id, template_id (null per heretar), source_policy (captured o current_legal) i expected_version. Admet un format diferent del conservat per demanar un altre paper del mateix document emès (tiquet o A4), sense crear revisió. | Els tres o cap. |
invoice_substitution | Una vista prèvia independent de la factura que substituiria factures simplificades: document_id (null si és nova), source_invoice_uuids i line_uuids (mateixa longitud i ordre, fins a 100), client_uuid, notes, prepared_date i template_id (null per heretar). Només amb document_type: invoice, purpose: complete i format: a4; no es desa ni se substitueix res. | Cap: omet els tres. |
Quan un origen persistit omet el bloc visual, el servidor previsualitza la plantilla que
s’aplicaria avui. Enviar només un o dos dels tres camps és un 422 pdf_presentation_invalid, amb
el que falta a param. El selector de marca draft exigeix el rol admin i actius que ja hagis
pujat.
Amb document_type: invoice, source.form.document.exemption_reason és una causa d’exempció de capçalera opcional (catàleg a GET /v1/tax-catalog): la seva menció legal obre les mencions automàtiques de la vista prèvia, igual que en desar la factura. Omet-lo o envia null per no fer servir causa. Una causa desconeguda, o enviada en un altre tipus de document, es rebutja.
Una bona manera de previsualitzar un disseny que estàs editant és reutilitzar la revisió que has llegit:
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.jsonLa resposta és l’artefacte, embolcallat a data. És 200 quan el PDF està llest i 202 mentre està
prepared, queued o processing; en aquest segon cas consulta metadata_url tal com es
descriu al bucle de reintent (abreujat):
{
"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
}
}L’artefacte també informa d’effective_font i warnings de la renderització, de
context_fingerprint, d’artifact_fingerprint i dels tokens que es descriuen a continuació.
Autoritat: què pot autoritzar una vista prèvia
context_authority indica fins on et pots refiar de la vista prèvia.
context_authority | Origen | Ús |
|---|---|---|
illustrative | example, unsaved_form, o un origen persistit amb un altre bloc visual. | Només per mirar. No té document_id, source_token ni selection_version. |
delivery_precondition | persisted_draft previsualitzant la plantilla que s’aplica. | Retorna source_token, selection_version, template_revision_id, commercial_identity_revision_id i expected_preview_context_fingerprint: els valors de la precondició de presentació de les operacions d’emetre, enviar i acceptar. Vegeu enviament de documents. |
revision_precondition | presentation_revision. | Els mateixos valors, vàlids només per a una revisió explícita d’aquest document. No autoritzen mai emetre, enviar, acceptar ni signar. |
substitution_precondition | invoice_substitution. | Retorna els mateixos valors que les altres precondicions, propis de la substitució d'aquesta factura. Cap operació v1 no els consumeix (POST /v1/companies/{company}/invoices/substitute-simplified no rep cap precondició) i mai no autoritzen emetre, enviar, acceptar ni signar. |
captured | Els artefactes document originals. | El PDF retingut d’un document capturat. |
Una vista prèvia no es converteix mai en el PDF oficial. Si després de previsualitzar canvia
l’origen, la selecció, la marca, la configuració o l’entorn de renderització, la precondició deixa de
coincidir i l’acció respon 409; torna a previsualitzar.
Vistes prèvies i l’interruptor de l’empresa
Les vistes prèvies noves i els artefactes de document nous necessiten que la sortida revisionada
estigui activa per a la teva empresa: l’interruptor de la plataforma encès i la inicialització de la
teva empresa acabada (el render_activation de només lectura dels
valors per defecte ho indica). Si no, la crida retorna
422 pdf_presentation_not_ready i no crea res. No es perd res més: reintentar més tard funciona, i
les lectures i la renderització clàssica continuen funcionant.
Preparar, renderitzar i llegir el PDF d’un document
L’artefacte official d’un document es crea a partir de la seva presentació retinguda: l’id del
bloc presentation que porta el document (vegeu
Presentacions de document), juntament amb el seu tipus de
document, propòsit i format de paper.
Preparar. POST /v1/companies/{company}/pdf-artifacts amb document_type, document_id, presentation_id,
purpose, format i variant. Crea o troba l’artefacte d’exactament aquesta presentació, així que
repetir la crida retorna el mateix.
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"
}'Renderitzar. POST /v1/companies/{company}/pdf-artifacts/{artifact}/render amb { "mode": "sync" } per esperar el
PDF dins la petició, o { "mode": "async" } per posar-lo a la cua i consultar-lo després.
Renderitzar un artefacte ready no torna a renderitzar ni a cobrar.
Llegir l’estat. GET /v1/companies/{company}/pdf-artifacts/{artifact} respon sempre 200 amb les metadades, sigui
quin sigui l’estat. Aquestes lectures no admeten query string, cos ni If-Match.
Descarregar. GET /v1/companies/{company}/pdf-artifacts/{artifact}/download retorna els bytes quan l’estat és
ready.
prepare i render responen 200 amb l’artefacte quan ja està ready, i 202 mentre està
prepared, queued o processing. state és un de prepared, queued, processing, ready,
failed, unavailable i corrupt. Un artefacte failed porta error amb el code
(pdf_generation_failed, pdf_engine_profile_unavailable, pdf_image_resource_unavailable o
pdf_payload_resource_unavailable), un indicador retryable i un correlation_id per donar-lo al
suport. retry_after és el nombre de segons que cal esperar mentre és pendent.
PDF originals i derivats
- La variant
officialés l’original: el PDF de la presentació capturada amb el document, amb una suma de comprovació immutable. És el que informapdf_output_state. Canviar després una plantilla, un valor per defecte o una marca no l’altera mai. - Un aspecte diferent per a un document ja emès és una presentació nova: revises el document de manera explícita i això crea una presentació derivada amb el seu propi artefacte. L’original continua sent llegible.
- La variant
alternativeés un altre artefacte de la mateixa presentació en un format de paper autoritzat. No substitueix mai l’original, no canvia mai el que publica el document i no és el que informapdf_output_state. Un estil diferent sempre necessita una presentació nova. - Si els bytes d’un original es perden o no superen la seva suma de comprovació, reps
500 pdf_original_unavailableo500 pdf_original_corrupt. Res no es regenera en silenci al seu lloc. - Una presentació derivada (una signatura, el QR legal o les dades legals actualitzades)
conserva el disseny del seu original. Si la versió actual ja no serveix aquest disseny,
preparar-la respon
500 pdf_original_layout_unavailable: l’original és íntegre i no es prepara res. Contacta amb el suport; una revisió explícita i autoritzada crea un altre artefacte sense substituir l’original.
Com responen les descàrregues
| Estat | Quan | Què fer |
|---|---|---|
200 | El PDF està llest. application/pdf, Content-Disposition: attachment, Cache-Control: private, no-store i un ETag fort: el SHA-256 dels bytes entre cometes. | Desa’l; conserva l’ETag. |
304 | Has enviat If-None-Match amb l’ETag actual. Es decideix després de l’autorització, la privacitat i una comprovació dels bytes desats. | Fes servir la teva còpia. |
202 | Només GET /v1/companies/{company}/{documents}/{id}/pdf: el PDF encara s’està generant, també quan una renderització ha fallat però es pot reintentar i aquesta petició l’ha tornat a reclamar (una fallada reintentable no es respon mai amb l’error anterior). JSON data amb pdf_url i pdf_output_state, i una capçalera Retry-After. | Espera i repeteix la mateixa petició. |
503 | GET .../download d’un artefacte que no està llest: pdf_artifact_not_ready amb Retry-After. Les metadades continuen responent 200. El mateix 503 arriba d’una vista prèvia, de prepare, de render, de GET /v1/companies/{company}/{documents}/{id}/pdf o de GET /v1/companies/{company}/invoices/{invoice}/pdf-link mentre una altra petició encara prepara aquest mateix PDF; aquesta petició no ha creat res. | Espera Retry-After segons i repeteix la mateixa petició. |
503 | pdf_generation_failed: els endpoints directes del document no han pogut produir el PDF; el document i les seves dades continuen intactes. | Espera i repeteix la mateixa petició. |
410 | pdf_signature_personal_data_forgotten: s’han esborrat les dades de la signatura del document. Terminal. | No reintentis; l’artefacte ja no té descàrrega. |
404 | pdf_artifact_not_found: desconegut, d’una altra empresa o una vista prèvia caducada. | Crea una vista prèvia nova. |
500 | pdf_original_unavailable, pdf_original_corrupt, pdf_original_layout_unavailable o una renderització que ha fallat de manera definitiva: pdf_engine_profile_unavailable, pdf_payload_resource_unavailable, pdf_image_resource_unavailable. No s’encua res. | Contacta amb el suport amb el request_id. |
GET /v1/companies/{company}/invoices/{invoice}/pdf, GET /v1/companies/{company}/quotes/{quote}/pdf, GET /v1/companies/{company}/proformas/{proforma}/pdf i
GET /v1/companies/{company}/delivery-notes/{delivery_note}/pdf, a nivell de document, retornen el mateix 200 amb els
bytes, el 304 amb If-None-Match i el 202 anterior. Accepten download=1 per a una disposició
d’adjunt, i les factures també format. Consulta la referència de
factures,
pressupostos,
factures proforma i
albarans. Quan l’original no es
pot servir responen amb un error en lloc d’un 202: s’apliquen els estats anteriors, més 409 quan la presentació retinguda no es pot lliurar com es demana: pdf_presentation_conflict (ha canviat des que la vas llegir), pdf_presentation_context_changed (per exemple, el format sol·licitat no coincideix amb el retingut) i, en factures, pdf_legal_source_changed (revisa-la amb source_policy: "current_legal"). Torna a llegir la presentació del document abans de demanar el PDF. Mentre un
document no té presentació retinguda, o la teva empresa continua renderitzant amb el flux clàssic, el
PDF es serveix com abans.
El bloc pdf_output_state
Les factures, els pressupostos, les factures proforma i els albarans porten un objecte
pdf_output_state de només lectura que resumeix el seu PDF original. No prepara ni renderitza mai
res.
{
"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
}| Camp | Significat |
|---|---|
state | prepared, queued i processing (pendent), ready, failed, unavailable, corrupt, o null quan el document encara no té un original capturat. |
presentation_id, artifact_id | La presentació retinguda i el seu artefacte original; artifact_id és el que passes a /v1/companies/{company}/pdf-artifacts/{artifact}. |
format, variant | Format de paper i official. |
unavailable_reason | privacy_erased (estat unavailable, l’original s’ha perdut per sempre) o original_corrupt (estat corrupt). |
error | { code, retryable, correlation_id } per a failed, corrupt i originals esborrats. |
retry_after | Segons que cal esperar mentre és pendent; en un altre cas null. |
download_url | Només quan està ready: la URL v1 relativa /v1/companies/{company}/pdf-artifacts/{id}/download, mai una ruta d’emmagatzematge. Resol-la contra el host de l’API o construeix la crida a partir d’artifact_id. |
Totes les claus hi són sempre; un document sense original capturat les té totes a null. Un
esborrat per privacitat es mostra com a state: unavailable amb
error.code: pdf_signature_personal_data_forgotten, sense retry_after i sense download_url.
Un bucle de reintent correcte
Consulta la lectura barata, espera el que demana el servidor, limita els intents i para davant les respostes terminals. No regeneris per “anar més de pressa”.
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.');
}- Respecta
Retry-Afteri el campretry_after. Són segons. Afegeix un petit increment entre intents i un topall al nombre d’intents. - Consulta, no tornis a crear. Repeteix el mateix
GET. Cridarprepareorendera cada consulta només hi afegeix soroll: ja retornen el mateix artefacte. - Reintenta un error a consciència. Quan
stateésfailedierror.retryableéstrue, torna a cridarrenderamb unaIdempotency-Keynova (la mateixa clau repetiria l’error desat). Siretryableésfalse, canvia abans la causa: l’actiu, la quantitat de dades o la configuració. - Una clau nova és una intenció nova. Reutilitza la mateixa clau només per reintentar la mateixa petició després d’un timeout.
- Para davant un
410. No canviarà. - Fes servir memòria cau amb l’
ETag. EnviaIf-None-Matchper obtenir un304gratuït.
Idempotència
preview, prepare i render necessiten Idempotency-Key. En aquestes tres l’empremta es calcula
sobre el cos cru, així que la mateixa clau amb un cos que difereix en un sol byte (també l’ordre
de les claus o els espais) retorna 422 idempotency_key_reused. Una repetició retorna la resposta
desada, amb Idempotent-Replayed: true, abans de fer cap feina i encara que les dades vives hagin
canviat des d’aleshores; l’autorització de qui crida es comprova abans. Dues claus amb cossos
equivalents porten al mateix artefacte. Consulta Idempotència.
Errors
| Estat | Codi | Significat |
|---|---|---|
403 | pdf_template_access_denied | El rol no està permès, o la secció de mòdul del tipus de document no està disponible per a tu. |
404 | pdf_artifact_not_found, pdf_presentation_not_found | No existeix, és d’una altra empresa o ha caducat. |
409 | pdf_presentation_conflict, pdf_presentation_context_changed | Ha canviat la selecció, l’origen, la marca o l’entorn després de la vista prèvia. Torna a previsualitzar. |
409 | pdf_legal_source_changed | Les dades legals d’una factura emesa han canviat des que es va capturar; l’original continua sent llegible i el lliurament nou queda bloquejat fins que la revisis de manera explícita. |
412 / 428 | pdf_presentation_etag_mismatch, pdf_presentation_precondition_required | Precondicions de presentació a les operacions que les fan servir. |
410 | pdf_signature_personal_data_forgotten | Esborrat terminal. |
422 | pdf_presentation_invalid | Un camp no és vàlid. param porta la ruta, com source.kind. |
422 | pdf_presentation_not_ready | La sortida revisionada encara no està activa per a la teva empresa. |
422 | idempotency_key_required | Falta la clau. |
429 | pdf_generation_budget_exceeded | Pressupost de renderització; espera Retry-After. |
500 | pdf_original_unavailable, pdf_original_corrupt, pdf_original_layout_unavailable, pdf_engine_profile_unavailable, pdf_image_resource_unavailable, pdf_payload_resource_unavailable | L’original, el seu disseny o els recursos de renderització no estan disponibles. Els dos últims no es poden reintentar. |
503 | pdf_artifact_not_ready | Encara no està llest; espera Retry-After. |
503 | pdf_generation_failed | Els endpoints directes del PDF del document no l’han pogut produir. El document està intacte; repeteix més tard. |
Ramifica segons code, no segons message. Consulta el model d’errors.
MCP
Les cinc operacions d’artefacte són les tools preview_pdf_template, prepare_pdf_artifact,
render_pdf_artifact, get_pdf_artifact i read_pdf_artifact, que retorna un enllaç de descàrrega
autoritzat en lloc dels bytes. Fan servir els mateixos scopes, accepten idempotency_key i segueixen
els mateixos estats. Consulta les tools MCP.