Digitaldocu Logo Verifactu

Documentación de Conexión API

Bienvenido a la especificación técnica de la API de **Digitaldocu Verifactu (Broker)**. Este middleware actúa como Componente de Facturación (CF) y centraliza las obligaciones normativas españolas del **Real Decreto 1007/2023 (Reglamento Verifactu)** y de la **Ley Crea y Crece**. Permite a cualquier software ERP o sistema de facturación principal (CPF) delegar la firma de facturas, el encadenamiento criptográfico, la generación de huellas, la remisión a la AEAT / SPFE, y la gestión de firmas de declaraciones responsables sin necesidad de implementar los protocolos complejos de forma nativa.

Características Principales de la Conexión

  • Autenticación Simple: Uso de cabecera estándar con token API único por cliente.
  • Protocolo no bloqueante: Recepción asíncrona de facturas con procesamiento en cola para respuesta inmediata y tolerancia a caídas de la AEAT.
  • Onboarding Legal: Flujo digital automatizado para firmas de Declaración Responsable (Modelos A y B) integradas en Digitaldocu.
  • Doble Remisión: Redirección simultánea y selectiva de registros a la AEAT y a la Solución Pública de Facturación Electrónica (Crea y Crece).

Arquitectura y Conexión de Actores

La normativa exige que todos los sistemas informáticos de facturación (SIF) garanticen la integridad, inmutabilidad y trazabilidad de los registros. En nuestra plataforma, esto se distribuye bajo un modelo de arquitectura dividida en tres actores principales:

ERP Cliente (CPF)
Componente Principal. Captura la venta y solicita la firma al Broker mediante API.
→
Broker Verifactu (CF)
Componente de Transmisión. Firma con certificado local, encadena huellas, y gestiona colas.
→
AEAT / SPFE
Sede Electrónica. Recibe de forma inmediata y retorna códigos de validación oficiales.

1. Componente Principal de Facturación (CPF) — El ERP del Cliente

Es el sistema que interactúa con el usuario final. Al guardar o emitir una factura, el ERP debe realizar una llamada HTTP POST al Broker Verifactu. Debe mostrar el código QR y la mención "Factura verificable en la sede electrónica de la AEAT" en el documento entregado al cliente, tomándolos del bloque cotejo que devuelve el propio 202 de esa llamada (o de GET /api/v1/invoices/{id}/cotejo). No hay que esperar al callback: el art. 6.5.a del RD 1619/2012 cuelga el QR de la EXPEDICIÓN de la factura y sus cuatro datos (NIF, número de serie y número, fecha, importe) son todos de la factura, no de la respuesta de la AEAT.

2. Componente de Facturación Central (CF) — El Broker

Recibe las solicitudes del ERP, encola los trabajos y procesa el encadenamiento de huellas SHA-256 en base a la última factura validada del cliente. Genera los documentos XML (SOAP de Verifactu o UBL de Crea y Crece), los firma digitalmente con el certificado de firma de la empresa y los envía a los servidores gubernamentales.

3. Receptor de la Factura (En Crea y Crece)

Las facturas del emisor se suben a la Solución Pública de Facturación Electrónica (SPFE). El receptor puede recibir la notificación de forma directa, y conectarse a la API del Broker para enviar el acuse de recibo o cambios en el estado de cobro/pago.

Autenticación

Todas las solicitudes dirigidas a los endpoints de la API deben autenticarse obligatoriamente mediante una cabecera personalizada en la petición HTTP. La clave es única por cliente, la genera siempre el servidor del Broker y opera en modo servicio: identifica a la empresa ante la API, pero no da acceso al panel de administración (el panel tiene su propio login de usuario, reservado al equipo del Broker).

Estructura de la Cabecera de Autenticación

Debe incluir la siguiente cabecera HTTP en todas las peticiones:

X-API-Key: su-token-api-unico-de-empresa

Nota: Las llamadas que no incluyan la cabecera recibirán un código de estado HTTP 401 Unauthorized. Si la API Key es errónea, se responderá con un código HTTP 403 Forbidden.

Ciclo de vida de la API Key

  • Se entrega una única vez, al dar de alta la empresa o al regenerarla (POST /api/v1/companies/{id}/regenerate-key, sólo administración).
  • El Broker no almacena el valor en claro (sólo su hash SHA-256): si se pierde, no se puede recuperar — debe regenerarse.
  • Los listados de administración muestran únicamente una pista (api_key_hint, p. ej. ddv_a1b2…f9z4).
  • Regenerar la clave revoca la anterior en el acto: planifique el cambio con el cliente.

Envío de Factura Verifactu (REST JSON)

Este es el canal más sencillo para ERPs. El sistema recibe un JSON estructurado con la información comercial básica, encola el trabajo de forma inmediata y devuelve un identificador de seguimiento. El broker se encargará de transformar los campos, aplicar las huellas y enviarlo asíncronamente a la AEAT.

POST /api/v1/invoices

Parámetros de Solicitud (JSON Body)

Campo Tipo Obligatorio Descripción
series string Opcional La serie de la factura (e.g. "F2026"). Puede ser una cadena vacía para series principales.
number string Obligatorio El número correlativo de factura dentro de la serie (e.g. "4921").
date string Obligatorio Fecha de expedición en formato ISO YYYY-MM-DD (e.g. "2026-06-07").
amount float Obligatorio Importe total de la factura en euros, impuestos incluidos (e.g. 121.00).
tax float Obligatorio Desglose del importe total de IVA aplicado en la factura (e.g. 21.00).
type string Obligatorio El tipo de registro. Valores permitidos: F1 (Factura ordinaria), F2 (Factura simplificada / ticket), ANUL (Anulación de factura previa).

