DeCA Profesional
es
EntrarCrear DeCA

API v1 · REST · JSON

Documentación de la API DeCA

La API de DeCA Profesional genera el Documento Electrónico de Control (DeCA) directamente desde tu ERP o TMS: envías los datos del transporte y recibes al momento el documento, su PDF con QR y la URL de verificación. Crea, corrige y anula DeCA, genera lotes de hasta 50, recibe webhooks firmados y descarga la especificación OpenAPI.

URL base: https://decaprofesional.es/api/v1

Importa la especificación OpenAPI en Postman, Insomnia, Make o Power Automate para tener todas las llamadas listas. Fechas en DD-MM-AAAA y horas en hora de Madrid.

Para qué sirve

La API está pensada para empresas de transporte y cargadores que ya gestionan sus pedidos o expediciones en un ERP o TMS y quieren que el DeCA se genere solo, sin volver a teclear datos.

  1. 1. Tu ERP / TMS

    Envía el pedido o expedición por HTTPS (JSON, Bearer + Idempotency-Key).

  2. 2. API DeCA

    Valida los datos obligatorios y genera el DeCA.

  3. 3. Recibes

    La referencia, el PDF con QR y la URL pública de verificación.

¿Necesitas explicárselo a tu empresa? En la página de la API DeCA tienes lo que hace, para quién es y su precio, sin detalles técnicos.

El acceso a la API es un complemento de pago: API Lite con Professional (39,95 €/mes + IVA) o API Advanced con Business (49,95 €/mes + IVA) para integraciones ERP/TMS. Los DeCA creados por la API cuentan en el mismo cupo mensual del plan que los creados desde el panel.

Primeros pasos

  1. Activa el complemento API desde Plan y facturación (API Lite con el plan Professional; API Advanced con el plan Business). Si tienes dudas, escríbenos a integraciones@praetoriaabogados.es.
  2. Abre la pestaña Integraciones: en cuanto el complemento está activo, los Administradores de la empresa la tienen en el menú de su panel. Es el centro de integración: allí creas, rotas y revocas tus claves (hasta 2 activas a la vez, todas con el mismo cupo y límites de la empresa), compruebas la conexión, pruebas expediciones en el simulador sin consumir cupo, configuras webhooks y descargas el kit. La clave completa solo se muestra una vez, al crearla.
  3. Prueba la conexión con GET /api/v1/me.
  4. Valida un DeCA con POST /api/v1/decas/validate: no crea nada ni consume cupo.
  5. Crea tu primer DeCA con POST /api/v1/decas y guarda la URL de verificación.
Probar la conexión
curl https://decaprofesional.es/api/v1/me \
  -H "Authorization: Bearer $DECA_API_KEY"

Autenticación

Cada petición lleva la clave de la integración en la cabecera Authorization. Solo HTTPS; no envíes nunca la clave en la URL ni la incluyas en código que se ejecute en el navegador.

Cabecera
Authorization: Bearer fvd_live_<id-de-la-clave>_<secreto>

La empresa siempre es la de la credencial: ningún campo de la petición (NIF, identificadores) cambia de empresa. Si pierdes una clave no se puede recuperar: pide una nueva o su rotación. Todo lo que tu credencial no puede ver responde 404.

API Lite y API Advanced

API Lite (plan Professional + 39,95 €/mes + IVA): validar, crear, consultar, descargar el PDF y corregir o anular los DeCA de tu integración; hasta 100 DeCA por API al mes dentro del cupo del plan, 1 clave y 20 peticiones por minuto. Cualquier otra ruta responde 403 api_advanced_required, y la creación número 101 del mes, 429 api_lite_limit_reached. Contrato solo con sus rutas: openapi-lite.json.

API Advanced (plan Business + 49,95 €/mes + IVA): todas las rutas de esta página — webhooks firmados, lotes, sincronización de Datos habituales, órdenes de carga, borradores, solicitudes de datos a terceros y envío automático por email (autoDispatch), con el cupo del plan Business.

Inicio rápido · API Lite
# 1) Valida sin crear nada
curl -X POST https://decaprofesional.es/api/v1/decas/validate -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -H "Idempotency-Key: val-001" -d @expedicion.json
# 2) Crea el DeCA
curl -X POST https://decaprofesional.es/api/v1/decas -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -H "Idempotency-Key: exp-001" -d @expedicion.json
# 3) Descarga el PDF
curl https://decaprofesional.es/api/v1/decas/$ID/pdf -H "Authorization: Bearer $KEY" -o deca.pdf
API Advanced · crear y enviar por email (autoDispatch)
curl -X POST https://decaprofesional.es/api/v1/decas -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -H "Idempotency-Key: exp-002" -d '{
  ...datos del DeCA...,
  "autoDispatch": { "recipients": [
    { "role": "driver", "email": "conductor@tuempresa.es", "name": "Conductor" },
    { "role": "shipper", "email": "trafico@cargador.es" }
  ] }
}'
# Respuesta: "autoDispatch": { "recipients": [{ "role": "driver", "email": "c***@tuempresa.es", "status": "sent" }, ...] }
# "sent" = aceptado por el servidor de correo (no confirma entrega ni lectura). Un reintento con la misma
# Idempotency-Key devuelve "status": "replayed" y no envía nada más. Máximo 3 destinatarios, sin CC.

Endpoints

GET/api/v1/meLite y Advanced

Comprueba la credencial: integración, empresa, permisos, límites y uso del mes.

El primer endpoint que debes llamar para verificar la conexión.

POST/api/v1/decas/validatedeca:createLite y Advanced

Valida un DeCA sin crearlo: mismas reglas que la creación.

No crea ningún DeCA, no genera PDF y no consume el cupo del plan.

POST/api/v1/decasdeca:createLite y AdvancedIdempotency-Key obligatoria

Crea un DeCA y devuelve la URL de verificación, la referencia y el PDF.

Responde 201 (o 200 si es un reintento con la misma Idempotency-Key). Con completionRequest (o draft: true) crea un borrador y responde 202: ver «Completar un DeCA por un tercero».

GET/api/v1/decasdeca:readSolo Advanced

Lista los DeCA, del más reciente al más antiguo, con paginación por cursor.

Filtros: limit (1–100, por defecto 25), cursor, externalId, groupageRef (referencia del grupaje, G-…), status (active | annulled), createdFrom, createdTo (DD-MM-AAAA, días en hora de Madrid; createdTo incluye todo ese día). Todas las fechas de la API van en DD-MM-AAAA y las horas en hora de Madrid.

GET/api/v1/decas/{id}deca:readLite y Advanced

El DeCA completo: estado, periodo del servicio, URL de verificación, PDF y datos.

GET/api/v1/decas/{id}/statusdeca:readLite y Advanced

Estado ligero (versión, disponibilidad, URL de verificación) para consultas periódicas.

GET/api/v1/decas/{id}/versionsdeca:readSolo Advanced

Historial de versiones: motivo del cambio, origen (api / user) y URL y PDF de cada una.

GET/api/v1/decas/{id}/pdfdeca:readLite y Advanced

Descarga el PDF (versión vigente o ?version=N).

Cabeceras X-Pdf-Sha256 (huella del PDF) y X-Deca-Status (active | annulled).

POST/api/v1/decas/{id}/correctionsdeca:correctLite y AdvancedIdempotency-Key obligatoria

Corrige un DeCA: crea una nueva versión con nueva URL, QR y PDF.

Responde 201 con la nueva versión (200 si es un reintento con la misma Idempotency-Key). Solo DeCA creados por la misma integración; las versiones anteriores se conservan.

GET/api/v1/decas/{id}/vehiclesdeca:readSolo Advanced

Vehículos del transporte: el inicial y cada cambio registrado, con el actual marcado.

POST/api/v1/decas/{id}/vehicle-changesdeca:correctSolo AdvancedIdempotency-Key obligatoria

Registra un cambio de vehículo (tractora y, si cambia, remolque) durante el transporte.

Es una corrección: nueva versión con nueva URL, QR y PDF, que no consume DeCA del plan. La fecha (DD-MM-AAAA) debe estar dentro del transporte y posterior al cambio anterior. Sin trailerPlate se mantiene el remolque anterior. Responde 201 (200 si es un reintento con la misma Idempotency-Key).

POST/api/v1/decas/{id}/annulmentdeca:annulLite y Advanced

