Ir al contenido

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.

bash
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:

bash
# 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ódigoCuándo
403 permission_errorel gate vf_direct_api.enabled está desactivado, o la key no tiene verifactu:write
409 conflict_errorya registraste una factura con esa (serie, numero) o ese external_id; o hay un reintento con la misma Idempotency-Key en vuelo
400 validation_errorfalta un campo, fecha no ISO, o el desglose es incoherente
422 verifactu_errorel 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.

    bash
    curl -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 mismo external_id da 409 — útil como red anti-duplicados a largo plazo y para consultar luego (GET /external-invoices?external_id=…).

Bilteo · Documentación