Respuesta exitosa (202 Accepted)

El 202 ya autoriza a expedir la factura con su QR: el art. 6.5.a del RD 1619/2012 (redacción del RD 1007/2023) cuelga el QR de la EXPEDICIÓN, y sus cuatro datos son todos de la factura. Lo devuelven igual las tres rutas de ingesta (/invoices, /invoices/xml y /invoices/{id}/subsanar).

{ "verifactu_job_id": 4921, "creaycrece_job_id": null, "status": "PENDING", "operacion": "ALTA", "subsana_a": null, "warnings": [], "cotejo": { "aplica": true, "motivo": null, "aeat_env": "PROD", "url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B88776655&numserie=F2026%2F4921&fecha=07-06-2026&importe=121.00", "qr_data_uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", "leyenda_superior": "QR tributario:", "leyenda_inferior": "Factura verificable en la sede electrónica de la AEAT", "tamano_mm_min": [30, 30], "tamano_mm_max": [40, 40], "nivel_correccion": "M", "norma_qr": "ISO/IEC 18004:2015", "aeat_registrado": false, "demo": false }, "registro": { "generado": false, "huella": null, "fecha_hora_gen": null } }
  • Cómo hay que imprimirlo. Las dos leyendas son obligatorias (la inferior admite como alternativa "VERI*FACTU"), con letra "bien visible, similar a la del resto de datos"; el QR va CUADRADO, entre tamano_mm_min y tamano_mm_max, nivel de corrección M, norma ISO/IEC 18004:2015.
  • registro.generado: false no es un error. La huella encadenada se calcula al componer el lote, después del 202. Si tu política de impresión exige el registro previo, espera al generado: true de GET /api/v1/invoices/{id}/registro. El QR nunca implica registro.
  • cotejo es null cuando la empresa no tiene Verifactu contratado, y {"aplica": false, "motivo": "..."} cuando no hay QR que imprimir (un type: "ANUL" no se expide como factura).
  • warnings lleva los avisos NO bloqueantes. Presta atención a BROKER-SERIE-VACIA: si envías series: "", el registro se remite con NumSerieFactura = /número (con barra), y ese es el identificador con el que la AEAT guarda la factura y el que lleva el QR.

Estado de remisión y aviso de incidencia (art. 16.4)

El artículo 16.4 de la Orden HAC/1177/2024 obliga al sistema informático a avisar de que se ha producido una incidencia que ha impedido la remisión de todos los registros de facturación generados, indicando cuántos faltan por remitir, y a mantener ese aviso visible «mientras quede alguno de estos por remitir».

El Broker calcula ese dato y lo pone aquí a su disposición con su propia API Key. Mostrarlo al usuario es responsabilidad de su ERP — igual que la impresión del código QR: el Broker no tiene pantalla delante de nadie. Es el mismo reparto que declaran las Declaraciones Responsables de los dos componentes.

GET /api/v1/status
{ "company_id": 4, "incidencia": { "activa": true, "desde": "2026-09-13T08:30:00+00:00", "pendientes": 37, "pendientes_en_cola": 35, "pendientes_detenidos": 2, "pendiente_mas_antiguo": "2026-09-13T08:12:41+00:00", "remite_a_aeat": true, "aviso": "Se ha producido una incidencia que ha impedido la remisión de todos los registros de facturación generados. Faltan 37 por remitir. 2 de ellos están detenidos: el reintento automático se ha agotado o la configuración impide el envío, y hace falta intervención para reanudar la remisión." }, "certificado": { "estado": "aviso", "instalado": true, "caduca_el": "2026-10-28T21:18:01+00:00", "dias_restantes": 45, "dias_caducado": null, "caducado": false, "caducidad_leida_el": "2026-09-13T20:18:01+00:00", "vigilancia_atrasada": false, "umbral_aviso_dias": 90, "umbral_critico_dias": 30, "mensaje": "El certificado de firma caduca el 28-10-2026, dentro de 45 días. Conviene renovarlo antes de que se acabe el plazo." } }

Cadencia: una petición por minuto. Nada de lo que devuelve esta ruta cambia más rápido — el reloj del artículo 16.2 es de 60 s y la caducidad del certificado se relee cada 12 h. Por encima de 120 peticiones por minuto y empresa, la respuesta es 429 con Retry-After: 60.

Caducidad del certificado de firma

El bloque certificado de GET /api/v1/status dice cuándo caduca el certificado de firma de esa empresa y cuántos días quedan. Viaja ahí, y no en un webhook, porque la caducidad es un suceso de la empresa: el webhook de cambio de estado se dispara por factura y nunca podría contarla. Es el único suceso seguro y con fecha conocida de todo el sistema: pasa cada 1-2 años y, el día que pasa, falla el mTLS, las facturas entran en reintentos y unas 8 horas después se para la cola entera de esa empresa.