Anula un DeCA. Es definitivo; repetir la llamada devuelve 200 sin cambios.

Solo DeCA creados por la misma integración.

POST/api/v1/decas/{id}/completion-requestdeca:createSolo AdvancedIdempotency-Key obligatoria

Pide a un tercero, por email, los datos que faltan en un borrador.

Responde 201 (200 si es un reintento con la misma Idempotency-Key). Solo borradores de la misma integración.

POST/api/v1/decas/{id}/completion-request/resenddeca:createSolo AdvancedIdempotency-Key obligatoria

Reenvía la solicitud con un enlace nuevo (el anterior deja de funcionar).

Responde 201 (200 si es un reintento). Máximo 5 reenvíos por borrador y hora.

POST/api/v1/decas/batchdeca:createSolo AdvancedIdempotency-Key obligatoria

Crea hasta 50 DeCA en una sola llamada (lote).

Cuerpo { items: [ …mismo formato que POST /decas… ] }. Responde 202 con el lote ya validado: cada elemento queda pending o failed con sus detalles (un error no afecta a los demás). La creación sigue en segundo plano; consulta GET /api/v1/batches/{id}. Cada elemento consume un DeCA del plan como una creación normal. La misma Idempotency-Key con el mismo cuerpo devuelve el mismo lote (200); nunca se duplica un DeCA.

GET/api/v1/batches/{id}deca:readSolo Advanced

Estado del lote y resultado de cada elemento (created, replayed, failed, pending).

status pasa de processing a completed cuando todos los elementos han terminado.

GET/api/v1/batches/{id}/pdfsdeca:readSolo Advanced

Descarga un ZIP con el PDF vigente de cada DeCA creado por el lote.

Responde 409 batch_without_documents si el lote aún no ha creado ninguno.

POST/api/v1/draftsdeca:createSolo AdvancedIdempotency-Key obligatoria

Abre un borrador nuevo e independiente (datos parciales). No consume cuota.

Responde 201 con draftId (200 si es un reintento con la misma Idempotency-Key).

GET/api/v1/draftsdeca:readSolo Advanced

Lista los borradores: status=open (por defecto) | generated | cancelled | all.

Paginado con limit y cursor, como /decas. Los borradores son de la empresa: con deca:read_company verás también los empezados en la web (origin: "web") y los de otras integraciones, y podrás completarlos, generarlos o descartarlos.

GET/api/v1/drafts/{id}deca:readSolo Advanced

Un borrador, con ready e issues (lo que falta para poder generarlo).

PATCH/api/v1/drafts/{id}deca:createSolo Advanced

Modifica o completa un borrador abierto (JSON Merge Patch).

Opcional If-Match con el ETag: 412 si otro cambio llegó antes.

POST/api/v1/drafts/{id}/generatedeca:createSolo Advanced

Genera el DeCA definitivo de ese borrador; los demás no cambian.

201 con el DeCA; repetir devuelve el mismo DeCA (200). Aquí se consume la cuota.

DELETE/api/v1/drafts/{id}deca:createSolo Advanced

Descarta un borrador abierto (queda como cancelled).

POST/api/v1/decas/{id}/service-completiondeca:correctSolo Advanced

Marca el servicio como finalizado (dato operativo; el DeCA, su PDF y su URL no cambian).

Cuerpo opcional { note } (hasta 500 caracteres). Idempotente: devuelve el DeCA con service.finished. 409 deca_annulled en un DeCA anulado. Webhook deca.service_finished.

DELETE/api/v1/decas/{id}/service-completiondeca:correctSolo Advanced

Reabre un servicio marcado como finalizado (vuelve a pendiente).

Idempotente. Webhook deca.service_reopened.

GET/api/v1/load-ordersdeca:readSolo Advanced

Lista las órdenes de carga: número, estado, formato (junta o separada), DeCA vinculado, matrículas pendientes y aceptación.

Filtros: decaId, status (pending_vehicle | active | cancelled), limit (1–100). Sin deca:read_company, solo las de DeCA creados por tu integración. Nunca incluye el precio ni las condiciones (están en el PDF). Para emitirla: POST /api/v1/decas con loadOrder.

GET/api/v1/load-orders/{id}deca:readSolo Advanced

Una orden de carga: estado, versión vigente, DeCA vinculado y aceptación del transportista.

GET/api/v1/load-orders/{id}/pdfdeca:readSolo Advanced

El PDF de la versión vigente de la orden de carga.

GET/api/v1/saved/companiessaved:readSolo Advanced

Lista las empresas habituales (cargadores y transportistas) de la empresa.

Orden de creación. Parámetros: limit (1–100, por defecto 50), cursor (nextCursor de la página anterior) y externalId (búsqueda exacta). Incluye también los registros creados en la web (externalId null).

GET/api/v1/saved/companies/{externalId}saved:readSolo Advanced

Lee un registro por el identificador de tu ERP o TMS (externalId).

PUT/api/v1/saved/companies/{externalId}saved:writeSolo Advanced

Crea o actualiza el registro con ese externalId (sincronización sin duplicados).

Responde 201 si lo crea y 200 si lo actualiza; repetir la misma llamada deja el mismo registro. Se valida con las mismas reglas que la web y aparece al momento en Datos habituales. Nunca cambia un DeCA ya generado.

DELETE/api/v1/saved/companies/{externalId}saved:writeSolo Advanced

Borra el registro con ese externalId.

Nunca cambia un DeCA ya generado.

GET/api/v1/saved/vehiclessaved:readSolo Advanced

Lista los vehículos habituales (con su conductor habitual) de la empresa.

Orden de creación. Parámetros: limit (1–100, por defecto 50), cursor (nextCursor de la página anterior) y externalId (búsqueda exacta). Incluye también los registros creados en la web (externalId null).

GET/api/v1/saved/vehicles/{externalId}saved:readSolo Advanced

Lee un registro por el identificador de tu ERP o TMS (externalId).

PUT/api/v1/saved/vehicles/{externalId}saved:writeSolo Advanced

Crea o actualiza el registro con ese externalId (sincronización sin duplicados).

Responde 201 si lo crea y 200 si lo actualiza; repetir la misma llamada deja el mismo registro. Se valida con las mismas reglas que la web y aparece al momento en Datos habituales. Nunca cambia un DeCA ya generado.

DELETE/api/v1/saved/vehicles/{externalId}saved:writeSolo Advanced

Borra el registro con ese externalId.

Nunca cambia un DeCA ya generado.

GET/api/v1/saved/placessaved:readSolo Advanced

Lista los lugares habituales de carga y descarga de la empresa.

Orden de creación. Parámetros: limit (1–100, por defecto 50), cursor (nextCursor de la página anterior) y externalId (búsqueda exacta). Incluye también los registros creados en la web (externalId null).

GET/api/v1/saved/places/{externalId}saved:readSolo Advanced

Lee un registro por el identificador de tu ERP o TMS (externalId).

PUT/api/v1/saved/places/{externalId}saved:writeSolo Advanced

Crea o actualiza el registro con ese externalId (sincronización sin duplicados).

Responde 201 si lo crea y 200 si lo actualiza; repetir la misma llamada deja el mismo registro. Se valida con las mismas reglas que la web y aparece al momento en Datos habituales. Nunca cambia un DeCA ya generado.

DELETE/api/v1/saved/places/{externalId}saved:writeSolo Advanced

Borra el registro con ese externalId.

Responde 409 saved_in_use si un envío habitual usa ese lugar.

GET/api/v1/saved/shipmentssaved:readSolo Advanced

Lista los envíos habituales (lugar de carga + lugar de descarga + mercancía) de la empresa.

Orden de creación. Parámetros: limit (1–100, por defecto 50), cursor (nextCursor de la página anterior) y externalId (búsqueda exacta). Incluye también los registros creados en la web (externalId null).

GET/api/v1/saved/shipments/{externalId}saved:readSolo Advanced

Lee un registro por el identificador de tu ERP o TMS (externalId).

PUT/api/v1/saved/shipments/{externalId}saved:writeSolo Advanced

Crea o actualiza el registro con ese externalId (sincronización sin duplicados).

