Skip to main content

API para socios

Envía traslados a un operador por API o desde tu agente de IA

Una API REST y un servidor MCP para cotizar, reservar, modificar y cancelar traslados con un operador de TransfersManager. Los precios son los del operador y es él quien te emite la clave desde su panel.

Qué es

La API para socios permite que una OTA, una agencia de viajes o un motor de reservas envíe trabajo directamente a un operador de transporte que usa TransfersManager. No te integras con la plataforma en general: te integras con un operador, con sus vehículos, sus zonas y su tarifa.

  • Obtienes una cotización con un precio firmado. La reserva se cobra exactamente por ese precio.
  • Creas, consultas, modificas y cancelas reservas, y las sigues con webhooks.
  • La misma clave sirve por REST y por MCP, así que un agente de IA puede reservar en tu nombre.
  • No se cobra nada a través de TransfersManager. Liquidas con el operador según vuestro acuerdo.

Consigue tu clave

El operador crea un socio para ti en su panel (Ajustes, Socios por API) y te entrega dos cosas: tu clave de API (empieza por tmp_) y, si quieres recibir eventos, un secreto de webhook (empieza por whsec_). Las dos se muestran una sola vez. Si pierdes una, pídele al operador que la renueve.

Autenticación y límites

Envía tu clave en la cabecera X-API-Key o como token Bearer. La clave está limitada a un operador y a ti: solo puede ver y cambiar tus propias reservas.

  • Límite por clave: 120 peticiones por minuto por defecto. El operador puede fijarlo entre 10 y 600.
  • Cada respuesta incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset.
  • Si te pasas, recibes un 429 con la cabecera Retry-After. Espera esos segundos antes de reintentar.
curl https://api.transfersmanager.ai/api/v1/partner/me \
  -H "X-API-Key: tmp_your_key_here"

URL base y OpenAPI

Todas las llamadas van a https://api.transfersmanager.ai y la especificación legible por máquina se publica como OpenAPI 3:

Endpoints

Todas las rutas son relativas a la URL base.

GET/api/v1/partner/meTu perfil de socio y el operador con el que trabajas.
GET/api/v1/partner/vehicle-classesLas clases de vehículo que ofrece el operador, con su capacidad de pasajeros y equipaje.
POST/api/v1/partner/quotesObtén opciones con precio para un trayecto. Cada opción lleva un quote_id firmado.
POST/api/v1/partner/bookingsCrea una reserva a partir de un quote_id.
GET/api/v1/partner/bookings?external_ref=&limit=&offset=Lista tus reservas. Filtra por external_ref y pagina con limit y offset.
GET/api/v1/partner/bookings/{ref}Consulta una reserva. La referencia puede ser el booking_id, el número de confirmación (TM26-XXXXXX) o tu propio external_ref.
PATCH/api/v1/partner/bookings/{ref}Modifica una reserva hasta que el conductor sale.
GET/api/v1/partner/bookings/{ref}/cancellationConsulta cuánto costaría cancelar ahora mismo, sin cancelar.
POST/api/v1/partner/bookings/{ref}/cancelCancela una reserva.

Cotizar un trayecto

Envía la recogida, el destino, la hora y el tamaño del grupo. La latitud y la longitud son opcionales: si las omites, geocodificamos la dirección.

  • pickup_time es ISO 8601 con desfase horario. Sin desfase, envía también "timezone": "Europe/Madrid" (o la zona que corresponda). Cualquier otra cosa devuelve 422.
  • La recogida debe ser como mínimo 30 minutos después de ahora.
  • Cada opción tiene un quote_id firmado. La reserva se cobra exactamente ese precio y la cotización vale 24 horas.
  • price_is_approximate indica cuándo el precio depende de algo que el operador confirmará.
Petición
POST /api/v1/partner/quotes
{
  "pickup": { "address": "Málaga Airport (AGP)", "lat": 36.6749, "lng": -4.4991 },
  "dropoff": { "address": "Hotel Puente Romano, Marbella" },
  "pickup_time": "2026-11-20T10:30:00+01:00",
  "passengers": 3,
  "luggage": 3,
  "child_seats": 1
}
Respuesta
{
  "pickup": { "address": "Málaga Airport (AGP)", "lat": 36.6749, "lng": -4.4991 },
  "dropoff": { "address": "Hotel Puente Romano, Marbella", "lat": 36.5016, "lng": -4.9456 },
  "pickup_time": "2026-11-20T10:30:00+01:00",
  "passengers": 3,
  "luggage": 3,
  "distance_km": 54.2,
  "options": [
    {
      "quote_id": "qt_...",
      "vehicle_class": { "code": "minivan", "name": "Minivan", "max_passengers": 6, "max_luggage": 6 },
      "price": 78.0,
      "currency": "EUR",
      "price_is_approximate": false,
      "expires_at": "2026-11-19T10:30:00+01:00"
    }
  ]
}

