Infonet Consultores API

API de DeCA

Dos maneras de dejar un documento electrónico de control administrativo en el repositorio de deca.infonetconsultores.com. Las dos acaban igual: un PDF con su URL de descarga directa y su QR, tal como pide la Resolución de 5 de junio de 2026. Elige según lo que ya tengas hecho.

Base URLhttps://deca.infonetconsultores.com
ProtocoloHTTPS (TLS 1.2 o superior) · JSON UTF-8
AuthToken Bearer en la cabecera Authorization, uno por cliente
Modo APOST /subir · tú generas el PDF con el QR y nos lo subes
Modo CPOST /deca · nos mandas los datos y te devolvemos PDF, QR y URL
Gestión/administrador · /maestros · /conductores · contraseña del administrador, maestros y conductores de la empresa, con su mismo token
ClientesGET /alta · POST /alta · consulta o da de alta un cliente y obtiene su token (aplicaciones de Infonet)
MunicipiosGET /municipios · lista pública de localidades del INE para origen_localidad y destino_localidad, sin token

Cuál de las dos usar

Si tu programa ya imprime un albarán o un documento de control y puedes pintarle un código QR, usa el modo A: sigues con tu plantilla y solo añades el QR y una llamada para subir el fichero. Es lo que hacen Arigest, Agrocom, GAP, Gesthorm, Facturacom y Recodat.

Si prefieres no tocar tu generador de PDF, o no tienes uno, usa el modo C: nos mandas los datos del documento y te devolvemos el PDF terminado, el QR como imagen y la URL. Es una sola llamada y no hay que preocuparse del formato del documento, del tamaño, de los metadatos ni del QR.

Modo A · subes tu PDFModo C · nos mandas los datos
Quién hace el PDFTu programaNosotros
Quién pone el QRTu programa. La URL es predecible: /documentos/<slug>/<nombre>, así que puedes generar el QR antes de subirNosotros, dentro del PDF y como PNG aparte
Qué envíasNombre del cliente, nombre del fichero y el PDF en base64Los datos del artículo 6 en JSON
Qué recibesConfirmación con carpeta, fichero y bytesURL, PDF en base64, QR en base64, identificador
Nombre del ficheroLo pones tú. Tiene que ser largo y aleatorio para que la URL no se pueda adivinarLo ponemos nosotros: DECA_<32 hex>.pdf
Queda registrado en la plataforma webNoSí, aparece en el listado de /app/ del cliente
Modificar un documentoSubes un fichero nuevo, con otro nombre, y entregas el nuevo PDFVuelves a llamar con el mismo numero_documento: se sustituye el anterior
Responsabilidad de que el PDF cumplaTuya: nativo (no escaneado), menos de 5 MB, QR con la URL exactaNuestra

Alta y credenciales

Cada empresa se identifica por su cliente aplicación: un nombre corto que decide la carpeta donde quedan sus documentos. Es el mismo texto que tu aplicación manda en el campo cliente de /subir, y se normaliza a minúsculas y guiones: HORMIGONES EJEMPLO pasa a ser hormigones-ejemplo. Una empresa solo puede tener un cliente aplicación y dos empresas no pueden compartirlo.

El alta se hace de dos maneras, y las dos llevan al mismo sitio:

El token del modo C es siempre el mismo: /alta te lo devuelve igual cada vez que lo pidas. Solo cambia si lo renovamos desde el panel, por ejemplo si se ha filtrado. El token del modo A (el de tu aplicación para /subir) es otro distinto.

En el modo A necesitas el cliente aplicación para construir la URL del QR. En el modo C no hace falta, la URL viene en la respuesta.

Repositorio, URL y QR

Todo documento acaba en https://deca.infonetconsultores.com/documentos/<slug>/<fichero>.pdf. Esa dirección es la que va dentro del QR y la que quien inspecciona descarga en un control. Cumple lo que exige la Resolución de 5 de junio de 2026:

En el modo C el QR contiene exactamente esa URL y nada más. En el modo A el QR lo pintas tú: pon la URL completa, sin parámetros ni texto añadido.

Lo que no hace falta: registrar el dominio, subir nada a ninguna plataforma del Ministerio, certificar la aplicación ni firmar el documento. La Resolución de 2026 quitó el registro previo que pedía la de 2023, y la FAQ del Ministerio lo dice con todas las letras.

Autenticación

Las dos llamadas se autentican igual: cabecera Authorization con el token del cliente. Los endpoints de gestión usan el mismo token que /deca. /alta usa en cambio el token maestro de las aplicaciones de Infonet, que no pertenece a ningún cliente; guárdalo solo en el servidor de la aplicación. Los tokens no caducan; si sospechas que uno se ha filtrado, llámanos y lo cambiamos en el momento. Nunca lo pongas en una URL ni en un formulario web visible.

Authorization: Bearer <TU_TOKEN>
SituaciónRespuesta
Cabecera ausente o mal formada401 + WWW-Authenticate: Bearer
Token presente pero incorrecto403 en /subir, 401 en /deca

Modo A: POST /subir

POST /subir Sube un PDF a la carpeta del cliente

Crea la carpeta del cliente si no existe y guarda el PDF con el nombre indicado. Nunca queda un PDF a medias: si algo falla, no se guarda nada.

Cabeceras

CabeceraTipoValor
AuthorizationstringobligatoriaBearer <token>
Content-Typestringrecomendadaapplication/json — si se omite o es distinta, el cuerpo se lee como formulario ($_POST)

Cuerpo de la petición

Se acepta application/json (recomendado) o application/x-www-form-urlencoded / multipart/form-data con los mismos nombres de campo.

CampoTipoDescripción
clientestringobligatorio El cliente aplicación: el mismo que usas en /alta y el que tiene la empresa en nuestro panel. Mándalo siempre igual, porque de él depende la carpeta y, por tanto, que los documentos de la aplicación y los de la web queden juntos. Se convierte en el nombre de la carpeta: se quitan acentos, se pasa a minúsculas y se sustituye todo lo no alfanumérico por guiones. Máximo 100 caracteres tras el saneado.
"Transportes Pérez, S.L." → carpeta transportes-perez-s-l
nombre_ficherostringobligatorio Nombre con el que se guardará el PDF. Se filtran los caracteres a [A-Za-z0-9._-] y se fuerza la extensión .pdf. Máximo 150 caracteres.
pdf_base64stringobligatorio Contenido del PDF codificado en Base64. Se admiten saltos de línea y espacios (se eliminan) y el formato Data URI data:application/pdf;base64,…. Máximo 20 MB ya decodificado.

