¿Ya tienes tu propio ERP o software de gestión de transporte?

Aprovecha el motor de DeCAFacil sin cambiar de herramienta: emite el DeCA directamente desde tu sistema, con la misma trazabilidad y el mismo QR legal que la aplicación web. Esta página es la documentación de referencia del canal /api/v1 para quien va a integrarlo.

Las claves de API son una mejora de la plan Pro (precio y condiciones en precios y tarifas): hace falta tenerla activa para crear y gestionar tus claves desde /claves-api. Fechas y condiciones de cobro en precios y tarifas — ver también modo de pruebas y condiciones.

Última actualización: 2 de octubre de 2026.

Qué es y para quién es

El canal de API está pensado para la misma empresa que hoy usaría la aplicación web, pero emitiendo desde su propio software en vez de entrar en la PWA. No es una API abierta para programadores en general: la clave pertenece a la empresa (identificada por su NIF), no a quien escribe la integración, y esa empresa debe figurar siempre como cargador contractual o transportista efectivo en cada DeCA que emita por esta vía — la misma regla de «parte activa» que rige el resto del servicio.

El valor práctico: no mantener dos bases de datos. DeCAFacil no necesita ni usa tus maestros de clientes, vehículos o direcciones — le mandas los datos del envío, genera el documento con su código QR, y tu sistema sigue siendo la única fuente de verdad para todo lo demás.

Cómo empezar

  1. Da de alta tu empresa en DeCAFacil — como cualquier cuenta, con nombre y NIF, desde la aplicación. La clave de API cuelga de una cuenta real, no hay atajos.
  2. Activa el plan Pro — desde /planes dentro de la aplicación. Precio y fechas en precios y tarifas (ver condiciones).
  3. Crea tu clave tú mismo — en /claves-api: le pones un identificador para reconocerla, aceptas el anexo de términos de la API y se genera al momento. El secreto se muestra una única vez: apúntalo en un lugar seguro en cuanto lo veas, porque ni nosotros podemos volver a mostrártelo.

Autenticación

Cada petición lleva la clave en la cabecera:

Authorization: Bearer <tu-clave>

Nada impide tener varias claves para la misma empresa (por ejemplo, una para oficina y otra para almacén): todas identifican a la misma titular y se pueden revocar por separado.

Cada clave lleva sus permisos (scopes): leer, crear, modificar, finalizar, avisar, anular y, desde la revisión 1.1.0, asignar. El rol oficina los incluye todos. Las claves oficina ya emitidas reciben asignar automáticamente; las de otros roles o con permisos elegidos a mano, no. Una clave equivale al titular de su cuenta: no está ligada a una persona concreta.

Qué puedes hacer

La referencia completa de peticiones y respuestas está disponible en la documentación interactiva (Swagger) y en el esquema OpenAPI. Estas son las operaciones disponibles en /api/v1:

  • Emitir un DeCA nuevo, con soporte de identificador propio para evitar duplicados si reintentas una petición.
  • Consultar un DeCA por su identificador, listar los tuyos con paginación, y descargar el PDF vigente.
  • Modificar un DeCA durante el servicio (fraccionar o eliminar envíos, con la misma lógica que la PWA).
  • Finalizar el servicio, cuando corresponda.
  • Avisar al conductor del documento generado.
  • Anular un DeCA.
  • Asignar un DeCA a una persona de tu equipo, o quitar la asignación (POST /deca/{id}/asignar, permiso asignar). Es una mejora del plan Pro y requiere que el equipo tenga a alguien más que el titular.
  • Listar las personas activas de tu cuenta, con su identificador, nombre y rol, para saber a quién asignar (GET /personas, permiso leer). No devuelve correo ni teléfono.

Las respuestas de DeCA incluyen además dos campos opcionales, creado_por_persona_id y asignado_a_persona_id, y el listado de DeCA admite el filtro asignado_a=<id> o asignado_a=ninguno.

Ejemplos de código

Nota sobre estos ejemplos. Emiten el mismo DeCA de ejemplo (Cargas Ejemplo SA → Transportes Ejemplo SL, Getafe → Valencia) que usa el resto de esta documentación. Sustituye <tu-clave> por tu clave real y, si no tienes una todavía, empieza por cómo empezar.

cURL

