Digitaldocu · Integración

API del DeCA

Para emitir documentos electrónicos de control administrativo desde tu propio sistema de gestión, sin teclear nada dos veces.

  1. Qué hace y qué no
  2. Cargador o transportista
  3. Tu clave
  4. Crear una expedición
  5. Adjuntar tu albarán
  6. Consultar el estado
  7. Corregir un DeCA en ruta
  8. Que te avisemos nosotros
  9. Errores
  10. Límites
  11. Aviso de buzón
  12. Registro de actividad para tu SIEM

1Qué hace y qué no

Mandas los datos del transporte. Nosotros componemos el DeCA en PDF, le estampamos el código QR, le incrustamos los datos estructurados eFTI, lo firmamos digitalmente si tu suscripción tiene firma, lo archivamos en tu Digitaldocu y avisamos al conductor.

Te devolvemos su número de expedición y su identificador. A partir de ahí el documento vive en tu Digitaldocu como cualquier otro.

Solo si tu contrato lleva tope. Por defecto no hay tope: se mide el consumo, se avisa y el exceso se factura, pero se emite siempre. Si tu contrato sí lo lleva, agotar la bolsa del periodo devuelve 402 volumen_agotado —en el plan mensual, también si la cuota está cancelada— y no se emite hasta el periodo siguiente (el día 1 en el mensual, el aniversario del alta en el anual), hasta comprar un pack o hasta ampliar el contrato. La respuesta trae periodo, usados y bolsa.

La bolsa es el cupo del periodo más los DeCAs de packs que te queden, en el mensual y en el anual. Los packs no caducan y se gastan después del cupo del periodo. Cada plan tiene sus packs: los que puedes comprar aparecen en Ajustes → Saldo. Si ahí no aparece ninguno, tu plan no tiene pack a la venta y el 402 se resuelve escribiéndonos para ampliar el contrato.

Conviene que tu programa lo contemple: es el único error que se resuelve con una gestión comercial o de pago. Ningún otro 4xx se arregla repitiendo la misma petición; reintenta solo ante fallos de red y 5xx (ver el punto 9).

La API manda los datos del DeCA. Tu suscripción decide cómo se emite. La firma digital, el aviso al conductor —por SMS o por correo— y la generación automática son ajustes de tu suscripción, no parámetros de la llamada. Se cambian desde el panel.

Uno de esos ajustes afecta a lo que se imprime: si tu suscripción tiene marcado No imprimir el nombre, el móvil ni el correo del conductor en el DeCA (en Los datos de tu empresa), conductor, movil y email_chofer se guardan —y el móvil y el correo sirven para el aviso— pero no salen en el PDF, y el nombre y el móvil tampoco en los datos eFTI (el correo no va nunca en ellos). Si los mandas y no los ves impresos, es por eso. Si no podemos leer ese ajuste al generar el DeCA, no lo generamos: la llamada devuelve un error y hay que repetirla. Si no lo podemos leer al rehacerlo en una versión nueva, salen impresos igualmente. Y sus datos eFTI no se generan mientras no lo podamos leer: se vuelve a intentar más tarde, así que, con el ajuste marcado, nunca llevan el nombre ni el móvil. Tampoco se generan sin él, desde la aplicación, el justificante de una entrega, el acta de recogida, el cierre de una entrega sin firma, la inspección final de un vehículo ni el expediente: hay que repetirlo en un momento. Pero unos datos eFTI nuevos no quitan los que el PDF ya llevaba: cuando se añaden a un PDF que ya los tenía —al estampar sobre él una entrega firmada o el acta de una recogida sin inspección de vehículo, o en una corrección que no lo rehace (la que pone una banda roja en la primera página)—, los anteriores siguen dentro tal como se generaron, con el nombre y el móvil si entonces los llevaban. Ese ajuste no cambia el acta de recogida firmada ni lo que la inspección de vehículo añade al PDF, si tu suscripción las usa: los dos llevan el nombre del conductor.

2Cargador o transportista

Un DeCA nombra siempre a las dos partes, así que la API pide los datos de las dos vengas de donde vengas. Lo que cambia es cuáles son tuyos:

Tus datos (los ponemos nosotros)Los de la otra parte (por envío)
Eres el cargador cargador, cargador_nif, cargador_domicilio transportista, transportista_nif
Eres el transportista transportista, transportista_nif cargador, cargador_nif, cargador_domicilio

Tus datos no hace falta que los mandes. Guárdalos una vez en el panel, en Los datos de tu empresa: dices de qué lado estás y los ponemos nosotros en cada DeCA. Si aun así los mandas en la petición, mandan los tuyos — lo guardado es solo el valor por defecto.

Merece la pena aunque tu programa pueda repetirlos: son los datos que no cambian nunca, y repetirlos mil veces son mil oportunidades de colar una errata. Un NIF mal escrito acaba impreso y firmado en un documento legal.

3Tu clave

Se crea desde el panel, en Claves para tu programa. Se enseña una sola vez: guárdala en cuanto la veas, porque ni nosotros podemos volver a mostrarla. Si se pierde, se revoca y se crea otra.

Authorization: Bearer deca_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

La clave identifica tu suscripción por sí sola: no hay que mandar ningún otro dato para decir quién eres. Solo sirve para lo que hay en este documento — no da acceso al resto de tus documentos de Digitaldocu.

Hay otras dos, cada una para una sola cosa: la de aviso de buzón (deca_aviso_, punto 11) y la de auditoría para tu SIEM (deca_audit_, punto 12). Ninguna de las dos sirve para emitir.

4Crear una expedición

POST https://deca.digitaldocu.es/api/deca/v1/expediciones

Obligatorios

Son los que exige la norma (Orden FOM/2861/2012, art. 6). Si falta alguno no se emite nada y te decimos cuáles. Los de tu propia empresa puedes omitirlos si los has guardado en el panel (ver el punto 2).

