API v1

Integra Hojas de Ruta en tu oficina

Crea credenciales por empresa y consulta el contrato para conectar ERP, flotas y procesos internos.

Inicio rápido

Crea la clave desde Perfil, guárdala una sola vez y verifica el acceso antes de conectar recursos.

Disponibilidad actual

Todo el contrato v1 descrito en esta página está activo. Las operaciones se aíslan por organización y respetan scopes, plan, cuota e idempotencia.

Probar la credencialcURL
curl --request GET \
  https://hojasderuta.com/api/v1/account \
  --header "Authorization: Bearer hdr_live_YOUR_KEY"
Emitir una hojacURL
curl --request POST \
  https://hojasderuta.com/api/v1/route-sheets \
  --header "Authorization: Bearer hdr_live_YOUR_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: 4f266965-0b3c-4d7a-a4c0-418cf3955236" \
  --data '{
    "externalId": "ERP-2026-184",
    "companyName": "Autocares Sierra",
    "companyNif": "B12345674",
    "contractorName": "Asociación Cultural Norte",
    "contractorNif": "G87654323",
    "origin": "Madrid",
    "destination": "Toledo",
    "serviceDate": "2026-10-05",
    "serviceStartTime": "08:30",
    "busPlate": "8246LRV",
    "driverId": "driver_01J..."
  }'
Respuesta 201JSON
{
  "data": {
    "id": "hr_01J...",
    "externalId": "ERP-2026-184",
    "status": "ready",
    "pdfVersion": 1,
    "pdfUrl": "/api/v1/route-sheets/hr_01J.../pdf",
    "qrUrl": "https://hojasderuta.com/deca/...",
    "createdAt": "2026-10-05T06:12:44Z"
  },
  "meta": {
    "requestId": "req_01J..."
  }
}
La respuesta es suficiente

Guarda data, ETag y requestId. No necesitas configurar un webhook para confirmar ni completar ninguna operación.

Formato
JSON UTF-8 y nombres de campo en camelCase.
Fechas
RFC 3339 en UTC. Las horas de servicio conservan Europe/Madrid.
Trazabilidad
Cada respuesta incluye requestId para soporte y auditoría.
Compatibilidad
Los cambios aditivos permanecen en v1. Los incompatibles abren una versión nueva.

Autenticación y permisos

Las integraciones usan credenciales de organización. No reutilizan la contraseña ni la sesión de una persona.

Clave visible una vez

La persona administradora copia el secreto al crearlo. Después sólo se conserva su hash.

Scopes mínimos

Cada credencial recibe permisos de lectura o escritura por recurso. Puede rotarse y revocarse sin afectar al resto.

Envía la clave en Authorization: Bearer hdr_live_.... No la incluyas en URLs, logs, archivos compartidos ni código que se ejecute en el navegador.

El administrador la crea desde Perfil → API e integraciones. El límite de claves activas depende del plan y una rotación invalida el secreto anterior inmediatamente.

  • account:read
  • usage:read
  • companies:read
  • companies:write
  • vehicles:read
  • vehicles:write
  • drivers:read
  • drivers:write
  • route_sheets:read
  • route_sheets:write
  • documents:read
  • webhooks:manage

Recursos y endpoints

Todos los paths parten de https://hojasderuta.com/api/v1 y devuelven JSON salvo la descarga del PDF.

El contrato /openapi.json incluye para cada operación un ejemplo de envío —parámetros, cabeceras y cuerpo cuando corresponda— y ejemplos de las respuestas JSON. Los borrados correctos devuelven 204 sin cuerpo y la descarga usa application/pdf.

Cuenta y uso

Consulta capacidades efectivas sin acceder a información de pago.

  • GET/accountOrganización, plan y capacidades
  • GET/usageCuotas comerciales y límites técnicos
  • GET/capabilitiesAcciones disponibles para la credencial

Datos maestros

Mantén las referencias que usa la oficina al preparar servicios.

  • GET / POST/driversListar y crear cuentas de conductor
  • GET / PATCH / DELETE/drivers/{id}Consultar, actualizar o retirar un conductor
  • POST/drivers/{id}/passwordCambiar la contraseña y cerrar sus sesiones
  • GET / POST/companiesEmpresas transportistas guardadas
  • GET / POST/vehiclesVehículos y matrículas
  • GET / PATCH / DELETE/companies/{id}Consultar, actualizar o retirar una empresa
  • GET / PATCH / DELETE/vehicles/{id}Consultar, actualizar o retirar un vehículo

