Verifactu Direct API · lote, estado, errores e idempotencia
La operativa alrededor del alta: registrar en lote, seguir el veredicto de la AEAT, los errores y cómo evitar duplicados.
Alta en lote
Hasta 75 facturas en una petición a POST /external-invoices/bulk. Es todo-o-nada: si una falla (validación o duplicado), no se registra ninguna — así no quedan huecos en tu cadena Veri*Factu. Cada factura admite los mismos campos que el alta individual.
curl -X POST https://api.test.bilteo.com/api/v1/external-invoices/bulk \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "facturas": [
{ "serie": "FV", "numero": "1070", "fecha_expedicion": "2026-04-15",
"tipo_factura": "F1", "descripcion": "Servicio mensual", "nif": "A39208155",
"nombre": "Gráficas Belmar SA",
"lineas": [ { "base_imponible": "120", "tipo_impositivo": "21", "cuota_repercutida": "25.20" } ],
"importe_total": "145.20" },
{ "serie": "TPV", "numero": "5588", "fecha_expedicion": "2026-04-15",
"tipo_factura": "F2", "descripcion": "Venta en tienda",
"lineas": [ { "base_imponible": "40", "tipo_impositivo": "10", "cuota_repercutida": "4" } ],
"importe_total": "44" }
] }'
# → 201, { "facturas": [ { "id": "EXT…", "numero": "1070", "verifactu_record_id": "VF…" }, … ] }El registro es síncrono; el envío a AEAT va después (igual que el alta individual). Para volúmenes de miles, trocea en lotes y/o consúltanos.
Seguir el estado
El alta es asíncrona: nace pending y la AEAT responde después. Recibe el veredicto por webhooks verifactu.accepted / verifactu.rejected / verifactu.error, o consúltalo con el verifactu_record_id:
# El registro Veri*Factu (detalle del veredicto)
curl https://api.test.bilteo.com/api/v1/verifactu-records/VF… \
-H "Authorization: Bearer bk_live_xxx"
# La propia factura: su `state` se sincroniza con el veredicto de AEAT
# (pending → registered | rejected | cancelled)
curl https://api.test.bilteo.com/api/v1/external-invoices/EXT… \
-H "Authorization: Bearer bk_live_xxx"
# y el listado con filtros:
curl "https://api.test.bilteo.com/api/v1/external-invoices?state=rejected" \
-H "Authorization: Bearer bk_live_xxx"Errores
| Código | Cuándo |
|---|---|
403 permission_error | el gate vf_direct_api.enabled está desactivado, o la key no tiene verifactu:write |
409 conflict_error | ya registraste una factura con esa (serie, numero) o ese external_id; o hay un reintento con la misma Idempotency-Key en vuelo |
400 validation_error | falta un campo, fecha no ISO, o el desglose es incoherente |
422 verifactu_error | el desglose es fiscalmente inválido, o con validar_cliente el NIF del destinatario no está censado en la AEAT |
Idempotencia
Dos mecanismos complementarios (ver Convenciones → Idempotencia):
Idempotency-Key(cabecera): para que un reintento del alta/anulación no duplique el registro. Manda una clave opaca única por operación; un reintento con la misma clave y cuerpo reproduce la respuesta original en vez de registrar de nuevo.bashcurl -X POST https://api.test.bilteo.com/api/v1/external-invoices \ -H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \ -H "Idempotency-Key: 7e9f…-uuid-por-operación" \ -d '{ … }'external_id(campo): enlace permanente con tu sistema. Un segundo alta con el mismoexternal_idda409— útil como red anti-duplicados a largo plazo y para consultar luego (GET /external-invoices?external_id=…).