Esquema

{
  "cliente":        "Transportes Perez SL",
  "nombre_fichero": "ALB-2026-0001.pdf",
  "pdf_base64":     "JVBERi0xLjQKMSAwIG9iajw8L1R5cGUv..."
}

Ejemplos de petición

# El PDF se codifica al vuelo y se inserta en el JSON
curl -X POST https://deca.infonetconsultores.com/subir \
  -H "Authorization: Bearer TU_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"cliente\":\"Transportes Perez SL\",
       \"nombre_fichero\":\"ALB-2026-0001.pdf\",
       \"pdf_base64\":\"$(base64 -w0 albaran.pdf)\"}"

Respuesta correcta

201
Created — fichero guardado
El PDF queda disponible en https://deca.infonetconsultores.com/documentos/<cliente>/<fichero>.
{
  "ok":      true,
  "cliente": "transportes-perez-sl",
  "fichero": "ALB-2026-0001.pdf",
  "ruta":    "documentos/transportes-perez-sl/ALB-2026-0001.pdf",
  "bytes":   152340
}
CampoTipoDescripción
okbooleanSiempre true en la respuesta 201.
clientestringSlug realmente usado como carpeta, ya saneado.
ficherostringNombre final tras el saneado y el forzado de extensión.
rutastringRuta del PDF dentro del repositorio. La URL de descarga es la base más esta ruta.
bytesintegerTamaño del PDF decodificado, en bytes.

Errores

Todos los errores devuelven {"ok": false, "error": "<mensaje>"} con el código HTTP correspondiente.

{
  "ok":    false,
  "error": "El contenido decodificado no es un PDF."
}

Catálogo completo

400
Bad Request — datos de entrada inválidos
Agrupa varios casos distintos; el campo error los distingue.
"El cuerpo no es un JSON válido."
"Falta el parámetro \"cliente\"." · "Falta el parámetro \"nombre_fichero\"." · "Falta el parámetro \"pdf_base64\"."
"Data URI mal formado en \"pdf_base64\"."
"El campo \"pdf_base64\" no contiene base64 válido."
"El nombre de cliente no contiene caracteres válidos." · "El nombre de fichero no contiene caracteres válidos."
"Ruta de destino no permitida."
403
Forbidden — token incorrecto
La cabecera existe pero el token no coincide.
"Token no válido."
413
Payload Too Large
El PDF decodificado supera los 20 MB. El JSON viaja un 33 % más grande por el Base64; el límite real del cuerpo de la petición es de 32 MB.
"El fichero supera el máximo de 20971520 bytes."
415
Unsupported Media Type — no es un PDF
El Base64 se decodifica bien pero el contenido no empieza por la firma %PDF-. Evita que se suban ejecutables o scripts renombrados.
"El contenido decodificado no es un PDF."
500
Internal Server Error — fallo de disco o de configuración
Error interno: no se ha guardado nada. Reintenta y, si se repite, llámanos.
"Servicio no configurado." (falta el token en el servidor; es cosa nuestra)
"No se pudo crear el directorio base de subidas." · "No se pudo crear la carpeta del cliente."
"No se pudo escribir el fichero en disco." · "No se pudo mover el fichero a su destino final."

Saneado de nombres

Ningún dato del cliente llega en crudo al sistema de ficheros. Estas son las transformaciones exactas:

Carpeta de cliente

EntradaCarpeta resultante
Transportes Pérez, S.L.transportes-perez-s-l
LOGÍSTICA DEL SURlogistica-del-sur
!!!400 — no quedan caracteres válidos

Nombre de fichero

EntradaFichero resultante
ALB-2026-0001.pdfALB-2026-0001.pdf sin cambios
albarán nº5.pdfalbar_n_5.pdf
facturafactura.pdf extensión forzada

Límites y validación

LímiteValor
Tamaño máximo del PDF20 MB una vez decodificado
Cuerpo máximo de la petición32 MB (el Base64 ocupa un 33 % más que el PDF)
SobrescrituraNo. Un nombre repetido devuelve 409
Longitud del nombre de cliente100 caracteres tras el saneado
Longitud del nombre de fichero150 caracteres, extensión .pdf incluida

Orden de validación

El endpoint corta en el primer fallo, en este orden:

1.  Método POST ................... 405
2.  Cabecera Authorization ........ 401
3.  Token válido .................. 403
4.  JSON parseable ................ 400
5.  Los tres campos presentes ..... 400
6.  Base64 decodificable .......... 400
7.  Tamaño <= 20 MB ............... 413
8.  Empieza por %PDF- ............. 415
9.  Nombres saneables ............. 400
10. No existe ya el fichero ....... 409
11. Guardado ...................... 500 si falla
     201 Created

Conversor PDF → Base64

El fichero no sale de tu navegador: la conversión es local con FileReader. Útil para preparar el cuerpo JSON de Postman.

Arrastra un PDF aquí o haz clic para elegirlo

Probar el endpoint del modo A

Lanza una petición real contra /subir con tu token. El token no se guarda en ningún sitio: vive solo en este campo mientras la pestaña esté abierta.

Modo C: POST /deca

POST /deca Genera el DeCA a partir de los datos y lo guarda

Mandas los datos del documento en JSON. Comprobamos que estén todos los del artículo 6 de la Orden FOM/2861/2012, generamos el PDF con el QR, lo guardamos en la carpeta del cliente, lo apuntamos en la base de datos y te devolvemos la URL, el PDF y el QR en la misma respuesta. Todo o nada: si algo falla no queda ningún fichero a medias.

Cabeceras

CabeceraTipoValor
AuthorizationstringobligatoriaBearer <token del cliente>
Content-Typestringobligatoriaapplication/json. Sin ella, el servidor responde 403 antes de procesar la petición.