curl -X POST https://app.decafacil.es/api/v1/deca \
  -H "Authorization: Bearer <tu-clave>" \
  -H "Content-Type: application/json" \
  -d '{
  "datos": {
    "cargador_contractual": {
      "nombre": "Cargas Ejemplo SA",
      "nif": "A76543214",
      "direccion": {
        "empresa": "Cargas Ejemplo SA",
        "via": "Calle Mayor 12",
        "localidad": "Getafe",
        "provincia": "Madrid",
        "codigo_postal": "28901",
        "pais": "España"
      }
    },
    "transportista_efectivo": {
      "nombre": "Transportes Ejemplo SL",
      "nif": "B12345674",
      "direccion": {
        "empresa": "Transportes Ejemplo SL",
        "via": "Polígono Industrial, Nave 4",
        "localidad": "Valencia",
        "provincia": "Valencia",
        "codigo_postal": "46001",
        "pais": "España"
      }
    },
    "matricula_vehiculo": "1234ABC",
    "envios": [
      {
        "origen": {
          "via": "Calle Mayor 12",
          "localidad": "Getafe",
          "provincia": "Madrid",
          "codigo_postal": "28901",
          "pais": "España"
        },
        "destino": {
          "via": "Polígono Industrial, Nave 4",
          "localidad": "Valencia",
          "provincia": "Valencia",
          "codigo_postal": "46001",
          "pais": "España"
        },
        "mercancia": {
          "naturaleza": "Palets de material de construcción",
          "peso_kg": 8200,
          "bultos": 12
        },
        "fecha_efectiva_servicio": "2026-08-28"
      }
    ]
  },
  "ejecutado_por": "Nombre Apellido",
  "external_id": "PED-2026-0842"
}'

Python

Con la librería requests (pip install requests):

import requests

respuesta = requests.post(
    "https://app.decafacil.es/api/v1/deca",
    headers={"Authorization": "Bearer <tu-clave>"},
    json={
  "datos": {
    "cargador_contractual": {
      "nombre": "Cargas Ejemplo SA",
      "nif": "A76543214",
      "direccion": {
        "empresa": "Cargas Ejemplo SA",
        "via": "Calle Mayor 12",
        "localidad": "Getafe",
        "provincia": "Madrid",
        "codigo_postal": "28901",
        "pais": "España"
      }
    },
    "transportista_efectivo": {
      "nombre": "Transportes Ejemplo SL",
      "nif": "B12345674",
      "direccion": {
        "empresa": "Transportes Ejemplo SL",
        "via": "Polígono Industrial, Nave 4",
        "localidad": "Valencia",
        "provincia": "Valencia",
        "codigo_postal": "46001",
        "pais": "España"
      }
    },
    "matricula_vehiculo": "1234ABC",
    "envios": [
      {
        "origen": {
          "via": "Calle Mayor 12",
          "localidad": "Getafe",
          "provincia": "Madrid",
          "codigo_postal": "28901",
          "pais": "España"
        },
        "destino": {
          "via": "Polígono Industrial, Nave 4",
          "localidad": "Valencia",
          "provincia": "Valencia",
          "codigo_postal": "46001",
          "pais": "España"
        },
        "mercancia": {
          "naturaleza": "Palets de material de construcción",
          "peso_kg": 8200,
          "bultos": 12
        },
        "fecha_efectiva_servicio": "2026-08-28"
      }
    ]
  },
  "ejecutado_por": "Nombre Apellido",
  "external_id": "PED-2026-0842"
},
)
respuesta.raise_for_status()
print(respuesta.json())

Node.js

Con el fetch incluido en Node 18 y versiones posteriores, sin dependencias:

const respuesta = await fetch("https://app.decafacil.es/api/v1/deca", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tu-clave>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "datos": {
    "cargador_contractual": {
      "nombre": "Cargas Ejemplo SA",
      "nif": "A76543214",
      "direccion": {
        "empresa": "Cargas Ejemplo SA",
        "via": "Calle Mayor 12",
        "localidad": "Getafe",
        "provincia": "Madrid",
        "codigo_postal": "28901",
        "pais": "España"
      }
    },
    "transportista_efectivo": {
      "nombre": "Transportes Ejemplo SL",
      "nif": "B12345674",
      "direccion": {
        "empresa": "Transportes Ejemplo SL",
        "via": "Polígono Industrial, Nave 4",
        "localidad": "Valencia",
        "provincia": "Valencia",
        "codigo_postal": "46001",
        "pais": "España"
      }
    },
    "matricula_vehiculo": "1234ABC",
    "envios": [
      {
        "origen": {
          "via": "Calle Mayor 12",
          "localidad": "Getafe",
          "provincia": "Madrid",
          "codigo_postal": "28901",
          "pais": "España"
        },
        "destino": {
          "via": "Polígono Industrial, Nave 4",
          "localidad": "Valencia",
          "provincia": "Valencia",
          "codigo_postal": "46001",
          "pais": "España"
        },
        "mercancia": {
          "naturaleza": "Palets de material de construcción",
          "peso_kg": 8200,
          "bultos": 12
        },
        "fecha_efectiva_servicio": "2026-08-28"
      }
    ]
  },
  "ejecutado_por": "Nombre Apellido",
  "external_id": "PED-2026-0842"
}),
});