Responde 201 si lo crea y 200 si lo actualiza; repetir la misma llamada deja el mismo registro. Se valida con las mismas reglas que la web y aparece al momento en Datos habituales. Los lugares se indican con su externalId (loadPlaceExternalId, unloadPlaceExternalId): sincronízalos antes. Nunca cambia un DeCA ya generado.

DELETE/api/v1/saved/shipments/{externalId}saved:writeSolo Advanced

Borra el registro con ese externalId.

Nunca cambia un DeCA ya generado.

Crear un DeCA

El cuerpo describe el transporte: fechas, vehículo, cargador contractual (shipper), transportista efectivo (carrier) y de 1 a 50 envíos. Los países van siempre en código ISO de dos letras (ES, FR, PT…). Los campos desconocidos se rechazan.

CampoObligatorioDescripción
externalIdRecomendadoTu identificador (pedido, expedición). 1–100 caracteres: letras, números y . _ - /. Único entre los DeCA activos de la empresa.
loadDate, unloadDateSíDD-MM-AAAA. La descarga no puede ser anterior a la carga.
vehicle.tractorPlateSíMatrícula (se normaliza: mayúsculas, sin espacios ni guiones). trailerPlate opcional.
vehicle.secondTrailerPlateNoSegundo remolque o semirremolque (conjuntos de dos remolques). Requiere trailerPlate y debe ser distinta.
shipper, carrierSíEmpresa (name, taxId, address; country, postalCode, city opcionales) o "self".
shipments[]SíloadLocation y unloadLocation (nombre, dirección, código postal, ciudad, provincia opcional, país ISO), goods, weight (número = kg, o texto como "22 palés"), recipient opcional.
referenceNoHasta 120 caracteres; se imprime en el DeCA.
notesNoHasta 1000 caracteres, una sola línea.
serviceTypeNogoods (por defecto) o passenger (transporte de viajeros).
groupageRefNoReferencia común a los DeCA independientes de un grupaje (un DeCA por cargador): «G-» seguido de 6 a 16 letras o dígitos. Filtra con GET /api/v1/decas?groupageRef=….
loadOrderNoEmite una orden de carga con el DeCA: format (joint, en el mismo documento, o separate), price, currency (EUR, USD, GBP, CHF), taxNote (unspecified, excluded, included), conditions, paymentTerms y billingInstructions. Solo si tu empresa es el cargador contractual y tu plan incluye la orden de carga.
cURL
curl -X POST https://decaprofesional.es/api/v1/decas \
  -H "Authorization: Bearer $DECA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d @deca.json
deca.json
{
  "externalId": "TR-45882",
  "reference": "Pedido 88231",
  "loadDate": "06-10-2026",
  "unloadDate": "07-10-2026",
  "vehicle": {
    "tractorPlate": "1234 KLM",
    "trailerPlate": "R-5678-BBC"
  },
  "shipper": {
    "name": "CERÁMICAS DEL MEDITERRÁNEO S.A.",
    "taxId": "A12345674",
    "country": "ES",
    "address": "Carretera de Alcora km 3",
    "postalCode": "12006",
    "city": "Castellón de la Plana"
  },
  "carrier": "self",
  "shipments": [
    {
      "loadLocation": {
        "name": "Planta 1",
        "address": "Carretera de Alcora km 3",
        "postalCode": "12006",
        "city": "Castellón de la Plana",
        "province": "Castellón",
        "country": "ES"
      },
      "unloadLocation": {
        "name": "Entrepôt Lyon Sud",
        "address": "12 Rue du Port",
        "postalCode": "69007",
        "city": "Lyon",
        "country": "FR"
      },
      "goods": "Azulejo cerámico en palés",
      "weight": 18400,
      "recipient": "Céramique Rhône SARL"
    }
  ]
}

Respuesta 201 Created con la cabecera Location: /api/v1/decas/{id}:

Respuesta 201
{
  "id": "cmg4x2k9a0001l508h3n7q2rz",
  "reference": "DECA-Q3VJ9KXA",
  "externalId": "TR-45882",
  "groupageRef": null,
  "status": "active",
  "availability": "public",
  "version": 1,
  "createdAt": "2026-10-05T09:41:12+02:00",
  "updatedAt": "2026-10-05T09:41:12+02:00",
  "annulledAt": null,
  "service": {
    "finished": false,
    "finishedAt": null,
    "note": null
  },
  "serviceType": "goods",
  "servicePeriod": {
    "start": "06-10-2026",
    "end": "07-10-2026",
    "publicUntil": "14-10-2026"
  },
  "verification": {
    "url": "https://decaprofesional.es/d/q3vj9kxa7Rm2…",
    "note": "La URL cambia con cada corrección: imprime o envía siempre la última."
  },
  "pdf": {
    "href": "/api/v1/decas/cmg4x2k9a0001l508h3n7q2rz/pdf",
    "sha256": "9f2c4e…",
    "contentType": "application/pdf"
  },
  "data": {
    "reference": "Pedido 88231",
    "notes": null,
    "loadDate": "06-10-2026",
    "unloadDate": "07-10-2026",
    "vehicle": {
      "tractorPlate": "1234KLM",
      "trailerPlate": "R5678BBC"
    },
    "currentVehicle": {
      "tractorPlate": "1234KLM",
      "trailerPlate": "R5678BBC"
    },
    "vehicleChanges": [],
    "shipper": {
      "name": "CERÁMICAS DEL MEDITERRÁNEO S.A.",
      "taxId": "A12345674",
      "country": "ES",
      "address": "Carretera de Alcora km 3",
      "postalCode": "12006",
      "city": "Castellón de la Plana"
    },
    "carrier": {
      "name": "TRANSPORTES EJEMPLO S.L.",
      "taxId": "B12345674",
      "country": null,
      "address": "Polígono Industrial 5",
      "postalCode": "46540",
      "city": "El Puig"
    },
    "shipments": [
      {
        "loadLocation": {
          "name": "Planta 1",
          "address": "Carretera de Alcora km 3",
          "postalCode": "12006",
          "city": "Castellón de la Plana",
          "province": "Castellón",
          "country": "ES",
          "countryName": "España"
        },
        "unloadLocation": {
          "name": "Entrepôt Lyon Sud",
          "address": "12 Rue du Port",
          "postalCode": "69007",
          "city": "Lyon",
          "province": null,
          "country": "FR",
          "countryName": "Francia"
        },
        "goods": "Azulejo cerámico en palés",
        "weight": "18.400 kg",
        "recipient": "Céramique Rhône SARL",
        "loadDate": "06-10-2026",
        "unloadDate": "07-10-2026"
      }
    ],
    "totalWeight": "18.400 kg"
  },
  "warnings": [],
  "links": {
    "self": "/api/v1/decas/cmg4x2k9a0001l508h3n7q2rz",
    "status": "/api/v1/decas/cmg4x2k9a0001l508h3n7q2rz/status",
    "versions": "/api/v1/decas/cmg4x2k9a0001l508h3n7q2rz/versions",
    "pdf": "/api/v1/decas/cmg4x2k9a0001l508h3n7q2rz/pdf"
  }
}

warnings recoge avisos que no impiden crear el DeCA (por ejemplo, un NIF con un formato poco habitual). availability indica si la URL pública está disponible (public), ya caducó (expired) o el DeCA está anulado (annulled).

Idempotency-Key y externalId

Idempotency-Key (cabecera, obligatoria al crear y corregir): una cadena única por operación, por ejemplo un UUID (8–255 caracteres). Si repites la misma llamada con la misma clave y el mismo cuerpo —por un corte de red o un reintento—, recibes el mismo DeCA con 200 y la cabecera Idempotent-Replayed: true; nunca se crea un segundo documento. La misma clave con un cuerpo distinto responde 409 idempotency_conflict.

externalId (cuerpo): el identificador de tu objeto de negocio. Si ya existe un DeCA activo con ese externalId, la API responde 409 duplicate_external_id con el existingId. Puedes buscarlo con GET /api/v1/decas?externalId=…. Un DeCA anulado libera su externalId.

Validar sin crear

POST /api/v1/decas/validate acepta el mismo cuerpo que la creación y aplica exactamente las mismas reglas, pero no crea ningún DeCA, no genera PDF y no consume el cupo del plan. Úsalo para probar tu integración y para comprobar los datos antes de emitir.

Respuesta 200
{
  "valid": true,
  "warnings": []
}

“self”: tu propia empresa

