Vistas previas y artefactos PDF
Previsualiza una plantilla antes de usarla, prepara y renderiza el PDF exacto de un documento, descárgalo con ETag y 304, y lee pdf_output_state con un bucle de reintento correcto.
Todo PDF que la API genera a partir de la biblioteca de plantillas es un artefacto: un PDF exacto e inmutable con su propio identificador, su estado y su suma de comprobación. Existen dos tipos.
purpose | Qué es | Vida útil |
|---|---|---|
document | El PDF original de un documento: se genera a partir de la presentación con la que se capturó el documento y sus bytes nunca cambian. | Se conserva con el documento. |
preview | Un borrador para comprobar cómo quedará una plantilla, una marca o un documento. Nunca es el PDF oficial de nada. | Caduca 24 horas después de crearse (expires_at). |
Cuatro grupos de operaciones cubren el flujo. La gestión de la biblioteca, los valores por defecto y la precedencia están en la guía de la biblioteca de plantillas PDF.
| Operación | Scopes | Idempotency-Key |
|---|---|---|
POST /v1/companies/{company}/pdf-template-previews | pdfs:read y pdf_templates:read | Obligatoria |
POST /v1/companies/{company}/pdf-artifacts (preparar) | pdfs:read | Obligatoria |
POST /v1/companies/{company}/pdf-artifacts/{artifact}/render | pdfs:read | Obligatoria |
GET /v1/companies/{company}/pdf-artifacts/{artifact} (metadatos) | pdfs:read | No aplica |
GET /v1/companies/{company}/pdf-artifacts/{artifact}/download | pdfs:read | No aplica |
GET /v1/companies/{company}/invoices/{invoice}/pdf y sus equivalentes de presupuesto, proforma y albarán | pdfs:read | No aplica |
Todas las rutas de esta guía cuelgan de /v1/companies/{company}/…: {company} es el id (UUID v7)
del NIF con el que operas, y en los ejemplos curl es $COMPANY_ID.
Alcanzar un NIF explica cómo resolverlo.
Los artefactos necesitan el rol admin o user y la sección de módulo de la familia del documento
(invoices, quotes, proformas o delivery_notes). Los artefactos de vista previa siguen además
el módulo pdf_templates y el scope pdf_templates:read, así que leer uno también exige ese scope.
Generar un PDF consume el mismo presupuesto de renderizado que los demás endpoints de PDF: el
trabajo nuevo puede devolver 429 pdf_generation_budget_exceeded con Retry-After, mientras que
las repeticiones y los artefactos ya renderizados no cuestan nada más (véanse los
límites de peticiones).
Previsualizar una plantilla
POST /v1/companies/{company}/pdf-template-previews genera un PDF sin guardar nada: no publica una plantilla, no cambia
un valor por defecto ni toca un documento. El cuerpo tiene cinco campos obligatorios y un source
cerrado:
| Campo | Valor |
|---|---|
document_type | invoice, quote, proforma o delivery-note. |
purpose | Facturas: complete, simplified, corrective, simplified_corrective. Otros tipos: complete. |
format | a4, ticket_80 o ticket_58 (tickets solo para facturas). |
locale | es, en o ca. |
source | De dónde salen los datos, véase abajo. |
Según el origen, otros tres campos describen el diseño que se genera: base_preset,
layout_version (siempre "1") y configuration (la configuración normalizada completa, tal como
la devuelve una revisión de plantilla).
source.kind | Datos que genera | Bloque visual |
|---|---|---|
example | Datos de ejemplo: scenario es standard, long, simplified, corrective, simplified_corrective, f3 o unvalued_delivery_note. commercial_identity es opcional: { "mode": "inherit" } (por defecto), { "mode": "exact_revision", "identity_id", "revision_id" } o { "mode": "draft", "configuration": { ... } } con las seis claves de marca y, opcionalmente, logo_dark_asset_id, brand_image_dark_asset_id, dark_background_fallback y brand_size. | Los tres, obligatorios. |
unsaved_form | El objeto form que envías (serie, contacto, documento, destinatario, líneas, condiciones de pago...), sin guardar nada. | Los tres, obligatorios. |
persisted_draft | Un borrador de documento existente: { "kind": "persisted_draft", "document_id" }. | Los tres o ninguno. |
presentation_revision | Un documento emitido y su presentación retenida: document_id, parent_id, template_id (null para heredar), source_policy (captured o current_legal) y expected_version. Admite un format distinto del conservado para pedir otro papel del mismo emitido (ticket o A4), sin crear revisión. | Los tres o ninguno. |
invoice_substitution | Una vista previa independiente de la factura que sustituiría a facturas simplificadas: document_id (null si es nueva), source_invoice_uuids y line_uuids (misma longitud y orden, hasta 100), client_uuid, notes, prepared_date y template_id (null para heredar). Solo con document_type: invoice, purpose: complete y format: a4; no se guarda ni se sustituye nada. | Ninguno: omite los tres. |
Cuando un origen persistido omite el bloque visual, el servidor previsualiza la plantilla que se
aplicaría hoy. Enviar solo uno o dos de los tres campos es un 422 pdf_presentation_invalid, con el
que falta en param. El selector de marca draft exige el rol admin y activos que ya hayas
subido.
Con document_type: invoice, source.form.document.exemption_reason es una causa de exención de cabecera opcional (catálogo en GET /v1/tax-catalog): su mención legal abre las menciones automáticas de la vista previa, igual que al guardar la factura. Omítelo o envía null para no usar causa. Una causa desconocida, o enviada en otro tipo de documento, se rechaza.
Una buena forma de previsualizar un diseño que estás editando es reutilizar la revisión que leíste:
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 respuesta es el artefacto, envuelto en data. Es 200 cuando el PDF está listo y 202 mientras
está prepared, queued o processing; en este segundo caso consulta metadata_url como se
describe en el bucle de reintento (abreviado):
{
"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
}
}El artefacto también informa de effective_font y warnings del renderizado, de
context_fingerprint, de artifact_fingerprint y de los tokens que se describen a continuación.
Autoridad: qué puede autorizar una vista previa
context_authority indica hasta dónde puedes fiarte de la vista previa.
context_authority | Origen | Uso |
|---|---|---|
illustrative | example, unsaved_form, o un origen persistido con otro bloque visual. | Solo para mirar. No tiene document_id, source_token ni selection_version. |
delivery_precondition | persisted_draft previsualizando la plantilla que se aplica. | Devuelve source_token, selection_version, template_revision_id, commercial_identity_revision_id y expected_preview_context_fingerprint: los valores de la precondición de presentación de las operaciones de emitir, enviar y aceptar. Véase envío de documentos. |
revision_precondition | presentation_revision. | Los mismos valores, válidos solo para una revisión explícita de ese documento. Nunca autorizan emitir, enviar, aceptar ni firmar. |
substitution_precondition | invoice_substitution. | Devuelve los mismos valores que las otras precondiciones, propios de la sustitución de esa factura. Ninguna operación v1 los consume (POST /v1/companies/{company}/invoices/substitute-simplified no recibe precondición) y nunca autorizan emitir, enviar, aceptar ni firmar. |
captured | Los artefactos document originales. | El PDF retenido de un documento capturado. |
Una vista previa nunca se convierte en el PDF oficial. Si después de previsualizar cambia el origen,
la selección, la marca, la configuración o el entorno de renderizado, la precondición deja de
coincidir y la acción responde 409; vuelve a previsualizar.
Vistas previas y el interruptor de la empresa
Las vistas previas nuevas y los artefactos de documento nuevos necesitan que la salida revisionada
esté activa para tu empresa: el interruptor de la plataforma encendido y la inicialización de tu
empresa terminada (el render_activation de solo lectura de los
valores por defecto lo indica). Si no, la llamada devuelve
422 pdf_presentation_not_ready y no crea nada. No se pierde nada más: reintentar más tarde
funciona, y las lecturas y el renderizado clásico siguen funcionando.
Preparar, renderizar y leer el PDF de un documento
El artefacto official de un documento se crea a partir de su presentación retenida: el id del
bloque presentation que lleva el documento (véase
Presentaciones de documento), junto con su tipo de documento,
propósito y formato de papel.
Preparar. POST /v1/companies/{company}/pdf-artifacts con document_type, document_id, presentation_id,
purpose, format y variant. Crea o encuentra el artefacto de exactamente esa presentación, así
que repetir la llamada devuelve el mismo.
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"
}'Renderizar. POST /v1/companies/{company}/pdf-artifacts/{artifact}/render con { "mode": "sync" } para esperar el
PDF dentro de la petición, o { "mode": "async" } para encolarlo y consultarlo después. Renderizar
un artefacto ready no vuelve a renderizar ni a cobrar.
Leer el estado. GET /v1/companies/{company}/pdf-artifacts/{artifact} responde siempre 200 con los metadatos,
sea cual sea el estado. Estas lecturas no admiten query string, cuerpo ni If-Match.
Descargar. GET /v1/companies/{company}/pdf-artifacts/{artifact}/download devuelve los bytes cuando el estado es
ready.
prepare y render responden 200 con el artefacto cuando ya está ready, y 202 mientras está
prepared, queued o processing. state es uno de prepared, queued, processing, ready,
failed, unavailable y corrupt. Un artefacto failed lleva error con el code
(pdf_generation_failed, pdf_engine_profile_unavailable, pdf_image_resource_unavailable o
pdf_payload_resource_unavailable), un indicador retryable y un correlation_id para dárselo a
soporte. retry_after es el número de segundos que hay que esperar mientras está pendiente.
PDF originales y derivados
- La variante
officiales el original: el PDF de la presentación capturada con el documento, con una suma de comprobación inmutable. Es el que informapdf_output_state. Cambiar después una plantilla, un valor por defecto o una marca nunca lo altera. - Un aspecto distinto para un documento ya emitido es una presentación nueva: revisas el documento de forma explícita y eso crea una presentación derivada con su propio artefacto. El original sigue siendo legible.
- La variante
alternativees otro artefacto de la misma presentación en un formato de papel autorizado. Nunca sustituye al original, nunca cambia lo que publica el documento y no es el que informapdf_output_state. Un estilo distinto siempre necesita una presentación nueva. - Si los bytes de un original se pierden o no superan su suma de comprobación, recibes
500 pdf_original_unavailableo500 pdf_original_corrupt. Nada se regenera en silencio en su lugar. - Una presentación derivada (una firma, el QR legal o los datos legales actualizados)
conserva el diseño de su original. Si la versión actual ya no sirve ese diseño, prepararla
responde
500 pdf_original_layout_unavailable: el original está íntegro y no se prepara nada. Contacta con soporte; una revisión explícita y autorizada crea otro artefacto sin sustituir el original.
Cómo responden las descargas
| Estado | Cuándo | Qué hacer |
|---|---|---|
200 | El PDF está listo. application/pdf, Content-Disposition: attachment, Cache-Control: private, no-store y un ETag fuerte: el SHA-256 de los bytes entre comillas. | Guárdalo; conserva el ETag. |
304 | Enviaste If-None-Match con el ETag actual. Se decide después de la autorización, la privacidad y una comprobación de los bytes guardados. | Usa tu copia. |
202 | Solo GET /v1/companies/{company}/{documents}/{id}/pdf: el PDF aún se está generando, también cuando un renderizado falló pero se puede reintentar y esta petición lo ha vuelto a reclamar (un fallo reintentable nunca se responde con el error anterior). JSON data con pdf_url y pdf_output_state, y una cabecera Retry-After. | Espera y repite la misma petición. |
503 | GET .../download de un artefacto que no está listo: pdf_artifact_not_ready con Retry-After. Los metadatos siguen respondiendo 200. El mismo 503 llega de una vista previa, de prepare, de render, de GET /v1/companies/{company}/{documents}/{id}/pdf o de GET /v1/companies/{company}/invoices/{invoice}/pdf-link mientras otra petición sigue preparando ese mismo PDF; esa petición no ha creado nada. | Espera Retry-After segundos y repite la misma petición. |
503 | pdf_generation_failed: los endpoints directos del documento no pudieron producir el PDF; el documento y sus datos siguen intactos. | Espera y repite la misma petición. |
410 | pdf_signature_personal_data_forgotten: se borraron los datos de la firma del documento. Terminal. | No reintentes; el artefacto ya no tiene descarga. |
404 | pdf_artifact_not_found: desconocido, de otra empresa o una vista previa caducada. | Crea una vista previa nueva. |
500 | pdf_original_unavailable, pdf_original_corrupt, pdf_original_layout_unavailable o un renderizado que falló de forma definitiva: pdf_engine_profile_unavailable, pdf_payload_resource_unavailable, pdf_image_resource_unavailable. No se encola nada. | Contacta con soporte con el request_id. |
GET /v1/companies/{company}/invoices/{invoice}/pdf, GET /v1/companies/{company}/quotes/{quote}/pdf, GET /v1/companies/{company}/proformas/{proforma}/pdf y
GET /v1/companies/{company}/delivery-notes/{delivery_note}/pdf, a nivel de documento, devuelven el mismo 200 con los
bytes, el 304 con If-None-Match y el 202 anterior. Aceptan download=1 para una disposición
de adjunto, y las facturas también format. Consulta la referencia de
facturas,
presupuestos,
facturas proforma y
albaranes. Cuando el original no
se puede servir responden con un error en lugar de un 202: se aplican los estados anteriores, más 409 cuando la presentación conservada no se puede entregar como se pide: pdf_presentation_conflict (cambió desde que la leíste), pdf_presentation_context_changed (por ejemplo, el format solicitado no coincide con el conservado) y, en facturas, pdf_legal_source_changed (revísala con source_policy: "current_legal"). Vuelve a leer la presentación del documento antes de pedir el PDF.
Mientras un documento no tiene presentación retenida, o tu empresa sigue renderizando con el flujo
clásico, el PDF se sirve como antes.
El bloque pdf_output_state
Las facturas, los presupuestos, las facturas proforma y los albaranes llevan un objeto
pdf_output_state de solo lectura que resume su PDF original. Nunca prepara ni renderiza nada.
{
"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
}| Campo | Significado |
|---|---|
state | prepared, queued y processing (pendiente), ready, failed, unavailable, corrupt, o null cuando el documento aún no tiene un original capturado. |
presentation_id, artifact_id | La presentación retenida y su artefacto original; artifact_id es lo que pasas a /v1/companies/{company}/pdf-artifacts/{artifact}. |
format, variant | Formato de papel y official. |
unavailable_reason | privacy_erased (estado unavailable, el original se ha perdido para siempre) u original_corrupt (estado corrupt). |
error | { code, retryable, correlation_id } para failed, corrupt y originales borrados. |
retry_after | Segundos que hay que esperar mientras está pendiente; en otro caso null. |
download_url | Solo cuando está ready: la URL v1 relativa /v1/companies/{company}/pdf-artifacts/{id}/download, nunca una ruta de almacenamiento. Resuélvela contra el host de la API o construye la llamada a partir de artifact_id. |
Todas las claves están siempre presentes; un documento sin original capturado las tiene todas a
null. Un borrado por privacidad se muestra como state: unavailable con
error.code: pdf_signature_personal_data_forgotten, sin retry_after y sin download_url.
Un bucle de reintento correcto
Consulta la lectura barata, espera lo que pide el servidor, limita los intentos y para ante las respuestas terminales. No regeneres para "ir más rápido".
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.');
}- Respeta
Retry-Aftery el camporetry_after. Son segundos. Añade un pequeño incremento entre intentos y un tope en el número de intentos. - Consulta, no vuelvas a crear. Repite el mismo
GET. Llamar aprepareorenderen cada consulta solo añade ruido: ya devuelven el mismo artefacto. - Reintenta un fallo a propósito. Cuando
stateesfailedyerror.retryableestrue, llama de nuevo arendercon unaIdempotency-Keynueva (la misma clave repetiría el fallo guardado). Siretryableesfalse, cambia antes la causa: el activo, la cantidad de datos o la configuración. - Una clave nueva es una intención nueva. Reutiliza la misma clave solo para reintentar la misma petición tras un timeout.
- Para ante un
410. No va a cambiar. - Usa caché con el
ETag. EnvíaIf-None-Matchpara obtener un304gratis.
Idempotencia
preview, prepare y render necesitan Idempotency-Key. En estas tres la huella se calcula
sobre el cuerpo crudo, así que la misma clave con un cuerpo que difiere en un solo byte (también
el orden de las claves o los espacios) devuelve 422 idempotency_key_reused. Una repetición devuelve
la respuesta guardada, con Idempotent-Replayed: true, antes de hacer ningún trabajo y aunque los
datos vivos hayan cambiado desde entonces; la autorización de quien llama se comprueba antes. Dos
claves con cuerpos equivalentes llevan al mismo artefacto. Consulta
Idempotencia.
Errores
| Estado | Código | Significado |
|---|---|---|
403 | pdf_template_access_denied | El rol no está permitido, o la sección de módulo del tipo de documento no está disponible para ti. |
404 | pdf_artifact_not_found, pdf_presentation_not_found | No existe, es de otra empresa o caducó. |
409 | pdf_presentation_conflict, pdf_presentation_context_changed | Cambió la selección, el origen, la marca o el entorno después de la vista previa. Vuelve a previsualizar. |
409 | pdf_legal_source_changed | Los datos legales de una factura emitida cambiaron desde que se capturó; el original sigue siendo legible y el envío nuevo queda bloqueado hasta que la revises de forma explícita. |
412 / 428 | pdf_presentation_etag_mismatch, pdf_presentation_precondition_required | Precondiciones de presentación en las operaciones que las usan. |
410 | pdf_signature_personal_data_forgotten | Borrado terminal. |
422 | pdf_presentation_invalid | Un campo no es válido. param trae la ruta, como source.kind. |
422 | pdf_presentation_not_ready | La salida revisionada aún no está activa para tu empresa. |
422 | idempotency_key_required | Falta la clave. |
429 | pdf_generation_budget_exceeded | Presupuesto de renderizado; 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 | El original, su diseño o los recursos de renderizado no están disponibles. Los dos últimos no son reintentables. |
503 | pdf_artifact_not_ready | Aún no está listo; espera Retry-After. |
503 | pdf_generation_failed | Los endpoints directos del PDF del documento no pudieron producirlo. El documento está intacto; repite más tarde. |
Ramifica según code, no según message. Consulta el modelo de errores.
MCP
Las cinco operaciones de artefacto son las tools preview_pdf_template, prepare_pdf_artifact,
render_pdf_artifact, get_pdf_artifact y read_pdf_artifact, que devuelve un enlace de descarga
autorizado en lugar de los bytes. Usan los mismos scopes, aceptan idempotency_key y siguen los
mismos estados. Consulta las tools MCP.