El Broker NO avisa a nadie. Este bloque enuncia un hecho —caduca tal día, quedan N, o no hay certificado, o no se puede usar— y ahí acaba su responsabilidad: no manda un correo, ni un SMS, ni sabe si el cliente está enterado. Convertir ese hecho en un aviso a una persona es responsabilidad de su ERP, exactamente igual que el aviso de incidencia del artículo 16.4 y que la impresión del código QR: el Broker no tiene pantalla delante de nadie. Leerlo al revés —«el Broker ya avisa»— es exactamente cómo se llega al día de la caducidad sin que nadie haya hecho nada.

  • estado ya viene resuelto, con siete valores posibles: ok, aviso y critico (bandas por días restantes), caducado, sin_certificado (no hay ninguno instalado: no es lo mismo que «no lo hemos mirado»), sin_revisar (instalado, caducidad todavía sin leer) e ilegible (instalado y el Broker no puede usarlo: el fichero no está, o no se puede abrir).
  • aviso nombra la ventana, no un aviso cursado. Es la misma palabra que usan el panel y el registro de incidencias del Broker para la banda de los 90 días. Que el estado sea aviso no significa que se haya avisado a nadie: significa que toca avisar.
  • caducidad_leida_el es cuándo leyó el Broker el fichero .p12 para sacar esa fecha — no cuándo se atendió nada. Si vigilancia_atrasada es true, esa lectura tiene más de dos vueltas del vigilante y los campos de este bloque no se están refrescando: escálelo.
  • umbral_aviso_dias y umbral_critico_dias viajan en la respuesta para que no haya que duplicar la regla: son los mismos que aplican el panel y /health/deep, y los decide el operador por entorno.
  • dias_restantes es negativo si ya caducó, y trunca hacia abajo (30 días y 6 horas son 30). Para contar los días transcurridos desde una caducidad no vale -dias_restantes: use dias_caducado, que vale 0 el mismo día en que caducó.
  • mensaje está redactado en castellano y listo para mostrar, y su consecuencia se ajusta al entorno: una cuenta de pruebas (incidencia.remite_a_aeat: false) nunca lee que su cola se vaya a parar, porque todavía no remite nada.
  • No devuelve el subject ni el issuer del certificado: el de un certificado de representante lleva el nombre y el DNI de una persona física, y no aportan nada a la única decisión que sostiene este bloque.
  • activa: false con pendientes > 0 no es un problema. El artículo 16.2 obliga a esperar entre envíos (inicialmente 60 s, el que indique la AEAT después), así que tener cola es el funcionamiento normal. El aviso lo explica.
  • aviso es null cuando no queda ningún registro por remitir: es exactamente la condición con la que el art. 16.4 deja de exigir la visualización.
  • remite_a_aeat: false significa que la cuenta está en entorno de pruebas y sus registros NO viajan a la AEAT. Nunca se declara incidencia en ese caso: sería una manifestación de contingencia falsa.
  • El mismo bloque incidencia viaja dentro de la respuesta de GET /api/v1/invoices/{id}, para que el aviso llegue sin añadir una llamada más.
  • Sólo devuelve datos de la empresa a la que pertenece la API Key. Sin cabecera, 401; con una clave que no resuelve a ninguna empresa, 403.

Envío de Factura Verifactu (Facturae XML)

Si su ERP ya genera facturas electrónicas firmadas o sin firmar en el formato estándar del Ministerio de Industria (**Facturae 3.2.x**), puede enviar el archivo XML crudo directamente. El Broker lo parsea, comprueba sus datos y procesa el registro Verifactu de forma equivalente.

POST /api/v1/invoices/xml

Subsanación de un registro emitido

Un registro ya emitido no se modifica ([SW] §9.1.2: "permanecerá inalterado"): se crea un eslabón nuevo que lo subsana. La respuesta es el mismo 202 de arriba, con su bloque cotejo.

POST /api/v1/invoices/{invoice_id}/subsanar
{ "motivo": "corrección del desglose de IVA", "description": "Servicios de consultoría de junio", "forzar_importes_subsanacion": false }

Cambio de contrato: si la subsanación mueve ImporteTotal o CuotaTotal, la respuesta es 422 con BROKER-SUBSANACION-IMPORTES, sea cual sea el estado del original — antes era solo un aviso. Con el QR entregado desde el 202, el papel ya está en la calle también cuando el original está PENDING, PENDING_RETRY o REJECTED, y una subsanación que cambia el importe invalida una factura ya entregada. El uso legítimo del flag forzar_importes_subsanacion: true es el alta por rechazo (S,X): el registro previo fue rechazado y la AEAT no llegó a tener la clave, así que no hay papel válido que invalidar.

Envío de Facturas Crea y Crece

Para empresas obligadas a operar bajo la ley **Crea y Crece**, la API permite enviar los datos de la factura en JSON (que el broker convertirá a XML UBL 2.1 estándar) o bien enviar directamente el documento XML UBL listo para la Solución Pública de Facturación Electrónica (SPFE).

POST /api/v1/creaycrece/invoices

Campos Adicionales del payload JSON

Campo Tipo Obligatorio Descripción
copy_indicator integer Opcional Indica el canal de copia. Utilice 1 para factura original emitida y 0 para copias informativas. (Por defecto: 1)

Registro de Estados de Pago (Crea y Crece)

La ley Crea y Crece obliga a reportar los estados de cobro y pago de las facturas emitidas para controlar la morosidad comercial. Los actores (proveedor o cliente) deben notificar al Broker los cambios de estado correspondientes.

POST /api/v1/creaycrece/payments

Parámetros del Estado de Pago