Crear una reserva

Envía el quote_id con tu propia referencia y el pasajero. Añade una cabecera Idempotency-Key (cualquier UUID) para que un reintento tras un timeout nunca cree una segunda reserva.

Petición
POST /api/v1/partner/bookings
Idempotency-Key: 6f1c2f0e-5b1e-4d0e-9a3b-0c1f7a2d9e11

{
  "quote_id": "qt_...",
  "external_ref": "OTA-123456",
  "passenger": { "name": "Ana García", "phone": "+34600000000", "email": "[email protected]" },
  "pickup_address": "Arrivals hall, Terminal 3",
  "dropoff_address": "Hotel Puente Romano",
  "flight_number": "FR1234",
  "meet_board_name": "GARCÍA",
  "notes": "One child seat needed"
}

Qué puedes recibir

201
Reserva creada.
200
El external_ref (o la Idempotency-Key) ya se usó con el mismo cuerpo. Recibes la reserva original con "duplicate": true.
409
El external_ref o la Idempotency-Key se reutilizó con un cuerpo distinto (external_ref_conflict, idempotency_key_conflict), o la primera petición sigue en curso (booking_in_progress).
422
invalid_quote, quote_expired, passengers_exceed_quote o pickup_too_soon.
429
operator_quota_exceeded: el operador ha alcanzado su cupo de reservas de la API para socios.

Pago: no se cobra nada a través de TransfersManager. Liquidas cada reserva con el operador.

El objeto de reserva

Todos los endpoints de reservas devuelven la misma estructura.

Ejemplo
{
  "booking_id": "...",
  "confirmation_number": "TM26-XXXXXX",
  "external_ref": "OTA-123456",
  "status": "confirmed",
  "pickup": { "address": "...", "lat": 36.6749, "lng": -4.4991 },
  "dropoff": { "address": "...", "lat": 36.5016, "lng": -4.9456 },
  "pickup_time": "2026-11-20T10:30:00+01:00",
  "passengers": 3,
  "luggage": 3,
  "child_seats": 1,
  "baby_seats": 0,
  "vehicle_class": { "code": "minivan", "name": "Minivan" },
  "price": 78.0,
  "currency": "EUR",
  "flight_number": "FR1234",
  "passenger": { "name": "Ana García", "phone": "+34600000000", "email": "[email protected]" },
  "driver": { "first_name": "Luis", "phone": "+34611111111" },
  "vehicle": { "brand": "Mercedes", "model": "V-Class", "color": "black", "license_plate": "1234ABC" },
  "cancellation": null,
  "created_at": "2026-11-01T09:12:44Z",
  "duplicate": false
}

Estados

  • pending_confirmation
  • confirmed
  • driver_assigned
  • driver_en_route
  • driver_arrived
  • picked_up
  • completed
  • no_show
  • cancelled
  • rejected

Modificar una reserva

PATCH cambia los datos que no alteran el precio. Mientras el conductor no haya salido, puedes cambiar libremente:

  • passenger, flight_number, meet_board_name y notes,
  • pickup_address y dropoff_address (como texto, para el conductor).

Cambiar la hora, los pasajeros, el equipaje o las sillas

Vuelve a cotizar con la misma recogida y el mismo destino y envía el nuevo quote_id en el PATCH. La reserva toma ese nuevo precio.

  • Una recogida o un destino a más de 1 km es otro trayecto: recibes 422 route_changed. Crea una reserva nueva y cancela la anterior.
  • Cuando el conductor ya ha salido, modificar devuelve 409 booking_already_started.

Cancelar una reserva

Al cancelar se aplica la política de cancelación del operador tal como estaba congelada cuando reservaste. Llama primero a GET /cancellation para enseñar a tu cliente cuánto cuesta y después a POST /cancel.

  • La respuesta incluye un objeto cancellation con hours_of_notice, refund_pct, fee_pct, fee_amount, currency, policy_source y cancelled_by.
  • fee_amount es lo que el operador puede facturarte.
  • Repetir la cancelación devuelve el mismo resultado.
  • Con el pasajero ya a bordo, cancelar devuelve 409.
POST /api/v1/partner/bookings/{ref}/cancel
{ "reason": "Customer changed plans" }