CampoEjemplo
cargadorINDUSTRIAS EJEMPLO, S.L.Cargador contractual
cargador_nifB00000000
cargador_domicilioCalle Ejemplo 1, Valencia
transportistaTRANSPORTES EJEMPLO, S.L.Transportista efectivo
transportista_nifB11111119
origenValenciaLugar de carga
destinoMadridLugar de entrega
fecha2026-08-25aaaa-mm-dd
mercanciaPalés de mercancía general
peso17000 · 17,00 Tn · 1.500 kgCon unidad o sin ella
matricula1234BCD · CR-1234-ABVehículo tractor. La actual o la provincial anterior a 2000

Todos los valores van como texto, también los que son números: "peso": "17000", no "peso": 17000. Un número sin comillas se trata como si el campo no viniera, y si es obligatorio te devolvemos campos_incompletos.

El peso admite la unidad que uses. Si mandas "17,00 Tn" lo convertimos a 17.000 kg; si mandas solo la cifra, la tomamos por kilos. Y comprobamos que sea un peso razonable para un camión: por encima de 60 t avisamos, porque suele ser un error de unidad.

Opcionales

Campo
conductorNombre del conductor
movilSu móvil, para el aviso
email_choferSu correo, para el aviso por correo. Si no es una dirección válida, el DeCA se emite igual y te lo decimos en avisos
remolqueMatrícula del remolque: R-1234-BCD, o la provincial antigua, M-12345-R
bultosTexto libre: 15, 26 big-bags
destinatarioQuien recibe la mercancía. También se acepta con el nombre cliente. Si no lo mandas, el documento lo deja en blanco
observacionesTexto libre que se imprime en el documento
autorizacionAutorización especial de circulación, si el transporte la lleva
albaranTu nº de albarán de este envío. Se imprime en el documento como referencia, para cruzarlo con tu albarán

email_chofer solo se usa si tu suscripción avisa por correo, o si tiene encendida la copia por correo. Es un ajuste de la suscripción, como el SMS, y no de la llamada. Si no, se guarda en el DeCA y no se envía nada.

Usa exactamente los nombres de estas tablas. Al crear, un campo que no está en ellas se ignora: matricula_remolque en vez de remolque deja el DeCA sin remolque. Cada valor admite hasta 400 caracteres.

Si tu suscripción es de residuos

Si tu suscripción tiene el perfil de residuos, el DeCA lleva además el bloque del Documento de Identificación (DI) del traslado, y por la API es obligatorio: si falta algo, no se emite nada y te devolvemos campos_incompletos con lo que falta en faltan. El nº de DI lo ponemos nosotros, con el contador de tu NIMA, y te lo devolvemos en numDi.

Campo
res_lerObligatorio. Código LER, seis cifras: 17 04 05
res_operacionObligatorio. Operación de tratamiento: R12, o su desagregado, R0401
res_operadorObligatorio. Operador del traslado
res_operador_nifObligatorio. Su NIF
res_operador_tipoObligatorio. Tipo de operador del modelo de DI: P01-P05, G01-G06, E01, E02, N02, A02, SIR, SCR
res_productorObligatorio. Productor o poseedor del residuo (origen)
res_productor_nifObligatorio. Su NIF
res_destinoObligatorio. Instalación de destino
res_destino_nifObligatorio. Su NIF
res_operador_nimaNIMA del operador (10 cifras)
res_productor_nimaNIMA del productor
res_destino_nimaNIMA de la instalación de destino
res_transportista_nimaNIMA del transportista
res_contratoNº del contrato de tratamiento, si lo hay
res_scrapSCRAP, si aplica
res_num_diSolo si numeras tú los DI: tu nº, 21 cifras (NIMA + año + contador). Si no lo mandas, lo asignamos nosotros

Los NIMA vacíos no impiden emitir. Solo los tiene quien está inscrito en el Registro de producción y gestión de residuos (nota 3 del modelo de DI): si no llegan, el DeCA se emite y te lo decimos en avisos.

Lo que necesita notificación previa no sale como DI: un LER peligroso (con *), el 20 03 01 de residuos domésticos mezclados o una operación de eliminación (D). Te devolvemos campos_incompletos con el motivo, o 409 res_requiere_notificacion, y no se emite nada.

Si tu suscripción no es de residuos y mandas campos res_*, te devolvemos 409 sin_perfil_residuos y no se emite nada: un DeCA sin el bloque no sirve como DI.

Con Idempotency-Key, un reintento del mismo envío recibe el mismo nº de DI. Los campos del bloque no se corrigen por /versiones.

201 Created

{ "ok": true,
  "documentId": 123456,
  "expedicion": "DECA-20261001-A1B2C3",
  "numDi": "280006692420260000123",
  "avisos": [] }

No emitas dos veces lo mismo

Manda tu propia referencia en la cabecera Idempotency-Key —tu número de expedición sirve—. Si repites la llamada porque se te cayó la red y no viste la respuesta, te devolvemos el mismo documento en vez de emitir otro.

curl -X POST https://deca.digitaldocu.es/api/deca/v1/expediciones \
  -H "Authorization: Bearer $DECA_KEY" \
  -H "Idempotency-Key: ALB-2026-000123" \
  -H "Content-Type: application/json" \
  -d '{
    "cargador": "INDUSTRIAS EJEMPLO, S.L.",
    "cargador_nif": "B00000000",
    "cargador_domicilio": "Calle Ejemplo 1, Valencia",
    "transportista": "TRANSPORTES EJEMPLO, S.L.",
    "transportista_nif": "B11111119",
    "origen": "Valencia",
    "destino": "Madrid",
    "fecha": "2026-08-25",
    "mercancia": "Palés de mercancía general",
    "peso": "17,00 Tn",
    "matricula": "1234BCD",
    "conductor": "Juan Ejemplo",
    "movil": "600000000",
    "email_chofer": "juan.ejemplo@example.com",
    "destinatario": "DISTRIBUCIONES EJEMPLO, S.A."
  }'
201 Created

{ "ok": true,
  "documentId": 123456,
  "expedicion": "DECA-20260825-A1B2C3",
  "avisos": [] }