Cuerpo

{
  "cargador":      { "nombre": "Hormigones Ejemplo SL", "nif": "B12345678", "domicilio": "Pol. Ind. Norte 4, 21007 Huelva" },
  "transportista": { "nombre": "Transportes Pérez SL", "nif": "B87654321" },
  "fecha_transporte":      "2026-10-06",
  "matricula_tractor":     "1234ABC",
  "matricula_remolque":    "R1234BCD",
  "autorizacion_especial": null,
  "numero_documento":      "ALB-2026-0001",
  "nif_vehiculo":          "12345678Z",
  "envios": [
    {
      "origen":            "Planta Norte",
      "origen_localidad":  "Huelva (Huelva)",
      "destino":           "Obra Avda. Andalucía 12",
      "destino_localidad": "Aljaraque (Huelva)",
      "mercancia":         "Hormigón HA-25",
      "peso":              "24.000 kg"
    }
  ],
  "observaciones": ""
}
CampoTipoQué es y cómo lo tratamos
cargador.nombrestringobligatorioNombre o razón social del cargador contractual: quien contrata directamente al transportista efectivo. Puede ser la planta, una agencia, una cooperativa o incluso otro transportista que subcontrata.
cargador.nifstringobligatorioEntre 8 y 9 caracteres alfanuméricos. Se pasa a mayúsculas y se quitan espacios, guiones y puntos. No comprobamos la letra de control.
cargador.domiciliostringobligatorioDomicilio del cargador contractual. La Orden lo pide solo para el cargador, no para el transportista.
transportista.nombrestringobligatorio*Nombre o razón social del transportista efectivo: el titular de la autorización con la que se hace el transporte. Si es un autónomo subcontratado, él.
* No se manda en un borrador (transportista_pendiente).
transportista.nifstringobligatorio*Igual que el del cargador.
fecha_transportestringobligatorioFormato AAAA-MM-DD. En el PDF se imprime como DD/MM/AAAA.
matricula_tractorstringobligatorio*Matrícula del vehículo. Se pasa a mayúsculas y se quitan espacios y guiones: 1234 abc se guarda como 1234ABC.
* No se manda en un borrador, ni tampoco matricula_remolque ni autorizacion_especial.
matricula_remolquestringopcionalSolo en conjuntos articulados. Si va, el PDF rotula las dos casillas como "Matrícula tractor" y "Matrícula remolque"; si no, una sola casilla "Matrícula".
autorizacion_especialstring | nullopcionalIdentificación de la autorización especial de circulación, solo cuando el vehículo la necesite. Si no va, la casilla no aparece en el PDF.
numero_documentostringopcionalTu número de albarán o referencia interna, hasta 40 caracteres. Se imprime en el PDF y se muestra en la plataforma web. No forma parte del artículo 6; es para que localices el documento. Si ya hay un DeCA activo con el mismo número en el mismo año, la llamada lo sustituye: ver Modificar un DeCA.
motivostringopcionalSolo cuenta cuando la llamada sustituye a un documento anterior. Por qué se cambia, hasta 200 caracteres ("cambio de remolque"). Si no lo mandas se escribe "Cambiado desde API".
nif_vehiculostringopcionalNIF/Vehículo del conductor: su DNI o NIE ("12345678Z") o la matrícula de su vehículo ("1234ABC"), según cómo lo tenga puesto su acceso: un solo campo, se comprueba como DNI/NIE o como matrícula. El documento queda a su nombre, como si lo hubiera hecho él en la plataforma web: le aparece en su lista (que solo enseña los de hoy y de ayer) y puede modificarlo. Debe ser el NIF/Vehículo de un acceso activo de la empresa (se pone en /conductores o en «Usuarios») y, si es conductor, el transportista.nif del documento tiene que ser el suyo. Si no, 422 y no se crea nada. Sin este campo, todo funciona como siempre.
transportista_pendientebooleanopcionalCon true se crea un borrador: sin transportista ni vehículo y sin PDF, pero ya con su URL y su QR. Quien lea el QR por primera vez escribe el NIF del transportista y la matrícula, y entonces se genera el PDF en esa misma URL. Sin este campo (o con false), todo como siempre.
enviosarrayal menos unoLista de envíos (cargas o descargas), con los campos de las filas siguientes. Varios envíos en un mismo DeCA solo si comparten cargador y transportista; el orden no implica secuencia.
envios[].origenstringobligatorio*Lugar de carga en texto libre: "Planta Norte", "Cantera El Pino, km 4".
* Pasa a ser opcional si mandas origen_localidad.
envios[].origen_localidadstringopcionalMunicipio de carga, exactamente como aparece en GET /municipios: "Huelva (Huelva)". Se comprueba contra la lista oficial del INE y se imprime a continuación del lugar: "Planta Norte - Huelva (Huelva)". Si no está en la lista, error 422. Detalle en Localidades.
envios[].destinostringobligatorio*Lugar de descarga en texto libre: "Obra Avda. Andalucía 12".
* Pasa a ser opcional si mandas destino_localidad.
envios[].destino_localidadstringopcionalMunicipio de descarga, igual que origen_localidad: "Aljaraque (Huelva)".
envios[].mercanciastringobligatorioNaturaleza de la mercancía: "Hormigón HA-25", "Zahorra artificial".
envios[].pesostringobligatorioTexto libre porque a veces no se puede pesar y la Orden admite otra magnitud ("24.000 kg", "8 m³"). Si empieza por un número, se escribe en formato español con separador de miles: "24000 kg" se guarda e imprime como "24.000 kg" y "1234.5 t" como "1.234,5 t".
observacionesstringopcionalReservas o indicaciones de cualquiera de las partes. Si la llamada sustituye a otro documento, la nota «Sustituye al documento… Motivo: …» la añadimos nosotros: no la escribas aquí (usa motivo).

Localidades de origen y destino · GET /municipios

El origen y el destino se pueden mandar de dos formas. Las dos son válidas; la segunda es la recomendada porque garantiza que la localidad está bien escrita y es la misma que usa la plataforma web.

  1. Solo texto libre en origen y destino, como hasta ahora. Se imprime tal cual, sin comprobar nada: "Planta Norte, Huelva".
  2. Con localidad: además (o en lugar) del texto, mandas origen_localidad y/o destino_localidad con un municipio de la lista oficial del INE. Lo comprobamos y en el documento sale «lugar - Localidad (Provincia)».