Campo Tipo Obligatorio Descripción
invoice_id integer Obligatorio ID interno de la factura Crea y Crece que ha cambiado de estado de pago.
status_type string Obligatorio El tipo de estado de cobro/pago. Valores válidos: PAID (Cobrada/Pagada completamente), REJECTED (Factura rechazada por el destinatario), DUE_DATE (Fecha de vencimiento alcanzada sin cobro).
event_date string Obligatorio Fecha en formato ISO YYYY-MM-DD en la que se produjo la transacción de pago o el rechazo.

Callback URL (Webhooks)

Dado que el procesamiento del Broker es asíncrono y en cola, no bloquea al ERP durante la llamada al endpoint. El broker enviará un **HTTP POST** con los resultados finales de validación (AEAT y SPFE) a la `callback_url` configurada para el cliente.

Esquema de Notificación del Webhook (POST)

Su servidor de callback en el ERP recibirá peticiones con el siguiente cuerpo JSON:

{ "invoice_id": 4921, "company_id": 7, "series": "F2026", "number": "4921", "status": "VALIDATED", "hash": "8F3A2290B551C...F012A", "prev_hash": "1C90AB77E4D2F...9911B", "aeat_csv": "ABCD1234EFGH5678", "aeat_message": "Aceptado con éxito en VERI*FACTU", "xml_payload": "<sum:RegFactuSistemaFacturacion>...", "xml_response": "<env:Envelope>...", "operacion": "ALTA", "subsana_a": null, "registro_vigente": true }

Importante: Su endpoint de callback debe responder con un código HTTP 200 OK o 204 No Content para confirmar la recepción del evento.

El callback no lleva el QR. El QR se entrega en el 202 de ingesta (bloque cotejo) y se puede volver a pedir en cualquier momento con GET /api/v1/invoices/{id}/cotejo: sus cuatro datos son de la FACTURA y ninguno viene de la respuesta de la AEAT, así que no hay que esperar a este webhook para imprimir. El Broker reintentará hasta 3 veces el envío si se producen fallos de red.

Crea y Crece — Endpoints avanzados

Endpoints especializados para integraciones avanzadas con la Solución Pública de Facturación Electrónica (SPFE): pre-validación schematron EN 16931 sin persistencia, envío estructurado con mapeo automático a UBL 2.1, ingesta de Facturae 3.2.x y un canal síncrono que devuelve directamente el CSV de aceptación.

POST /api/v1/creaycrece/validate

Pre-validación del XML UBL contra el conjunto de reglas schematron EN 16931 (perfil CIUS-FACE-B2G). No persiste nada en base de datos ni encola trabajos. Útil para que el ERP detecte errores estructurales antes de emitir.

Cabeceras

CabeceraValor
Content-Typeapplication/xml

No requiere X-API-Key (endpoint de utilidad pública: no lee ni escribe dato alguno de ninguna empresa). Sí tiene badén, porque cada llamada arranca Saxon: máximo 512 KiB de cuerpo (413 si se pasa) y 30 validaciones por minuto e IP (429 con Retry-After).

Cuerpo

<?xml version="1.0" encoding="UTF-8"?> <Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"> <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID> ... </Invoice>

Respuesta exitosa (200 OK)

{ "is_valid": false, "flavor": "CIUS-FACE-B2G", "findings": [ { "rule_id": "BR-CO-15", "severity": "fatal", "text": "Invoice total amount with VAT must equal the sum of line net amounts + VAT total.", "location": "/Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount" } ], "fatal_count": 1, "warning_count": 0 }

Errores

HTTPCuándo ocurre
400XML mal formado o vacío
415Content-Type distinto de application/xml
POST /api/v1/creaycrece/invoices/structured

Recibe una factura en JSON estructurado siguiendo el modelo Invoice del broker, la transforma a UBL 2.1, ejecuta schematron EN 16931 y encola el trabajo de envío a la SPFE.

Cabeceras

CabeceraValor
X-API-KeyClave de empresa (obligatorio)
Content-Typeapplication/json
X-Skip-Validationtrue para omitir schematron (opcional)

Cuerpo (JSON)

{ "series": "F2026", "number": "5021", "issue_date": "2026-06-08", "type_code": "380", "currency": "EUR", "seller": { "name": "Ofimática Digital Soluciones SLU", "tax_id": "B88776655", "address_line": "Calle Mayor 12", "city": "Madrid", "postal_code": "28013", "country_code": "ES" }, "buyer": { "name": "Cliente Ejemplo SA", "tax_id": "A12345678", "address_line": "Avenida Diagonal 200", "city": "Barcelona", "postal_code": "08018", "country_code": "ES" }, "lines": [ { "line_id": "1", "description": "Servicios profesionales", "quantity": 10, "net_unit_price": 50.00, "line_net_amount": 500.00, "vat_category": "S", "vat_rate": 21.00 } ], "payment_means_code": "30", "payment_iban": "ES7621000813610123456789", "due_date": "2026-07-08", "is_copy": false }

Respuesta exitosa (202 Accepted)

{ "job_id": 4521, "status": "PENDING", "unique_code": "B88776655-F2026-5021", "validated": true }

Errores

HTTPCuándo ocurre
401Falta cabecera X-API-Key
403API Key inválida
409Duplicado (mismo unique_code)
422Validación schematron EN 16931 falló (incluye findings en el cuerpo)
POST /api/v1/creaycrece/invoices/facturae

