# Simple Verifactu API > API REST para registrar facturas en el sistema Veri*factu de la AEAT española. > Alta de NIFs autorizados, emisión, anulación y rectificación de facturas, > con el QR obligatorio incluido en la respuesta. URL base: `https://api.simpleverifactuapi.com` Esquema OpenAPI: https://api.simpleverifactuapi.com/schema ← referencia exhaustiva de campos y endpoints Este fichero es la guía de integración completa: no hace falta leer nada más para integrar la API. La documentación para personas, con ejemplos en curl, Python, JavaScript, PHP y Ruby, está en https://simpleverifactuapi.com/docs **Contexto normativo** (qué es Veri*factu, a quién obliga, desde cuándo y con qué sanciones) — no hace falta para integrar, pero sí para responder preguntas sobre la obligación: https://simpleverifactuapi.com/docs#que-es-verifactu --- ## Visión general Dos recursos, ambos con las mismas cuatro operaciones: | Operación | Facturas | NIFs autorizados | |---|---|---| | Listar | `GET /invoices/` | `GET /authorized-nifs/` | | Leer | `GET /invoices/{id}` | `GET /authorized-nifs/{id}` | | Crear | `POST /invoices/` | `POST /authorized-nifs/` | | Anular / borrar | `DELETE /invoices/{id}` | `DELETE /authorized-nifs/{id}` | **No hay `PUT` ni `PATCH`.** Una factura registrada es un asiento fiscal inmutable: para corregirla se emite una rectificativa, para anularla se hace `DELETE` (que registra una anulación, no borra nada). Cualquier diseño que asuma "editar una factura" es incorrecto. La barra final es opcional: `/invoices` y `/invoices/` funcionan igual. Los identificadores son UUID. Los listados devuelven **un array JSON plano, sin paginar ni envolver** en `results`/`count`. ### Flujo mínimo de integración 1. Loguearse a través de la web y obtener la api key. 2. Crear un AuzorizedNIF a través del dashboard. También puede crearse con `POST /authorized-nifs/`. 2. El usuario otorga el apoderamiento en la sede de la AEAT (trámite manual, fuera de la API — ver más abajo). Es un formulário muy rápido de rellenar. 3. `POST /invoices/` — emitir facturas con ese `seller_nif`. 4. Actualizar la factura con: * El QR a partir de `qr_url` al inicio con un tamaño de entre 30x30 y 40x40mm. Si es digital, peude ser directamente la url. * Añadir la frase "Factura verificable en la sede electrónica de la AEAT". El paso 2 no bloquea al 3: se puede facturar antes, y esas facturas quedan en `pending_nif_verification` hasta que el apoderamiento exista. --- ## Autenticación Todas las peticiones llevan la API key en la cabecera `Authorization`: ``` Authorization: Bearer sk_test_xxxxxxxxxxxxxxxx ``` Dos tipos de key, y la diferencia importa: | Prefijo | Entorno | Efecto | |---|---|---| | `sk_test_` | Pruebas | Registra contra el entorno de pruebas de la AEAT. Sin efectos fiscales. | | `sk_live_` | Producción | Registra contra la AEAT real. **Irreversible**: una factura registrada no se borra, se anula con otro registro. | Reglas al integrar: - Lee la key de una variable de entorno (`SIMPLE_VERIFACTU_API_KEY`). Nunca la hardcodees ni la subas al repositorio. - Empieza siempre con `sk_test_`. No cambies a `sk_live_` hasta que el flujo completo funcione en pruebas. - La key `sk_live_` solo se muestra una vez al generarla. Si el usuario no la tiene a mano, tiene que generar otra; no hay forma de recuperarla. Errores de autenticación: | Situación | HTTP | `type` | |---|---|---| | No se envía cabecera `Authorization` | 401 | `/errors/not-authenticated` | | La key no es válida | 401 | `/errors/authentication-failed` | Límite de peticiones: **20 por segundo**. Al superarlo, 429 con `/errors/throttled`; reintenta con backoff exponencial. --- ## NIFs autorizados Un NIF autorizado es un NIF emisor para el que el usuario nos ha dado poderes de facturación. **Ningún `seller_nif` puede facturar sin estar dado de alta antes.** ### Crear `POST /authorized-nifs/` | Campo | Tipo | Descripción | |---|---|---| | `nif` | string · requerido | NIF español válido a autorizar. | ```json { "nif": "B12345678" } ``` Respuesta `201`: ```json { "id": "a1b2c3d4-...", "nif": "B12345678", "status": "unverified" } ``` Nace siempre en `unverified`. Pasa a `verified` solo cuando se registra con éxito la primera factura de ese NIF en la AEAT: **no hay endpoint para verificarlo a mano y no hay que esperar a `verified` para poder facturar.** | Estado | Significado | |---|---| | `unverified` | Permanecerá en este estado hasta que registres el apoderamiento en la AEAT y crees una factura de forma exitosa para ese `seller_nif`. | | `verified` | Estado terminal. Se ha comprobado que tenemos poderes para emitir facturas para este NIF. | Si el NIF ya estaba dado de alta: 409 `/errors/duplicate-authorized-nif`. Cada NIF solo se registra una vez; ese 409 se puede tratar como "ya está listo". ### Listar y leer `GET /authorized-nifs/` — array plano: ```json [{ "id": "a1b2c3d4-...", "nif": "B12345678", "status": "verified" }] ``` Filtros disponibles: | Campo | Tipo | Descripción | |---|---|---| | `nif` | string · opcional | Filtra por NIF exacto. | | `status` | string · opcional | Filtra por estado. Valores posibles: `unverified`, `verified`. | `GET /authorized-nifs/{id}` devuelve uno solo: ```json { "id": "a1b2c3d4-...", "nif": "B12345678", "status": "verified" } ``` ### Borrar `DELETE /authorized-nifs/{id}` retira la autorización. No afecta a las facturas ya registradas, que siguen existiendo ante la AEAT. ### Campos | Campo | Tipo | Descripción | |---|---|---| | `id` | uuid · read only | UUID único del NIF autorizado. | | `nif` | string | El NIF del que nos vas a autorizar para emitir facturas. | | `status` | string · read only | Estado del NIF autorizado: `verified` o `unverified`. | --- ## Crear una factura `POST /invoices/` ### Orden de operaciones El `seller_nif` debe estar dado de alta **antes** de facturar, o la petición falla con 400 `/errors/seller-nif-not-authorized`: 1. `POST /authorized-nifs/` con el NIF emisor. 2. `POST /invoices/`. Dar de alta el NIF no basta para que la AEAT acepte el registro: el usuario tiene que otorgar el apoderamiento en la sede de la AEAT, un trámite manual fuera de la API. Hasta que lo haga, las facturas se crean en `pending_nif_verification` y se reintentan solas. **Eso no es un error y no hay que reintentar desde el cliente.** ### Cuerpo de la petición ```json { "invoice_number": "FAC-2024-001", "description": "Prestación de servicios de consultoría", "seller": { "nif": "B12345678", "name": "Mi Empresa S.L." }, "customer": { "nif": "A87654321", "name": "Cliente S.A." }, "expedition_date": "2024-01-15", "lines": [{ "base_amount": 100.00, "iva": 21 }] } ``` | Campo | Tipo | Descripción | |---|---|---| | `invoice_number` | string · requerido | Número único de la factura (e.g. "FAC-2024-001"). | | `description` | string · requerido | Descripción de la operación facturada (qué se ha vendido o qué servicio se ha prestado). Máximo 500 caracteres. | | `seller` | objeto · requerido | Emisor de la factura. | | `seller.nif` | string · requerido | NIF del emisor. Debe estar autorizado en la cuenta. | | `seller.name` | string · requerido | Nombre o razón social del emisor. | | `customer` | objeto · opcional | Receptor de la factura. Omitirlo crea una factura simplificada (un ticket). | | `customer.nif` | string · requerido | NIF del receptor. | | `customer.name` | string · requerido | Nombre o razón social del receptor. | | `expedition_date` | string · requerido | Fecha de expedición en formato ISO 8601 (YYYY-MM-DD). La fecha de expedición debe ser anterior a la fecha actual. | | `lines` | array · requerido | Líneas de la factura. Cada línea lleva su importe en una de las dos convenciones — `base_amount` o `total_amount` — y **todas las líneas de una factura deben usar la misma**. | | `lines[].base_amount` | number · requiere uno de los dos | Importe de la línea sin IVA, con dos decimales. Úsalo si fijas precios sin IVA (e.g. "1.000 € + IVA"): preserva exacta la base. | | `lines[].total_amount` | number · requiere uno de los dos | Importe de la línea con el IVA incluido, con dos decimales. Úsalo si fijas precios finales (e.g. "un café, 1,50 €"): preserva exacto el total. | | `lines[].iva` | integer · requerido | Tipo de IVA aplicado. Valores posibles: `0`, `4`, `10`, `21`. | | `rectified_invoice` | uuid · opcional | UUID de la factura original que se rectifica con esta. Debe ser del mismo tipo: una simplificada solo se rectifica con otra simplificada. | `seller` y `customer` son **objetos** `{nif, name}`, no campos planos. No existen `seller_nif` ni `customer_name` en el cuerpo (sí como filtros en el listado, ojo). Invariantes que conviene respetar en el cliente: - **Los importes van como número**, con dos decimales: `100.00`, no `"100.00"`. Igual en la respuesta. Al parsearlos, mételos en un tipo decimal (`Decimal`, `BigDecimal`, `decimal`) en vez de acumular en coma flotante: un importe suelto va y vuelve exacto, pero sumar cientos de facturas en float arrastra céntimos. Ojo en Java con `new BigDecimal(double)`, que no hace lo que parece: usa `BigDecimal.valueOf`. - Un número no lleva los ceros a la derecha: 121,10 € llega como `121.1`. Formatea a dos decimales al imprimir. - `iva` es un **entero** de un conjunto cerrado: `0`, `4`, `10`, `21`. - Cada línea lleva **`base_amount` o `total_amount`, exactamente uno**, y todas las líneas de una factura deben usar el mismo — ver la sección siguiente. - Los `total_amount`, `base_amount` e `iva_amount` **de la factura** no se envían: los calcula el servidor a partir de `lines`. - El IVA **no se calcula línea a línea**. Se agregan los importes por tipo impositivo y el reparto se resuelve una vez sobre cada agregado, redondeando al alza a partir del medio céntimo. Es lo que exige el desglose de la AEAT, y puede dar un céntimo de diferencia respecto a calcular el IVA de cada línea por tu cuenta: manda las líneas y usa el `iva_amount` que devuelve la respuesta. - `expedition_date` no puede ser futura, ni anterior al arranque de Veri*factu, ni tener más de 20 años. - `invoice_number` no admite los caracteres `< > " ' =`, prohibidos por la AEAT. - Los NIF se normalizan (trim y mayúsculas) antes de validar el formato. - `customer` va **entero o nada**: mandar solo `nif` o solo `name` es un error de validación. No hay forma de identificar al cliente a medias. - `description` es **obligatorio** (máx. 500 caracteres). Debe describir la operación facturada — qué se ha vendido o qué servicio se ha prestado (e.g. "Prestación de servicios de consultoría"). ### Precios con IVA incluido `base_amount` (sin IVA) y `total_amount` (con el IVA dentro) son las dos formas de expresar el importe de una línea. **Manda uno u otro, nunca los dos**, y no los mezcles entre las líneas de una misma factura: ambos casos son 400 `/errors/validation-error`. Cuál usar depende de qué cifra ha decidido el usuario, porque es la que se preserva exacta: | El usuario fija… | Campo | Se preserva exacto | |---|---|---| | el precio sin IVA («1.000 € + IVA», B2B) | `base_amount` | la base | | el precio final («un café, 1,50 €», retail) | `total_amount` | el total | **No calcules la base tú mismo a partir de un precio final.** Con dos decimales en base y cuota hay totales inalcanzables desde cualquier base: a 21%, la base 8,26 da 9,99 € y la 8,27 da 10,01 €, así que **10,00 € no se puede expresar en base**. Es el 17% de los importes al 21%, el 9% al 10% y el 4% al 4%. Si el usuario ha dicho 10,00 €, manda `total_amount`: ```json { "invoice_number": "FAC-2024-001", "description": "Prestación de servicios de consultoría", "seller": { "nif": "B12345678", "name": "Mi Empresa S.L." }, "customer": { "nif": "A87654321", "name": "Cliente S.A." }, "expedition_date": "2024-01-15", "lines": [{ "total_amount": 10.00, "iva": 21 }] } ``` La respuesta devuelve el total pedido, sin desviación: ```json { "total_amount": 10.00, "base_amount": 8.26, "iva_amount": 1.74, "lines": [{ "base_amount": null, "total_amount": 10.00, "iva": 21 }] } ``` Con `total_amount`, la cuota se calcula desde el bruto (`bruto × tipo / (100 + tipo)`) y la base es el resto, de forma que `base + cuota` es exactamente el importe enviado. Eso hace que la cuota se aparte de `base × tipo` en menos de un céntimo; es deliberado, y queda muy dentro del margen de ±10,00 € que admiten las validaciones de `CuotaRepercutida`, `CuotaTotal` e `ImporteTotal` de la AEAT. En las líneas de la respuesta vienen **siempre los dos campos**, con `null` en el que no se usó, así que puedes saber en qué convención se guardó cada línea sin recordarlo. ### Factura simplificada (ticket) **Omitir `customer` es lo único que hace falta** para emitir una factura simplificada — lo que la AEAT llama tipo `F2` y el resto del mundo, un ticket. No hay ningún flag que activar: ```json { "invoice_number": "TIC-2024-001", "description": "Venta de mercancía en tienda", "seller": { "nif": "B12345678", "name": "Mi Empresa S.L." }, "expedition_date": "2024-01-15", "lines": [{ "base_amount": 10.00, "iva": 21 }] } ``` En la respuesta, `customer` viene a `null`. Mandar `"customer": null` explícitamente equivale a omitirlo. Dos reglas que importan al integrar: - **Tope de 3.000 €** (base + IVA) por factura simplificada, rectificativas incluidas. Es un límite de la AEAT; lo comprobamos antes de enviar, así que se devuelve como 400 `/errors/validation-error`. Por encima de esa cifra hay que identificar al cliente y emitirla como factura completa. - Una simplificada **solo se rectifica con otra simplificada** — ver la sección de rectificativas. ### Respuesta `201 Created` ```json { "id": "a1b2c3d4-...", "status": "created", "invoice_number": "FAC-2024-001", "description": "Prestación de servicios de consultoría", "seller": { "nif": "B12345678", "name": "Mi Empresa S.L." }, "customer": { "nif": "A87654321", "name": "Cliente S.A." }, "expedition_date": "2024-01-15", "total_amount": 121.00, "base_amount": 100.00, "iva_amount": 21.00, "qr_url": "https://www2.agenciatributaria.gob.es/...", "lines": [{ "base_amount": 100.00, "total_amount": null, "iva": 21 }], "rectified_invoice": null } ``` `status` es **siempre un string en minúsculas**, nunca un entero. No compares contra números. El `status` inicial no siempre es `created`: si la AEAT no está disponible o falta el apoderamiento, será `pending_creation` o `pending_nif_verification`. **Las tres respuestas son un `201` correcto**; se resuelven solas en segundo plano. `qr_url` es la URL que hay que pintar como QR en el PDF de la factura. #### Estados de la factura | Estado | Significado | |---|---| | `created` | Factura creada y registrada. | | `deleted` | Registrada la anulación. | | `rectified` | Rectificada por otra factura. | | `pending_creation / pending_deletion` | Estado inicial tras la creación o el borrado. Se mantiene en este estado hasta que se consigue registrar en la AEAT. El primer intento será síncrono. Si no es posible registrar la factura, permanecerá en este estado hasta que se registre. Se reintentará periódicamente. | | `pending_nif_verification` | Si se crea una factura pero aún no has registrado el apoderamiento en la AEAT, se crea en este estado. Se reintenta periódicamente. | | `errored_on_creation / errored_on_deletion` | Si, tras un reintento, una factura en estado `pending_creation` / `pending_deletion` falla, pasará a este estado. | Los estados `pending_*` son normales, no errores: la API reintenta sola contra la AEAT. No hay que reintentar desde el cliente ni tratarlos como fallo. Para saber si uno se ha resuelto, se relee la factura con `GET /invoices/{id}`. ### Errores esperables | HTTP | `type` | Qué hacer | |---|---|---| | 400 | `/errors/validation-error` | Leer `invalid_params`: cada entrada trae `name` y `reason`. | | 400 | `/errors/seller-nif-not-authorized` | Dar de alta el NIF con `POST /authorized-nifs/` primero. | | 409 | `/errors/duplicate-invoice-number` | Ya existe esa factura para ese `seller_nif`. **No reintentar.** | | 402 | `/errors/verifactu-invoice-rejected` | La AEAT rechazó un dato. Corregirlo; reintentar igual volverá a fallar. | | 400 | `/errors/rectification-type-mismatch` | Solo en rectificativas: el tipo no cuadra con el de la original. | ### Idempotencia No hay cabecera de idempotencia, pero el par (`seller_nif`, `invoice_number`) es único. Un reintento de una petición que sí llegó devuelve 409 `/errors/duplicate-invoice-number`. **Si estás reintentando tras un fallo de red, trata ese 409 como éxito**: significa que la factura ya se registró. Recupérala con `GET /invoices/?seller_nif=...&invoice_number=...` si necesitas su `id` o su `qr_url`. --- ## Consultar facturas ### Listar `GET /invoices/` Devuelve **un array JSON plano, sin paginación ni envoltorio**: ```json [ { "id": "a1b2c3d4-...", "status": "created", "invoice_number": "FAC-2024-001", "description": "Prestación de servicios de consultoría", "seller": { "nif": "B12345678", "name": "Mi Empresa S.L." }, "customer": { "nif": "A87654321", "name": "Cliente S.A." }, "expedition_date": "2024-01-15", "total_amount": 121.00, "base_amount": 100.00, "iva_amount": 21.00, "qr_url": "https://www2.agenciatributaria.gob.es/...", "lines": [{ "base_amount": 100.00, "total_amount": null, "iva": 21 }], "rectified_invoice": null } ] ``` Filtros, todos combinables por AND y de coincidencia exacta salvo las fechas: | Campo | Tipo | Descripción | |---|---|---| | `invoice_number` | string · opcional | Filtra por número de factura exacto. | | `seller_nif` | string · opcional | Filtra por NIF del emisor exacto. Ojo: los filtros son planos, aunque en el cuerpo el emisor sea el objeto `seller`. | | `customer_nif` | string · opcional | Filtra por NIF del receptor exacto. Las facturas simplificadas nunca aparecen aquí: no tienen receptor. | | `expedition_date_after` | string · opcional | Solo facturas expedidas en o después de esta fecha (YYYY-MM-DD). | | `expedition_date_before` | string · opcional | Solo facturas expedidas en o antes de esta fecha (YYYY-MM-DD). | | `status` | string · opcional | Filtra por estado. Valores posibles: `created`, `deleted`, `rectified`, `pending_nif_verification`, `pending_creation`, `pending_deletion`, `errored_on_creation`, `errored_on_deletion`. | ``` GET /invoices/?seller_nif=B12345678&status=created&expedition_date_after=2024-01-01 ``` El listado está acotado a la clave usada: una key `sk_test_` solo ve facturas de pruebas y una `sk_live_` solo las de producción. No hace falta filtrar por entorno. ### Leer una `GET /invoices/{id}` con el UUID: ```json { "id": "a1b2c3d4-...", "status": "created", "invoice_number": "FAC-2024-001", "description": "Prestación de servicios de consultoría", "seller": { "nif": "B12345678", "name": "Mi Empresa S.L." }, "customer": { "nif": "A87654321", "name": "Cliente S.A." }, "expedition_date": "2024-01-15", "total_amount": 121.00, "base_amount": 100.00, "iva_amount": 21.00, "qr_url": "https://www2.agenciatributaria.gob.es/...", "lines": [{ "base_amount": 100.00, "total_amount": null, "iva": 21 }], "rectified_invoice": null } ``` Si no existe, o es de otro usuario o de otro entorno: 404 `/errors/not-found`. ### Consultar el estado de un registro pendiente Una factura en `pending_creation` o `pending_nif_verification` se resuelve sola en segundo plano. Para saber si ya se registró, se relee con `GET /invoices/{id}` y se mira `status`. **No hay webhooks todavía**: si necesitas enterarte del cambio, haz polling espaciado (minutos, no segundos) o compruébalo la próxima vez que hagas falta. --- ## Anular una factura `DELETE /invoices/{id}` No borra nada: registra ante la AEAT un **asiento de anulación**. La factura sigue existiendo y pasa a `deleted`. ### La respuesta tiene dos formas Este es el detalle que más se falla al integrar: | Situación | HTTP | Cuerpo | |---|---|---| | Caso normal | `200` | La factura completa, ya con `"status": "deleted"` | | La factura estaba en `pending_nif_verification` | `204` | Vacío | El `204` ocurre porque ese registro nunca llegó a confirmarse en la AEAT: se descarta directamente, sin necesidad de anularlo. **Un cliente que asuma siempre cuerpo JSON reventará al parsear el 204.** Comprueba el código antes de leer el body. ### Errores | HTTP | `type` | Motivo | |---|---|---| | 400 | `/errors/invoice-already-rectified` | La factura ya se usó como base de una rectificativa. Hay que anular la rectificativa primero. | | 404 | `/errors/not-found` | No existe, o es de otro usuario o entorno. | ### Efecto secundario al anular una rectificativa Si anulas una factura rectificativa, la original que rectificaba **vuelve a `created`**. Es coherente: al desaparecer la corrección, el asiento original recupera su validez. --- ## Rectificar una factura No existe `PATCH`. Una factura emitida no se modifica: se emite otra que la rectifica (rectificativa por sustitución: R1 en la terminología de la AEAT, o R5 si lo que se rectifica es una factura simplificada). `POST /invoices/` con **`rectified_invoice`** apuntando al UUID de la original: ```json { "id": "e5f6g7h8-...", "status": "created", "invoice_number": "FAC-2024-002", "description": "Prestación de servicios de consultoría", "seller": { "nif": "B12345678", "name": "Mi Empresa S.L." }, "customer": { "nif": "A87654321", "name": "Cliente S.A." }, "expedition_date": "2024-01-20", "total_amount": 121.00, "base_amount": 100.00, "iva_amount": 21.00, "qr_url": "https://www2.agenciatributaria.gob.es/...", "lines": [{ "base_amount": 100.00, "total_amount": null, "iva": 21 }], "rectified_invoice": "a1b2c3d4-..." } ``` Puntos importantes: - La rectificativa lleva **su propio `invoice_number`**, distinto del original. - Los importes son los **correctos definitivos**, no la diferencia respecto a la factura original. - Al registrarse, la original pasa automáticamente a `rectified`. No hay que hacer nada más sobre ella. - Una factura solo se puede rectificar una vez. El segundo intento devuelve 400 `/errors/invoice-not-rectifiable`. - **La rectificativa tiene que ser del mismo tipo que la original**: si la original lleva `customer`, la rectificativa también; si es simplificada (sin `customer`), la rectificativa tampoco lo lleva. Cruzarlas devuelve 400 `/errors/rectification-type-mismatch`. En la práctica: copia la presencia o ausencia de `customer` de la factura que estás rectificando. ### Errores | HTTP | `type` | Motivo | |---|---|---| | 400 | `/errors/invoice-not-rectifiable` | La original ya está `deleted` o `rectified`. | | 400 | `/errors/rectification-type-mismatch` | Una simplificada solo se rectifica con otra simplificada, y una completa con otra completa. | | 404 | `/errors/not-found` | El UUID de `rectified_invoice` no existe. | ### Elegir entre anular y rectificar - Datos incorrectos, la operación existió → **rectificativa**. - La operación no debió existir → **`DELETE`** (anulación). --- ## Entorno de pruebas El entorno lo determina **la API key**, no un parámetro ni una URL distinta. La URL base es siempre la misma. | Key | Entorno AEAT | Efectos fiscales | |---|---|---| | `sk_test_` | Pruebas | Ninguno | | `sk_live_` | Real | **Irreversibles** | Los datos de los dos entornos están aislados: una key solo lista, lee y borra facturas de su propio entorno. Una factura creada en pruebas devuelve 404 al consultarla con una key de producción. Al integrar, usa `sk_test_` hasta que el flujo completo funcione: alta de NIF, creación, consulta y anulación. El cambio a producción es solo cambiar la variable de entorno. En pruebas se puede usar cualquier NIF con formato válido; no hace falta apoderamiento real, así que las facturas no se quedan en `pending_nif_verification` por ese motivo. ### Límite de peticiones 20 peticiones por segundo por IP, con margen para ráfagas cortas de hasta 40. Al superarlo, 429 `/errors/throttled`. Reintenta con backoff exponencial. --- ## Obligaciones en el PDF de la factura Registrar la factura en la API **no basta para cumplir el reglamento**. El documento que se entrega al cliente tiene que llevar, además: 1. **El código QR** generado a partir de `qr_url`, que viene en la respuesta de creación. Debe ir en la primera página, legible y con un tamaño de entre 30x30 y 40x40 mm. 2. **La mención literal "Factura verificable en la sede electrónica de la AEAT", junto al QR. `qr_url` ya es la URL completa de verificación en la sede de la AEAT: solo hay que codificarla como QR, sin construir nada ni añadirle parámetros. Cualquier librería estándar de QR vale. Si estás generando el PDF, hazlo después de recibir el `201`: hasta entonces no tienes `qr_url`. ### Declaración responsable Un software que cree facturas tiene que ir acompañado de una declaración responsable que acredite que cumple el reglamento. Es un documento legal que firma quien desarrolla o comercializa el software (estilo Términos y Conciciones, Privacidad, etc). --- ## Catálogo de errores Todos los errores usan RFC 9457 (`application/problem+json`). | Campo | Tipo | Descripción | |---|---|---| | `status` | int | Código HTTP de la respuesta. Se añade también en el cuerpo para facilitar su lectura. | | `type` | string | Código que identifica de forma estable el error. Es el que se debe usar si se quiere programar una respuesta automática ante el error. | | `title` | string | Descripción legible del error en lenguaje humano. Se puede usar para imprimir en los logs. No está pensado para devolverse directamente al usuario final. | | `invalid_params` | array | null | Información de los campos erróneos. Solo en errores de validación. | | `invalid_params[].name` | string | Nombre del campo. | | `invalid_params[].reason` | string | Mensaje de error. | El campo `type` identifica la clase de error de forma estable: es el que hay que usar para programar una reacción automática, nunca el `title`. | `type` | HTTP | Significado | |---|---|---| | `/errors/invoice-already-rectified` | 400 | Se intenta borrar (anular) una factura que ya se usó como base de una rectificativa. | | `/errors/invoice-not-rectifiable` | 400 | Se intenta rectificar una factura que ya está borrada o rectificada. | | `/errors/rectification-type-mismatch` | 400 | Una factura simplificada solo puede rectificarse con otra simplificada, y una completa con otra completa. Manda `customer` (o no) igual que la factura que rectificas. | | `/errors/seller-nif-not-authorized` | 400 | El `seller_nif` de la factura no está en los NIFs autorizados del usuario. | | `/errors/duplicate-invoice-number` | 409 | Ya existe una factura con ese `invoice_number` para ese `seller_nif`. | | `/errors/duplicate-authorized-nif` | 409 | Ese NIF ya está dado de alta en este entorno. Cada NIF solo puede registrarse una vez por entorno. | | `/errors/verifactu-invoice-rejected` | 402 | La AEAT ha rechazado la factura por un dato incorrecto. | | `/errors/validation-error` | 400 | Fallo de validación de algún campo; el detalle va en `invalid_params`. | | `/errors/not-found` | 404 | El recurso solicitado no existe. | | `/errors/permission-denied` | 403 | El usuario autenticado no tiene permiso para realizar esa acción. | | `/errors/not-authenticated` | 401 | La petición no incluye API key. | | `/errors/authentication-failed` | 401 | La API key no es válida. | | `/errors/method-not-allowed` | 405 | Se ha usado un método HTTP no soportado en ese endpoint. | | `/errors/throttled` | 429 | Se ha superado el límite de peticiones (20 peticiones por segundo). | | `/errors/parse-error` | 400 | El cuerpo de la petición no se ha podido interpretar. | | `/errors/unsupported-media-type` | 415 | El `Content-Type` de la petición no está soportado. | | `/errors/internal-server-error` | 500 | Excepción no controlada (no debería ocurrir, pero ya sabes cómo funciona esto). | Ejemplo de error de validación: ```json { "type": "/errors/validation-error", "title": "One or more fields failed validation.", "status": 400, "invalid_params": [ {"name": "seller.nif", "reason": "NIF format is incorrect."}, {"name": "expedition_date", "reason": "Expedition date cannot be in the future."} ] } ```