Se puede usar en un campo y no en el otro, y cada envío va por su cuenta. Así queda en el PDF:

MandasSale en el documento
"origen": "Planta Norte"Planta Norte
"origen": "Planta Norte", "origen_localidad": "Huelva (Huelva)"Planta Norte - Huelva (Huelva)
"origen_localidad": "Huelva (Huelva)" (sin origen o vacío)Huelva (Huelva)
"origen_localidad": "huelva (HUELVA)"Huelva (Huelva) · se acepta sin respetar mayúsculas ni tildes y se escribe en su forma oficial
"origen": "Planta Norte - Huelva (Huelva)", "origen_localidad": "Huelva (Huelva)"Planta Norte - Huelva (Huelva) · si el texto ya termina en esa localidad, no se repite
"origen_localidad": "Huelva"Error 422: falta la provincia entre paréntesis. Tiene que ser una entrada de la lista
Ni origen ni origen_localidadError 422: envios[0].origen: obligatorio

Plataforma web: allí la localidad es siempre obligatoria. Un DeCA que mandaste solo con texto es válido tal cual, pero si alguien lo modifica desde la web tendrá que elegir las localidades de la lista (la web propone la que reconoce en el texto). Si lo modificas por API, sigue siendo opcional.

GET /municipios

La lista de localidades válidas: los 8.132 municipios de España según el INE, cada uno como "Localidad (Provincia)". Es pública: no lleva token. Úsala para rellenar un desplegable o un buscador en tu programa y manda en origen_localidad / destino_localidad el texto exacto que devuelve.

PeticiónDevuelve
GET /municipiosLa lista completa (unos 210 KB). Cambia como mucho una vez al año: guárdala en tu programa y no la pidas en cada envío.
GET /municipios?q=alcalaLos que contienen ese texto, sin importar mayúsculas ni tildes; primero los que empiezan por él. Hasta 50.
GET /municipios?q=alcala&limite=200Igual, hasta el número indicado (máximo 500).
curl "https://deca.infonetconsultores.com/municipios?q=aljaraque"

{
  "ok":         true,
  "total":      1,
  "municipios": [ "Aljaraque (Huelva)" ]
}

Si no hay coincidencias, "total": 0 y "municipios": [] (sigue siendo 200). Cualquier método que no sea GET devuelve 405. La respuesta se puede cachear una semana (Cache-Control y ETag).

¿Y la plataforma web? En la web la localidad es obligatoria y se elige de esta misma lista. Por API es opcional para no romper integraciones que ya mandan texto libre; si empiezas una integración nueva, manda siempre la localidad.

Ejemplo

curl -X POST https://deca.infonetconsultores.com/deca \
  -H "Authorization: Bearer TU_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @deca.json
# PowerShell
$cuerpo = Get-Content deca.json -Raw
Invoke-RestMethod -Method Post -Uri "https://deca.infonetconsultores.com/deca" `
  -Headers @{ Authorization = "Bearer TU_TOKEN" } `
  -ContentType "application/json; charset=utf-8" -Body $cuerpo
// PHP
$ch = curl_init('https://deca.infonetconsultores.com/deca');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer TU_TOKEN', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($datos, JSON_UNESCAPED_UNICODE),
]);
$r = json_decode(curl_exec($ch), true);
file_put_contents('deca.pdf', base64_decode($r['pdf_base64']));

Respuesta 201 (o 200)

{
  "ok":            true,
  "accion":        "creado",
  "id":            123,
  "estado":        "activo",
  "sustituye_a":   null,
  "fichero":       "DECA_3f9a1c0d8b2e4f6a9c1d3e5f7a9b0c2e.pdf",
  "url":           "https://deca.infonetconsultores.com/documentos/hormigones-ejemplo-sl/DECA_3f9a1c0d8b2e4f6a9c1d3e5f7a9b0c2e.pdf",
  "creado":        "2026-10-06T07:30:00+02:00",
  "conductor":     null,
  "pdf_base64":    "JVBERi0xLjcK…",
  "qr_png_base64": "iVBORw0KGgo…"
}
CampoTipoDescripción
okbooleanSiempre true en un 201.
accionstring"creado": documento nuevo. "sustituido": documento nuevo que sustituye al activo con el mismo número (aunque los datos sean idénticos).
idintegerIdentificador interno del documento. Sirve para localizarlo en la plataforma web y para consultarlo con GET /deca/{id}.
estadostring"activo": DeCA con su PDF. "pendiente": borrador, aún sin transportista ni PDF.
sustituye_ainteger | nullCon "sustituido", el id del documento anterior, que queda marcado como sustituido. Si no, null.
ficherostringDECA_ seguido de 32 caracteres hexadecimales aleatorios.
urlstringLa dirección de descarga directa. Es lo que contiene el QR. Es lo que hay que hacer llegar a quien conduce si no le mandas el PDF.
creadostringFecha y hora de creación, ISO 8601 con zona horaria. Coincide con los metadatos del PDF.
conductorstring | nullCon nif_vehiculo, el acceso a cuyo nombre queda el documento ("hormigonesejemplo.luis"). Si no, null.
pdf_base64string | nullEl PDF completo, unos 12 KB. Descodifícalo y guárdalo, imprímelo o envíalo. En un borrador, null: el PDF sale cuando se completa (recógelo en la url o con GET /deca/{id}).
qr_png_base64stringEl mismo QR que va en el PDF, como PNG, por si quieres pegarlo en tu propio albarán o mandarlo suelto.

Errores

Todos los errores devuelven JSON con "ok": false y un "error" legible. El 422 añade además la lista "errores" con un texto por campo.