if (!respuesta.ok) {
  throw new Error(`Error ${respuesta.status}: ${await respuesta.text()}`);
}

console.log(await respuesta.json());

PHP

Con la extensión curl, incluida por defecto en la mayoría de instalaciones de PHP:

<?php
$datos = [
    "datos" => [
        "cargador_contractual" => [
            "nombre" => "Cargas Ejemplo SA",
            "nif" => "A76543214",
            "direccion" => [
                "empresa" => "Cargas Ejemplo SA",
                "via" => "Calle Mayor 12",
                "localidad" => "Getafe",
                "provincia" => "Madrid",
                "codigo_postal" => "28901",
                "pais" => "España",
            ],
        ],
        "transportista_efectivo" => [
            "nombre" => "Transportes Ejemplo SL",
            "nif" => "B12345674",
            "direccion" => [
                "empresa" => "Transportes Ejemplo SL",
                "via" => "Polígono Industrial, Nave 4",
                "localidad" => "Valencia",
                "provincia" => "Valencia",
                "codigo_postal" => "46001",
                "pais" => "España",
            ],
        ],
        "matricula_vehiculo" => "1234ABC",
        "envios" => [
            [
                "origen" => [
                    "via" => "Calle Mayor 12",
                    "localidad" => "Getafe",
                    "provincia" => "Madrid",
                    "codigo_postal" => "28901",
                    "pais" => "España",
                ],
                "destino" => [
                    "via" => "Polígono Industrial, Nave 4",
                    "localidad" => "Valencia",
                    "provincia" => "Valencia",
                    "codigo_postal" => "46001",
                    "pais" => "España",
                ],
                "mercancia" => [
                    "naturaleza" => "Palets de material de construcción",
                    "peso_kg" => 8200,
                    "bultos" => 12,
                ],
                "fecha_efectiva_servicio" => "2026-08-28",
            ],
        ],
    ],
    "ejecutado_por" => "Nombre Apellido",
    "external_id" => "PED-2026-0842",
];

$ch = curl_init("https://app.decafacil.es/api/v1/deca");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer <tu-clave>",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode($datos),
]);

$respuesta = curl_exec($ch);
$codigo = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $respuesta;

Respuesta

Los cuatro ejemplos anteriores, si la clave y los datos son correctos, devuelven 201 Created con el mismo cuerpo:

{
  "id": 4213,
  "token": "a1b2c3d4e5f6",
  "url": "https://d.decafacil.es/d/a1b2c3d4e5f6",
  "n_version": 1,
  "external_id": "PED-2026-0842"
}

Asignar un DeCA a una persona del equipo (cURL)

Con el identificador de persona obtenido de GET /personas. Enviar null quita la asignación:

curl -X POST https://app.decafacil.es/api/v1/deca/4213/asignar \
  -H "Authorization: Bearer <tu-clave>" \
  -H "Content-Type: application/json" \
  -d '{"persona_id": 17}'

Respuesta 200:

{"id": 4213, "asignado_a_persona_id": 17, "anterior": null}

Si la persona no es válida (inactiva, de otra cuenta o el propio titular) la respuesta es un error con el formato habitual:

{"detail": {"code": "persona_no_asignable", "mensaje": "..."}}

Documentación interactiva y colección Postman

Si prefieres explorar el esquema completo en vez de leerlo aquí: documentación interactiva (Swagger) — prueba cada operación desde el navegador con tu propia clave — o su equivalente en ReDoc. El esquema en bruto está en /api/v1/openapi.json (OpenAPI 3.1), por si tu herramienta genera un cliente a partir de él.

Para probar la API sin escribir nada: descarga la colección Postman (en el orden natural de una integración: verificación, emitir, consultar, modificar, avisar/finalizar, anular, y la carpeta «3 bis. Equipo y asignación», con la prueba de asignación de la revisión 1.1.0). Si la cuenta no tiene el plan Pro activo o el equipo solo cuenta con el titular, los 403 de esas peticiones son el resultado esperado. Trae una guarda de seguridad incorporada — bloquea sola cualquier ejecución si detecta que la clave no está en modo de pruebas, para que un «Run collection» sin pensar no emita DeCA reales.

Errores