Acepta un documento Facturae 3.2.x directamente. El broker realiza el mapeo a UBL 2.1, lanza la validación schematron y encola el envío.

Cabeceras

CabeceraValor
X-API-KeyClave de empresa (obligatorio)
Content-Typeapplication/xml

Cuerpo

XML Facturae 3.2.x crudo (sin firmar o firmado XAdES).

Respuesta exitosa (202 Accepted)

{ "job_id": 4522, "status": "PENDING", "unique_code": "B88776655-F2026-5022", "validated": true, "source_format": "facturae-3.2.x" }

Errores

HTTPCuándo ocurre
400Facturae mal formado o no soportado
409Duplicado
422Validación EN 16931 falló tras el mapeo
POST /api/v1/creaycrece/invoices/sync

Canal síncrono: ejecuta validación, transmisión a SPFE y devuelve directamente en la respuesta HTTP el CSV de aceptación y el código de respuesta. Útil para flujos interactivos donde no se quiere consumir el callback.

Cabeceras

CabeceraValor
X-API-KeyClave de empresa (obligatorio)
Content-Typeapplication/json

Cuerpo (JSON)

Mismo modelo Invoice que /invoices/structured.

Respuesta exitosa (200 OK)

{ "job_id": 4523, "status": "VALIDATED", "unique_code": "B88776655-F2026-5023", "accepted": true, "csv": "ABCD1234EFGH5678", "response_code": "0", "description": "Aceptada por SPFE" }

Errores

HTTPCuándo ocurre
422Validación schematron falló (no se transmite)
502Transporte SOAP a SPFE falló (red, timeout o servicio caído)

Incidencias y contingencia SPFE

El artículo 5.6 del RD 238/2026 permite declarar una incidencia técnica con la SPFE que abre una ventana de subsanación de 4 días durante la cual los reintentos se reanchorian al momento de la subsanación.

POST /api/v1/creaycrece/incidents/subsanate

Marca la subsanación de la incidencia técnica, reanclando la ventana de 4 días y reagendando todas las facturas y pagos en estado PENDING_RETRY. Puede aplicarse a una empresa concreta o de forma global a todas.

Cabeceras

CabeceraValor
X-Admin-TokenSi está configurado DASHBOARD_TOKEN (obligatorio en ese caso)
Content-Typeapplication/json

Cuerpo (JSON)

{ "company_id": 17, "window_days": 4, "notes": "Restablecido servicio SPFE tras incidencia AEAT del 2026-06-08." }

Respuesta exitosa (200 OK)

{ "subsanated_at": "2026-06-08T10:42:11Z", "contingency_until": "2026-06-12T10:42:11Z", "window_days": 4, "invoices_rescheduled": 23, "payments_rescheduled": 5, "scope": "company", "company_id": 17 }

Errores

HTTPCuándo ocurre
401Falta X-Admin-Token cuando es obligatorio
404company_id no encontrado

Autorizaciones (Apoderamiento / Colaboración Social)

Gestión de las autorizaciones vigentes que permiten al broker actuar en nombre del obligado tributario, ya sea bajo régimen de apoderamiento (representación) o de colaboración social (acuerdo AEAT).

PUT /api/v1/creaycrece/authorizations

Upsert de la autorización vigente para una empresa. Si ya existe una autorización del mismo tipo, se sustituye.

Cabeceras

CabeceraValor
X-Admin-TokenObligatorio
Content-Typeapplication/json

Cuerpo (JSON)

{ "company_id": 17, "kind": "APODERAMIENTO", "scope": "VERIFACTU,CREAYCRECE_OUT,CREAYCRECE_IN", "granted_at": "2026-06-08", "valid_until": "2028-06-08", "aeat_reference": "APO-2026-00012345" }

kind admite APODERAMIENTO o COLABORACION_SOCIAL. scope es CSV con uno o varios de VERIFACTU, CREAYCRECE_OUT, CREAYCRECE_IN.

Respuesta exitosa (200 OK)

{ "authorization_id": 42, "company_id": 17, "kind": "APODERAMIENTO", "scope": "VERIFACTU,CREAYCRECE_OUT,CREAYCRECE_IN", "granted_at": "2026-06-08", "valid_until": "2028-06-08", "aeat_reference": "APO-2026-00012345" }
GET /api/v1/creaycrece/authorizations/{company_id}

Devuelve la autorización vigente de la empresa. Responde 404 si no existe.

Cabeceras

CabeceraValor
X-API-KeyObligatorio — la clave de esa empresa. Alternativa: X-Admin-Token (operador del bróker).

Acotado a la empresa de la ruta: sin credencial responde 401, y con la clave de otra empresa 403.

Respuesta exitosa (200 OK)

{ "authorization_id": 42, "company_id": 17, "kind": "APODERAMIENTO", "scope": "VERIFACTU,CREAYCRECE_OUT,CREAYCRECE_IN", "granted_at": "2026-06-08", "valid_until": "2028-06-08", "revoked": false }

Errores

HTTPCuándo ocurre
404Empresa sin autorización vigente
POST /api/v1/creaycrece/authorizations/{company_id}/revoke

Marca la autorización vigente como revocada (cese de la representación).

Cabeceras

CabeceraValor
X-Admin-TokenObligatorio

Respuesta exitosa (200 OK)