En avisos llegan las dudas que no impiden emitir: un NIF que no pasa la letra de control, una matrícula sin forma de matrícula española, un peso de más de 60 t, una fecha que no llegó como aaaa-mm-dd o un email_chofer que no es una dirección válida. El DeCA ya está emitido; conviene enseñárselas a quien lo mandó.

Si repites con la misma Idempotency-Key y los mismos datos:

{ "ok": true, "repetida": true,
  "documentId": 123456,
  "expedicion": "DECA-20260825-A1B2C3" }

Y si repites la referencia con datos DISTINTOS, te decimos que no. Eso no es un reintento: es una corrección, y devolverte el documento viejo con un 200 te la perdería en silencio —tu sistema la daría por aplicada y el DeCA seguiría diciendo lo de antes—.

409 Conflict

{ "code": "datos_distintos",
  "documentId": 123456,
  "expedicion": "DECA-20260825-A1B2C3",
  "error": "Esta Idempotency-Key ya emitió un DeCA con otros datos…" }

Para corregir un documento ya emitido, versiónalo: conserva la versión anterior, que es lo que exige el apartado Quinto de la Resolución. Para emitir uno nuevo, manda otra referencia.

Si borras el DeCA desde el panel de DeCA, su referencia queda libre: puedes volver a emitir con la misma Idempotency-Key y los datos corregidos, y se emite un DeCA nuevo.

La comparación se hace sobre los datos del transporte; observaciones, autorizacion, email_chofer y albaran no cuentan para ella.

Ante un 5xx al crear, o si no llegas a ver la respuesta, repite con la misma Idempotency-Key: si el DeCA llegó a emitirse, te devolvemos ese mismo documento.

Si repites antes de que el DeCA exista, recibes 409 en_curso. Espera unos segundos y vuelve a repetir con la misma referencia. No cambies de Idempotency-Key: con otra, emitiríamos otro DeCA.

Durante unos segundos después de crearse, un DeCA puede no aparecer todavía en la consulta ni admitir adjuntos: si una llamada inmediata devuelve 404, repítela.

5Adjuntar tu albarán

POST /api/deca/v1/expediciones/{expedicion}/adjuntos

Para colgar del DeCA el documento de origen: tu albarán, tu CMR, el tique de báscula. Queda enlazado al DeCA y no se lo enseñamos al destinatario salvo que alguien lo comparta a mano desde el panel — suele llevar datos del cargador que el receptor no tiene por qué ver.

curl -X POST \
  "https://deca.digitaldocu.es/api/deca/v1/expediciones/DECA-20260825-A1B2C3/adjuntos" \
  -H "Authorization: Bearer $DECA_KEY" \
  -F "tipo=albaran" \
  -F "file=@albaran-000123.pdf"

Valores de tipo: albaran, cmr, foto_carga, ticket_bascula, packing_list, otros.

Manda el fichero como multipart/form-data, no en base64 dentro de un JSON. Base64 infla el fichero un tercio: por multipart entra hasta 4 MB, y por JSON el fichero no puede pasar de unos 3 MB. Un cuerpo de más de unos 4,5 MB lo rechaza la plataforma con un 413 que no trae JSON ni code.

Si aun así lo tienes más a mano en JSON, manda tipo, nombre y fileBase64. El nombre con su extensión: es lo que decide el formato.

Formatos: PDF, JPG o PNG, reconocidos por la extensión del nombre del fichero. Otro formato devuelve 400, igual que un tipo que no esté en la lista.

6Consultar el estado

GET /api/deca/v1/expediciones/{expedicion}
{ "ok": true,
  "expedicion": "DECA-20260825-A1B2C3",
  "documentId": 123456,
  "estado": "En tránsito",
  "entregado": false,
  "fechaFirma": null,
  "matricula": "1234BCD",
  "conductor": "Juan Ejemplo",
  "origen": "Valencia",
  "destino": "Madrid",
  "enlace": "https://backend.digitaldocu.com/api/Documents/Share/…" }

fechaFirma es la hora de la firma de la entrega, en formato dd/mm/aaaa hh:mm y hora española; null mientras no se ha entregado.

El enlace es el mismo al que apunta el QR del documento, y es fijo: pedirlo otra vez no lo renueva. Está abierto hasta 30 días desde la emisión o desde la fecha del transporte, si es posterior; si tu empresa ha pactado con nosotros un plazo más corto (de 8 días como mínimo), ese en lugar de los 30, salvo que el documento no tenga fecha del transporte. Una vez firmada la entrega, el plazo pasa a ser de siete días naturales desde la firma, que es el del apartado Tercero.5 de la Resolución de 5 de junio de 2026 (en un DeCA con varias entregas, los siete días cuentan desde que se cierra la última). Pasado eso deja de servir el PDF.

Qué expediciones encuentra. Las de la carpeta compartida de tu suscripción, que es donde se archivan las emitidas por la API, por el buzón y por una hoja de ruta.

En un reparto con varias entregas, cada entrega es su propio DeCA con su propio número de expedición. Usa el que te devolvimos al crear cada una.

Un DeCA con varias entregas (lo emite tu empresa desde el panel o desde una hoja de ruta en CSV; por la API no se emiten) solo sale aquí si está en esa carpeta compartida; los de una hoja de ruta están siempre en ella. Entonces destino es "Varios (N envíos)", origen el común o "Varios", estado pasa por "En tránsito", "En tránsito (k/N)" y, al cerrarse la última entrega, "Entregado (…)", y entregado y fechaFirma no cambian hasta ese cierre. Entonces fechaFirma es el fin del servicio, calculado como firmado_iso en el aviso entrega_firmada (apartado 8): la hora más tardía entre la del cierre que completa el documento y las de las entregas cerradas antes, sin contar las anuladas.

7Corregir un DeCA en ruta

POST /api/deca/v1/expediciones/{expedicion}/versiones