En shipper o carrier puedes enviar el texto "self" en lugar de los datos de la empresa: se usan los datos de la empresa de la credencial tal como figuran en su ficha de DeCA Profesional. Si la ficha está incompleta, la respuesta es 422 con el detalle self_company_incomplete.

PDF, QR y URL de verificación

Cada DeCA tiene una URL pública de verificación (verification.url): es la que codifica el QR impreso en el PDF y la que se muestra en una inspección, sin necesidad de cuenta. El PDF se descarga con tu credencial en GET /api/v1/decas/{id}/pdf (o ?version=N); la cabecera X-Pdf-Sha256 permite comprobar su integridad. La URL pública está disponible durante el servicio y los 7 días siguientes (servicePeriod.publicUntil).

PDF del DeCA que genera la API para el ejemplo de esta página: referencia DECA-Q3VJ9KXA, cargador, transportista, ruta Castellón–Lyon, mercancía y matrículas; en la segunda página, la verificación pública con el QR y su URL
El PDF que genera la API para el ejemplo de esta página (datos ficticios). El QR de la verificación pública codifica la misma URL que devuelve verification.url.
Descargar el PDF
curl https://decaprofesional.es/api/v1/decas/<id>/pdf \
  -H "Authorization: Bearer $DECA_API_KEY" -o deca.pdf

Correcciones

Una corrección sustituye todos los datos del DeCA y crea una nueva versión con nueva URL de verificación, nuevo QR y nuevo PDF. Las versiones anteriores se conservan (GET /versions). Imprime o envía siempre la última URL. El externalId no se puede cambiar y un DeCA anulado no se puede corregir. Responde 201 con el DeCA en su nueva versión (200 si es un reintento con la misma Idempotency-Key).

Opcionalmente envía baseVersionNo (la versión que leíste): si el DeCA ha cambiado desde entonces responde 409 version_conflict en lugar de sobrescribir. Una corrección sigue siendo el mismo transporte: si cambian a la vez las fechas, la ruta (localidades) y las partes (NIF del cargador o del transportista) respecto a la versión 1, responde 422 new_service_required y debes crear un DeCA nuevo. Las correcciones no consumen DeCA del plan.

  1. 1. POST /decas

    Versión 1 con su URL de verificación.

  2. 2. POST /corrections

    Versión 2 con URL, QR y PDF nuevos; la anterior se conserva.

  3. 3. Imprime o envía

    Siempre la última URL.

  4. 4. POST /annulment

    Definitivo: la URL muestra que el DeCA está anulado.

POST /api/v1/decas/{id}/corrections (con Idempotency-Key)
{
  "changeReason": "Cambio de matrícula del remolque",
  "deca": "…el mismo cuerpo completo que al crear…"
}

Cambio de vehículo

Si la tractora (o el remolque) cambia durante el transporte, registra el cambio en lugar de crear otro DeCA. Se genera una nueva versión con nueva URL, QR y PDF, que muestra el vehículo inicial y cada cambio con su fecha; no consume DeCA del plan. La fecha debe estar dentro del transporte y puedes registrar cambios sucesivos. Consulta el historial con GET /api/v1/decas/{id}/vehicles; en el recurso del DeCA, data.currentVehicle es el vehículo actual y data.vehicleChanges la lista de cambios.

POST /api/v1/decas/{id}/vehicle-changes (con Idempotency-Key)
{
  "tractorPlate": "5678 NPR",
  "effectiveAt": "07-10-2026",
  "reason": "Avería de la tractora en ruta"
}

Anulación

La anulación es definitiva: la URL pública pasa a mostrar que el documento está anulado y el QR sigue resolviendo a ese aviso. Repetir la llamada devuelve 200 sin cambios. El motivo es obligatorio (3–500 caracteres).

POST /api/v1/decas/{id}/annulment
{
  "reason": "Pedido cancelado por el cliente"
}

Completar un DeCA por un tercero

Si a tu ERP le faltan datos que conoce otra empresa —por ejemplo, la matrícula que asignará el transportista o la localidad exacta de descarga—, puedes crear el DeCA igualmente y pedir esos datos a un tercero. Es la misma función «Completar por la contraparte» de la web: el tercero recibe un email con un enlace seguro, rellena solo los campos que le pides y, cuando ya no falta ningún dato obligatorio, el DeCA se genera automáticamente y pasa a vigente, con su PDF, su QR y su URL de verificación. Requiere el mismo acceso que el resto de la API (plan Business con el complemento API activo).

Añade completionRequest al cuerpo de POST /api/v1/decas, con el email del destinatario (recipientEmail) y los campos que debe completar (fields). Esos campos pueden llegar vacíos; todos los demás deben ser válidos, igual que en una creación normal. La respuesta es 202 Accepted con un borrador (object: "deca_draft"), no con un DeCA vigente. Opcionalmente: recipientName, recipientType (shipper | carrier | authorized_person) y expiresInHours (1–48; por defecto 24).

Campo que se puede pedirQué es
vehicle.tractorPlateMatrícula del vehículo tractor
vehicle.trailerPlateMatrícula del remolque o semirremolque
shipments[N].unloadLocationEl lugar de descarga completo: name, address, postalCode y city
shipments[N].unloadLocation.city (o .name, .address, .postalCode)Un solo dato del lugar de descarga
shipments[N].unloadDateFecha de descarga
shipments[N].weightPeso de la mercancía en kg de ese envío (no en servicios de viajeros). El tercero escribe solo el número (vale 12.500, 12,5 t u 800 kg) y se guarda normalizado, p. ej. «12.500 kg». Se puede pedir solo el peso aunque el resto esté completo. En un DeCA ya emitido el peso se cambia con una corrección (POST /decas/{id}/corrections).

Igual que en la web, solo se pueden pedir las matrículas y los datos de descarga: el lugar de carga, la provincia, el país, las partes, la mercancía y el peso los aporta siempre quien crea el DeCA (si no, la API responde 422 invalid_completion_field).

POST /api/v1/decas (con Idempotency-Key)
{
  "externalId": "TR-45882",
  "loadDate": "06-10-2026",
  "unloadDate": "07-10-2026",
  "vehicle": {
    "tractorPlate": "",
    "trailerPlate": ""
  },
  "shipper": {
    "name": "CERÁMICAS DEL MEDITERRÁNEO S.A.",
    "taxId": "A12345674",
    "country": "ES",
    "address": "Carretera de Alcora km 3",
    "postalCode": "12006",
    "city": "Castellón de la Plana"
  },
  "carrier": "self",
  "shipments": [
    {
      "loadLocation": {
        "name": "Planta 1",
        "address": "Carretera de Alcora km 3",
        "postalCode": "12006",
        "city": "Castellón de la Plana",
        "country": "ES"
      },
      "unloadLocation": {
        "name": "Almacenes Norte",
        "address": "Polígono Arasur, calle C 14",
        "postalCode": "01230",
        "city": "",
        "country": "ES"
      },
      "goods": "Azulejo cerámico en palés",
      "weight": 18400
    }
  ],
  "completionRequest": {
    "recipientEmail": "operaciones@transportista.example",
    "recipientType": "carrier",
    "fields": [
      "vehicle.tractorPlate",
      "vehicle.trailerPlate",
      "shipments[0].unloadLocation.city"
    ]
  }
}
Respuesta 202
{
  "id": "cmupjfyb600uvvos0tycclmsp",
  "object": "deca_draft",
  "status": "pending_completion",
  "decaId": null,
  "externalId": "TR-45882",
  "missingFields": [
    "shipments[0].unloadLocation.city",
    "vehicle.tractorPlate"
  ],
  "createdAt": "2026-10-01T14:56:31+02:00",
  "updatedAt": "2026-10-01T14:56:31+02:00",
  "completionRequest": {
    "id": "cmupjfybh00uxvos0uwlt3pjv",
    "status": "pending",
    "requestedFields": [
      "shipments[0].unloadLocation.city",
      "vehicle.tractorPlate",
      "vehicle.trailerPlate"
    ],
    "recipientEmail": "operaciones@transportista.example",
    "createdAt": "2026-10-01T14:56:31+02:00",
    "expiresAt": "2026-10-02T14:56:31+02:00",
    "completedAt": null
  },
  "warnings": []
}

El enlace seguro