Todos los errores de /api/v1 — validación de datos incluida — llevan la misma forma, con un código estable para que tu integración pueda distinguir un caso de otro sin depender del texto:

{"detail": {"code": "external_id_duplicado", "mensaje": "..."}}

El texto en mensaje ya viene traducido al español (incluidos los errores de validación de campos). Sin soporte de integración dedicado (ver condiciones), este mensaje es, en la práctica, tu primera línea de soporte.

Modo de pruebas

Al crear una clave en /claves-api eliges entre real y pruebas: con una clave de pruebas, los documentos que generes quedan marcados de forma visible en el PDF y no se cuentan como emisiones reales. Es el entorno recomendado para probar tu integración antes de pasar a una clave real — puedes tener las dos a la vez, cada una con su propio secreto.

Webhooks

Los webhooks están disponibles. Con suscripción activa, el titular puede crear hasta cinco destinos activos desde Webhooks, en la aplicación. Cada destino se configura para un modo —real o pruebas— y para los eventos que quiera recibir. Al crearlo se acepta el anexo de términos. Los avisos corresponden a documentos propios de la cuenta, tanto si se emitieron desde la PWA como desde la API; no se notifican operaciones sobre documentos de terceros. La configuración se hace con la sesión de la aplicación: no añade rutas ni credenciales al esquema de /api/v1.

El destino debe ser una URL HTTPS pública por el puerto 443. No se siguen redirecciones. Guarda el secreto completo, que empieza por whsec_, al crear o rotar el destino: no vuelve a mostrarse.

EventoCuándo se emite
deca.creadoAl emitir un documento; lleva external_id si la API lo recibió.
deca.finalizadoFinalización manual desde PWA/API o automática por ciclo de vida; indica automatico.
deca.modificadoCorrección de un documento existente; informa método, versión y nombres de campos cambiados.
deca.reemplazadoSe emite un documento nuevo que sustituye al anterior.
deca.anuladoAnulación desde PWA/API.
deca.expiradoLa URL pública alcanza su fecha de expiración.
deca.asignadoCambia la persona del equipo asignada al documento.
webhook.testEvento sintético generado por el botón «Enviar prueba».

Formato del aviso

Cada entrega es un POST con JSON UTF-8. El sobre es estable: id es el identificador único del evento, type su tipo, created_at la fecha de creación, mode el modo del documento y data los datos específicos del evento.

{
  "id": "evt_…",
  "type": "deca.finalizado",
  "created_at": "2026-10-04T12:00:00Z",
  "mode": "real",
  "data": {
    "deca_id": 123,
    "estado": "finalizado",
    "automatico": false,
    "origen": "api"
  }
}

Todos los eventos de documento incluyen deca_id y origen (pwa, api o sistema). Además, deca.creado incluye external_id y n_version; deca.finalizado incluye estado, fin_servicio, url_expira_en y automatico; deca.modificado y deca.reemplazado incluyen metodo, n_version, campos, reemplaza_a_id y reemplazado_por_id; deca.anulado y deca.expirado incluyen estado y, cuando corresponde, url_expira_en; y deca.asignado incluye persona_anterior_id y persona_nueva_id. El evento webhook.test solo contiene un mensaje de prueba. Los avisos no incluyen el contenido legal del DeCA ni los datos personales de sus partes.

Verificación de firma

Cada petición incluye X-DeCA-Event-ID, X-DeCA-Timestamp y X-DeCA-Signature: t=<timestamp>,v1=<firma_hex>. Calcula HMAC-SHA256 con el secreto como clave y, como mensaje, los bytes ASCII del timestamp, un punto y el cuerpo HTTP original. Verifica la firma con comparación constante antes de interpretar el JSON:

import hashlib, hmac

def verificar_webhook(secret: str, timestamp: str, signature: str, raw_body: bytes) -> bool:
    recibida = dict(parte.split("=", 1) for parte in signature.split(","))
    esperado = hmac.new(secret.encode(), timestamp.encode("ascii") + b"." + raw_body,
                        hashlib.sha256).hexdigest()
    return (recibida.get("t") == timestamp
            and hmac.compare_digest(recibida.get("v1", ""), esperado))

Confirmación y reintentos

Rechaza marcas de tiempo fuera de la ventana que defina tu servicio y deduplica por X-DeCA-Event-ID. Responde rápido con cualquier código 2xx; cualquier otro resultado, un error de red o una espera agotada se reintenta. La entrega es al menos una vez: puede repetirse y el orden de recepción no está garantizado.

Se realizan como máximo diez intentos, con esperas de 1 minuto, 5 minutos, 15 minutos, 1 hora, 6 horas, 12 horas y después 24 horas entre reintentos. Consulta en la aplicación el estado y los últimos intentos de cada destino. No hay SLA.