HTTPCuándoCuerpo
405Cualquier método que no sea POST ni GET{"ok":false,"error":"Método no permitido. Use POST (alta) o GET /deca/{id} (consulta)."} y cabecera Allow: GET, POST
401Sin cabecera Authorization{"ok":false,"error":"Falta la cabecera Authorization: Bearer <token>."}
401Token que no corresponde a ningún cliente activo{"ok":false,"error":"Token no válido."}
403Token correcto pero la cuota del cliente ha caducado{"ok":false,"error":"La cuota del servicio DeCA ha caducado. Contacta con Infonet Consultores (959 54 07 12)."}
403POST sin Content-Type ni cuerpoPágina HTML del servidor, no JSON. Manda siempre Content-Type: application/json.
400El cuerpo no es un objeto JSON válido (JSON roto, texto suelto, codificación que no es UTF-8){"ok":false,"error":"El cuerpo debe ser un objeto JSON válido."}
422Faltan datos o tienen formato incorrecto{"ok":false,"error":"Datos incompletos o inválidos.","errores":[…]}
409Otra llamada estaba sustituyendo el mismo número de documento a la vez y no se pudo resolver tras varios intentos{"ok":false,"error":"Otra petición está modificando este mismo número de documento. Vuelve a intentarlo."}
500Fallo al escribir el PDF o en la base de datos{"ok":false,"error":"No se pudo generar el documento."}. Si se repite, llámanos.

Mensajes posibles en errores (422)

Cada texto empieza por el campo al que se refiere, con la misma ruta que en el JSON. Puedes mostrarlos tal cual al usuario o mapearlos a tus propios campos.

MensajeMotivo
cargador.nombre: obligatorioEl campo falta o está vacío tras recortar espacios.
cargador.nif: obligatorio
cargador.domicilio: obligatorio
transportista.nombre: obligatorio
transportista.nif: obligatorio
fecha_transporte: obligatorio
matricula_tractor: obligatorio
cargador.nif: formato inválido (8-9 caracteres alfanuméricos)Tras normalizar, no quedan entre 8 y 9 letras o dígitos.
transportista.nif: formato inválido (8-9 caracteres alfanuméricos)
fecha_transporte: formato AAAA-MM-DDNo cumple el formato o no es una fecha real (30 de febrero).
matricula_tractor: caracteres no válidosTras normalizar quedan menos de 2 o más de 12 caracteres, o hay algo que no es letra o dígito.
matricula_remolque: caracteres no válidos
envios: se requiere al menos un envíoenvios falta, no es un array o está vacío.
envios[0]: debe ser un objetoUn elemento de envios no es un objeto. El índice empieza en 0.
envios[0].origen: obligatorio
envios[0].destino: obligatorio
envios[0].mercancia: obligatorio
envios[0].peso: obligatorio
Falta ese dato en el envío indicado. origen y destino no lo dan si viene su _localidad.
envios[0].origen_localidad: no es una localidad de la lista (GET /municipios)
envios[0].destino_localidad: no es una localidad de la lista (GET /municipios)
El texto no coincide con ninguna entrada de GET /municipios, ni siquiera sin mayúsculas ni tildes. Suele ser que falta la provincia: "Huelva" en vez de "Huelva (Huelva)".
nif_vehiculo: NIF/Vehículo no válido (DNI o NIE con su letra correcta, o la matrícula del vehículo)No es un DNI (8 cifras y letra) ni un NIE (X, Y o Z, 7 cifras y letra) con la letra correcta, ni una matrícula (de 4 a 12 letras y números).
nif_vehiculo: no hay ningún acceso activo de la empresa con el NIF/Vehículo 12345678ZEse NIF/Vehículo no está puesto a ningún acceso de la empresa, o el acceso está desactivado. Ponlo con POST /conductores ("nif_vehiculo") o en «Usuarios».
nif_vehiculo: el conductor 12345678Z es de Transportes Pérez SL (B87654321), no del transportista B11111111El conductor solo puede hacer DeCA de su transportista, igual que en la plataforma web.
transportista_pendiente: true o falseEl campo viene con otro valor ("si", 1…).
transportista.nif: no se envía con transportista_pendiente (lo pone quien lee el QR)En un borrador sobran transportista, matricula_tractor, matricula_remolque y autorizacion_especial: hay un mensaje así por cada uno que venga relleno.
nif_vehiculo: el conductor 12345678Z ya tiene transportista; un borrador (transportista_pendiente) no puede ir a su nombreUn conductor siempre trabaja con su transportista: si ya sabes quién conduce, manda el DeCA completo.
{
  "ok":      false,
  "error":   "Datos incompletos o inválidos.",
  "errores": [
    "cargador.domicilio: obligatorio",
    "fecha_transporte: formato AAAA-MM-DD",
    "envios[1].peso: obligatorio"
  ]
}

Modificar un DeCA: mismo número de documento

No hay endpoint de edición ni de borrado. Para modificar, vuelve a llamar a POST /deca con el mismo numero_documento y el DeCA completo ya corregido (todos los campos, no solo lo que cambia). Es lo mismo que hace el botón «Modificar» de la plataforma web, y cumple la segunda opción de la Resolución para cambios una vez empezado el servicio:

// 1.ª llamada                                  → 201, "accion": "creado", "id": 123
{ "numero_documento": "ALB-2026-0001", "matricula_remolque": "R1234BCD", … }

// Cambia el remolque: mismo número, todo el DeCA → 201, "accion": "sustituido", "id": 124, "sustituye_a": 123
{ "numero_documento": "ALB-2026-0001", "matricula_remolque": "R5678FGH", "motivo": "cambio de remolque", … }
Cuándo se considera el mismo documento
Mismo cliente (token), mismo numero_documento y mismo año de fecha_transporte, y el anterior está activoSe sustituye
Número escrito exactamente igual. ALB-1 y alb-1 son números distintosDocumento nuevo
Sin numero_documento (o vacío)Siempre documento nuevo, como hasta ahora
Mismo número pero fecha_transporte de otro año (numeraciones que empiezan de cero cada año)Documento nuevo; el del otro año no se toca
Mismo número y exactamente los mismos datos que el activoSe sustituye igual: cada llamada genera una versión nueva (otra URL, otro PDF)
El activo con ese número se hizo en la plataforma webSe sustituye igual: es el mismo transporte

Codificación: manda el JSON en UTF-8. Si llega en otra codificación (por ejemplo Windows-1252 desde un programa antiguo), la reconvertimos y no falla, pero mejor evitarlo.

