Integrar tu software
Tu programa sigue numerando, calculando y maquetando. En el momento de emitir, hace una llamada; con lo que vuelve, imprime el QR y las leyendas. Esta página recorre esa integración de principio a fin.
En qué consiste
Una integración completa hace cuatro cosas, y sólo la primera es imprescindible para empezar:
- 1
Emitir
Al confirmar una factura,POST /v1/facturas. La respuesta trae el identificador del registro, la huella, la URL del QR y las leyendas. Guarda esos datos junto a la factura. - 2
Imprimir
Pinta el QR (30–40 mm) y las dos leyendas en el PDF. Puedes dibujar la URL con tu propia librería o pedir el símbolo ya renderizado aGET /v1/registros/{id}/qr. - 3
Enterarte del resultado
La remisión a la AEAT es asíncrona. Suscribe un webhook o consultaGET /v1/facturas/{id}para saber si la AEAT aceptó, aceptó con errores o rechazó. - 4
Anular cuando toque
POST /v1/anulacionesemite el registro de anulación encadenado. Corregir es emitir una rectificativa (otra alta, tipoR1–R5).
Autenticación e idempotencia
Todas las llamadas a https://verifactu-api.zctechpartners.cloud/v1 llevan Authorization: Bearer <clave>. Usa una clave de perfil integración; el entorno lo decide su prefijo (vf_test_ preproducción, vf_live_ producción), como se explica en Entornos y claves.
Toda petición POST exige además una cabecera Idempotency-Key (mínimo 8 caracteres; un UUID es lo natural). Misma clave y mismo cuerpo durante 24 horas devuelven la misma respuesta, sin crear un segundo registro: un doble clic o un reintento de red nunca producen dos facturas ante la AEAT. Misma clave con un cuerpo distinto es 409. Un error de validación no consume la clave, así que puedes corregir el cuerpo y reintentar con ella.
Authorization: Bearer vf_test_…
Idempotency-Key: 6f1d0a3e-… # una por factura; guárdala con ella
Content-Type: application/jsonGuarda la Idempotency-Key junto a la factura en tu base de datos antes de llamar. Si el proceso muere a medias, al reintentar con la misma clave recuperas la respuesta original en vez de duplicar.
Emitir una factura
{
"obligado_nif": "B21778451",
"num_serie_factura": "FAC-2027-0001",
"serie": "FAC-2027",
"fecha_expedicion": "2027-01-07",
"tipo_factura": "F1",
"descripcion_operacion": "Servicios de consultoría, enero 2027",
"destinatario": { "nif": "12345678Z", "nombre_razon": "CLIENTE SL" },
"lineas": [
{ "descripcion": "Consultoría", "base": 1000.00, "tipo_iva": 21, "cuota": 210.00, "calificacion": "S1" }
],
"cuota_total": 210.00,
"importe_total": 1210.00
}| Campo | Qué es |
|---|---|
obligado_nif | NIF del emisor, dado de alta como obligado en el entorno de la clave. |
num_serie_factura | El número completo de la factura, exactamente como figura en el documento (1–60 caracteres, sin caracteres de control). La AEAT no impone formato. Va intacto a la huella y al QR. |
serie | Opcional. Código de serie para agrupar en consultas. No se concatena al número ni afecta a la cadena. |
fecha_expedicion | Fecha de expedición en formato AAAA-MM-DD. |
tipo_factura | F1 completa, F2 simplificada, F3 sustitutiva de simplificadas, R1–R5 rectificativas. |
destinatario | Obligatorio en F1: nif (nacional) o id_otro (no residente: codigo_pais, id_type, id) y nombre_razon. Su NIF no puede ser el del emisor. |
lineas | De 1 a 12 desgloses. Cada uno: base, tipo_iva y cuota cuando aplica, calificacion (S1 sujeta, S2 inversión del sujeto pasivo, N1/N2 no sujeta, E1–E6 exenta), opcionalmente recargo_equivalencia y regimen_especial (por defecto 01, régimen general). |
cuota_total / importe_total | Suma de cuotas y total de la factura. importe_total es el que va al QR: base más cuota, sin restar retenciones como el IRPF. |
{
"id": "0d3f2c1e-…",
"tipo": "alta",
"num_serie_factura": "FAC-2027-0001",
"huella": "3F0A9C…",
"estado": "pendiente",
"qr": {
"url": "https://prewww2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B21778451&numserie=FAC-2027-0001&fecha=07-01-2027&importe=1210.00",
"leyendas": ["VERI*FACTU", "Factura verificable en la sede electrónica de la AEAT"]
}
}En ese momento el registro ya existe, está encadenado y tiene huella; la remisión a la AEAT ocurre después, en lotes que el planificador forma cada minuto. Guarda id, huella, qr.url y el importe registrado con la factura: son los datos que imprimirás y con los que cotejarás.
503 chain-locked con Retry-After. Reintenta con la misma Idempotency-Key.QR y leyendas en el documento
El reglamento fija cómo va el QR: ISO/IEC 18004, nivel de corrección M, entre 30 y 40 mm de lado, con una zona de silencio de al menos 2 mm, y precedido del rótulo «QR tributario». Junto a él, las dos leyendas que devuelve la API, siempre en castellano aunque la factura esté en otro idioma. Tienes dos opciones:
- Dibujar tú la URL (
qr.url) con cualquier librería de QR, respetando los parámetros anteriores. - Pedir el símbolo ya renderizado:
GET /v1/registros/{id}/qrdevuelve un SVG (image/svg+xml) con la corrección y el margen correctos, listo para incrustar a 30–40 mm. Sólo existe para registros de alta: una anulación no lleva QR.
const res = await fetch(`${API}/v1/registros/${registroId}/qr`, {
headers: { Authorization: `Bearer ${CLAVE}` },
})
const svg = await res.text() // guárdalo con la factura y dibújalo en el PDFSi la factura lleva retención (IRPF), imprime los dos importes con su rótulo: el total de la factura (el que va en el QR) y el total a pagar. La propia AEAT lo recomienda para que el cotejo cuadre con lo que ve el receptor.
Consultar el estado
GET /v1/facturas/{id} (o GET /v1/registros/{id}, que también sirve para anulaciones) devuelve el registro con su estado, lo que la AEAT respondió en el último intento (aeat: estado, código, descripción, CSV) y el historial completo de envios. acciones_disponibles indica si procede reenvio (envío agotado sin llegar a la AEAT) o subsanacion (rechazado o aceptado con errores).
| estado | Qué hacer en tu sistema |
|---|---|
pendiente · encolado · enviando | Nada. La factura ya está registrada y encadenada; la remisión está en curso. |
aceptado | Marcar como registrada. El QR es cotejable en la Sede. |
aceptado_con_errores | Registrada. Revisar el detalle (código y descripción) por si conviene una rectificativa; no reenviar. |
rechazado | Si el motivo es un fallo técnico de la AEAT (códigos 1129, 1241, 1243, 1256), la API reemite estos mismos datos por su cuenta. En cualquier otro caso el dato está mal y la corrección tiene que venir de tu sistema: mostrar el motivo a la persona, no editar la factura ya emitida y no reintentar el mismo número por POST /v1/facturas. |
subsanado | Existe un registro posterior que corrige a éste. Nada que hacer. |
error_persistente | No se pudo entregar a la AEAT tras los reintentos. Avisar; reencolar con POST /v1/registros/{id}/reenvio o desde la consola. |
Para listados: GET /v1/registros con filtros estado, tipo, obligado_nif o obligado_id, desde, hasta, paginado por cursor (page.next_cursor). Para no sondear, usa webhooks.
Anular y rectificar
Anulación
{
"obligado_nif": "B21778451",
"num_serie_factura_anulada": "FAC-2027-0001",
"fecha_expedicion_anulada": "2027-01-07"
}Emite un registro de anulación encadenado. La respuesta es la misma forma que el alta, sin qr. Si anulas una factura que no llegó a registrarse por la API (anterior a la integración), indica "sin_registro_previo": "S".
Rectificativa
Es un alta con tipo_factura R1–R5, tipo_rectificativa (S por sustitución, I por diferencias), la lista facturas_rectificadas (número y fecha de cada una) y, por sustitución, importe_rectificacion con base_rectificada y cuota_rectificada. Lleva su propio QR.
Errores
Los errores siguen RFC 9457 (application/problem+json): type es un identificador estable bajo https://verifactu-api.zctechpartners.cloud/problems/…, detail es un texto mostrable y, en validación, errors enumera campo y mensaje. Toda respuesta lleva request_id; cítalo si nos escribes.
| HTTP | type | Cuándo |
|---|---|---|
| 400 | malformed-request | JSON ilegible o cabeceras imposibles. |
| 401 | unauthorized | Clave ausente, inválida o revocada. |
| 403 | forbidden | La clave no tiene la capacidad o el obligado está fuera de su ámbito. |
| 404 | not-found | Obligado inexistente en el entorno de la clave; registro inexistente. |
| 409 | conflict / idempotency-conflict | Factura ya registrada (mismo número y fecha) / misma Idempotency-Key con otro cuerpo. |
| 413 · 415 | payload-too-large / unsupported-media-type | Cuerpo mayor del permitido / no es JSON. |
| 422 | validation-error | Campos inválidos o importes que no cuadran. Ver errors[]. |
| 429 | rate-limited | Más de 120 peticiones por minuto con la clave. Respeta Retry-After. |
| 503 | chain-locked | Cadena del obligado ocupada. Reintenta con la misma Idempotency-Key. |
{
"type": "https://verifactu-api.zctechpartners.cloud/problems/validation-error",
"title": "Error de validación",
"status": 422,
"detail": "El cuerpo no supera la validación.",
"request_id": "7f0c…",
"errors": [
{ "campo": "lineas.0.cuota", "mensaje": "base × tipo_iva no cuadra con cuota" }
]
}Ejemplo real: el CRM ONE
ZC Tech Partners factura con su propio CRM (ONE) integrado con esta API; es el cliente cero. Sirve como referencia de cómo queda una integración terminada:
- Un interruptor por empresa activa VERI*FACTU. Apagado, el programa es idéntico al de antes.
- Con el interruptor encendido, «Marcar enviada» se sustituye por Emitir, que llama a
POST /v1/facturascon unaIdempotency-Keyguardada en la fila y almacena en la factura el id del registro, la huella, la URL del QR, el SVG del QR y el importe registrado. - Una factura registrada no se puede editar ni borrar: la base de datos lo impide con disparadores y la interfaz retira los botones. Anular emite
POST /v1/anulaciones. - El PDF imprime el QR de 35 mm con «QR tributario» encima y «QR VERI*FACTU» debajo, las dos leyendas en castellano y, si hay IRPF, el total de la factura y el total a pagar.
- Un webhook firmado actualiza el estado del acuse (pendiente, aceptado, aceptado con errores, rechazado) que se muestra en la ficha de la factura, con enlace para cotejar en la Sede.
Lista de comprobación
- La clave de integración vive en la configuración del servidor, nunca en el navegador ni en el repositorio.
- Cada factura guarda su
Idempotency-Keyantes de llamar y reutiliza la misma al reintentar. num_serie_facturaes exactamente el número impreso;importe_totales base más cuota, sin retenciones.- El PDF lleva el QR a 30–40 mm, «QR tributario», las dos leyendas y, con IRPF, los dos totales.
- Una factura registrada no se puede editar ni borrar en tu programa.
- Tu sistema reacciona a
rechazadoyerror_persistente(webhook o consulta) y muestra el motivo. - Has probado el ciclo completo en preproducción, incluido cotejar un QR en el entorno de pruebas de la Sede.