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.
En esta página
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:
https://api.transfersmanager.ai- https://api.transfersmanager.ai/api/v1/partner/openapi.json
Endpoints
Todas las rutas son relativas a la URL base.
| GET | /api/v1/partner/me | Tu perfil de socio y el operador con el que trabajas. |
| GET | /api/v1/partner/vehicle-classes | Las clases de vehículo que ofrece el operador, con su capacidad de pasajeros y equipaje. |
| POST | /api/v1/partner/quotes | Obtén opciones con precio para un trayecto. Cada opción lleva un quote_id firmado. |
| POST | /api/v1/partner/bookings | Crea 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}/cancellation | Consulta cuánto costaría cancelar ahora mismo, sin cancelar. |
| POST | /api/v1/partner/bookings/{ref}/cancel | Cancela 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á.
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
}{
"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.
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.
{
"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_confirmationconfirmeddriver_assigneddriver_en_routedriver_arrivedpicked_upcompletedno_showcancelledrejected
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
- Lee el cuerpo en bruto, antes de interpretar el JSON.
- Calcula el HMAC-SHA256 de "<t>.<cuerpo en bruto>" y compáralo con v1 en tiempo constante.
- Rechaza la petición si t tiene más de 5 minutos.
- Deduplica por X-TM-Delivery-Id y responde con cualquier 2xx.
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);
}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 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