La norma permite modificar el DeCA una vez iniciada la marcha, pero obliga a dejar constancia. Por eso esto no reescribe nada: crea una versión nueva, con su motivo y su fecha, y la anterior se conserva. Es el mismo botón «Nueva versión» del panel, puesto al alcance de tu programa.

El QR no cambia nunca. El enlace público de la expedición es el mismo antes y después, así que el conductor que ya lo tiene sigue enseñando el mismo código: si se estropea el camión o se desvía el destino, mandas la corrección y no hay que reenviarle nada.

Si cambias movil, avisamos al conductor nuevo por el canal configurado y el enlace del anterior deja de permitir firmar.

curl -X POST \
  "https://deca.digitaldocu.es/api/deca/v1/expediciones/DECA-20260825-A1B2C3/versiones" \
  -H "Authorization: Bearer $DECA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "changes": { "matricula": "5678JKL", "conductor": "Ana Ejemplo", "bultos": "14" },
    "motivo": "Cambio de vehiculo por averia"
  }'

Campos admitidos en changes:

cargador, cargador_nif, cargador_domicilio, transportista, transportista_nif, conductor, movil, origen, destino, fecha, destinatario (o cliente), mercancia, peso, bultos, matricula, remolque, albaran.

observaciones y autorizacion no se corrigen por aquí.

Manda siempre motivo (hasta 200 caracteres). Queda en el historial de versiones y se imprime en el PDF, junto a los datos que dejan de ser válidos. Si no lo mandas, el documento dirá «Motivo: no consignado».

{ "ok": true,
  "commentary": "Cambio de vehiculo por averia",
  "fecha": "25/08/2026 10:15",
  "applied": ["Matricula: 5678JKL", "Conductor: Ana Ejemplo", "Bultos: 14"],
  "signed": true,
  "sealed": false }

fecha va en formato dd/mm/aaaa hh:mm y en hora UTC, no en hora española. applied dice qué ha quedado cambiado: compáralo con lo que mandaste. Si la respuesta trae kwErrors, esos cambios no han quedado guardados. signed y sealed dicen si la versión nueva salió firmada o sellada, y signError por qué no, si no.

Un nombre de campo mal escrito se rechaza, no se ignora. Si mandas uno que no está en la lista te devolvemos 400 campos_desconocidos con los que admitimos, para que no te quedes creyendo que cambiaste algo que sigue igual. Y los valores pasan por las mismas comprobaciones que al emitir: una matrícula se guarda normalizada y, si algo no cuadra, viaja en avisos sin bloquear el cambio.

Los valores, en cambio, tienen que ser texto no vacío. Un número sin comillas o una cadena vacía se ignoran sin error, y por esta vía no se puede borrar un campo.

Una expedición ya firmada por el destinatario devuelve 409 ya_entregado: está cerrada y no admite más versiones. Un DeCA con varias entregas no se corrige por la API: devuelve 409 multientrega_sin_version (se corrige desde el panel, con «Nueva versión»).

8Que te avisemos nosotros

En vez de preguntar cada poco por cada expedición, dinos una dirección y te llamamos nosotros cuando pasa algo. Se configura en el panel, en Avisar a tu sistema.

EventoCuándo
deca_emitidoCuando el DeCA está terminado: con su QR, sus datos eFTI y, si la tienes, su firma. No al crearlo — te mandaríamos a descargar un PDF a medias
deca_modificadoCada vez que se versiona, con el antes y el después de cada campo
entrega_firmadaCuando el destinatario firma la entrega: desde el enlace del SMS o del QR, en la app del conductor o al cerrar la inspección final del vehículo. En un DeCA con varias entregas, uno solo, cuando se cierra la última (ver la nota de abajo)
parada_registrada_NSolo en hojas de ruta de viajeros: cada vez que el conductor registra una parada intermedia. N es el orden de la parada. En un reparto de mercancías con varias entregas no se usa: si cada entrega es su propio DeCA, cada uno trae su propio entrega_firmada; si van todas en un único DeCA, llega uno solo, con el detalle de cada entrega
POST https://erp.tuempresa.com/webhooks/deca
X-DeCA-Evento: deca_modificado

{ "evento": "deca_modificado",
  "documentId": 123456,
  "expedicion": "DECA-20260825-A1B2C3",
  "version": 2,
  "motivo": "Cambio de vehículo por avería en origen",
  "cambios": [
    { "campo": "dca_matricula", "nombre": "Matricula",
      "antes": "1234ABC", "ahora": "4916MDH" }
  ],
  "enlace": "https://…" }

Los cambios vienen con el valor anterior y el nuevo a propósito: es lo que te deja conciliar sin volver a preguntarnos nada. Solo aparecen los campos que ya tenían valor: uno que estaba vacío y se rellena al corregir no sale en cambios.

deca_emitido y deca_modificado vienen apagados y se encienden con sus dos casillas en Avisar a tu sistema. No es burocracia: son eventos nuevos, y encenderlos para todo el mundo llenaría de mensajes desconocidos ERPs que no los esperan.

Si emites por la API, deca_emitido puede llegarte antes que el 201 de la propia llamada: el aviso sale en cuanto el documento está terminado, y la respuesta después. Tu receptor tiene que aceptar un documentId que todavía no conoce.

POST https://erp.tuempresa.com/webhooks/deca
X-DeCA-Evento: deca_emitido

{ "evento": "deca_emitido",
  "documentId": 123456,
  "tenant": "…",
  "expedicion": "DECA-20260825-A1B2C3",
  "via": "api",
  "enlace": "https://…",
  "enviado_at": "2026-08-25T10:15:03.412Z" }
POST https://erp.tuempresa.com/webhooks/deca
X-DeCA-Evento: entrega_firmada
X-DeCA-Firma:  sha256=a1b2c3…

{ "evento": "entrega_firmada",
  "documentId": 123456,
  "expedicion": "DECA-20260825-A1B2C3",
  "matricula": "1234BCD",
  "firmado_iso": "2026-08-25T14:32:00+02:00",
  "firmado_at": "25/08/2026 12:32",
  "firmante": "Ana Ejemplo",
  "firmante_nif": "00000000T",
  "incidencias": "Sin Incidencias",
  "observaciones": null,
  "via": "enlace" }

