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.
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.
curl --request GET \
https://hojasderuta.com/api/v1/account \
--header "Authorization: Bearer hdr_live_YOUR_KEY"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..."
}'{
"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..."
}
}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.
La persona administradora copia el secreto al crearlo. Después sólo se conserva su hash.
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:readusage:readcompanies:readcompanies:writevehicles:readvehicles:writedrivers:readdrivers:writeroute_sheets:readroute_sheets:writedocuments:readwebhooks: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.
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
}
}'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..."
}
}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
}
}'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.
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"{
"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.
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.
| Plan | Conductores | Hojas al mes | Claves API | Comportamiento API |
|---|---|---|---|---|
| Gratis | 2 | 10 | 1 | La cuota se comparte entre app y API |
| Base | 5 | 40 | 2 | Pensado para una oficina pequeña |
| Flota | 15 | Sin límite comercial | 5 | Mantiene protección técnica |
| Ilimitado | Sin límite comercial | Sin límite comercial | 20 | Mantiene protección técnica |
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.
| HTTP | Código | Significado |
|---|---|---|
| 401 | invalid_api_key | La credencial no existe, caducó o fue revocada. |
| 403 | insufficient_scope | La credencial no tiene permiso para la operación. |
| 403 | plan_driver_limit_reached | La cuenta agotó las plazas de conductor de su plan. |
| 403 | driver_management_forbidden | La API no puede modificar la cuenta administradora. |
| 403 | plan_route_sheet_monthly_limit_reached | La cuenta agotó su cuota mensual. |
| 404 | resource_not_found | El recurso no existe o pertenece a otra organización. |
| 409 | idempotency_conflict | La misma clave se reutilizó con otro contenido. |
| 409 | invalid_status_transition | El cambio de estado no es válido. |
| 412 | resource_version_mismatch | El recurso cambió desde la última lectura. |
| 428 | precondition_required | Falta el ETag vigente en If-Match. |
| 422 | validation_failed | Uno o más campos no cumplen el contrato. |
| 429 | rate_limit_exceeded | Se superó el límite técnico temporal. |
{
"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.
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.createdroute_sheet.deleteddriver.createddriver.updateddriver.deletedcompany.createdcompany.updatedcompany.deletedvehicle.createdvehicle.updatedvehicle.deletedsubscription.changedusage.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.
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"]
}'{
"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"
}
}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.