El enlace se envía solo por email al destinatario; la API nunca lo devuelve. Es personal, de un solo uso y caduca a las expiresInHours horas. El tercero no necesita cuenta, solo ve el nombre de tu empresa y los campos que le pides, y no puede modificar ningún otro dato. El email indica qué empresa solicita los datos, la fecha de caducidad y qué ocurre al enviarlos.

Cuando el tercero completa los datos

Los datos se validan y se incorporan al borrador. Si ya no falta ningún dato obligatorio, el DeCA se genera automáticamente con tu integración como autora ( conserva tu externalId), el borrador pasa a issued y su decaId es el DeCA vigente; el tercero recibe una copia por email. Si aún faltan datos, el borrador queda en draft y missingFields te dice cuáles.

Consultar el estado

GET /api/v1/decas/{id} responde tanto con el id del borrador como con el del DeCA ya emitido (que también incluye completionRequest). Estados del borrador:

  • pending_completion — Hay una solicitud abierta: el tercero aún no ha enviado los datos.
  • draft — Faltan datos (missingFields) y no hay ninguna solicitud abierta: pide los datos o reenvía.
  • ready — No falta nada, pero el DeCA no se pudo emitir (por ejemplo, cupo del plan agotado). Se puede emitir desde el panel.
  • issued — El DeCA ya es vigente: consúltalo con decaId.
  • cancelled — El borrador se canceló desde el panel.

completionRequest.status: pending, completed, expired o replaced (sustituida por un reenvío).

GET /api/v1/decas/{id} — DeCA ya generado
{
  "id": "cmupjfyb600uvvos0tycclmsp",
  "object": "deca_draft",
  "status": "issued",
  "decaId": "cmupjfyzn00vzvos06903zz7y",
  "externalId": "TR-45882",
  "missingFields": [],
  "createdAt": "2026-10-01T14:56:31+02:00",
  "updatedAt": "2026-10-01T14:56:32+02:00",
  "completionRequest": {
    "id": "cmupjfynd00vmvos07hmyqdfy",
    "status": "completed",
    "requestedFields": [
      "shipments[0].unloadLocation.city",
      "vehicle.tractorPlate",
      "vehicle.trailerPlate"
    ],
    "recipientEmail": "operaciones@transportista.example",
    "createdAt": "2026-10-01T14:56:32+02:00",
    "expiresAt": "2026-10-02T14:56:32+02:00",
    "completedAt": "2026-10-01T14:56:32+02:00"
  }
}
GET /api/v1/decas/{id} — enlace caducado, faltan datos
{
  "id": "cmupjfyb600uvvos0tycclmsp",
  "object": "deca_draft",
  "status": "draft",
  "decaId": null,
  "externalId": "TR-45882",
  "missingFields": [
    "shipments[0].unloadLocation.city",
    "vehicle.tractorPlate"
  ],
  "createdAt": "2026-10-01T14:56:31+02:00",
  "updatedAt": "2026-10-01T14:56:31+02:00",
  "completionRequest": {
    "id": "cmupjfybh00uxvos0uwlt3pjv",
    "status": "expired",
    "requestedFields": [
      "shipments[0].unloadLocation.city",
      "vehicle.tractorPlate",
      "vehicle.trailerPlate"
    ],
    "recipientEmail": "operaciones@transportista.example",
    "createdAt": "2026-10-01T14:56:31+02:00",
    "expiresAt": "2026-10-01T14:56:30+02:00",
    "completedAt": null
  }
}

Pedir los datos después

También puedes crear primero el borrador ("draft": true en lugar de completionRequest) y pedir los datos más tarde. Si ya hay una solicitud pendiente, la respuesta es 409 completion_request_pending; si el DeCA ya es vigente, 409 deca_already_issued (usa una corrección).

POST /api/v1/decas/{id}/completion-request (con Idempotency-Key)
{
  "recipientEmail": "operaciones@transportista.example",
  "fields": [
    "vehicle.tractorPlate",
    "shipments[0].unloadLocation.city"
  ],
  "expiresInHours": 48
}
Respuesta 201
{
  "id": "cmupjfyhp00v8vos059cdhihy",
  "completionRequest": {
    "id": "cmupjfyij00vcvos0ga6ikvio",
    "status": "pending",
    "requestedFields": [
      "shipments[0].unloadLocation.city",
      "vehicle.tractorPlate"
    ],
    "recipientEmail": "operaciones@transportista.example",
    "createdAt": "2026-10-01T14:56:31+02:00",
    "expiresAt": "2026-10-03T14:56:31+02:00",
    "completedAt": null
  }
}

Reenviar la solicitud

POST /api/v1/decas/{id}/completion-request/resend envía un enlace nuevo al mismo destinatario con los campos que aún no se han completado; el enlace anterior deja de funcionar. Úsalo, por ejemplo, cuando el enlace ha caducado. Cuerpo opcional: { "expiresInHours": 24 }. Máximo 5 reenvíos por borrador y hora; una solicitud ya completada responde 409 completion_request_completed.

Respuesta 201
{
  "id": "cmupjfyb600uvvos0tycclmsp",
  "completionRequest": {
    "id": "cmupjfynd00vmvos07hmyqdfy",
    "status": "pending",
    "requestedFields": [
      "shipments[0].unloadLocation.city",
      "vehicle.tractorPlate",
      "vehicle.trailerPlate"
    ],
    "recipientEmail": "operaciones@transportista.example",
    "createdAt": "2026-10-01T14:56:32+02:00",
    "expiresAt": "2026-10-02T14:56:32+02:00",
    "completedAt": null
  }
}

Seguridad e idempotencia

Una credencial solo ve y gestiona los borradores de su propia integración (con deca:read_company, puede leer todos los de su empresa). Todas estas llamadas exigen Idempotency-Key: si repites una con la misma clave, recibes la misma respuesta (200, Idempotent-Replayed: true) sin crear otro borrador, otra solicitud ni otro email. Cada paso queda registrado: quién lo pidió, cuándo se abrió el enlace, quién completó qué y cuándo se generó el DeCA.

Borradores: varios transportes a la vez

Si tu sistema prepara varios transportes en paralelo, abre un borrador por cada uno con POST /api/v1/drafts. Cada borrador es independiente y tiene su propio draftId: puedes tener todos los que necesites abiertos a la vez, completarlos poco a poco y generar cada DeCA cuando esté listo, sin que un borrador pise a otro. Un borrador no consume cuota de tu plan; la cuota se consume solo al generar el DeCA definitivo.

El cuerpo es el mismo que el de POST /api/v1/decas, pero puede estar incompleto. Cada respuesta indica ready y, si falta algo, issues con los mismos detalles de error que daría la creación directa.

Los borradores abiertos también aparecen en la web, en Mis DeCA › Borradores pendientes (con la etiqueta «API» y tu externalId), para los usuarios de tu empresa que pueden crear DeCA. Desde allí se pueden continuar en el asistente, generar o descartar. Si se genera desde la web, el borrador queda generated con su decaId (y generatedByApiCredentialId vacío); si se descarta, queda cancelled. Consúltalo con GET /api/v1/drafts/{id}.

POST /api/v1/drafts (Idempotency-Key obligatoria)
{
  "externalId": "VIAJE-4471",
  "reference": "Pedido 88231",
  "loadDate": "06-10-2026",
  "unloadDate": "07-10-2026",
  "shipper": {
    "name": "CERÁMICAS DEL MEDITERRÁNEO S.A.",
    "taxId": "A12345674",
    "country": "ES",
    "address": "Carretera de Alcora km 3",
    "postalCode": "12006",
    "city": "Castellón de la Plana"
  },
  "carrier": "self"
}
Respuesta 201
{
  "id": "cmuqr2x0k0001vo8k3d1f9a7b",
  "draftId": "cmuqr2x0k0001vo8k3d1f9a7b",
  "object": "api_draft",
  "origin": "api",
  "status": "open",
  "version": 1,
  "externalId": "VIAJE-4471",
  "data": {
    "...": "el cuerpo que enviaste"
  },
  "ready": false,
  "issues": [
    {
      "path": "/vehicle",
      "code": "invalid_type",
      "message": "Requerido"
    },
    {
      "path": "/shipments",
      "code": "invalid_type",
      "message": "Requerido"
    }
  ],
  "decaId": null,
  "generatedAt": null,
  "cancelledAt": null,
  "createdAt": "2026-10-02T11:12:04+02:00",
  "updatedAt": "2026-10-02T11:12:04+02:00",
  "links": {
    "self": "/api/v1/drafts/cmuqr2x0k0001vo8k3d1f9a7b",
    "generate": "/api/v1/drafts/cmuqr2x0k0001vo8k3d1f9a7b/generate"
  }
}