Reintentos: cada llamada genera un documento. Con número de documento, un reintento tras un timeout sustituye al anterior y solo queda uno activo (el último). Sin número, cada llamada crea un documento nuevo; guarda la respuesta antes de reintentar.

Borrador sin transportista: lo completa quien lee el QR

Para cuando aún no sabes qué transportista ni qué camión harán el viaje. Manda el DeCA sin transportista, matricula_tractor, matricula_remolque ni autorizacion_especial y con "transportista_pendiente": true:

{
  "cargador":         { "nombre": "Hormigones Ejemplo SL", "nif": "B12345678", "domicilio": "Pol. Ind. Norte 4, 21007 Huelva" },
  "fecha_transporte": "2026-10-06",
  "numero_documento": "ALB-2026-0002",
  "envios": [{ "origen_localidad": "Huelva (Huelva)", "destino_localidad": "Aljaraque (Huelva)", "mercancia": "Hormigón HA-25", "peso": "24.000 kg" }],
  "transportista_pendiente": true
}
// → 201, "estado": "pendiente", "pdf_base64": null, "url" y "qr_png_base64" ya válidos

GET /deca/{id}: estado y PDF de un DeCA

Con el mismo Authorization: Bearer. También vale GET /deca?id=123. Solo ves los documentos de tu empresa (si no, 404).

curl https://deca.infonetconsultores.com/deca/123 -H "Authorization: Bearer TU_TOKEN"

{
  "ok":                 true,
  "id":                 123,
  "estado":             "activo",
  "numero_documento":   "ALB-2026-0002",
  "fecha_transporte":   "2026-10-06",
  "sustituye_a":        null,
  "sustituido_por":     null,
  "fichero":            "DECA_3f9a….pdf",
  "url":                "https://deca.infonetconsultores.com/documentos/…/DECA_3f9a….pdf",
  "creado":             "2026-10-05T18:02:00+02:00",
  "cerrado":            "2026-10-06T06:41:00+02:00",
  "transportista":      { "nombre": "Transportes Pérez SL", "nif": "B87654321" },
  "matricula_tractor":  "1234ABC",
  "matricula_remolque": "R1234BCD",
  "pdf_base64":         "JVBERi0xLjcK…",
  "qr_png_base64":      "iVBORw0KGgo…"
}
estadoQué significapdf_base64
activoDeCA válido. Si venía de un borrador, cerrado dice cuándo se completó.El PDF
pendienteBorrador esperando a que alguien lea el QR y ponga transportista y matrícula. transportista es null.null
sustituidoHay otro más nuevo con el mismo número: su id en sustituido_por.El PDF, si llegó a tenerlo
caducadoBorrador cuyo día de transporte pasó sin completarse.null
anuladoBorrador anulado por la oficina.null

Gestión: POST /administrador

Los endpoints de gestión se llaman con el token de la empresa, el mismo de POST /deca (el que devuelve /alta), en la cabecera Authorization: Bearer <token>. Todo lo que hacen queda dentro de esa empresa: no se ve ni se toca nada de otra. Responden JSON con "ok": true o "ok": false y "error".

POST /administrador Contraseña nueva para <cliente>.administrador

Pone contraseña nueva al acceso web <cliente>.administrador que crea /alta. Sin cuerpo se genera una aleatoria de 16 caracteres; con {"password": "..."} se pone esa (de 8 a 72 caracteres, sin espacios al principio ni al final). Si el acceso estaba desactivado, se reactiva; si no existe, se crea como acceso de oficina (HTTP 201). La contraseña anterior deja de valer en el momento.

# Aleatoria
curl -X POST https://deca.infonetconsultores.com/administrador \
  -H "Authorization: Bearer TOKEN_EMPRESA"

# Elegida
curl -X POST https://deca.infonetconsultores.com/administrador \
  -H "Authorization: Bearer TOKEN_EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"password":"MiClaveSegura1"}'
{
  "ok":       true,
  "creado":   false,
  "usuario":  "hormigonesejemplo.administrador",
  "password": "Xk7mPq2RtW9vHn4c",
  "acceso":   "https://deca.infonetconsultores.com/app/"
}

200 si el acceso ya existía, 201 si se ha creado. 409 si la empresa no tiene matriz de usuarios (empresas antiguas dadas de alta a mano): no tiene <cliente>.administrador; pídenoslo.

Gestión: /maestros

GET/maestros?tipo=transportistasLista (activos)
GET/maestros?tipo=transportistas&id=5Uno por id
GET/maestros?tipo=transportistas&clave=B12345678Uno por NIF, matrícula o nombre
POST/maestros?tipo=transportistasCrea, o devuelve el que ya hay
PUT/maestros?tipo=transportistas&id=5Modifica
DELETE/maestros?tipo=transportistas&id=5Desactiva

Los maestros son las listas de la plataforma web con las que se rellena un DeCA: cargadores, transportistas (empresas de transporte), vehículos y mercancías. Por API se consultan y se mantienen igual que desde la web. POST /deca no los necesita: sigue recibiendo los datos en texto.

tipoClave (única en la empresa)Campos
cargadoresnifnombre, nif, domicilio — todos obligatorios
transportistasnifnombre (razón social), nif — obligatorios. Opcional vehiculos: lista de sus vehículos, ver abajo
vehiculosmatriculamatricula (obligatoria), transportista_id o transportista_nif (obligatorio, el transportista debe existir), matricula_remolque, autorizacion (hasta 60)
mercanciasnombrenombre — obligatorio
remolquesmatriculaSolo en empresas con remolques separados. matricula (obligatoria), transportista_id o transportista_nif (obligatorio), autorizacion (autorización especial, hasta 60)

El tipo va en la URL (o en el JSON en POST y PUT). NIF y matrículas se normalizan como en el modo C: "b-12.345 678" se guarda "B12345678", "1234 abc" se guarda "1234ABC".

Transportista con sus vehículos

En POST y PUT de tipo=transportistas (y en el transportista de /conductores) puedes mandar "vehiculos": una lista de objetos con matricula (obligatoria), matricula_remolque y autorizacion. Para cada uno:

Esto vale también cuando el transportista ya existe (HTTP 200): no se cambian sus datos, pero sí se añaden o actualizan los vehículos. La respuesta trae "vehiculos_creados" (cuántos eran nuevos) y el transportista con todos sus vehículos activos en maestro.vehiculos. Un transportista siempre sale con su lista vehiculos, también en GET.

# Alta de una empresa de transporte con sus vehículos
curl -X POST "https://deca.infonetconsultores.com/maestros?tipo=transportistas" \
  -H "Authorization: Bearer TOKEN_EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Transportes Pérez SL","nif":"B87654321",
       "vehiculos":[{"matricula":"1234ABC","matricula_remolque":"R1234BCD"},{"matricula":"5678DEF"}]}'

# Un vehículo suelto
curl -X POST "https://deca.infonetconsultores.com/maestros?tipo=vehiculos" \
  -H "Authorization: Bearer TOKEN_EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"transportista_nif":"B87654321","matricula":"1234ABC","matricula_remolque":"R1234BCD"}'

# ¿Existe este NIF?
curl "https://deca.infonetconsultores.com/maestros?tipo=transportistas&clave=B87654321" \
  -H "Authorization: Bearer TOKEN_EMPRESA"

Respuesta de un elemento (POST, PUT, DELETE y GET con id o clave):

{
  "ok":                true,
  "tipo":              "transportistas",
  "creado":            true,   // solo en POST
  "reactivado":        false,  // solo en POST
  "vehiculos_creados": 2,      // solo si mandas "vehiculos"
  "maestro": {
    "id":     5,
    "nombre": "Transportes Pérez SL",
    "nif":    "B87654321",
    "activo": true,
    "creado": "2026-10-05T11:40:00+02:00",
    "vehiculos": [   // solo en transportistas
      {"id": 8, "matricula": "1234ABC", "matricula_remolque": "R1234BCD", "autorizacion": ""},
      {"id": 9, "matricula": "5678DEF", "matricula_remolque": "", "autorizacion": ""}
    ]
  }
}

La lista (GET sin id) devuelve {"ok": true, "tipo": "...", "total": 3, "maestros": [ ... ]} con elementos de la misma forma.

Remolques separados

Por defecto cada vehículo lleva su remolque y su autorización: todo lo de arriba. Para empresas cuyos tractores y remolques se van combinando, Infonet puede activar los remolques separados. Sin esa opción no cambia nada; con ella:

En una empresa sin la opción, tipo=remolques o "remolques" en un transportista devuelven 422 explicando que hay que pedirla a Infonet.

# Empresa con remolques separados: tractores y remolques por separado
curl -X POST "https://deca.infonetconsultores.com/maestros?tipo=transportistas" \
  -H "Authorization: Bearer TOKEN_EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Transportes Pérez SL","nif":"B87654321",
       "vehiculos":[{"matricula":"1234ABC"},{"matricula":"5678DEF"}],
       "remolques":[{"matricula":"R1234BCD","autorizacion":"AE-2026-15"},{"matricula":"R5678FGH"}]}'

Gestión: GET · POST /conductores

POST/conductoresCrea o actualiza un conductor y su transportista
GET/conductoresLista (o ?usuario=luis o ?nif_vehiculo=12345678Z para uno)

Un conductor es un acceso a la plataforma web ligado a una empresa de transporte: solo puede usar ese transportista y sus vehículos y solo ve los documentos que crea él. Con una llamada se crea el acceso y, si hace falta, la empresa de transporte.

CampoTipoQué es
nombrestringobligatorioNombre descriptivo de la persona: "Luis Pérez".
usuariostringobligatorioLo que va tras el punto: con "luis" el acceso es <cliente>.luis. Letras, números, punto, guion o arroba.
passwordstringobligatoriaLa contraseña que elijas, de 8 a 72 caracteres, sin espacios al principio ni al final. No se devuelve.
nif_vehiculostringopcionalNIF/Vehículo: el DNI o NIE del conductor, con su letra correcta ("12345678Z"), o la matrícula de su vehículo ("1234ABC") si los DeCA van al camión. Un solo campo: se comprueba como DNI/NIE o como matrícula. Único dentro de la empresa. Permite crear DeCA a su nombre con nif_vehiculo. Si no mandas el campo no se cambia; con "" se quita.
transportista.nifstringobligatorioNIF de la empresa de transporte. Si ya está en los maestros de la empresa, el conductor se enlaza a ese transportista (y se reactiva si estaba desactivado).
transportista.nombrestringsi no existeRazón social. Solo se usa si el NIF no existe y hay que crear el transportista; si existe, no se cambia.
transportista.vehiculosarrayopcionalVehículos del transportista: [{"matricula": "1234ABC", "matricula_remolque": "R1234BCD", "autorizacion": ""}]. Las mismas reglas que en /maestros: los nuevos se crean, los existentes pasan a este transportista y se actualizan.
transportista.remolquesarrayopcionalSolo con remolques separados: [{"matricula": "R1234BCD", "autorizacion": ""}], con las mismas reglas.
curl -X POST https://deca.infonetconsultores.com/conductores \
  -H "Authorization: Bearer TOKEN_EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{
    "nombre": "Luis Pérez",
    "usuario": "luis",
    "password": "LuisClave2026",
    "nif_vehiculo": "12345678Z",
    "transportista": {
      "nombre": "Transportes Pérez SL",
      "nif": "B87654321",
      "vehiculos": [{"matricula": "1234ABC", "matricula_remolque": "R1234BCD"}]
    }
  }'
{
  "ok":                   true,
  "creado":               true,
  "transportista_creado": false,
  "vehiculos_creados":    1,
  "id":                   31,
  "usuario":              "hormigonesejemplo.luis",
  "nombre":               "Luis Pérez",
  "nif_vehiculo":         "12345678Z",
  "activo":               true,
  "ultimo_acceso":        null,
  "transportista": {
    "id": 5, "nombre": "Transportes Pérez SL", "nif": "B87654321", "activo": true,
    "vehiculos": [{"id": 8, "matricula": "1234ABC", "matricula_remolque": "R1234BCD", "autorizacion": ""}]
  },
  "acceso":               "https://deca.infonetconsultores.com/app/"
}