{ "authorization_id": 42, "company_id": 17, "revoked": true, "revoked_at": "2026-06-08T11:05:22Z" }

Flujo de firma de apoderamiento

Proceso digital en tres pasos para generar y firmar el documento de apoderamiento con OTP por SMS y firma manuscrita en imagen. Al completarse, se emite automáticamente la Authorization correspondiente.

POST /api/v1/creaycrece/apoderamiento/{company_id}/draft

Genera el PDF borrador del documento de apoderamiento para revisión del representante.

Cabeceras

CabeceraValor
X-API-KeyObligatorio — la clave de esa empresa. Alternativa: X-Admin-Token (operador del bróker).

Acotado a la empresa de la ruta: sin credencial responde 401, y con la clave de otra empresa 403.

Cuerpo (JSON)

{ "scope": "VERIFACTU,CREAYCRECE_OUT,CREAYCRECE_IN", "valid_until": "2028-06-08" }

Respuesta exitosa (200 OK)

Cuerpo binario PDF con cabecera Content-Type: application/pdf.

POST /api/v1/creaycrece/apoderamiento/{company_id}/otp/send

Emite un código OTP para autorizar la firma del apoderamiento.

Aviso: este canal todavía NO tiene pasarela de SMS. El código se genera pero no sale del servidor, así que la respuesta devuelve "sent": false y "estado_envio": "SIMULADO" — antes devolvía "sent": true, que era falso. En un despliegue de producción (APP_ENV=prod) este endpoint responde 503 diciéndolo, en vez de fingir un envío. El código sólo se puede recuperar con DEBUG_RETURN_OTP=true en un entorno de desarrollo.

El destino no lo elige quien llama: el código sale siempre al móvil del representante registrado en la ficha de la empresa (dr_phone). El phone del cuerpo se usa sólo para cotejar; si no coincide con el registrado, la respuesta es 400 y no se envía nada. Si la empresa no tiene móvil registrado, también 400. Ritmo acotado: un código cada 30 s y como mucho 5 por hora y empresa (429 al pasarse).

Cabeceras

CabeceraValor
X-API-KeyObligatorio — la clave de esa empresa. Alternativa: X-Admin-Token (operador del bróker).

Acotado a la empresa de la ruta: sin credencial responde 401, y con la clave de otra empresa 403.

Cuerpo (JSON)

{ "phone": "+34600123456" }

Respuesta exitosa (200 OK)

{ "company_id": 17, "phone": "••••••456", "sent": true, "ttl_seconds": 600, "otp_code": "482910" }

otp_code solo se devuelve cuando la variable de entorno DEBUG_RETURN_OTP=true. En producción nunca viaja en la respuesta.

POST /api/v1/creaycrece/apoderamiento/{company_id}/sign

Verifica el OTP, aplica la firma manuscrita al PDF y emite la autorización en BD.

Cabeceras

CabeceraValor
X-API-KeyObligatorio — la clave de esa empresa. Alternativa: X-Admin-Token (operador del bróker).

Acotado a la empresa de la ruta: sin credencial responde 401, y con la clave de otra empresa 403.

Cuerpo (JSON)

{ "otp_code": "482910", "signature_image_b64": "iVBORw0KGgoAAAANSUhEUgAA...", "scope": "VERIFACTU,CREAYCRECE_OUT,CREAYCRECE_IN", "valid_until": "2028-06-08" }

Respuesta exitosa (200 OK)

{ "company_id": 17, "authorization_id": 42, "scope": "VERIFACTU,CREAYCRECE_OUT,CREAYCRECE_IN", "granted_at": "2026-06-08", "valid_until": "2028-06-08", "signed_pdf_path": "/storage/apoderamientos/17/2026-06-08_signed.pdf" }

Errores

HTTPCuándo ocurre
400OTP incorrecto, expirado o ya consumido
429Cinco OTP fallidos seguidos: el código queda invalidado, hay que pedir uno nuevo
404No existe borrador previo para la empresa

Gestión de empresas (CRUD)

Operaciones administrativas sobre el catálogo de empresas registradas en el broker. Todas las operaciones de escritura requieren X-Admin-Token.

GET /api/v1/companies

Lista todas las empresas registradas. Todas las operaciones de este bloque (lecturas incluidas) requieren credencial de administración (sesión del panel o X-Admin-Token); las API keys nunca aparecen en claro, sólo su pista api_key_hint.

Respuesta exitosa (200 OK)

[ { "id": 17, "nif": "B88776655", "name": "Ofimática Digital Soluciones SLU", "api_key_hint": "ddv_kJ8w…Qsg4", "aeat_env": "TEST", "service_verifactu": 1, "service_creaycrece": 1, "invoice_count": 112 } ]
POST /api/v1/companies

Alta o upsert (por NIF) de empresa. Si el NIF ya existe se actualizan sus campos.

Cabeceras

CabeceraValor
X-Admin-TokenObligatorio
Content-Typeapplication/json

Cuerpo (JSON)

{ "nif": "B88776655", "name": "Ofimática Digital Soluciones SLU", "cert_name": "B88776655.p12", "callback_url": "https://erp.cliente.com/hooks/verifactu", "aeat_env": "TEST", "service_verifactu": true, "service_creaycrece": true, "address": "Calle Mayor 12, 28013 Madrid", "rep_name": "Luis López", "rep_dni": "12345678Z", "rep_role": "Administrador único", "erp_name": "Digitaldocu ERP", "erp_version": "2026.06", "dr_email": "facturacion@cliente.com", "dr_phone": "+34600123456" }