Para completarlo, envía solo lo que cambia con PATCH /api/v1/drafts/{id} (JSON Merge Patch: los objetos se combinan, las listas como shipments se sustituyen enteras y null borra un campo). Si quieres evitar sobrescribir un cambio hecho por otro proceso, envía la cabecera If-Match con el ETag que recibiste: si el borrador cambió entretanto, la respuesta es 412.

PATCH /api/v1/drafts/{id}
{
  "vehicle": {
    "tractorPlate": "1234 KLM"
  },
  "shipments": [
    {
      "loadLocation": {
        "name": "Planta 1",
        "address": "Carretera de Alcora km 3",
        "postalCode": "12006",
        "city": "Castellón de la Plana",
        "country": "ES"
      },
      "unloadLocation": {
        "name": "Almacenes Norte",
        "address": "Polígono Arasur, calle C 14",
        "postalCode": "01230",
        "city": "Ribabellosa",
        "country": "ES"
      },
      "goods": "Azulejo cerámico en palés",
      "weight": 18400
    }
  ]
}

Cuando ready sea true, genera el DeCA con POST /api/v1/drafts/{id}/generate. Pasa exactamente las mismas validaciones y el mismo límite de plan que POST /api/v1/decas y responde 201 con el DeCA vigente (PDF, QR y URL de verificación). El borrador queda generated con su decaId y la fecha de generación; ya no se puede modificar, y repetir la llamada devuelve el mismo DeCA (200), nunca uno nuevo. El resto de borradores no cambia. GET /api/v1/drafts lista los abiertos (?status=generated, cancelled o all para el resto) y DELETE /api/v1/drafts/{id} descarta uno que ya no necesites.

Cada borrador pertenece a tu empresa y a la integración que lo creó: con otra clave, un draftId ajeno responde 404, igual que uno que no existe. Esta función es nueva y no cambia nada de lo anterior: POST /api/v1/decas con draft: true o completionRequest sigue funcionando igual.

Ejemplo completo: una operación real de transporte

Así encaja todo en un día normal: tu ERP o TMS prepara el transporte, tu equipo lo revisa en el panel si hace falta, el transportista completa las matrículas y tu sistema recibe cada paso por webhook. Cada paso es una llamada de esta página.

  1. 1. Sincroniza tus datos habituales desde el ERP

    PUT /api/v1/saved/companies/CLI-0042 · PUT /api/v1/saved/places/ALM-VLC

    Con tu identificador estable (externalId): repetir la llamada actualiza, nunca duplica. Lo que cambies en el panel se ve en la API y al revés.

  2. 2. Crea el borrador del transporte

    POST /api/v1/drafts (Idempotency-Key)

    Con lo que el ERP ya sabe (cargador, lugares, mercancía, fechas). Puede faltar la matrícula: responde ready=false e issues con lo pendiente. No consume cupo.

  3. 3. Continúalo desde el panel si hace falta

    Mis DeCA › Borradores pendientes

    El borrador aparece con la etiqueta «API» y tu externalId. Con deca:read_company tu integración ve también los borradores iniciados en la web, incluidos los programados.

  4. 4. Pide las matrículas al transportista

    POST /api/v1/decas/{id}/completion-request

    Un enlace temporal para que el transportista complete solo los campos pedidos. Cuando los completa recibes completion.completed (solo los nombres de los campos, nunca los valores).

  5. 5. Genera el DeCA y su orden de carga

    POST /api/v1/decas con loadOrder { format: "separate", price, conditions }

    Un DeCA con su QR y su PDF, y la orden vinculada (junta o separada) si tu empresa es el cargador. Recibes deca.created y load_order.issued. Repetir con la misma Idempotency-Key no crea nada nuevo.

  6. 6. Recibe las confirmaciones por webhook

    deca.created · load_order.issued · load_order.accepted · deca.corrected · deca.service_finished

    Verifica la firma DeCA-Signature y descarta duplicados por id de evento. load_order.accepted llega cuando el transportista acepta la orden por su enlace.

  7. 7. Recupera referencias y documentos

    GET /api/v1/decas/{id} · GET /api/v1/decas/{id}/pdf · GET /api/v1/load-orders?decaId={id}

    La referencia DECA-…, la URL de verificación, el PDF vigente, la orden con su estado y aceptación, y service.finished cuando se marca el servicio como finalizado.

Entorno de pruebas (sandbox)

Crea en Integraciones → Centro de integración una clave de pruebas: empieza por fvd_test_. Con ella tu integración llama a las mismas URL con los mismos cuerpos y recibe las mismas validaciones y errores, pero los documentos son de prueba: no son DeCA, no consumen tu cupo, no aparecen en el Historial ni en la facturación y su PDF lleva la marca «PRUEBA · SIN VALIDEZ». Cuando todo funcione, cambia la clave por una real (fvd_live_) sin tocar nada más.

  • Funcionan con la clave de pruebas: GET /me (responde mode: "test"), POST /decas/validate, POST /decas, GET /decas, GET /decas/{id}, /status, /pdf, /corrections y /annulment. El resto responde 403 sandbox_not_supported.
  • Las respuestas llevan livemode: false, el id empieza por test_ y la referencia por PRUEBA-.
  • Los webhooks de tus documentos de prueba se envían a tus endpoints con livemode: false: así pruebas también la recepción.
  • Hasta 2 claves de pruebas activas, aparte de las 2 reales.

Errores a demanda. Crea un documento con externalId: "PRUEBA-ERROR-<código>" y la API responderá ese error, igual que en real, para que compruebes cómo lo trata tu programa:

externalIdHTTPRespuesta
PRUEBA-ERROR-duplicate_external_id409Ya existe un DeCA activo con este externalId.
PRUEBA-ERROR-plan_allowance_exhausted402Has alcanzado los DeCA incluidos en tu plan este mes.
PRUEBA-ERROR-rate_limited429Límite de peticiones por minuto superado.
PRUEBA-ERROR-daily_create_ceiling429Límite técnico diario de creación alcanzado.
PRUEBA-ERROR-generation_failed500No se pudo generar el DeCA. Inténtalo de nuevo.
PRUEBA-ERROR-service_unavailable503La API no está disponible temporalmente.

Webhooks: tu sistema se entera solo

Configura en Integraciones → Centro de integración → Webhooks la URL https:// de tu sistema y los eventos que te interesan. Cada aviso es un POST JSON firmado; tu servidor debe responder 2xx en menos de 10 segundos. Si no, reintentamos a los 1, 5 y 30 minutos y a las 2, 6, 12 y 24 horas, siempre con el mismo DeCA-Event-Id, para que puedas descartar duplicados. Desde el panel puedes enviar un evento de prueba, ver cada entrega y reenviar las fallidas.

EventoCuándo
deca.createdSe crea un DeCA en tu empresa (web, API, borrador o tercero).
deca.correctedSe corrige un DeCA o se registra un cambio de vehículo (nueva versión).
deca.annulledSe anula un DeCA.
draft.generatedUn borrador de la API se convierte en DeCA.
completion.completedUn tercero completa los datos que le pediste (solo los nombres de los campos, nunca los valores).
deca.service_finishedSe marca el servicio como finalizado (dato operativo: el DeCA, su PDF y su estado administrativo no cambian). Incluye la observación, si la hay.
deca.service_reopenedSe reabre un servicio marcado como finalizado (vuelve a pendiente).
load_order.issuedSe emite una orden de carga (con su DeCA, sola o pendiente de matrículas). Incluye número, formato, estado y el DeCA vinculado; nunca el precio ni las condiciones.
load_order.acceptedEl transportista acepta una versión de la orden por su enlace. Incluye la versión aceptada y la fecha; el nombre escrito no se envía.
webhook.testEvento de prueba enviado desde el panel.

Cabeceras: DeCA-Event-Id, DeCA-Event-Type y DeCA-Signature con el formato t=<segundos>,v1=<HMAC-SHA256 hex de "<t>.<cuerpo>">, calculado con el secreto que se muestra una sola vez al crear el endpoint. Los avisos incluyen identificadores y referencias, no los datos personales del DeCA: consúltalos con la API si los necesitas.