Errores de gestión

HTTPCuándo
401Falta el token o no es de ninguna empresa activa.
403La cuota de la empresa ha caducado.
400El cuerpo no es JSON válido.
404El maestro o el conductor no existe en la empresa.
409/administrador: empresa sin matriz. /conductores: el usuario es de oficina o ya lo usa otra empresa.
422Datos que faltan o no valen. El cuerpo lleva "errores" con un texto por problema: ["password: La contraseña debe tener entre 8 y 72 caracteres.", "transportista.nif: obligatorio, 8 o 9 caracteres alfanuméricos"]. En maestros, también tipo desconocido.
405Método no admitido por ese endpoint (la cabecera Allow dice cuáles).

Clientes: GET · POST /alta

GET /alta?cliente=<cliente aplicación> ¿Existe? Y si existe, su token
POST /alta Lo da de alta si no existe; si existe, devuelve el que hay

Para las aplicaciones de Infonet. Con el cliente aplicación averiguas si la empresa ya está dada de alta y obtienes su token del modo C, o la das de alta en el momento. Nunca se crean duplicados: un POST con un cliente aplicación que ya existe no crea otro, devuelve el existente. Por eso puedes llamar a POST /alta siempre que tu aplicación necesite el token, sin comprobar antes.

El token devuelto es siempre el mismo para cada empresa. Guárdalo y úsalo en POST /deca; si lo pierdes, vuelve a pedirlo. Solo cambia si lo renovamos desde nuestro panel.

Empresas dadas de alta antes del 2 de octubre de 2026. De ellas solo guardábamos el resumen del token, no el token, así que no lo podemos devolver. La primera vez que pidas por /alta una de esas empresas se genera un token nuevo, te lo devolvemos, y el que tuvieras guardado deja de valer en ese momento. Sustitúyelo por el devuelto en cuanto lo recibas. A partir de ahí es siempre el mismo.

Una empresa nueva se crea con un año de cuota, con el cliente aplicación como nombre provisional y con un acceso a la plataforma web: el usuario <cliente>.administrador (por ejemplo hormigonesejemplo.administrador) con una contraseña aleatoria. Usuario y contraseña vienen en la respuesta 201 y solo en esa: guárdalos o pásaselos a la empresa en ese momento. Su nombre, contacto y más accesos se completan en el panel.

Cabeceras

CabeceraTipoValor
AuthorizationstringobligatoriaBearer <token maestro>. No es el token de ningún cliente.
Content-Typestringen POSTapplication/json

Parámetros

CampoTipoQué es
clientestringobligatorioEl cliente aplicación, el mismo que mandas en /subir. En GET va en la URL; en POST, en el JSON. Se normaliza igual que la carpeta: "HORMIGONES EJEMPLO", "hormigones ejemplo" y "hormigones-ejemplo" son el mismo cliente.
nombrestringopcional, solo POSTNombre de la empresa si se crea ahora. Si no va, se usa el cliente aplicación hasta que lo completemos en el panel. No cambia el nombre de una empresa que ya existía.

Ejemplos

# ¿Existe? Si existe, su token
curl https://deca.infonetconsultores.com/alta?cliente=hormigonesejemplo \
  -H "Authorization: Bearer TOKEN_MAESTRO"

# Alta si no existe (o el existente si ya está)
curl -X POST https://deca.infonetconsultores.com/alta \
  -H "Authorization: Bearer TOKEN_MAESTRO" \
  -H "Content-Type: application/json" \
  -d '{"cliente":"hormigonesejemplo"}'

Respuesta

{
  "ok":        true,
  "existe":    true,
  "creado":    false,
  "id":        12,
  "cliente":   "hormigonesejemplo",
  "nombre":    "Hormigones Ejemplo SL",
  "token":     "3f9a1c0d8b2e4f6a9c1d3e5f7a9b0c2e…",
  "caducidad": "2027-10-06"
}

Si el POST la acaba de crear (HTTP 201), además:

{
  "ok":        true,
  "existe":    true,
  "creado":    true,
  …
  "usuario":   "hormigonesejemplo.administrador",
  "password":  "Xk7mPq2RtW9vHn4c",
  "acceso":    "https://deca.infonetconsultores.com/app/"
}
CampoTipoDescripción
existebooleantrue en toda respuesta correcta: la empresa existe (o se acaba de crear).
creadobooleantrue si este POST la ha creado (HTTP 201); false si ya existía (HTTP 200).
idintegerIdentificador interno de la empresa.
clientestringCliente aplicación ya normalizado: el nombre de la carpeta. También se devuelve como slug por compatibilidad.
nombrestringNombre de la empresa.
tokenstringToken del modo C, 64 caracteres hexadecimales. Siempre el mismo para la empresa.
caducidadstring | nullFecha hasta la que tiene cuota, AAAA-MM-DD. Pasada esa fecha, POST /deca responde 403.
usuariostringsolo en 201 Usuario de la plataforma web: <cliente>.administrador, solo letras y números delante del punto (hasta 20). Si otra empresa ya usa ese prefijo, se numera: hormigonesejemplo2.administrador.
passwordstringsolo en 201 Contraseña de ese usuario, 16 caracteres aleatorios. No se guarda en claro: no se vuelve a devolver. Si se pierde, la renovamos desde el panel.
accesostringsolo en 201 Dirección de entrada a la plataforma web.

Errores

HTTPCuándo
404GET de un cliente aplicación que no existe. El cuerpo lleva "existe": false. Con POST no hay 404: se crea.
401Falta el token maestro o no es correcto.
403La empresa existe pero está desactivada.
400En POST, el cuerpo no es JSON válido.
422Falta cliente o queda vacío tras normalizar.
405Método distinto de GET o POST.
503El alta por API está deshabilitada en el servidor.

Compatibilidad: un POST con solo {"nombre": "..."}, como en la primera versión, sigue funcionando: el nombre hace de cliente aplicación.

Reintentos: si el POST de alta se corta (timeout) y lo repites, el segundo responde 200 con el token pero ya sin usuario ni password. Con ese token, POST /administrador le pone una contraseña nueva.