Límites y buen uso

El canal aplica límites de frecuencia por clave; si los superas, la respuesta indica cuánto esperar antes de reintentar (cabecera Retry-After). Se aplican además los mismos límites de documentos que al resto del servicio: los de tu plan más 500 por cada Módulo500; si los superas te avisamos y te pedimos que revises tu suscripción, y la emisión solo se bloquea cuando el exceso supera los 150 documentos.

Versionado y compatibilidad

/api/v1 es la única versión del canal por ahora, y la mantenemos estable: los cambios dentro de v1 son siempre aditivos — campos nuevos opcionales, operaciones nuevas — nunca un campo que desaparece o cambia de significado. Si alguna vez hiciera falta un cambio incompatible, se abriría como /api/v2 en paralelo, avisando con antelación a quien tenga clave activa, en vez de desactivar v1 de un día para otro.

Historial de revisiones

  • 4 de octubre de 2026. Se incorporan los webhooks salientes: configuración de destinos desde la aplicación, eventos de ciclo de vida del DeCA, firma HMAC-SHA256 y reintentos. Esta incorporación no añade ni modifica rutas de /api/v1.
  • 1.1.0 · 2 de octubre de 2026. Asignación de DeCA a personas del equipo: operaciones POST /deca/{id}/asignar y GET /personas, permiso nuevo asignar (incluido en el rol oficina), campos creado_por_persona_id y asignado_a_persona_id en las respuestas de DeCA y filtro asignado_a en el listado. Cambio aditivo: no rompe ninguna integración existente.

Para que tu integración no dependa de detalles que sí pueden cambiar: programa siempre contra el code del error (sección anterior), nunca contra el texto de mensaje — ese texto puede mejorar con el tiempo sin que sea, en ningún caso, un cambio que rompa nada.

Condiciones

El uso de este canal se rige, además de por las condiciones generales del servicio, por el anexo de términos de la API: custodia de credenciales, destinos webhook, tarifa, alcance del soporte y ausencia de SLA. Se acepta al solicitar una clave o al crear un destino webhook, no antes.

Preguntas frecuentes

¿Esto sustituye a la aplicación web?
No. Es un canal alternativo para la misma cuenta: los DeCA que emitas por la API aparecen en tu cuenta igual que los que emitirías desde la PWA, con su propia marca de origen para que puedas distinguirlos si lo necesitas.
¿Puedo tener varias claves para la misma empresa?
Sí. Nada impide una clave por integración (oficina, almacén, un ERP y una herramienta de reporting, por ejemplo), todas ligadas a la misma empresa y revocables por separado.
Mi ERP es un servicio de un tercero (SaaS). ¿Quién responde de los datos ahí?
Ese proveedor es encargado del tratamiento de tu empresa, no de DeCAFacil — la relación con él, y su cumplimiento, es responsabilidad tuya. DeCAFacil solo recibe y procesa lo que tu integración le envía.
¿Cuánto cuesta?
La plan Pro (de la que forman parte las claves de API) tiene el precio y las condiciones actualizadas en la comparativa completa. Las fechas de cobro figuran allí; te avisaremos con preaviso razonable y nunca con carácter retroactivo — el detalle está en el anexo de términos de la API.
¿Cómo sé si un DeCA cambió después de emitirlo (por ejemplo, si alguien lo corrigió o lo finalizó por teléfono)?
Con suscripción activa, configura un destino en Webhooks, dentro de la aplicación. Recibirás un aviso cuando se cree, finalice —manual o automáticamente—, modifique, reemplace, anule, expire o reasigne un documento propio, ya venga de la API o de la PWA. Verifica siempre la firma HMAC-SHA256 sobre el cuerpo original, deduplica por el identificador del evento y responde con un 2xx. Las entregas pueden repetirse y no garantizan orden.
¿Puedo asignar un DeCA a un conductor o empleado desde la API?
Sí, si tu cuenta tiene la suscripción activa y su equipo incluye a alguien más que el titular. Consulta las personas con GET /personas y asigna con POST /deca/{id}/asignar (permiso asignar, incluido en el rol oficina). La relación es de una persona por documento, sin histórico.

¿Tienes ya cuenta en DeCAFacil?

Activa la suscripción y crea tu clave tú mismo desde «Mis claves de API», sin esperar a que te contestemos.

Activar suscripción →

Seguir leyendo

Esta página describe el estado actual del canal de API, en evolución. Si algo no coincide con lo que ves al integrarlo, escríbenos y lo corregimos.