evento.json
{
  "id": "evt_4n8Qx2kY0pLmRs7T",
  "type": "deca.created",
  "createdAt": "2026-10-08T10:41:00+02:00",
  "livemode": true,
  "data": {
    "deca": {
      "id": "cmupjfyhp00v8vos059cdhihy",
      "reference": "DECA-A4F2C9E1",
      "externalId": "TR-45882",
      "status": "active",
      "version": 1,
      "verificationUrl": "https://decaprofesional.es/d/A4F2C9E1…",
      "links": {
        "self": "/api/v1/decas/cmupjfyhp00v8vos059cdhihy",
        "pdf": "/api/v1/decas/cmupjfyhp00v8vos059cdhihy/pdf"
      }
    },
    "source": "api"
  }
}
verificar-firma.mjs
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: el cuerpo EXACTO recibido (sin volver a serializarlo); secret: dcwh_… (Integraciones → Webhooks)
export function webhookValido(rawBody, signatureHeader, secret) {
  const m = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signatureHeader ?? "");
  if (!m) return false;
  if (Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false; // más de 5 min: posible reenvío malicioso
  const esperado = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest();
  const recibido = Buffer.from(m[2], "hex");
  return esperado.length === recibido.length && timingSafeEqual(esperado, recibido);
}
// Responde 2xx en menos de 10 s y procesa después. Usa DeCA-Event-Id para no procesar dos veces el mismo evento.

Power Automate y Make: conectar sin escribir código

Si tu ERP o TMS ya se conecta con Power Automate o con Make (Odoo, Sage, Holded, Dynamics y muchos más lo hacen), puedes generar DeCA sin escribir código: la herramienta lee la expedición de tu sistema y llama a la API. Lo que no podemos hacer por ti es que tu programa exporte sus datos: eso depende de que tenga integración con estas herramientas o una API propia. No son integraciones nativas: te damos la definición para crear un conector personalizado y una guía paso a paso.

Power Automate (Microsoft)

Un conector personalizado con todas las llamadas de la API, creado a partir del archivo Swagger del kit. Después cada llamada aparece como una acción más en tus flujos.

  1. Descarga deca-power-automate.swagger.json (enlace de abajo).
  2. En Power Automate, abre Datos → Conectores personalizados → Nuevo conector personalizado → Importar un archivo OpenAPI, ponle de nombre «DeCA Profesional» y elige el archivo.
  3. En Seguridad, deja «Clave de API», con el nombre de parámetro Authorization y la ubicación Encabezado.
  4. Pulsa «Crear conector». En Prueba, crea una conexión y pega como clave: Bearer seguido de tu clave de API (créala en Integraciones → Centro de integración).
  5. Prueba GET /api/v1/me: si responde con el nombre de tu empresa, la conexión funciona.
  6. En tu flujo (por ejemplo, «cuando se crea un albarán en Dynamics, SharePoint o Excel»), añade la acción «Crea un DeCA» y relaciona cada campo con el de tu sistema según la tabla de equivalencias.

Los conectores personalizados necesitan una licencia de Power Automate que los incluya (Premium). Pon un valor distinto en Idempotency-Key en cada expedición, por ejemplo la función guid().

Make

Con el módulo HTTP «Make a request» llamas a cualquier endpoint, y con «Webhooks → Custom webhook» recibes los avisos de DeCA Profesional.

  1. Añade el módulo HTTP → Make a request.
  2. URL: https://decaprofesional.es/api/v1/decas · Método: POST.
  3. Cabeceras: Authorization = Bearer <tu clave> · Idempotency-Key = {{uuid}} · Content-Type = application/json.
  4. Body type: Raw · Content type: JSON (application/json) · Request content: el JSON del DeCA con los campos de tu ERP mapeados (tabla de equivalencias). Marca «Parse response».
  5. Para recibir avisos, crea un módulo Webhooks → Custom webhook, copia su URL https y añádela en Integraciones → Centro de integración → Webhooks.
  6. Para mantener tus datos habituales al día, usa el mismo módulo HTTP con PUT /api/v1/saved/{recurso}/{externalId} y el código de tu ERP como externalId.

Guarda la clave en una conexión o variable de Make, nunca en un texto visible del escenario. Si tu escenario no puede verificar la firma de los webhooks, usa solo el id del evento y lee el DeCA con la API.

Tabla de equivalencias: de tu ERP a la API

El concepto es el mismo en cualquier ERP; la columna de Odoo indica el campo estándar de sus módulos Inventario, Contactos y Flota. En Sage, Holded o Dynamics busca el campo equivalente: el nombre exacto depende de cada instalación.

En tu ERPCampo de la APIEjemplo en OdooConsejo
Número de albarán o expediciónexternalIdstock.picking · nameÚnico por expedición: evita duplicados.
Fecha de cargaloadDatestock.picking · scheduled_dateEn DD-MM-AAAA.
Fecha de descargaunloadDatestock.picking · date_deadlineEn DD-MM-AAAA; igual o posterior a la carga.
Cargador (cliente o tu empresa)shipperres.partner · name, vat, street, zip, city"self" si eres tú.
Transportistacarrierres.partner del transportista"self" si transportas tú.
Matrícula de la tractoravehicle.tractorPlatefleet.vehicle · license_plateCon o sin espacios ni guiones.
Matrícula del remolquevehicle.trailerPlatefleet.vehicle · license_plateOpcional.
Almacén o dirección de cargashipments[0].loadLocationstock.picking · picking_type_id.warehouse_id.partner_idname, address, postalCode, city, country.
Dirección de entregashipments[0].unloadLocationstock.picking · partner_idSi falta, pídela a la otra parte (completionRequest).
Descripción de la mercancíashipments[0].goodsstock.move · product_id.nameTexto breve, por ejemplo «Palés de azulejo».
Pesoshipments[0].weightstock.picking · weightNúmero en kg o texto con su unidad.

Permisos (scopes)

Cada credencial tiene solo los permisos que necesita su integración.

deca:createCrear DeCA (POST /decas, también por lotes) y validarlos (POST /decas/validate), crear, modificar y generar borradores, y pedir datos a un tercero.
deca:readLeer y listar los DeCA creados por esta integración, sus borradores, lotes y órdenes de carga.
deca:read_companyAmpliar la lectura a todos los DeCA de la empresa. Nunca amplía la escritura.
deca:correctCorregir DeCA creados por esta integración, registrar cambios de vehículo y marcar o reabrir el servicio finalizado.
deca:annulAnular DeCA creados por esta integración.
saved:readLeer los datos habituales de la empresa: empresas, vehículos, lugares y envíos habituales.
saved:writeCrear, actualizar (PUT por externalId) y borrar datos habituales desde tu ERP o TMS.

Límites

  • 60 peticiones por minuto por credencial (cabeceras X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset).
  • Un techo técnico diario de creación por credencial (5000 DeCA) y un bloqueo temporal tras varios intentos de autenticación fallidos.
  • Cuerpo de hasta 256 KB y hasta 50 envíos por DeCA.
  • El cupo mensual de DeCA es el de tu plan; GET /api/v1/me indica el uso y lo que queda.
  • Al superar un límite recibes 429 con Retry-After: espera y reintenta.

Errores

Todos los errores tienen el mismo formato. code es estable y es lo que debe leer tu integración; message es informativo. Si contactas con soporte, indica el requestId.

