Documentación de APIs / Suppliers / v2 / Guía de integración
API Suppliers E-VTC v2 — Guía de integración para proveedores
Versión del documento: 1.0 (agosto 2026)
Audiencia: equipos de desarrollo de proveedores (suppliers) que integran su aplicación
con la plataforma para recibir, gestionar y ejecutar reservas subcontratadas.
Base URL: https://<dominio-que-se-os-facilite>/api-ext/v2
Entorno de pruebas (sandbox): se os facilitarán credenciales y URL propias. El desarrollo y las pruebas se hacen siempre contra la sandbox; las credenciales de producción nunca son las mismas que las de pruebas.
1. Visión general
El modelo es pull: vuestra aplicación consulta periódicamente las reservas que la plataforma os ha asignado y responde sobre ellas. La plataforma nunca llama a vuestros servidores.
Ciclo de vida típico de una reserva:
(la plataforma os asigna una reserva)
│
▼
GET /suppliers/bookings?status=NEW,... ← vuestro polling la descubre
│
▼
POST /bookings/{ref}/responses ACCEPT ← la aceptáis (o REJECT)
│
▼
POST /bookings/{ref}/assign-driver ← asignáis conductor
POST /bookings/{ref}/assign-vehicle ← y vehículo
│
▼
POST /bookings/{ref}/driver/events ← reportáis la ejecución:
DRIVER_DEPARTED_TO_PICKUP el conductor sale…
DRIVER_LIVE_LOCATION (repetido, cada 5-20s) …posición en tiempo real…
DRIVER_ARRIVED_AT_PICKUP …llega a la recogida…
DRIVER_DEPARTED_TO_DROPOFF …pasajero a bordo…
DRIVER_ARRIVED_AT_DROPOFF …viaje completado
En paralelo, vuestro polling debe vigilar modificaciones (PENDING_AMENDMENT) y
cancelaciones (PENDING_CANCELLATION / CANCELLED) iniciadas por la plataforma
(secciones 7 y 8).
2. Autenticación
OAuth2 client credentials. Se os entregan un client_id y un client_secret.
Obtener token
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" \
-X POST "https://<dominio>/api-ext/v2/oauth/token?grant_type=client_credentials"
⚠️ Las credenciales van en el header Authorization: Basic (como hace curl -u).
Enviarlas en el body devuelve 401 "Invalid Authorization header".
Respuesta:
{ "access_token": "eyJ0eXAi...", "token_type": "Bearer", "expires_in": 900 }
Reglas
- El token caduca a los 900 segundos. Renovadlo de forma proactiva ~60 s antes.
- Cualquier respuesta
401("error": "AUTHENTICATION_ERROR") → renovar token y reintentar la petición una vez. No hay que tratar el 401 como error de negocio. - El token se envía en todas las llamadas:
Authorization: Bearer <access_token>.
3. Polling de reservas
Petición
GET /suppliers/bookings?status=NEW,ACCEPTED,DRIVER_ASSIGNED,PENDING_AMENDMENT,PENDING_CANCELLATION,CANCELLED
| Parámetro | Tipo | Defecto | Notas |
|---|---|---|---|
status |
CSV de estados | — | Estados filtrables: NEW, ACCEPTED, DRIVER_ASSIGNED, PENDING_AMENDMENT, PENDING_CANCELLATION, COMPLETE, CANCELLED |
pickUpDateFrom |
ISO-8601 UTC | ayer 00:00 | Límite inferior de la fecha de recogida |
pickUpDateTo |
ISO-8601 UTC | sin límite | |
size |
entero | 400 | Máximo 500 |
after |
cursor opaco | — | Lo devuelve la propia API en links — no construirlo a mano |
Respuesta
{
"links": [
{ "rel": "previous", "href": "/api-ext/v2/suppliers/bookings?...", "type": "GET" },
{ "rel": "next", "href": "/api-ext/v2/suppliers/bookings?...", "type": "GET" }
],
"bookings": [ { ...reserva... } ]
}
Reglas
- Paginación: seguir siempre el link con
rel == "next". No usar la posición en el array (previouspuede aparecer antes quenext). Sin linknext→ última página. - Frecuencia recomendada: cada 5-15 minutos, con un solo poller (no en paralelo).
- Consultad también
ACCEPTED/DRIVER_ASSIGNEDaunque ya tengáis esas reservas: es como se detectan ediciones que no generanPENDING_AMENDMENT(comparad contenido contra vuestra copia local).
4. El objeto reserva
{
"links": [ { "rel": "self|accept|reject", "href": "...", "type": "GET|POST" } ],
"bookingReference": 35541,
"state_hash": "2baeff65b1688e2d4d46bc713c6deef3a45427bda6289851ca5807f68a92008f",
"status": "NEW",
"booked_date": "2026-08-01T08:10:19Z",
"pickup_date_time": "2026-08-01T09:40:19Z",
"pickup_date_time_zone": "Europe/Madrid",
"vehicle_type": "PCA",
"num_adults": 5, "num_children": 0, "num_babies": 0,
"flight_number": "FR2835",
"meet_and_greet": true,
"meet_and_greet_message": "Sr. Ejemplo",
"price": { "amount": 45, "amountFormatted": "45,00 €", "currency": "EUR" },
"passenger": {
"name": "Sr. Ejemplo", "telephone_number": "+34600000000",
"client_reference": "944384370", "client_email": null
},
"additional_comments": "PEOPLE_CARRIER",
"pickup": { "latitude": "41.304459", "longitude": "2.079780", "address": "Barcelona Airport (BCN)", "type": "AIRPORT" },
"dropoff": { "latitude": "41.390186", "longitude": "2.172837", "address": "Palau de la Música, Barcelona", "type": "STREET_ADDRESS" }
}
El state_hash
Es la huella del contenido de la reserva (fecha, precio, pasajeros, vuelo, viajero, origen/destino, extras). Cambia si y solo si cambia algún dato de la reserva. No cambia con las transiciones de estado que iniciáis vosotros (aceptar, asignar conductor, eventos): podéis encadenar operaciones con el mismo hash sin releer.
Es obligatorio enviarlo en todos los POST, tomado del último GET:
- En
/responses,/assign-drivery/assign-vehiclese valida de forma estricta: si la plataforma editó la reserva después de vuestro GET, recibiréis400 {"error": "INVALID_STATE_HASH"}. Reacción correcta: re-GET de la reserva, procesar los cambios, reintentar con el hash nuevo. Es un flujo normal, no un error. - En
/driver/eventsla validación es laxa: la progresión del servicio nunca se bloquea por un hash desfasado (de las divergencias de contenido ahí se encargan los 409 de la sección 6.4).
5. Máquina de estados
ACCEPT assign-driver
NEW ────────────────────► ACCEPTED ────────────────────► DRIVER_ASSIGNED
│ │ │
│ REJECT │ (plataforma edita) │ driver/events...
▼ ▼ ▼
(desaparece PENDING_AMENDMENT ◄──────────────── (también desde aquí)
del listado) │ ACCEPT → vuelve al estado previo │
│ REJECT → rechazo de los cambios ▼
▼ COMPLETE
(plataforma cancela) (tras ARRIVED_AT_DROPOFF)
PENDING_CANCELLATION ── ACCEPT (confirmar) ──► CANCELLED
| Estado | Significado | Qué debéis hacer |
|---|---|---|
NEW |
Reserva asignada, pendiente de vuestra respuesta | ACCEPT o REJECT |
ACCEPTED |
Aceptada | Asignar conductor y vehículo |
DRIVER_ASSIGNED |
Con conductor | Reportar eventos de ejecución |
PENDING_AMENDMENT |
La plataforma la ha modificado | Re-leer, aplicar cambios, ACCEPT (o REJECT) |
PENDING_CANCELLATION |
La plataforma la cancela | Confirmar con ACCEPT (ver cancellationOrigin, §8) |
CANCELLED |
Cancelación consumada | Cerrarla en vuestro sistema |
COMPLETE |
Ejecutada | Nada — estado final |
Conveniencias del servidor (comportamiento garantizado):
- ACCEPT sobre una reserva ya aceptada → no-op (idempotente, no da error).
- assign-driver sobre una reserva en NEW → la auto-acepta primero.
- Un evento de conductor repetido o inferior al ya registrado → 200 sin efecto.
6. Operaciones
Todas responden 2xx en éxito. Errores: sección 9.
6.1 POST /suppliers/bookings/{ref}/responses
Un único endpoint cuyo significado depende del estado actual de la reserva:
| Estado actual | ACCEPT significa |
REJECT significa |
|---|---|---|
NEW |
Aceptar la reserva | Rechazarla |
PENDING_AMENDMENT |
Aceptar la modificación | Rechazar los cambios |
PENDING_CANCELLATION |
Confirmar la cancelación | Rechazar vuestro propio rechazo pendiente (caso raro, ver §8) |
curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-X POST "https://<dominio>/api-ext/v2/suppliers/bookings/35541/responses" \
-d '{"state_hash": "<hash del último GET>", "supplierResponse": "ACCEPT"}'
Respuesta: 201 con body [].
6.2 POST /suppliers/bookings/{ref}/assign-driver
{
"state_hash": "<hash>",
"driver": {
"firstName": "Nombre", // obligatorio
"lastName": "Apellidos", // obligatorio
"telephoneNumber": "+34600000000",// obligatorio
"photoURL": "https://..." // opcional
}
}
Respuesta: 200 {"status": "ok", "bookingReference": 35541, "message": "Driver assigned successfully."}
6.3 POST /suppliers/bookings/{ref}/assign-vehicle
{
"state_hash": "<hash>",
"vehicle": {
"registration": "1234ABC", // obligatorio
"make": "Mercedes", "model": "Vito", "colour": "Negro" // opcionales
}
}
6.4 POST /suppliers/bookings/{ref}/driver/events
{
"state_hash": "<hash>",
"event_type": "DRIVER_DEPARTED_TO_PICKUP",
"occurred_at": "2026-08-01T09:12:00Z",
"latitude": 41.3874, // obligatorio en DRIVER_LIVE_LOCATION, recomendado en el resto
"longitude": 2.1686
}
Eventos, en este orden:
DRIVER_DEPARTED_TO_PICKUP— el conductor sale hacia la recogida. Primer evento obligatorio; enviadlo cuando el conductor arranca de verdad (es lo que activa el seguimiento para el cliente final).DRIVER_LIVE_LOCATION— posición GPS. Repetible; cadencia recomendada 5-20 s entre el DEPARTED y el ARRIVED_AT_DROPOFF. Dejad de enviarlo al terminar el servicio.DRIVER_ARRIVED_AT_PICKUP— en el punto de recogida.DRIVER_DEPARTED_TO_DROPOFF— pasajero a bordo.DRIVER_ARRIVED_AT_DROPOFF— viaje completado (cierra la reserva →COMPLETE).
Reglas:
- occurred_at en UTC estricto (YYYY-MM-DDTHH:MM:SSZ), nunca en el futuro, y
incremental respecto al último evento registrado (400 INVALID_EVENT_DATE si no).
- Si se pierde un evento intermedio, enviad el más reciente: el servidor avanza al
estado más reciente reportado sin exigir los intermedios. Nunca retrocede.
- Con una modificación o cancelación pendiente los eventos devuelven
409 AMENDMENT_PENDING / 409 CANCELLATION_PENDING: resolvedla primero (§7/§8).
- DRIVER_SUBMITTED_CUSTOMER_NO_SHOW está bloqueado por API (405): contactad con
la plataforma para gestionar un no-show.
7. Modificaciones (PENDING_AMENDMENT)
- Vuestro polling ve la reserva en
PENDING_AMENDMENT(o detectáis contenido distinto en unaACCEPTED/DRIVER_ASSIGNED). GET /suppliers/bookings/{ref}→ contenido actualizado +state_hashnuevo.- Aplicad los cambios en vuestro sistema.
POST /responsesconACCEPT(acepta los cambios y vuelve al estado previo) oREJECT.
8. Cancelaciones y cancellationOrigin
Cuando una reserva está en PENDING_CANCELLATION o CANCELLED, el payload incluye:
{ "status": "PENDING_CANCELLATION", "cancellationOrigin": "SENDER_CANCELLED", ... }
cancellationOrigin |
Significa | Qué hacer |
|---|---|---|
SENDER_CANCELLED |
La plataforma os cancela la reserva | Confirmad con ACCEPT y anulad en vuestro sistema |
SUPPLIER_REJECT |
Es vuestro propio rechazo (cuya respuesta se perdió) | Cerrad la reserva como rechazada por vosotros — no es una cancelación nueva |
Reintentos y respuestas perdidas (importante)
Si enviáis un REJECT y no recibís respuesta (timeout), reintentad con seguridad:
GET /bookings/{ref}→ si devuelve404 {"error": "BOOKING_NO_LONGER_AVAILABLE", "lastKnownStatus": "...", "cancellationOrigin": "SUPPLIER_REJECT"}→ vuestro rechazo sí llegó: cerradlo localmente y no repitáis el POST.- Si devuelve la reserva con
cancellationOrigin: SUPPLIER_REJECT→ reenviad el POST (el servidor lo cierra de forma idempotente). 404 {"error": "BOOKING_NOT_FOUND"}→ la referencia no os pertenece o nunca existió.
Regla general de reintentos: 5xx y timeouts se reintentan con backoff; los 4xx nunca se reintentan sin re-GET previo.
9. Catálogo de errores
Discriminad siempre por el campo error. message y description son texto para
humanos y pueden cambiar sin previo aviso.
error |
HTTP | Cuándo | Acción |
|---|---|---|---|
AUTHENTICATION_ERROR |
401 | Token inválido o caducado | Renovar token y reintentar (1 vez) |
INVALID_INPUT |
400 | Payload/parámetro mal formado | Corregir la petición; no reintentar tal cual |
INVALID_STATE_HASH |
400 | El hash no corresponde a la versión actual | Re-GET, procesar cambios, reintentar con hash nuevo |
INVALID_PAGE_CURSOR |
400 | Cursor after corrupto |
Reiniciar paginación desde la primera página |
BOOKING_NOT_FOUND |
404 | Referencia inexistente o ajena | Comprobar referencia; no reintentar |
BOOKING_NO_LONGER_AVAILABLE |
404 | La reserva ya no os está asignada (trae lastKnownStatus y cancellationOrigin) |
Cerrar según cancellationOrigin |
BOOKING_STATUS_INCOMPATIBLE |
400 | Operación no válida en el estado actual | Re-GET y decidir según el estado real |
AMENDMENT_PENDING |
409 | Hay una modificación sin aceptar | Resolver la modificación (§7) y reintentar |
CANCELLATION_PENDING |
409 | Hay una cancelación sin confirmar | Resolver la cancelación (§8) |
DRIVER_NOT_ASSIGNED |
409 | Evento sin conductor asignado | assign-driver primero |
INVALID_EVENT_DATE |
400 | occurred_at no incremental o futuro |
Corregir el timestamp |
EVENT_NOT_ALLOWED |
405 | Evento bloqueado por API (no-show) | Contactar con la plataforma |
BOOKING_ALREADY_COMPLETED |
409 | Cancelación sobre reserva completada | Tratar como definitivo |
BOOKING_ALREADY_CANCELLED |
409 | Ya estaba cancelada | Tratar como definitivo |
CANCELLATION_FAILED |
409 | La cancelación no pudo aplicarse | Re-GET y reintentar |
CONCURRENT_MODIFICATION |
409 | Otra petición modificó la reserva a la vez | Re-GET y reintentar |
USER_NOT_CONFIGURED |
500 | Problema de alta en la plataforma | Contactar con la plataforma |
SERVER_ERROR |
500 | Error interno | Reintentar con backoff exponencial |
10. Buenas prácticas (resumen)
- Un solo poller, cada 5-15 min, siguiendo
rel: next. state_hashen todos los POST, del último GET. AnteINVALID_STATE_HASH: re-GET → aplicar cambios → reintentar. Automatizadlo: es un flujo normal.- 401 → renovar y reintentar una vez, en cualquier operación.
- Nunca parsear
message/description: solo el campoerror. - Backoff en 5xx/timeout; jamás reintentar un 4xx sin re-GET.
- Eventos de conductor en el momento en que ocurren (el DEPARTED activa el seguimiento del cliente final — enviarlo tarde lo inutiliza), GPS cada 5-20 s, y cortar el GPS al completar.
- Guardad el
bookingReferencey vuestro últimostate_hash/payload por reserva: lo necesitaréis para detectar ediciones y para los reintentos idempotentes.
Anexo A — Tabla de transiciones observable
| Estado × operación | ACCEPT |
REJECT |
assign-driver |
assign-vehicle |
driver/events |
|---|---|---|---|---|---|
NEW |
→ ACCEPTED | desaparece | auto-acepta → DRIVER_ASSIGNED | 400 | 409 DRIVER_NOT_ASSIGNED |
ACCEPTED |
no-op | 400 | → DRIVER_ASSIGNED | ok | 409 DRIVER_NOT_ASSIGNED |
DRIVER_ASSIGNED |
no-op | 400 | ok (reemplaza) | ok | ok |
PENDING_AMENDMENT |
acepta cambios → estado previo | rechaza cambios | 409 AMENDMENT_PENDING | 409 | 409 AMENDMENT_PENDING |
PENDING_CANCELLATION |
confirma → CANCELLED | (ver §8) | 409 CANCELLATION_PENDING | 409 | 409 CANCELLATION_PENDING |
CANCELLED / COMPLETE |
409 | 409 | 400/409 | 400/409 | 400/409 |
Especificación técnica completa: openapi-suppliers-v2.yaml (importable en Postman).
Nota sobre los identificadores
booking. Las rutas (/suppliers/bookings), el campobookingReferencey los códigosBOOKING_*son nombres del contrato HTTP y nada más: la reserva puede provenir de cualquier agencia o canal, y su origen no cambia el flujo ni los campos. No hay que tratar de forma distinta una reserva según de dónde venga.