Respuesta exitosa (200 OK)

{ "success": true, "message": "Configuración de empresa guardada.", "api_key": "ddv_kJ8…(sólo en altas nuevas — se muestra UNA única vez)", "api_key_shown_once": true }

La api_key la genera el servidor y sólo aparece en la respuesta del alta (o de POST /api/v1/companies/{company_id}/regenerate-key). Guárdela en ese momento: el Broker no la almacena en claro y no puede volver a mostrarla.

PUT /api/v1/companies/{company_id}

Actualiza la ficha por ID. Soporta cambio de NIF: si el NIF cambia, el broker migra automáticamente todos los unique_code de las facturas Crea y Crece asociadas para preservar la trazabilidad.

Cabeceras

CabeceraValor
X-Admin-TokenObligatorio
Content-Typeapplication/json

Cuerpo (JSON)

Mismo modelo CompanyConfig que el endpoint POST /companies.

Respuesta exitosa (200 OK)

{ "success": true, "company_id": 17, "nif_changed": true, "old_nif": "B88776600", "migrated_invoices": 134 }

Errores

HTTPCuándo ocurre
404company_id no encontrado
409El nuevo NIF ya está en uso por otra empresa
POST /api/v1/companies/{company_id}/certificate

Sube el certificado de firma de la empresa en formato PKCS#12 (.p12 / .pfx).

Cabeceras

CabeceraValor
X-Admin-TokenObligatorio
Content-Typemultipart/form-data

Cuerpo (multipart)

CampoTipoDescripción
filefileArchivo binario .p12 o .pfx
passwordstringContraseña del PKCS#12

Respuesta exitosa (200 OK)

{ "success": true, "company_id": 17, "cert_subject": "CN=Ofimatica Digital, O=ODSL, C=ES", "valid_until": "2028-03-15" }
GET /api/v1/companies/{company_id}/declaraciones

Estado y texto de las Declaraciones Responsables (Modelos A y B) de una empresa. Es un endpoint de administración del bróker, no de integración: exige X-Admin-Token y una API key de empresa no sirve. Si lo que necesitas es la declaración responsable del componente para mostrarla dentro de tu sistema (artículo 15.3 de la Orden HAC/1177/2024), usa GET /api/v1/declaracion, que va con tu propia X-API-Key.

Cabeceras

CabeceraObligatoriedadDescripción
X-Admin-TokenObligatorioToken de administración del bróker.

Respuesta exitosa (200 OK)

{ "company_id": 17, "nif": "B12345678", "name": "Acme Solutions SL", "modelo_a_status": "FIRMADO", "modelo_a_doc_id": "99001", "modelo_a_firma_fase": "SELLADO", "modelo_a_sellada": true, "modelo_a_sif_version": "1.0.0", "modelo_a_emitida_el": "2026-05-12", "modelo_a_firmado_el": "2026-05-12T07:04:11+00:00", "modelo_a_ampara_version_actual": true, "modelo_a_text_es_borrador": false, "modelo_a_text": "DECLARACIÓN RESPONSABLE DEL SISTEMA INFORMÁTICO DE FACTURACIÓN…", "modelo_b_status": "PENDIENTE", "modelo_b_text": "…", "sif_version_actual": "1.0.0" }

modelo_X_text es el documento que se emitió: si la declaración ya se mandó a firmar, se recompone con la versión y la fecha de aquel día, no con las de hoy. Mientras no se haya emitido ninguna, modelo_X_text_es_borrador vale true y el texto es un borrador. modelo_X_ampara_version_actual en false significa que la declaración firmada no cubre la versión que el bróker está declarando a la AEAT (artículo 13.2 del Real Decreto 1007/2023): hay que reemitirla.

GET /api/v1/declaracion

La declaración responsable del componente de facturación, con tu X-API-Key. Existe para que puedas mostrarla dentro de tu sistema informático «de manera legible e individualizada […] y accesible por el usuario de forma rápida, fácil e intuitiva», que es lo que exige el artículo 15.3 de la Orden HAC/1177/2024. Devuelve sólo lo de tu empresa.

Respuesta exitosa (200 OK)

{ "company_id": 17, "modelo_a": { "titulo": "DECLARACIÓN RESPONSABLE DEL SISTEMA INFORMÁTICO DE FACTURACIÓN", "texto": "DECLARACIÓN RESPONSABLE DEL SISTEMA INFORMÁTICO DE FACTURACIÓN\n\na) Nombre…", "estado": "FIRMADO", "documento_id": "99001", "sif_version": "1.0.0", "emitida_el": "2026-05-12", "firmado_el": "2026-05-12T07:04:11+00:00", "ampara_version_actual": true, "text_es_borrador": false }, "modelo_b": { "estado": "PENDIENTE", "documento_id": "", "sif_version": "" }, "sif_version_actual": "1.0.0" }

Cotejo AEAT y monitorización

Endpoints de consulta para imprimir el QR de cotejo en la factura, inspeccionar los XML SOAP enviados / recibidos, leer la cola y los logs de auditoría, y verificar la integridad de la cadena de huellas SHA-256.

GET /api/v1/invoices/{invoice_id}/cotejo

Devuelve la URL pública de cotejo AEAT y el QR PNG (codificado en data URI). Es el MISMO bloque que ya viene en el 202 de ingesta: se puede pedir en cualquier momento y no exige que la factura esté validada.