Usa firmado_iso: la hora de la firma en ISO 8601, en hora española y con su desfase, así que tu sistema la lee sin ambigüedad. firmado_at es el mismo instante en el formato dd/mm/aaaa hh:mm y en hora UTC; se mantiene para las integraciones que ya lo leen.

via dice por dónde se firmó: "enlace" (SMS o QR), "app" o "inspeccion". incidencias es siempre "Sin Incidencias" o "Con Incidencias", salvo en un DeCA con varias entregas (la nota siguiente).

Un DeCA con varias entregas. Lo emite tu empresa desde el panel, si tiene activada la opción «Varias entregas en un mismo viaje», o desde una hoja de ruta en CSV, si en esa opción ha elegido «Un único DeCA con todas las entregas» (con «Preguntar al generar», cada entrega de la hoja sale en su propio DeCA); por la API no se emiten. Trae un solo entrega_firmada, cuando se cierra la última de sus entregas, ya sea firmada, cerrada sin firma o anulada, y con dos campos más: n_envios, el número de entregas, y envios, una por entrega, con orden, destino, estado ("entregada", "entregada_incidencias", "cerrada_sin_firma" o "anulada"), via ("puerta", que es el enlace del conductor, "app" u "oficina") y cerrado_iso, la hora de su cierre. En la cabecera, firmante, firmante_nif, incidencias y observaciones llegan a null: serían los de una sola entrega. via puede ser también "oficina" (la última entrega la cerró la oficina, sin firma o anulándola), y firmado_iso es la hora más tardía entre la del cierre que completa el documento y las de las entregas cerradas antes, sin contar las anuladas.

Todos los avisos llevan además tenant —el identificador de tu suscripción— y enviado_at, en ISO 8601 y UTC. Ojo: enviado_at es la hora del primer intento, también en los reenvíos.

Comprueba que el aviso es nuestro

La cabecera X-DeCA-Firma es el HMAC-SHA256 del cuerpo, con el secreto que verás en el panel al configurar la dirección. Compáralo antes de fiarte del mensaje:

# Python
import hmac, hashlib
esperado = "sha256=" + hmac.new(
    SECRETO.encode(), cuerpo_en_bruto, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(esperado, cabecera_firma):
    return 401

9Errores

CódigoQué pasaQué hacer
401Clave ausente, mal escrita o revocadaRevisa la cabecera
403 clave_solo_avisoEsa clave es de aviso de buzón (empieza por deca_aviso_): no sirve para la APIUsa una clave de la API (deca_live_)
403 clave_solo_auditoriaEsa clave es de auditoría (empieza por deca_audit_): solo lee el registro de actividadUsa una clave de la API (deca_live_)
400 campos_incompletosFaltan obligatoriosMira faltan
400 json_invalidoEl cuerpo no es JSON válido. error dice la linea, la columna y el motivo: casi siempre unas comillas ", una barra \ o un tabulador sin escapar dentro de un textoEscapa los textos (\", \\, \t). Mándalo en UTF-8; si llega en Windows-1252 (lo normal en VB6) también lo leemos
400 json_no_objetoEl cuerpo es JSON, pero no un objeto {…}Manda un objeto
404 no_encontradaEsa expedición no existeRevisa el número
402 volumen_agotadoSe acabó la bolsa del periodo (solo con tope pactado)Compra un pack en Ajustes → Saldo si tu plan lo ofrece; si no, amplía el contrato o espera al periodo siguiente
400 campos_desconocidosUn campo de changes no existeMira admitidos
400 sin_cambiosNo viene ningún valor que aplicarRevisa el cuerpo
409 ya_entregadoEl DeCA ya está firmado: no admite versionesEmite otro
409 multientrega_sin_versionEs un DeCA con varias entregas: no se corrige por la APICorrígelo desde el panel, con «Nueva versión»
409 entregadaYa está firmada y cerradaNo admite cambios
409 en_cursoOtra llamada con esa Idempotency-Key está emitiendo ahora mismoEspera unos segundos y repite con la misma referencia
409 datos_distintosEsa Idempotency-Key ya emitió un DeCA con otros datosVersiónalo, o manda otra referencia
409Tu suscripción no tiene la estructura DeCA montada (sin code)Escríbenos
409 sin_perfil_residuosMandas campos res_* y tu suscripción no es de residuosPide que se active el perfil, o quita los res_*
409 res_requiere_notificacionEl traslado necesita notificación previa: no sale como DITramítalo por la vía de la notificación previa
409 di_sin_nima · di_sin_contadorTu suscripción de residuos no tiene configurado el NIMA o el contador con que numerar los DIEscríbenos
409 di_repetido · di_saltoEl res_num_di que mandas ya está dado, o salta demasiadoRevisa tu numeración, o no lo mandes y lo asignamos nosotros
409 di_ya_emitidoEse envío ya emitió su DeCA con ese nº de DI. Trae expedicionNada: ya está emitido
400Adjunto: tipo no válido (con admitidos) o formato no admitidoRevisa el tipo y la extensión del nombre
413 demasiado_grandeEl adjunto pasa de 4 MBComprímelo
413El cuerpo entero pasa de unos 4,5 MB (sin JSON ni code)Manda el adjunto por multipart
500 error_internoFallo nuestro. Trae incidenciaEspera y reintenta; si se repite, mándanos el número de incidencia
502 · 503Servicio no disponible o sin respuestaReintenta. Al crear, con la misma Idempotency-Key

Cuando faltan datos te decimos exactamente cuáles, para que puedas programar contra ello:

400 Bad Request

{ "error": "faltan datos obligatorios del DeCA",
  "code": "campos_incompletos",
  "faltan": ["cargador_nif", "origen", "peso"],
  "avisos": [
    { "campo": "cargador_nif",
      "nivel": "falta",
      "motivo": "obligatorio: no viene en la petición" }
  ] }

Las respuestas en JSON van en UTF-8 (Content-Type: application/json; charset=utf-8), con las tildes y la ñ tal cual: petición, no petici\u00f3n.

10Límites

Adjunto4 MB por fichero en multipart; unos 3 MB si va en base64 dentro de un JSON
FormatosPDF, JPG o PNG, reconocidos por la extensión del nombre del fichero
Claves activas10 por suscripción, más 2 de aviso de buzón y 2 de auditoría
Avisos de buzón12 por minuto y suscripción
Peso admitidoHasta 60 t (por encima, avisamos)
Cada campo400 caracteres
motivo200 caracteres
Idempotency-Key120 caracteres
Número de expedición en la ruta60 caracteres

Lo que pase de esos topes se recorta sin avisar.

Un DeCA entregado es registro legal y no se toca. Una vez que el destinatario ha firmado la recepción, esa expedición no admite documentos nuevos ni modificaciones. Si te equivocaste, emite otra.

11Aviso de buzón

Para programas que no llaman a la API, sino que dejan el PDF del albarán o del ticket en una carpeta que Digitaldocu sube a tu buzón. El piloto automático revisa ese buzón cada cinco minutos. Con el aviso, tu programa nos dice que acaba de dejar un PDF y revisamos tu buzón en ese momento.

No es otra forma de emitir: el PDF entra por el buzón como siempre y sale DeCA solo si el piloto automático lo lee sin dudas. Lo que no, se queda en el buzón para que lo revise una persona, igual que sin aviso. Tu suscripción tiene que tener encendido el piloto automático (Ajustes → Generar DeCAs solo).

El piloto automático no lee los buzones que en Digitaldocu están marcados como de facturas o con proceso automático, salvo que sea el buzón que tu suscripción tiene configurado para DeCA: esos documentos ya los trata Digitaldocu. Si tu carpeta sube a uno de ellos, el aviso responde 202 igual, pero el PDF no se procesa: haz que suba a otro buzón.

POST https://deca.digitaldocu.es/api/deca/v1/aviso

La clave

Una clave de la API (punto 3), que crea el administrador de tu empresa en Ajustes. Va en la cabecera Authorization, nunca en la dirección. Guárdala como cualquier clave de la API: además de avisar, sirve para emitir. Las claves que empiezan por deca_aviso_, que dábamos antes y solo sirven para avisar, siguen valiendo mientras no se revoquen.

Si tu Digitaldocu nos avisa ya de cada documento nuevo (el webhook «Documentos · NewDocument», que damos de alta nosotros), no hace falta llamar: el aviso llega solo.

La llamada

Deja el PDF en la carpeta, espera unos 10 segundos para que Digitaldocu lo suba al buzón y llama. El cuerpo es opcional. Si mandas el nombre del fichero, ese PDF va el primero de la revisión y, si todavía no está en el buzón, volvemos a buscarlo a los 10, 30, 60 y 120 segundos. Sin nombre, revisamos el buzón una vez.

curl -X POST https://deca.digitaldocu.es/api/deca/v1/aviso \
  -H "Authorization: Bearer deca_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"fichero": "T-000123.pdf"}'

Respondemos al momento, antes de procesar nada. El trabajo sigue por nuestra parte:

202 Accepted

{ "ok": true, "aviso": "recibido", "fichero": "T-000123.pdf" }

Manda solo el nombre del fichero. Si mandas la ruta completa, nos quedamos con lo que va detrás de la última barra, pero en el JSON las barras van dobladas: {"fichero": "C:\\Tickets\\T-000123.pdf"}. Con las barras sin doblar el JSON no es válido (400), o peor: \t y \n se leen como un tabulador y un salto de línea, y el nombre ya no es el del fichero.

Desde Visual Basic 6

Con MSXML2.ServerXMLHTTP.6.0 (viene con Windows) y tiempos de espera cortos, para que el programa no se quede esperando si no hay red. Lo que no sea un 202 se apunta en un fichero junto al programa: en un .exe compilado, Debug.Print no escribe nada, y sin ese fichero un 409 o una clave revocada pasarían semanas sin que nadie lo viera.

Private Const URL_AVISO As String = "https://deca.digitaldocu.es/api/deca/v1/aviso"
Private Const CLAVE_AVISO As String = "deca_live_xxxxxxxxxxxxxxxx"

' Llamar DESPUÉS de dejar el PDF en la carpeta y de esperar unos 10 segundos.
Public Sub AvisarBuzonDeCA(ByVal rutaPdf As String)
    Dim http As Object
    Dim nombre As String
    On Error GoTo Fallo
    nombre = Mid$(rutaPdf, InStrRev(rutaPdf, "\") + 1)
    nombre = Replace(Replace(nombre, "\", "\\"), """", "\""")
    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    ' Resolver, conectar, enviar y recibir, en milisegundos.
    http.setTimeouts 3000, 3000, 5000, 8000
    http.Open "POST", URL_AVISO, False
    http.setRequestHeader "Authorization", "Bearer " & CLAVE_AVISO
    http.setRequestHeader "Content-Type", "application/json"
    http.send "{""fichero"": """ & nombre & """}"
    If http.Status <> 202 Then
        ' Se apunta y se sigue: no se reintenta.
        ApuntarAviso http.Status & " " & http.responseText
    End If
    Set http = Nothing
    Exit Sub
Fallo:
    ' Sin red, sin respuesta a tiempo o sin TLS 1.2: el programa sigue.
    ApuntarAviso "no enviado (" & Hex$(Err.Number) & "): " & Err.Description
    Set http = Nothing
End Sub

' Una línea por fallo en aviso_deca.log, junto al programa. Nunca corta nada.
Private Sub ApuntarAviso(ByVal texto As String)
    Dim f As Integer
    On Error Resume Next
    f = FreeFile
    Open App.Path & "\aviso_deca.log" For Append As #f
    Print #f, Format$(Now, "yyyy-mm-dd hh:nn:ss") & "  " & texto
    Close #f
End Sub

Si prefieres WinHttp.WinHttpRequest.5.1, la llamada es la misma: SetTimeouts, Open, SetRequestHeader y Send.

TLS 1.2. El servidor solo acepta TLS 1.2 o superior. Windows 8, 10 y 11 lo traen puesto. En Windows 7, las dos librerías de arriba no lo usan hasta instalar la actualización KB3140245 y crear el valor DefaultSecureProtocols = 0xA00 (DWORD) en HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp y, como VB6 es de 32 bits, también en HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp. Sin eso, cada aviso falla con el error 80072F7D (se ve en aviso_deca.log). En Windows XP no funciona.

Para comprobar que llega: en nuestro panel se ve la fecha del último aviso de cada clave. Si pasan los tickets y sigue en «todavía sin usar», mira aviso_deca.log.

Si la llamada falla, tu programa sigue sin más. No reintentes ni esperes: la revisión de cada cinco minutos recoge el PDF igual. Lo que no cambia es lo de siempre: el camión no sale hasta que el conductor tiene el documento.

Respuestas

CódigoQué pasaQué hacer
202Aviso recibido. Revisamos tu buzón ahoraNada
400 fichero_no_valido · cuerpo_no_validoEl cuerpo no es JSON, o fichero no es textoRevisa el cuerpo
401 clave_no_valida · sin_claveClave ausente, mal escrita o revocadaRevisa la cabecera
403 clave_sin_permisoEsa clave no sirve para avisarUsa la clave de aviso
405La llamada no es un POSTUsa POST
409 piloto_apagadoEl piloto automático de tu suscripción está apagadoEnciéndelo en Ajustes → Generar DeCAs solo
409 suscripcion_inactivaTu suscripción no está activa para DeCAEscríbenos
413El cuerpo pasa de 4 KBManda solo el nombre del fichero
429 demasiados_avisosMás de 12 avisos en un minutoNada: lo que haya en el buzón se procesa en la revisión de cada cinco minutos
503 no_disponibleNo hemos podido recibir el aviso (fallo nuestro)Nada: lo mismo

La API (puntos 4 a 7) rechaza la clave de aviso con 403 clave_solo_aviso. El aviso rechaza la de auditoría con 403 clave_solo_auditoria.

Cuánto tarda

No es instantáneo. Una vez el PDF está en el buzón, leerlo, generar el DeCA, sellarlo y avisar al conductor lleva del orden de medio minuto con un papel que ya sabemos leer, y algo más la primera vez que llega un modelo de papel nuevo. A eso se suma lo que tarde Digitaldocu en subir el PDF de la carpeta al buzón. Cada revisión coge como mucho 5 papeles; el que nos avisas va el primero.

Si llegan varios avisos seguidos, no hace falta esperar entre uno y otro: los atendemos en la revisión que esté en marcha o en la siguiente.

12Registro de actividad para tu SIEM

Para que tu sistema de seguridad (Splunk, Sentinel, Elastic, Wazuh…) se lleve solo el registro de actividad de tu suscripción: quién entra, desde qué IP y qué hace. Es el mismo registro que ves en la pestaña Actividad y que exportas en CSV, en líneas JSON con los campos del Elastic Common Schema (ECS 8.11) y las huellas que encadenan cada evento con el anterior.

GET https://deca.digitaldocu.es/api/deca/v1/actividad

La clave

La crea y la revoca el administrador de tu suscripción, en la pestaña Actividad → Clave para tu SIEM. Solo se puede crear con la pestaña activada, y como mucho hay dos activas: la que usas y la que la sustituye cuando la cambias. Se enseña una sola vez. Empieza por deca_audit_.

Solo sirve para leer este registro. Con ella no se puede emitir, consultar ni corregir nada, ni avisar: el resto de la API la rechaza con 403 clave_solo_auditoria. Y al revés: una clave de la API (deca_live_) o de aviso (deca_aviso_) no lee el registro. La suscripción la dice la clave; no hay que mandar ningún otro dato.

Va en la cabecera, Authorization: Bearer deca_audit_… o X-DeCA-Api-Key: deca_audit_…. Nunca en la dirección: una clave puesta en la URL no se lee, y acabaría en los registros de cualquier proxy intermedio.

La llamada

ParámetroQué es
desdeEl cursor: devuelve los eventos con número mayor que este (el propio desde no se repite). Entero de 0 a 9223372036854775807; por defecto, 0
limiteCuántos eventos como mucho, de 1 a 1000; por defecto, 500

Cualquier otro parámetro se ignora. Solo GET.

curl -sS -D cabeceras.txt \
  -H "Authorization: Bearer deca_audit_xxxxxxxxxxxxxxxx" \
  "https://deca.digitaldocu.es/api/deca/v1/actividad?desde=0&limite=500"

La respuesta

200 con Content-Type: application/x-ndjson; charset=utf-8: un objeto JSON por línea, cada una terminada en salto de línea, en orden de número de evento. Puede venir vacía: no es un error, es que no hay nada nuevo.

{"@timestamp":"2026-09-28T07:14:03.512907Z","ecs":{"version":"8.11.0"},
 "event":{"kind":"event","dataset":"deca.actividad","module":"deca","id":"48213",
  "sequence":48213,"action":"doc.descarga","outcome":"success","provider":"proxy",
  "category":["file"],"type":["access"]},
 "message":"…","organization":{"id":"transportes-ejemplo"},
 "user":{"id":"…","email":"…"},"source":{"ip":"203.0.113.24"},
 "deca":{"at_local":"…","actor_id":"…","ip":"203.0.113.24","ip_fiable":true,
  "ref":null,"detalle":{},
  "cadena":{"v":1,"id":48213,"at":"2026-09-28T07:14:03.512907Z",
   "tenant":"transportes-ejemplo","atribucion":"sesion","tipo":"doc.descarga",
   "resultado":"ok","http":200,"origen":"proxy","actor_tipo":"usuario",
   "document_id":91234,"h_pii":"9f…","hash_prev":"3c…","hash":"a7…"}}}

(En la respuesta real, cada evento va en una sola línea; aquí está partido para que se lea.) source.ip solo va cuando la IP está verificada; la IP tal cual está siempre en deca.ip, con deca.ip_fiable. Los apuntes de nuestro personal salen enmascarados, igual que en la pestaña. deca.detalle cambia de forma según el tipo de evento: en Elasticsearch, mapéalo como flattened.

Cabeceras de la respuesta:

CabeceraQué dice
X-DeCA-SiguienteEl desde de tu siguiente llamada: el número del último evento de esta página, o el mismo desde si no ha venido ninguno
X-DeCA-Mas1 si queda más ahora mismo: pide la página siguiente ya, sin esperar. 0 si vas al día
X-DeCA-LineasCuántos eventos trae la página
X-DeCA-Ultimo-HashLa huella del último evento de la página (no va si la página está vacía)
X-DeCA-Cabeza-Id · X-DeCA-Cabeza-HashEl último evento de tu registro en el momento de la lectura. Con X-DeCA-Mas: 0, si tu última huella no es esta, falta la cola: avísanos
X-DeCA-Ancla-Id · X-DeCA-Ancla-HashSolo si ya se ha borrado algo por antigüedad (12 meses) y lo borrado llega hasta la activación de la pestaña: el último evento borrado, con el que encadena el primero que se te exporta. Mientras la purga solo haya borrado eventos anteriores a la activación, no va

Todas las respuestas llevan Cache-Control: no-store.

Cómo leerlo

Guarda X-DeCA-Siguiente y úsalo como desde en la llamada siguiente. Mientras X-DeCA-Mas sea 1, sigue pidiendo; con 0, vuelve dentro de unos minutos (cada 5 es un buen ritmo). Si una llamada falla, repítela con el mismo desde: te devolvemos lo mismo más lo nuevo, sin huecos ni repetidos. Los números de evento no son consecutivos (la numeración es común a toda la plataforma): lo que prueba que no falta nada es la cadena, no el número.

Solo desde que se activó la pestaña. El registro se sirve desde la primera vez que se activó la pestaña Actividad de tu suscripción, igual que en la pestaña y en el CSV; lo anterior no se te exporta. Esa fecha no cambia aunque la pestaña se desactive y se vuelva a activar. Si se desactiva, la lectura contesta 403 no_visible desde la llamada siguiente, aunque la clave siga activa.

Empezar desde hoy, sin descargar lo que ya haya: haz una primera llamada con desde=9223372036854775806. Vuelve sin eventos, pero con la cabeza: usa X-DeCA-Cabeza-Id como tu desde y X-DeCA-Cabeza-Hash como la huella anterior que esperas. Así también el primer evento que recibas se puede comprobar. (Si todavía no hay ningún evento, no viene cabeza: empieza desde 0.)

Comprueba la cadena

Cada evento guarda la huella del anterior (hash_prev) y la suya (hash), en deca.cadena. Con eso tu SIEM comprueba dos cosas: que no falta ningún evento entre dos que tiene, y que ninguno ha cambiado después de copiarlo. Usa siempre los campos de deca.cadena, no los de ECS: un pipeline puede reformatear @timestamp, y la huella se calcula sobre el texto exacto de deca.cadena.at (UTC, seis decimales y Z).

# Python
import hashlib, json

CAMPOS = ("v", "id", "at", "tenant", "atribucion", "tipo",
          "resultado", "http", "origen", "actor_tipo", "document_id")

def ns(x):
    # null → "-,"; si no, "<octetos UTF-8>:<texto>,"
    if x is None:
        return "-,"
    s = str(x)          # los números, en decimal
    return f"{len(s.encode('utf-8'))}:{s},"

def comprobar(linea, huella_anterior):
    c = json.loads(linea)["deca"]["cadena"]
    if huella_anterior is not None and c["hash_prev"] != huella_anterior:
        raise ValueError(f"falta algo antes del evento {c['id']}")
    nucleo = "".join(ns(c[k]) for k in CAMPOS).encode("utf-8")
    prev = bytes.fromhex(c["hash_prev"]) if c["hash_prev"] else bytes(32)
    h = hashlib.sha256(prev + nucleo + bytes.fromhex(c["h_pii"])).hexdigest()
    if h != c["hash"]:
        raise ValueError(f"el evento {c['id']} no casa con su huella")
    return c["hash"]    # la huella anterior del evento siguiente

Errores

Siempre en JSON, {"ok": false, "code": "…", "error": "…"}, nunca como líneas NDJSON.

CódigoQué pasaQué hacer
401 sin_claveNo viene la clave en la cabeceraMándala en Authorization, no en la URL
401 clave_no_validaClave mal escrita o revocadaCrea otra en la pestaña Actividad
403 clave_sin_permisoEs una clave de la API o de aviso, no de auditoríaUsa la deca_audit_
403 no_visibleLa pestaña Actividad de tu suscripción está desactivadaPide que se active; mientras, no hay nada que leer
400 parametro_no_validodesde o limite fuera de rango, o repetidosRevisa la dirección
405 metodo_no_admitidoLa llamada no es un GETUsa GET
429 demasiadas_consultasMás de 30 consultas en un minuto. Trae Retry-AfterEspera lo que diga y sigue con el mismo desde
503 no_disponibleNo hemos podido leer el registro ahora (fallo nuestro)Reintenta con el mismo desde

Límites

Consultas30 por minuto y suscripción
Eventos por página1000 como mucho (500 si no dices nada). Una página puede traer menos si pasa de unos 3,5 MB: entonces viene X-DeCA-Mas: 1
Claves de auditoría2 activas por suscripción
AntigüedadLo que haya en el registro, que se conserva 12 meses, y nunca antes de la primera activación de la pestaña

Tu copia es tuya. Cada lectura queda apuntada en el propio registro (un resumen cada 15 minutos por clave). Lo que tu SIEM guarde, cuánto tiempo, quién lo ve y cómo aplica las supresiones es responsabilidad de tu empresa, como responsable del tratamiento.