Documentos

Emite y consulta hojas de ruta con el mismo control que la aplicación.

  • GET / POST/route-sheetsListado y emisión de hojas
  • GET/route-sheets/{id}Detalle de la hoja emitida
  • DELETE/route-sheets/{id}Borrado durante la ventana permitida
  • GET/route-sheets/{id}/pdfDescarga autenticada del PDF vigente

Opcional: webhooks

Sólo para sistemas que necesitan recibir cambios realizados fuera del ERP.

  • GET / POST/webhooksListar y crear destinos
  • GET / PATCH / DELETE/webhooks/{id}Consultar, pausar o retirar un destino
  • POST/webhooks/{id}/rotate-secretRotar el secreto de firma
  • POST/webhooks/{id}/testEnviar un evento de prueba
  • GET/webhook-deliveriesHistorial de entregas e intentos

Gestión completa de conductores

Crea cuentas activas, sincroniza la identidad del ERP, actualiza permisos y renueva contraseñas respetando las plazas del plan.

  • AltaPOST crea la cuenta y devuelve el conductor completo con ETag.
  • EdiciónPATCH actualiza identidad, teléfono, externalId y permisos.
  • AccesoEl cambio de contraseña revoca todas las sesiones anteriores.
  • BajaDELETE elimina al conductor; la cuenta administradora está protegida.
Crear un conductorcURL
curl --request POST \
  https://hojasderuta.com/api/v1/drivers \
  --header "Authorization: Bearer hdr_live_YOUR_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: driver-ERP-1042-v1" \
  --data '{
    "externalId": "ERP-DRIVER-1042",
    "name": "Ana García López",
    "email": "ana.garcia@example.com",
    "driverNif": "12345678Z",
    "phone": "600 123 456",
    "password": "contraseña-inicial-segura",
    "permissions": {
      "editRouteSheets": true,
      "editRouteSheetCompany": false,
      "editRouteSheetVehicle": true,
      "editRouteSheetContractor": true,
      "manageCompanies": false,
      "manageDrivers": false,
      "manageVehicles": false,
      "viewAllRouteSheets": false,
      "manageAllRouteSheets": false
    }
  }'
Respuesta completaHTTP
HTTP/1.1 201 Created
Content-Type: application/json
ETag: "V8y4hX2mQ0cR7fP1kL9sAw"

{
  "data": {
    "id": "user_01J...",
    "externalId": "ERP-DRIVER-1042",
    "name": "Ana García López",
    "hasAccount": true,
    "email": "ana.garcia@example.com",
    "driverNif": "12345678Z",
    "phone": "+34600123456",
    "permissions": {
      "editRouteSheets": true,
      "editRouteSheetCompany": false,
      "editRouteSheetVehicle": true,
      "editRouteSheetContractor": true,
      "manageCompanies": false,
      "manageDrivers": false,
      "manageVehicles": false,
      "viewAllRouteSheets": false,
      "manageAllRouteSheets": false
    },
    "createdAt": "2026-07-28T10:15:30.000Z",
    "updatedAt": "2026-07-28T10:15:30.000Z"
  },
  "meta": {
    "requestId": "req_01J..."
  }
}
Actualizar con If-MatchcURL
curl --request PATCH \
  https://hojasderuta.com/api/v1/drivers/user_01J... \
  --header "Authorization: Bearer hdr_live_YOUR_KEY" \
  --header "Content-Type: application/json" \
  --header 'If-Match: "V8y4hX2mQ0cR7fP1kL9sAw"' \
  --data '{
    "phone": "+34 611 222 333",
    "permissions": {
      "editRouteSheets": false,
      "manageCompanies": true,
      "manageDrivers": false,
      "manageVehicles": true,
      "viewAllRouteSheets": false,
      "manageAllRouteSheets": false
    }
  }'
Cambiar contraseñacURL
curl --request POST \
  https://hojasderuta.com/api/v1/drivers/user_01J.../password \
  --header "Authorization: Bearer hdr_live_YOUR_KEY" \
  --header "Content-Type: application/json" \
  --header 'If-Match: "NEW_ETAG_FROM_PREVIOUS_RESPONSE"' \
  --data '{
    "password": "nueva-contraseña-segura"
  }'

El alta requiere el scope drivers:write, una plaza disponible en el plan y una Idempotency-Key. Para actualizar, eliminar o cambiar la contraseña, lee primero el conductor y envía su ETag en If-Match. Un cambio de contraseña cierra todas sus sesiones.

