Facturación desde terminales desatendidos
Emite una factura simplificada ya cobrada desde un terminal de autoservicio o una máquina expendedora en UNA sola llamada idempotente, imprime el QR VERI*FACTU que devuelve y sabe qué hacer cuando una llamada falla, se repite o se rechaza.
Un terminal de autoservicio o una máquina expendedora venden, cobran e imprimen en
segundos, sin nadie que corrija un error. Esta guía explica cómo un terminal así
usa POST /v1/invoices
para crear, emitir, cobrar y registrar una factura simplificada en una sola
llamada, y qué recibe para poder imprimirla.
La arquitectura es la que la AEAT admite para los terminales: un terminal más un sistema central que genera el registro de facturación y lo devuelve, de modo que el terminal imprime la factura con su QR (FAQ de desarrolladores de la AEAT de 4 de diciembre de 2025, sección 5). Factuarea es ese sistema central. Genera, numera, encadena, firma cuando el modo lo exige y remite todos los registros; el terminal no hace nada de eso.
El software que corre en el terminal tiene obligaciones propias, entre ellas una declaración propia. Están en Cumplimiento del componente del integrador.
Cuándo aplica
A una empresa que emite facturas simplificadas desde un dispositivo sin operador y tiene lo siguiente:
- Facturas simplificadas habilitadas para la empresa. Si no,
type: F2responde422simplified_invoices_disabled. - VeriFactu activado, en cualquiera de los dos modos. Compruébalo con
GET /v1/verifactu/config. Sin él no se puede generar el alta y el bloqueverifacturespondestatus: failedconerror_code: verifactu_not_enabled. - Una API key con
invoices:write(yverifactu:readpara seguir el registro). Empieza con una clavefact_test_: los registros de una empresa de prueba nunca se remiten a la AEAT y su QR apunta al servicio de preproducción de la AEAT. - Una serie de facturas simplificadas. No necesitas elegirla: sin
series_idel ticket se numera en tu serie de simplificadas por defecto, que se crea sola la primera vez. Si envías unseries_id, debe ser de una serie de facturas simplificadas (invoice_kind: simplified); con una serie de facturas completas la llamada responde422series_invoice_kind_mismatchy no queda ningún borrador.GET /v1/series/default?document_type=invoice&invoice_kind=simplifieddevuelve la serie que se usará.
Una factura simplificada solo vale para operaciones de hasta 3.000 € IVA incluido y nunca para operaciones intracomunitarias, con inversión del sujeto pasivo o de exportación. Consulta Facturas simplificadas o completas.
La llamada
Todo va en un único POST /v1/invoices. Estos son los campos que lo convierten en
un cobro desatendido:
| Campo | Qué hace |
|---|---|
type: "F2" | Factura simplificada. Sin client_id es un ticket anónimo; con uno, una factura simplificada cualificada. |
external_id | La identidad de esta venta. No caduca nunca: es lo que hace seguro un reintento tardío. |
prices_include_tax: true | Cada unit_price es el precio final que pagó el cliente, con IVA incluido. |
payment | Registra el pago (method; paid_at y reference opcionales) por todo el importe tras emitir. |
options.register_verifactu: true | Genera el alta VERI*FACTU antes de responder. |
options.wait_for_pdf: true | Espera hasta unos 15 segundos al PDF A4. |
options.send_automatically y options.send_to | Envía la factura por email una vez emitida. |
operation_on | El día en que se realizó la operación, cuando difiere de issued_on. |
curl -X POST https://api.factuarea.com/v1/invoices \
-H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
-H "Content-Type: application/json" \
-d '{
"type": "F2",
"issued_on": "2026-06-01",
"due_on": "2026-06-01",
"external_id": "KIOSK-0042-20260601-000187",
"prices_include_tax": true,
"lines": [
{ "description": "Lavado exprés", "quantity": 1, "unit_price": 5.00, "tax_rate": 21 }
],
"payment": { "method": "credit_card", "reference": "TPV-8841-000187" },
"options": { "register_verifactu": true, "wait_for_pdf": true }
}'Por orden, el sistema crea el borrador, lo emite (número definitivo), registra el pago, genera el alta con su huella y su QR, envía el email si lo pediste y prepara el PDF. La respuesta es la factura más tres bloques:
{
"data": {
"id": "019e5b7a-3c1d-7a52-b4e1-6f2d9c8a1e37",
"type": "F2",
"number": "S-2026-001",
"status": "paid",
"subtotal": 4.13,
"total_vat": 0.87,
"total": 5,
"verifactu": {
"status": "registered",
"error_code": null,
"aeat_status": "pending",
"huella": "98F790A3765977E437FAEBCF1EF9B2B0B3462174D3D36375047185223BDE4308",
"qr_url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345678&numserie=S-2026-001&fecha=01-06-2026&importe=5.00",
"qr_png_base64": "iVBORw0KGgo…",
"legend": "VERI*FACTU",
"csv": null
},
"pdf": {
"status": "ready",
"url": "https://app.factuarea.com/api/pdf/materialized?company=01931b3e-1111-7a2e-9a8b-3c5d6e7f8a01&expires=1780389734&type=invoice&uuid=019e5b7a-3c1d-7a52-b4e1-6f2d9c8a1e37&signature=67d5ef33797d2d164f32bd6ceedeeefd82ceed8cb2f04023db5a5e5c926fe085",
"expires_at": "2026-06-02T10:42:14+02:00"
},
"public_url": "https://app.factuarea.com/d/019e5b7a-3c1d-7a52-b4e1-6f2d9c8a1e37"
}
}(El recurso de factura lleva todas sus claves habituales; aquí solo se muestran las
relevantes.) Los tres bloques aparecen solo en un cobro desatendido, es decir,
en una petición con type: F2, un bloque payment u options.register_verifactu.
Los listados, el detalle y los webhooks nunca los llevan.
verifactues lo que imprimes.statusesregisteredcuando el alta existe yfailedcuando la factura está emitida pero no se pudo generar el alta.aeat_statuses el estado del registro ante la AEAT: la transmisión a la AEAT sigue su propio curso, por lotes, después de que hayas respondido al cliente.csvesnullhasta que la AEAT lo acepta.pdfes el PDF A4.readysignifica que está materializado yurllo sirve al instante;pendingsignifica que la generación está en cola y la URL responde404durante unos segundos. Nunca es un error.public_urles la página desde la que el cliente descarga la factura.
Precios con IVA incluido
Un terminal sabe lo que pagó el cliente, no la base neta. Con
prices_include_tax: true cada unit_price es el precio final y Factuarea calcula
la base al céntimo para que el total de la factura sea igual a la suma de los
importes que enviaste. Un ticket de 5,00 € al 21 %, por ejemplo, se factura con una
base de 4,13 € más 0,87 € de IVA, y el total es exactamente los 5,00 € cobrados.
El redondeo rara vez permite un reparto exacto: 0,60 € al 21 % no tiene ninguna base que dé 0,60 € como línea suelta. La regla es cualquier línea, y partir:
- La línea con la base mayor absorbe los céntimos; si no puede, se prueba cada una de las demás de mayor a menor base, sin tocar nunca las líneas de importe cero.
- Si ninguna puede, la línea mayor se parte en dos: la original, con su cantidad, su descuento y sus vínculos, y una línea complementaria de una unidad con la misma descripción y el mismo IVA, cuya base es la menor posible (de 1 a 3 céntimos). El total sigue siendo exactamente lo que cobraste.
- Solo se rechaza con
422amount_reconciliation_failed, sin emitir nada, una diferencia mayor que un céntimo por línea, que ya no es redondeo. Una retención o un recargo de equivalencia pueden provocarla.
En este modo no se admiten líneas de catálogo (product_id).
IVA de una línea. La línea toma el tax_rate que envías, luego el impuesto al
que hace referencia, luego el impuesto de su producto y, si no hay ninguno, el
IVA por defecto de la empresa para facturas. Si la empresa tampoco lo tiene, la
llamada responde 422 missing_required_param con error.param: lines.2.tax_rate
y error.line_index: 2, el índice de la línea que hay que corregir, empezando en
cero. La API nunca adivina un tipo. Los errores de dominio que nacen de una línea
llevan line_index del mismo modo (un precio ausente, una causa de exención fuera
de su catálogo…); es aditivo, y param sigue nombrando el campo.
Mostrar el QR
El QR es el QR tributario de la AEAT: hay que mostrarlo como exige la AEAT (Orden HAC/1177/2024 y la especificación del QR de la AEAT). En la práctica:
- Tamaño y margen. Entre 30 × 30 mm y 40 × 40 mm, con al menos 2 mm de margen en blanco alrededor. Factuarea imprime 30 mm en los tickets.
- Nivel. Nivel de corrección de errores M.
qr_png_base64ya es de nivel M y no lleva prefijodata:. Si tu impresora necesita otra resolución, escala la imagen por un factor entero o sin interpolación para que los módulos se mantengan nítidos. - Rótulo y leyenda. El rótulo
QR tributario:va encima del código y lalegendque llega en la respuesta va debajo:VERI*FACTUen modo verificable, o la frase «Factura verificable en la sede electrónica de la AEAT» en el otro. La leyenda usa una letra no menor que la del resto de los datos de la factura. - Lugar. Al principio del documento, junto con los datos de la factura.
- No lo alteres. Imprime
qr_png_base64yhuellaexactamente como llegan. No los recalcules ni cambies importes, número o fecha después de la respuesta.
Si prefieres no maquetar el recibo tú mismo, pide a Factuarea que te lo dé ya formateado para un rollo térmico:
curl "https://api.factuarea.com/v1/invoices/019e5b7a-3c1d-7a52-b4e1-6f2d9c8a1e37/pdf?format=ticket_80" \
-H "Authorization: Bearer fact_live_3pXnR2VbY7TcA9eFmN5z8KqW" \
--output ticket.pdfformat es a4 (el valor por defecto, con la plantilla de la empresa),
ticket_80 (rollo de 80 mm) o ticket_58 (rollo de 58 mm). Un ticket se maqueta
para una impresora de recibos: QR de 30 mm, sin banda de cabecera ni pie, y una
altura ajustada al contenido. Cada formato se genera y se guarda en caché por
separado, así que pedir un ticket nunca cambia el A4 y la URL firmada del A4 sigue
funcionando. El ticket lleva el contenido obligatorio de una factura simplificada,
la fecha de la operación cuando difiere y el destinatario cuando la factura es
cualificada. El ETag cambia cuando se crea el alta, así que un PDF descargado
antes (sin QR) nunca se revalida como vigente. Consulta
GET /v1/invoices/{invoice}/pdf.
Reintentos, repeticiones y concurrencia
La red va a perder la respuesta de una llamada que sí tuvo éxito. Para eso está
external_id, que es independiente de la cabecera Idempotency-Key: no
caduca y se guarda en la factura. Consulta Idempotencia.
- Reenvía la misma petición, con el mismo
external_id. Si la factura ya está emitida, la API responde200conIdempotent-Replayed: truey esa factura. Antes de responder completa lo que faltaba: emite un borrador que nunca se emitió, registra el pago si aún hay saldo y genera el alta. No crea nada nuevo ni consume otro número. - El email no se envía dos veces. Una repetición no vuelve a enviar por email una factura cuyo email ya está encolado o entregado.
- Una factura cancelada o anulada no recibe pago ni alta; la repetición solo informa de su estado.
- Otra venta, otro
external_id. Si el tipo o el total de la repetición difieren de la factura emitida, la respuesta es409idempotency_key_reusedconsubcode: unattended_replay_mismatchyparam: external_id. Es un fallo en cómo construyes los identificadores: no reutilices nunca uno. - Dos peticiones simultáneas con el mismo
external_idproducen una factura, un número y un pago. La segunda espera hasta 20 segundos a la primera y después la repite. Si la primera no ha terminado, la segunda responde409resource_lockedconparam: external_idy no ha escrito nada: envía la misma petición otra vez pasados unos segundos.
Elige un external_id por venta que sea estable y único en la empresa, como
idCajero-fecha-secuencia (KIOSK-0042-20260601-000187), y guárdalo antes de la
primera llamada.
Cuando verifactu.status es failed
La factura está emitida y numerada, pero no se pudo generar su alta.
verifactu.error_code dice por qué: certificate_missing, certificate_expired,
certificate_revoked, certificate_nif_mismatch, clock_drift_exceeded,
representation_required, system_certificate_unavailable o
verifactu_not_enabled.
El terminal no debe entregar todavía el documento como factura válida: trata la
venta como pendiente, conserva su external_id, resuelve la causa (un certificado,
una representación, la activación de VeriFactu de la empresa) y reenvía la misma
petición. La repetición genera el alta y responde 200. Consulta
Alta automática en VeriFactu.
Errores que el terminal debe tratar
| Respuesta | Significado | Qué hacer |
|---|---|---|
200 + Idempotent-Replayed: true | La venta ya estaba emitida. | Imprime lo que llega, si el primer intento no imprimió. |
409 idempotency_key_reused (unattended_replay_mismatch) | El external_id pertenece a otro tipo o total. | Corrige el generador de identificadores. No reintentes. |
409 resource_locked | Otra petición con el mismo external_id sigue en curso. | Envía la misma petición otra vez en unos segundos. |
422 missing_required_param, param: options.send_to | Se pidió un email sin destinatario (sin cliente, o con un cliente sin email). | No se creó nada. Envía un send_to o no pidas el email. |
422 missing_required_param, param: lines.N.tax_rate | Una línea no tiene IVA y la empresa no tiene uno por defecto. | Envía tax_rate o configura el IVA por defecto. |
422 simplified_invoices_disabled / simplified_invoice_not_allowed | Las simplificadas no están habilitadas, o el importe supera 3.000 € IVA incluido. | Habilítalas en la empresa, o emite una factura completa (F1). |
422 amount_reconciliation_failed | Los importes con IVA incluido no pueden sumar exactamente. | Revisa el IVA, los descuentos, la retención y el recargo de cada línea. |
422 verifactu_not_eligible | Algo no cabe en el registro de la AEAT, o la empresa no puede firmar. | Consulta las dos tablas siguientes. No se emite nada y no se consume número. |
429 y 5xx | Límite o fallo transitorio. | Reintenta con espera creciente, con el mismo external_id. |
422 verifactu_not_eligible: un dato no cabe en el registro
Antes de confirmar la emisión, Factuarea comprueba que el alta que generará la
factura es válida para la AEAT. Si no lo es, la factura no se emite, no consume
número y te recuperas corrigiendo el dato y repitiendo la llamada. error.param lo
nombra, con el vocabulario de la API:
error.param | Qué no cabe |
|---|---|
client_id | Nombre del cliente ausente o de más de 120 caracteres; un NIF español que no tiene 9 caracteres; una identificación extranjera de más de 20; un país que la AEAT no admite; una factura completa sin cliente. |
series_id | El número de la factura tiene más de 60 caracteres o caracteres que la AEAT no admite (solo ASCII imprimible, y ni ", ', <, > ni =). |
original_invoice_id | El número de la factura rectificada no es admisible. |
simplified_invoice_uuids | El número de una factura simplificada sustituida por una F3 no es admisible. |
company_name | La razón social de la empresa tiene más de 120 caracteres. Una razón social ausente es 422 business_rule_violation, con este mismo param. |
lines | Más de 12 desgloses fiscales distintos, o una base, cuota o recargo que no cabe en 12 cifras enteras y 2 decimales. |
total | Un total, o su cuota, que no cabe en el formato; una F2 por encima de 3.000 €. |
type | La marca de factura simplificada cualificada con un tipo que no la admite. |
422 verifactu_not_eligible: la empresa no puede firmar
Solo para una empresa que tiene VeriFactu activado en modo NO VERI*FACTU y no
tiene un certificado utilizable. En ese modo cada registro de facturación se firma,
y un registro solo cuenta como generado cuando está firmado, así que la factura no
se emite ni se anula. El error lleva subcode: signing_certificate_unavailable y el
motivo en error.param:
error.param | Qué falta | Quién lo resuelve |
|---|---|---|
certificate | El certificado de la empresa está ausente, caducado, revocado, emitido para otro NIF o ilegible. | La empresa: Ajustes → Certificado digital. |
representation | La representación que permite a Factuarea firmar en su nombre no está activa (modos de remisión por tercero). | La empresa: registrarla, o pasar a su propio certificado. |
system_certificate | El certificado de Factuarea no está disponible. | Factuarea. Reintenta en unos minutos; si persiste, contacta con soporte. |
La factura queda exactamente como estaba (un borrador sin número) y no se consume nada. Una empresa con VeriFactu desactivado, o en modo VERI*FACTU, nunca se bloquea por esta regla.
Conversiones, devoluciones y errores
- Devolución de la mercancía o del dinero. Una devolución es una factura
rectificativa:
POST /v1/invoices/{invoice}/corrective. La rectificativa de una factura simplificada anónima se registra comoR5; la de una cualificada, comoR1aR4con la misma marca. Consulta Facturas rectificativas. - El cliente pide una factura completa de tickets ya emitidos:
POST /v1/invoices/substitute-simplifiedagrupa las facturas simplificadas bajo unaF3. - Una operación emitida por error y ya cobrada, como un cobro duplicado:
POST /v1/invoices/{invoice}/annulconrevert_collections: truerevierte todos los pagos vigentes y anula la factura en una sola operación atómica. Si falla algún paso no se revierte nada. Es una anulación, no un reembolso: si el cliente tiene que recuperar su dinero, emite una rectificativa.can-annulte lo anticipa (requires_collection_reversal,active_collections_amount). - La conversión de un presupuesto, una proforma o un albarán en factura responde
con
warningsywarning_codes(zero_rate_line_without_exemption) cuando una línea queda al 0 % sin causa de exención, porque esos documentos no la modelan. No bloquea: la factura es un borrador y fijas la causa antes de emitirla.
Sin conexión no hay factura
No existe un modo sin conexión. Un terminal sin conexión no emite: la factura existe solo cuando la API ha respondido, porque su registro de facturación debe generarse de forma simultánea o inmediatamente anterior a su emisión (RD 1007/2023, art. 9). El terminal no debe imprimir un documento como factura con una numeración propia para enviarla después. Qué hacer con una venta que no se puede facturar en ese momento es una decisión del operador, ajena a Factuarea.
Cumplimiento del componente del integrador
Lo que debe hacer y declarar el software de tu terminal.
Idempotencia
Idempotency-Key y el external_id duradero.
Alta automática en VeriFactu
Lotes, reintentos y las palancas sobre un registro.
Facturas simplificadas o completas
F1, F2 y F3 y la factura simplificada cualificada.