Respuesta exitosa (200 OK)

{ "invoice_id": 4921, "aplica": true, "motivo": null, "aeat_env": "TEST", "url": "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=B88776655&numserie=F2026%2F4921&fecha=08-06-2026&importe=121.00", "qr_data_uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", "leyenda_superior": "QR tributario:", "leyenda_inferior": "Factura verificable en la sede electrónica de la AEAT", "tamano_mm_min": [30, 30], "tamano_mm_max": [40, 40], "nivel_correccion": "M", "norma_qr": "ISO/IEC 18004:2015", "aeat_registrado": false, "demo": false, "csv": null, "status": "PENDING", "estado_remoto": null, "anulada": false, "anulacion_id": null, "anulacion_status": null, "vigente": true, "superseded_by": null }
  • La URL apunta a prewww2.aeat.es con aeat_env=TEST y a www2.agenciatributaria.gob.es en PROD. La ruta es /wlpl/TIKE-CONT/ValidarQR y los parámetros son nif, numserie, fecha (DD-MM-AAAA) e importe (2 decimales).
  • numserie es serie/número — el mismo NumSerieFactura con el que se remite el registro. Si emites sin serie lleva barra inicial (/4921) y el 202 te lo avisa con BROKER-SERIE-VACIA.
  • aeat_registrado: false significa «expedida, todavía no confirmada por la AEAT». El QR es válido igual: nunca implica registro. csv sigue vacío hasta que la AEAT registre.
  • aplica: false + motivo cuando NO hay QR que imprimir: un registro de anulación (type: "ANUL") no se expide como factura, y una empresa en aeat_env=MOCK sobre un despliegue de producción no tiene cotejo de demo servible.
  • anulada: true avisa de que existe un registro de anulación de esa clave: el cotejo de la AEAT responderá «no consta» (resultado 01, que engloba las anuladas) aunque aeat_registrado sea true.
  • demo: true avisa de que esa URL NO es de la AEAT: es el cotejo simulado del propio bróker (aeat_env=MOCK).
GET /api/v1/invoices/{invoice_id}/registro

¿Existe ya el registro de facturación (la huella encadenada) de esta factura? Sin red y sin AEAT. En el 202 todavía no existe: la huella y FechaHoraHusoGenRegistro se calculan al componer el lote.

{ "generado": true, "huella": "8F3A2290B551C...F012A", "prev_hash": "1C90AB77E4D2F...9911B", "fecha_hora_gen": "2026-06-08T10:14:22+02:00", "status": "VALIDATED", "aeat_registrado": true }

El contrato: el 202 te autoriza a expedir la factura con su QR; si tu política de impresión exige el registro previo, espera al generado: true de aquí. A diferencia del cotejo, este endpoint NO resuelve linaje: responde por ESTE id.

GET /api/v1/invoices

Lista facturas. Acepta el parámetro opcional ?company_id=N para filtrar.

Respuesta exitosa (200 OK)

[ { "invoice_id": 4921, "company_id": 17, "series": "F2026", "number": "1002", "date": "2026-06-08", "amount": 121.00, "status": "VALIDATED" } ]
GET /api/v1/invoices/{invoice_id}

Detalle completo de una factura.

Cabeceras

CabeceraValor
X-API-KeyObligatorio

Respuesta exitosa (200 OK)

{ "invoice_id": 4921, "company_id": 17, "series": "F2026", "number": "1002", "date": "2026-06-08", "amount": 121.00, "tax": 21.00, "type": "F1", "status": "VALIDATED", "aeat_response_code": "0", "huella_digital_sha256": "8F3A2290B551C...F012A", "previous_huella_sha256": "1D04A...C0CA", "csv": "ABCD1234EFGH5678" }
GET /api/v1/invoices/{invoice_id}/xml/request

Devuelve el XML SOAP enviado a la AEAT para esta factura. Cabecera Content-Type: application/xml.

GET /api/v1/invoices/{invoice_id}/xml/response

Devuelve el XML SOAP de respuesta recibido de la AEAT.

GET /api/v1/logs?company_id=N

Event log / auditoría de la empresa indicada.

Respuesta exitosa (200 OK)

[ { "log_id": 9921, "company_id": 17, "timestamp": "2026-06-08T10:42:11Z", "event": "INVOICE_VALIDATED", "invoice_id": 4921, "detail": "Aceptado con éxito en VERI*FACTU" } ]
GET /api/v1/queue/stats

Contadores actuales de la cola de procesamiento.

Respuesta exitosa (200 OK)

{ "pending": 12, "processing": 3, "validated": 1582, "rejected": 4 }
POST /api/v1/system/audit?company_id=N

Lanza una auditoría de integridad de la cadena de huellas SHA-256 de las facturas de la empresa. Detecta saltos, huellas rotas o registros faltantes.

Cabeceras

CabeceraValor
X-Admin-TokenObligatorio

Respuesta exitosa (200 OK)

{ "healthy": true, "status": "healthy", "message": "Cadena de huellas íntegra. 1582 registros verificados.", "counts": { "total": 1586, "validated": 1582 }, "rejected_count": 4, "pending_count": 0, "details": [] }

status puede tomar los valores healthy, warning, broken o no_data. Cuando healthy=false, details[] describe los huecos o discrepancias encontradas.