externalId es el identificador estable del conductor en tu ERP. El email debe ser único globalmente; NIF y externalId deben ser únicos dentro de la organización. La contraseña nunca aparece en respuestas, logs de entrega ni webhooks.

Los conductores creados manualmente desde Gestión pueden no tener cuenta de acceso. También aparecen en los listados de la API con hasAccount: false y sin campo email; pueden asignarse a hojas de ruta y consumen una plaza del plan, pero no admiten cambios de contraseña ni permisos de acceso. Desde Gestión, un administrador puede concederles acceso más adelante o revocarlo sin eliminar el conductor.

Emitir hojas de ruta

El servidor valida el plan, genera el PDF nativo, crea el QR y registra la auditoría antes de confirmar la emisión.

  • ValidaciónComprueba campos, conductor, vehículo, horario y permisos.
  • Reserva de cuotaBloquea una unidad mensual para evitar excesos concurrentes.
  • DocumentoGenera el PDF, su huella y su QR.
  • ConfirmaciónDevuelve 201 sólo cuando el documento ya está disponible.

Desde la aplicación se pueden registrar cambios antes y durante el servicio, conservando su trazabilidad. Los endpoints antiguos de edición y revisión de esta API devuelven un conflicto (409). La firma opcional se recoge en un justificante independiente desde la aplicación.

La anulación sólo se admite antes del inicio del servicio y conserva los documentos durante al menos un año. Una hoja emitida sigue contando en la cuota mensual aunque después se elimine. La URL /deca/{token} es pública únicamente para entregar el PDF del QR y no funciona como API de integración.

Respuestas, polling e idempotencia

La respuesta HTTP es autoritativa. Para importar cambios externos, consulta las listas incrementalmente.

Respuesta autoritativa
Cada POST o PATCH devuelve el recurso confirmado. Guarda data, ETag y requestId directamente en el ERP.
Idempotency-Key
Obligatoria al crear conductores, emitir hojas. Repetir la misma petición devuelve el resultado original.
externalId
Identificador propio del ERP, único dentro de la integración y el tipo de recurso.
Cursor
Las listas usan cursores opacos. No construyas ni modifiques su contenido.
updatedAfter
Permite hacer polling incremental: recupera sólo los cambios posteriores a la última sincronización y continúa con el cursor.
ETag e If-Match
GET devuelve ETag. PATCH y DELETE exigen enviarlo en If-Match para evitar sobrescrituras.
Consultar cambioscURL
curl --get \
  https://hojasderuta.com/api/v1/drivers \
  --header "Authorization: Bearer hdr_live_YOUR_KEY" \
  --data-urlencode "updatedAfter=2026-07-28T10:00:00.000Z" \
  --data-urlencode "limit=100"
Página incrementalJSON
{
  "data": [
    {
      "id": "user_01J...",
      "externalId": "ERP-DRIVER-1042",
      "name": "Ana García López",
      "updatedAt": "2026-07-28T10:15:30.000Z"
    }
  ],
  "meta": {
    "requestId": "req_01J...",
    "count": 1,
    "hasMore": false
  }
}

Si meta.hasMore es true, repite la misma consulta añadiendo cursor=meta.nextCursor. Avanza tu marca temporal sólo después de procesar todas las páginas; conserva un pequeño solapamiento para tolerar relojes y reintentos.

Cómo detectar eliminaciones

updatedAfter devuelve altas y modificaciones, no tombstones. Si otro usuario puede eliminar datos desde la app, realiza una reconciliación completa periódica o activa el webhook de eliminación.

Planes, cuotas y rate limits

La API comparte los límites comerciales de la cuenta. Además mantiene límites técnicos para proteger la disponibilidad.

Límites por plan
PlanConductoresHojas al mesClaves APIComportamiento API
Gratis2101La cuota se comparte entre app y API
Base5402Pensado para una oficina pequeña
Flota15Sin límite comercial5Mantiene protección técnica
IlimitadoSin límite comercialSin límite comercial20Mantiene protección técnica
Sin límite comercial no significa sin protección

Flota e Ilimitado mantienen límites de peticiones, escrituras y concurrencia. Un 429 incluye Retry-After.

Consulta GET /usage para mostrar uso, restante y fecha de renovación. Las hojas creadas desde la app y la API cuentan en el mismo periodo mensual UTC.

El límite técnico actual es de 300 peticiones API por minuto e IP, con un máximo general de 120 mutaciones por minuto. Las respuestas incluyen RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset.

Errores predecibles