// Response: the booking, now with
"status": "cancelled",
"cancellation": {
  "hours_of_notice": 30.5,
  "refund_pct": 100,
  "fee_pct": 0,
  "fee_amount": 0.0,
  "currency": "EUR",
  "policy_source": "operator",
  "cancelled_by": "partner"
}

Webhooks

El operador configura en su panel la URL de tu webhook (https pública) y te da el secreto. A partir de ahí te llamamos con cada cambio en tus reservas. Los webhooks nunca incluyen datos del pasajero.

Eventos

booking.confirmed
El operador confirmó la reserva.
booking.driver_assigned
Se asignó un conductor. Incluye driver (first_name, phone) y vehicle.
booking.driver_unassigned
Se retiró al conductor; vendrá otro.
booking.driver_en_route
El conductor va de camino.
booking.driver_arrived
El conductor está en el punto de recogida.
booking.picked_up
El pasajero ya está a bordo.
booking.completed
El trayecto ha terminado.
booking.no_show
El pasajero no se presentó.
booking.cancelled
Cancelada. Incluye el objeto cancellation y cancelled_by (partner u operator).
ping
Evento de prueba enviado desde el panel del operador.

Cuerpo

{
  "id": "whd_...",
  "event": "booking.driver_assigned",
  "created_at": "2026-11-20T08:02:11Z",
  "data": {
    "booking_id": "...",
    "confirmation_number": "TM26-XXXXXX",
    "external_ref": "OTA-123456",
    "status": "driver_assigned",
    "occurred_at": "2026-11-20T08:02:10Z",
    "driver": { "first_name": "Luis", "phone": "+34611111111" },
    "vehicle": { "brand": "Mercedes", "model": "V-Class", "color": "black", "license_plate": "1234ABC" }
  }
}

Cabeceras

  • X-TM-Event: el nombre del evento.
  • X-TM-Delivery-Id: único por entrega. Úsalo para deduplicar.
  • X-TM-Signature: t=<marca de tiempo unix>,v1=<hex>, donde v1 es el HMAC-SHA256 de "<t>.<cuerpo en bruto>" con tu secreto de webhook.

Verifica cada entrega

  1. Lee el cuerpo en bruto, antes de interpretar el JSON.
  2. Calcula el HMAC-SHA256 de "<t>.<cuerpo en bruto>" y compáralo con v1 en tiempo constante.
  3. Rechaza la petición si t tiene más de 5 minutos.
  4. Deduplica por X-TM-Delivery-Id y responde con cualquier 2xx.
Node.js
import crypto from 'node:crypto';

// rawBody: the request body as a string or Buffer, BEFORE JSON.parse.
export function verifyTmSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hashlib
import hmac
import time


def verify_tm_signature(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    try:
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > 300:  # older than 5 minutes
        return False

    expected = hmac.new(
        secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Si no respondes con un 2xx, reintentamos con espera exponencial: desde 30 segundos hasta 1 hora y después a diario. Los eventos de una misma reserva se entregan en orden.

Desde tu agente de IA (MCP)

El mismo operador, los mismos precios y la misma clave están disponibles para agentes de IA mediante un servidor MCP remoto (Streamable HTTP) en https://mcp.transfersmanager.ai/mcp. Usa tu clave como token Bearer.

Claude Code
claude mcp add --transport http transfersmanager https://mcp.transfersmanager.ai/mcp \
  --header "Authorization: Bearer tmp_your_key_here"

En cualquier otro cliente, añade un servidor MCP remoto con esa URL y la cabecera Authorization: Bearer tmp_...

https://mcp.transfersmanager.ai/mcp

Herramientas

quote_transfer
Cotiza un trayecto y devuelve opciones.
check_availability
Comprueba que el operador puede atender un trayecto.
create_booking
Reserva una opción. Recibe el option_id de quote_transfer, tu client_reference (se usa para la idempotencia) y passenger_name.
get_booking
Consulta el estado de una reserva.
cancel_booking
Con confirm=false muestra primero la tarifa. Con confirm=true cancela.
list_vehicle_classes
Las clases de vehículo del operador.

Errores

Los errores comparten un formato, con un código estable sobre el que puedes decidir y un mensaje para personas:

{ "detail": { "code": "quote_expired", "message": "This quote has expired. Request a new one." } }
401 invalid_key
La clave falta, es incorrecta o está revocada.
402 subscription_required
El plan del operador no incluye la API para socios.
404
No encontrado. También es lo que recibes con una reserva que no es tuya.
409 / 422 / 429
Consulta cada endpoint más arriba.

¿Eres operador?

Abre tu panel, ve a Ajustes, Socios por API y emite una clave a la primera OTA que te mande trabajo.

Empezar con TransfersManager