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


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


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:


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:

  1. 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).
  2. 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.
  3. DRIVER_ARRIVED_AT_PICKUP — en el punto de recogida.
  4. DRIVER_DEPARTED_TO_DROPOFF — pasajero a bordo.
  5. 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)

  1. Vuestro polling ve la reserva en PENDING_AMENDMENT (o detectáis contenido distinto en una ACCEPTED/DRIVER_ASSIGNED).
  2. GET /suppliers/bookings/{ref} → contenido actualizado + state_hash nuevo.
  3. Aplicad los cambios en vuestro sistema.
  4. POST /responses con ACCEPT (acepta los cambios y vuelve al estado previo) o REJECT.

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:

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)

  1. Un solo poller, cada 5-15 min, siguiendo rel: next.
  2. state_hash en todos los POST, del último GET. Ante INVALID_STATE_HASH: re-GET → aplicar cambios → reintentar. Automatizadlo: es un flujo normal.
  3. 401 → renovar y reintentar una vez, en cualquier operación.
  4. Nunca parsear message/description: solo el campo error.
  5. Backoff en 5xx/timeout; jamás reintentar un 4xx sin re-GET.
  6. 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.
  7. Guardad el bookingReference y vuestro último state_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 campo bookingReference y los códigos BOOKING_* 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.