Los errores siguen application/problem+json. El código estable sirve para automatizar decisiones; detail es sólo informativo.

Códigos de error
HTTPCódigoSignificado
401invalid_api_keyLa credencial no existe, caducó o fue revocada.
403insufficient_scopeLa credencial no tiene permiso para la operación.
403plan_driver_limit_reachedLa cuenta agotó las plazas de conductor de su plan.
403driver_management_forbiddenLa API no puede modificar la cuenta administradora.
403plan_route_sheet_monthly_limit_reachedLa cuenta agotó su cuota mensual.
404resource_not_foundEl recurso no existe o pertenece a otra organización.
409idempotency_conflictLa misma clave se reutilizó con otro contenido.
409invalid_status_transitionEl cambio de estado no es válido.
412resource_version_mismatchEl recurso cambió desde la última lectura.
428precondition_requiredFalta el ETag vigente en If-Match.
422validation_failedUno o más campos no cumplen el contrato.
429rate_limit_exceededSe superó el límite técnico temporal.
Cuota agotadaapplication/problem+json
{
  "type": "https://hojasderuta.com/problems/plan-route-sheet-monthly-limit-reached",
  "title": "Acceso no permitido",
  "status": 403,
  "code": "plan_route_sheet_monthly_limit_reached",
  "detail": "La cuenta ha agotado las hojas disponibles este mes.",
  "requestId": "req_01J...",
  "limit": 5,
  "used": 5,
  "resetsAt": "2026-11-01T00:00:00Z"
}

Webhooks opcionales

No determinan el resultado ni son necesarios para crear o gestionar recursos. Úsalos sólo si el ERP debe recibir cambios realizados desde la app.

Empieza sin webhooks

Para una integración de oficina sencilla, procesa la respuesta de cada operación y consulta GET con updatedAfter. Puedes activar webhooks más adelante.

  • route_sheet.created
  • route_sheet.deleted
  • driver.created
  • driver.updated
  • driver.deleted
  • company.created
  • company.updated
  • company.deleted
  • vehicle.created
  • vehicle.updated
  • vehicle.deleted
  • subscription.changed
  • usage.threshold_reached

Entrega verificable

Cada petición incluye X-Hojas-Evento-Id, X-Hojas-Timestamp y X-Hojas-Signature. Verifica la firma SHA-256 sobre timestamp.cuerpo antes de procesarlo.

Responde con 2xx después de persistir el evento. Cada entrega realiza hasta tres intentos con espera creciente y queda registrada en /webhook-deliveries.

El secreto whsec_ sólo se devuelve al crear o rotar el webhook. En el servidor se conserva cifrado con AES-256-GCM.

Crear un destinocURL
curl --request POST \
  https://hojasderuta.com/api/v1/webhooks \
  --header "Authorization: Bearer hdr_live_YOUR_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "ERP producción",
    "url": "https://erp.example.com/webhooks/hojas-de-ruta",
    "events": ["route_sheet.created", "route_sheet.deleted"]
  }'
Evento de emisiónJSON
{
  "id": "evt_01J...",
  "type": "route_sheet.created",
  "createdAt": "2026-10-05T09:42:18Z",
  "organizationId": "org_01J...",
  "data": {
    "id": "hr_01J...",
    "externalId": "ERP-2026-184",
    "pdfVersion": 1,
    "status": "ready"
  }
}
Verificar la firma en Node.jsJavaScript
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyHojasWebhook(rawBody, timestamp, signatureHeader, secret) {
  const received = signatureHeader.split("v1=")[1] || "";
  const expected = createHmac("sha256", secret)
    .update(timestamp + "." + rawBody)
    .digest("hex");
  const left = Buffer.from(received, "hex");
  const right = Buffer.from(expected, "hex");
  return left.length === right.length && timingSafeEqual(left, right);
}

Lista de seguridad

Una clave API es una credencial de servidor. Debe tratarse con el mismo cuidado que una contraseña de producción.

  • Guarda la clave en un gestor de secretos o almacén de credenciales del sistema operativo.
  • Usa una credencial distinta por programa, sede y entorno.
  • Concede sólo los scopes necesarios para la integración.
  • Rota la clave de forma periódica y revócala al retirar el programa.
  • No registres claves, NIF, firmas ni cuerpos completos en logs.
  • Comprueba requestId y códigos estables al investigar incidencias.
  • Aplica timeout, reintentos limitados y backoff con variación aleatoria.
  • No llames a la API directamente desde JavaScript público del navegador.