Factuarea APIDevelopers

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: F2 responde 422 simplified_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 bloque verifactu responde status: failed con error_code: verifactu_not_enabled.
  • Una API key con invoices:write (y verifactu:read para seguir el registro). Empieza con una clave fact_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_id el ticket se numera en tu serie de simplificadas por defecto, que se crea sola la primera vez. Si envías un series_id, debe ser de una serie de facturas simplificadas (invoice_kind: simplified); con una serie de facturas completas la llamada responde 422 series_invoice_kind_mismatch y no queda ningún borrador. GET /v1/series/default?document_type=invoice&invoice_kind=simplified devuelve 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:

CampoQué hace
type: "F2"Factura simplificada. Sin client_id es un ticket anónimo; con uno, una factura simplificada cualificada.
external_idLa identidad de esta venta. No caduca nunca: es lo que hace seguro un reintento tardío.
prices_include_tax: trueCada unit_price es el precio final que pagó el cliente, con IVA incluido.
paymentRegistra el pago (method; paid_at y reference opcionales) por todo el importe tras emitir.
options.register_verifactu: trueGenera el alta VERI*FACTU antes de responder.
options.wait_for_pdf: trueEspera hasta unos 15 segundos al PDF A4.
options.send_automatically y options.send_toEnvía la factura por email una vez emitida.
operation_onEl 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.

  • verifactu es lo que imprimes. status es registered cuando el alta existe y failed cuando la factura está emitida pero no se pudo generar el alta. aeat_status es 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. csv es null hasta que la AEAT lo acepta.
  • pdf es el PDF A4. ready significa que está materializado y url lo sirve al instante; pending significa que la generación está en cola y la URL responde 404 durante unos segundos. Nunca es un error.
  • public_url es 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:

  1. 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.
  2. 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.
  3. Solo se rechaza con 422 amount_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_base64 ya es de nivel M y no lleva prefijo data:. 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 la legend que llega en la respuesta va debajo: VERI*FACTU en 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_base64 y huella exactamente 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.pdf

format 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 responde 200 con Idempotent-Replayed: true y 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 es 409 idempotency_key_reused con subcode: unattended_replay_mismatch y param: external_id. Es un fallo en cómo construyes los identificadores: no reutilices nunca uno.
  • Dos peticiones simultáneas con el mismo external_id producen 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 responde 409 resource_locked con param: external_id y 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

RespuestaSignificadoQué hacer
200 + Idempotent-Replayed: trueLa 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_lockedOtra 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_toSe 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_rateUna 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_allowedLas 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_failedLos 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_eligibleAlgo 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 5xxLí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.paramQué no cabe
client_idNombre 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_idEl 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_idEl número de la factura rectificada no es admisible.
simplified_invoice_uuidsEl número de una factura simplificada sustituida por una F3 no es admisible.
company_nameLa 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.
linesMás de 12 desgloses fiscales distintos, o una base, cuota o recargo que no cabe en 12 cifras enteras y 2 decimales.
totalUn total, o su cuota, que no cabe en el formato; una F2 por encima de 3.000 €.
typeLa 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.paramQué faltaQuién lo resuelve
certificateEl certificado de la empresa está ausente, caducado, revocado, emitido para otro NIF o ilegible.La empresa: Ajustes → Certificado digital.
representationLa 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_certificateEl 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 como R5; la de una cualificada, como R1 a R4 con la misma marca. Consulta Facturas rectificativas.
  • El cliente pide una factura completa de tickets ya emitidos: POST /v1/invoices/substitute-simplified agrupa las facturas simplificadas bajo una F3.
  • Una operación emitida por error y ya cobrada, como un cobro duplicado: POST /v1/invoices/{invoice}/annul con revert_collections: true revierte 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-annul te lo anticipa (requires_collection_reversal, active_collections_amount).
  • La conversión de un presupuesto, una proforma o un albarán en factura responde con warnings y warning_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.

En esta página

¿Te echamos una mano?Contactar con soporte