Ejemplo 422
{
  "error": {
    "code": "validation",
    "message": "El DeCA no cumple los requisitos obligatorios.",
    "requestId": "req_7f3a9c…",
    "details": [
      {
        "path": "/shipments/0/unloadLocation/country",
        "code": "invalid_country",
        "message": "Usa un código de país ISO 3166-1 alfa-2 (p. ej. ES, FR, PT)"
      }
    ]
  }
}
HTTPcodeSignificado
400invalid_json, idempotency_key_required, invalid_queryPetición mal formada, falta la Idempotency-Key o un filtro no es válido.
401invalid_credentials, credential_revoked, credential_expiredClave desconocida o incorrecta (misma respuesta en ambos casos), revocada o caducada.
402business_plan_required, plan_allowance_exhaustedEl plan de la empresa no incluye la API, o se ha agotado el cupo de DeCA del plan.
403api_addon_required, api_advanced_required, professional_plan_required, company_inactive, ip_not_allowed, insufficient_scope, sandbox_not_supportedEl complemento API no está activo, la cuenta no está activa, IP no autorizada, permiso insuficiente o endpoint no disponible con una clave de pruebas.
429api_lite_limit_reachedAPI Lite: ya se han creado por API los 100 DeCA del mes (dentro del cupo del plan). Se renueva con el periodo del plan; para más, API Advanced.
404not_foundNo existe o no es visible para esta credencial (nunca se distingue).
409duplicate_external_id, idempotency_conflict, deca_annulled, version_conflict, batch_without_documents, saved_in_use, company_inactiveexternalId ya usado por un DeCA activo (incluye existingId), clave de idempotencia reutilizada con otro cuerpo, DeCA anulado, corrección simultánea o lote que aún no ha creado ningún DeCA, lugar habitual usado por un envío habitual, o la empresa no tiene un Administrador al sincronizar datos habituales.
402 / 403 / 409 / 422load_order_plan_required, load_order_not_shipper, load_order_company_nif_missing, load_order_invalid, load_order_passengerOrden de carga con POST /decas (loadOrder): el plan no incluye la orden de carga (402), tu empresa no es el cargador contractual (403), falta el NIF de tu empresa (409), los datos comerciales no son válidos (422, details indica el campo en /loadOrder/…) o el servicio es de viajeros (422). No se crea ningún DeCA.
409deca_already_issued, completion_request_pending, completion_request_completed, draft_cancelledSolicitudes a un tercero: el DeCA ya es vigente (usa una corrección), ya hay una solicitud pendiente (usa /resend), la solicitud ya se completó, o el borrador se canceló.
409draft_not_open, draft_limit_reached, draft_has_preparation, completion_request_no_emailBorradores: ya generado o cancelado, límite de borradores abiertos alcanzado (incluye limit), borrador web vinculado a una solicitud a un tercero (genéralo con /api/v1/decas/{id}), o la solicitud no tiene email de destinatario.
412precondition_failedIf-Match no corresponde: el borrador ha cambiado desde tu última lectura. Vuelve a leerlo (cabecera ETag) y aplica tu cambio de nuevo.
404completion_request_not_foundReenvío pedido para un DeCA sin ninguna solicitud a un tercero.
422invalid_draft, invalid_completion_request, new_service_requiredEl borrador o el cambio no es un objeto JSON válido (o incluye campos solo de POST /decas), la solicitud a un tercero no es válida, o la corrección cambia el servicio (crea un DeCA nuevo).
413 / 415payload_too_large, unsupported_media_typeCuerpo de más de 256 KB, o no es application/json.
422validationDatos no válidos. details indica cada campo con un puntero JSON y un código (required, unknown_field, invalid_country, too_short, too_long, invalid_format, invalid_value, self_company_incomplete…).
422invalid_completion_fieldUn campo de completionRequest.fields no se puede pedir a un tercero (unknown_field) o se refiere a un envío que no existe (unknown_shipment).
429rate_limited, daily_create_ceiling, too_many_failed_attemptsLímite de peticiones superado. Respeta la cabecera Retry-After.
500generation_failed, internal_errorError al generar (incluye correlationId y retryable: reintenta con la misma Idempotency-Key) o error interno.
503service_unavailable, pdf_unavailableServicio o PDF temporalmente no disponible: reintenta.

Preguntas frecuentes

¿Qué hace la API DeCA de DeCA Profesional que no haga una integración básica?

Además de crear el DeCA con su PDF y QR, permite corregirlo (nueva versión), registrar un cambio de vehículo, anularlo, trabajar con borradores, pedir a un tercero los datos que faltan, crear lotes de hasta 50 DeCA en una llamada con un ZIP de los PDF y recibir webhooks firmados cuando algo cambia. Todo está descrito en una especificación OpenAPI y hay ejemplos listos para Node.js, Python, PHP, Postman y Power Automate.

¿Puedo recibir un aviso en mi ERP cuando se crea, corrige o anula un DeCA?

Sí, con webhooks: das de alta una URL https en el centro de integración y recibes un aviso firmado (DeCA-Signature) por cada DeCA creado, corregido o anulado, borrador generado, solicitud completada por un tercero, servicio finalizado o reabierto, y orden de carga emitida o aceptada. Si tu servidor no responde, se reintenta automáticamente y el identificador del evento no cambia, para que no lo proceses dos veces.

¿Puedo generar muchos DeCA a la vez?

Sí: POST /api/v1/decas/batch crea hasta 50 DeCA en una sola llamada. Cada uno se valida y se crea por separado, un error no afecta a los demás, un reintento con la misma Idempotency-Key no duplica nada y puedes descargar todos los PDF en un ZIP.

¿Dónde pongo la clave de la API?

No se introduce en DeCA Profesional: se configura en tu propio programa de gestión (ERP o TMS), normalmente lo hace tu informático o el proveedor de ese programa. Con ella, tu programa envía los datos del transporte a DeCA Profesional y recibe al momento el DeCA, su PDF con QR y la URL de verificación, sin que nadie tenga que rellenarlo en la web. Si no usas un ERP o TMS, o no tienes a nadie que haga la integración, no necesitas la API: puedes crear tus DeCA desde la web.

¿Hay un entorno de pruebas?

Sí. Crea una clave de pruebas (empieza por fvd_test_) en Integraciones → Centro de integración: llamas a las mismas URL y recibes las mismas validaciones, pero los documentos son de prueba, no consumen cupo, no aparecen en el Historial y su PDF lleva la marca PRUEBA. Con externalId PRUEBA-ERROR-<código> reproduces errores. Y si solo quieres validar un cuerpo, POST /api/v1/decas/validate no crea nada.

¿Qué pasa si se corta la conexión mientras creo un DeCA?

Repite la misma llamada con la misma Idempotency-Key y el mismo cuerpo: si el DeCA ya se creó, recibes ese mismo DeCA (200 e Idempotent-Replayed: true); nunca se duplica.

¿Cómo evito crear dos DeCA para el mismo pedido?

Envía tu identificador de pedido o expedición en externalId. Si ya hay un DeCA activo con ese externalId, la API responde 409 duplicate_external_id con el existingId, y puedes consultarlo con GET /api/v1/decas?externalId=….

¿El QR cambia si corrijo el DeCA?

Sí. Cada corrección crea una versión nueva con nueva URL de verificación, nuevo QR y nuevo PDF. Guarda y comparte siempre la última verification.url; las versiones anteriores se conservan en GET /versions.

¿Puedo corregir o anular desde la API un DeCA creado en el panel?

No. La API solo corrige y anula los DeCA creados por la misma integración. Con el permiso deca:read_company puedes leer todos los DeCA de la empresa, pero nunca modificarlos.

¿Cuántos DeCA puedo crear?

Los de tu plan: los DeCA creados por la API y desde el panel comparten el mismo cupo mensual. GET /api/v1/me te indica cuántos llevas este mes y cuántos te quedan.

¿En qué formato van los países y las fechas?

Países en código ISO 3166-1 de dos letras (ES, FR, PT…) y fechas en DD-MM-AAAA (las horas, en hora de Madrid con su desfase, p. ej. 2026-10-08T10:41:00+02:00). El PDF muestra el nombre del país en español.

¿Puedo crear un DeCA aunque me falten la matrícula o el lugar de descarga?

Sí, si se los pides a un tercero: envía completionRequest con su email y los campos que faltan (por ejemplo vehicle.tractorPlate). Se crea un borrador, el tercero recibe un enlace seguro y, cuando lo completa, el DeCA se genera automáticamente. Consulta el estado con GET /api/v1/decas/{id}.

¿Cuánto tiempo está disponible la URL pública?

Durante el servicio y los 7 días siguientes a la fecha de descarga (servicePeriod.publicUntil). Con tu credencial puedes descargar el PDF en cualquier momento.

Soporte para integraciones

¿Vas a conectar tu ERP o TMS?

Cuéntanos qué software usas y te ayudamos con el acceso, las credenciales y las primeras pruebas.

Escribir a integraciones@praetoriaabogados.es
Documentación API DeCA: endpoints y OpenAPI | DeCA Profesional