Autenticación y modos
Cada petición necesita tu clave en la cabecera Authorization: Bearer …. Entregamos las claves tras una breve conversación – no hay registro libre.
https://api.listnride.com/v1Bicicletas y precios reales, reservas simuladas: el enlace abre una página de prueba, nadie paga y el arrendador no recibe aviso.
Reservas reales a través del pago de ListNRide.
curl "https://api.listnride.com/v1/rides?lat=52.52&lng=13.40" \
-H "Authorization: Bearer lnr_test_…"Cómo funciona una reserva
No necesitas un pago propio: el cliente reserva y paga con nosotros y después vuelve a tu web.
Encontrar bicicletas
GET /v1/ridespor lugar, periodo y categoría.Crear una sesión de pago
POST /v1/checkout/sessions– recibes el precio y un enlace.Redirigir al cliente
Reserva y pago en ListNRide y vuelta a tu web.
Recibir el estado
Por webhook o con
GET /v1/bookings/{id}.
El modo de prueba paso a paso
Con una clave de prueba todo funciona como en real – solo que nadie paga y ningún arrendador interviene. Prueba el recorrido completo una vez antes de salir en real.
Crear una sesión
POST /v1/checkout/sessionsconlnr_test_…. Precios y disponibilidad son reales.Abrir el enlace
La
urlabre una página de prueba en lugar del pago. Elige allí «Complete test booking».Comprobar la vuelta
El cliente llega a tu
return_urlconbooking=bkg_test_…ystatus=success.Comprobar el webhook
Si hay una URL de webhook configurada,
booking.confirmedcon"mode": "test"llega en menos de un minuto.
Endpoints
Bicicletas cerca de un lugar o de un arrendador. Con un periodo, availability.units_available indica las unidades libres.
| Parámetro | Descripción |
|---|---|
| lat, lng | Centro de la búsqueda (o lister) |
| radius_km | 0,1–100, por defecto 15 |
| bbox | Zona del mapa minLng,minLat,maxLng,maxLat en vez de lat/lng |
| from, to | Periodo, fecha o fecha-hora ISO |
| quantity | Número de bicicletas iguales, por defecto 1 |
| category | p. ej. e-bike,cargo-bike |
| price_min price_max | Precio por día en céntimos, con tarifa de servicio y seguro básico |
| rider_height | Altura del ciclista en cm (150–210) – encuentra bicicletas de la talla adecuada y unisize |
| frame_size brand | Talla de cuadro o marca exacta, p. ej. M o Gazelle |
| accessories | Equipamiento incluido, debe estar todo: helmet,lock,lights,basket,childseat,trailer,gps,repair_kit |
| instant_booking top_booked | true: solo confirmación inmediata / solo las más reservadas |
| min_rating lister_type type | Valoración mínima, business o private, bike o accessory |
| sort | recommended · distance · price_asc · price_desc · rating · popular |
| limit, cursor | Paginación, máximo 100 por página |
{
"object": "list",
"data": [{
"id": "ride_oEbNgLPApUJf52PkgHU49A",
"name": "KRZBERG – Single Speed",
"category": { "id": "single-speed" },
"price": { "currency": "EUR", "per_day": 2500 },
"lister": { "id": "lis_VTknvPc20LBQbXE9p7dt6A" },
"instant_booking": true,
"distance_km": 1.46
}],
"has_more": true, "next_cursor": "eyJvIjoyMH0"
}Una bicicleta con descripción, todas las fotos, equipamiento, fianza y seguro.
Además, todo lo que necesita una página de producto: precios totales de 1 a 28 días, altura del ciclista, reglas de reserva, horarios, condiciones de cancelación y extras reservables con zonas de entrega.
{
"price": { "per_day": 2500, "tiers": [
{ "days": 1, "total": 2500 },
{ "days": 7, "total": 15599, "discount": 1400 } ] },
"size": { "rider_height": { "range": "175-185" } },
"booking_rules": { "min_days": 1, "time_slot_minutes": 30 },
"opening_hours": { "monday": ["09:00-18:00"], … },
"cancellation_policy": { "by_customer": [
{ "hours_before_pickup_min": 72, "refund_percent_of_rental": 100 }, … ] },
"extras": [{ "code": "insurance_plus" }, { "code": "delivery" }]
}Unidades libres por día y franjas de recogida y devolución en hora local, hasta 92 días por petición. Los días de cierre y los horarios ya están incluidos – ideal para tu propio selector de fechas. La comprobación vinculante la hacen POST /v1/quotes y el pago.
{
"object": "availability",
"units_total": 4,
"days": [{
"date": "2026-11-17",
"units_available": 3,
"closed": false,
"pickup_windows": ["09:00-18:00"],
"return_windows": ["09:00-18:00"]
}, …]
}Opiniones verificadas de una bicicleta, las más recientes primero, con media y número total. Los autores aparecen con nombre e inicial. Todas las opiniones de un arrendador: GET /v1/listers/{id}/reviews.
Perfil público de un arrendador: nombre, ubicación, valoración, reservas completadas, número de bicicletas, confirmación inmediata, reglas de reserva, horarios y zonas de entrega. Los particulares aparecen con su nombre y sin calle.
Las condiciones de cancelación que se aplican a cada reserva: desde 72 horas antes de la recogida se reembolsa todo; por debajo, una parte del alquiler según la franja. Si cancela el arrendador, el cliente recupera todo.
Todas las categorías con nombre en alemán e inglés. Una categoría principal como e-bike incluye sus subcategorías en la búsqueda.
Todos los lugares con bicicletas reservables, primero los que más tienen. Con q buscas en todos los idiomas (p. ej. q=munich), con country filtras por país y locale elige el idioma de los nombres. Un lugar: GET /v1/places/{id}.
En vez de una bicicleta concreta, tu cliente reserva una clase – p. ej. «E-bike, gama media» en Berlín – y ListNRide encuentra un arrendador adecuado. Consulta con place o lat/lng. Se reserva con una sesión de pago con class y size (altura del ciclista).
{
"id": "berlin.e-bike.mid",
"object": "class",
"category": { "id": "e-bike" },
"tier": { "id": "mid" },
"price": { "currency": "EUR", "per_day": 3900 },
"sizes": ["150-165", "165-175", "175-185", "185-205", "Unisize"],
"listers_nearby": 12
}Calcula el precio exacto como el pago de ListNRide: alquiler, descuento, tarifa de servicio, seguro básico, extras (insurance_plus) y entrega a una dirección, sin reservar nada. Un presupuesto incluye bicicletas de un arrendador o una clase.
curl -X POST https://api.listnride.com/v1/quotes \
-H "Authorization: Bearer lnr_test_…" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-11-17", "to": "2026-11-19",
"items": [{ "ride": "ride_jK6n…", "quantity": 2,
"extras": ["insurance_plus"] }],
"delivery_address": "Torstraße 1, 10119 Berlin"
}'
# → "amounts": { "rental": 6000, "service_fee": 1100,
# "insurance_plus": 900, "delivery": 1500, "total": 9600 }Crea una sesión de pago. El pago de ListNRide comprueba precio y disponibilidad. El enlace es válido 24 horas; cantidad y horarios vienen preseleccionados en el pago.
| Campo | Descripción |
|---|---|
| ride * | ID de la bicicleta ride_… |
| class, size | En vez de ride: una clase de GET /v1/classes y la altura del ciclista (165-175 … o Unisize) |
| from, to * | Día de recogida y devolución, YYYY-MM-DD |
| pickup_time, return_time | HH:MM, por defecto 10:00 / 17:00 |
| quantity | Cantidad, 1–50, por defecto 1 |
| return_url * | A dónde vuelve el cliente tras reservar (https), con checkout_session, booking y status=success |
| cancel_url | Opcional, si el cliente cancela (https) |
| locale | de · en · fr · es · it · nl |
| client_reference | Tu propia referencia |
curl -X POST https://api.listnride.com/v1/checkout/sessions \
-H "Authorization: Bearer lnr_test_…" \
-H "Content-Type: application/json" \
-d '{
"ride": "ride_jK6nvpDczET3Adx8AKKn1w",
"from": "2026-11-17", "to": "2026-11-18",
"pickup_time": "09:00", "return_time": "17:00",
"return_url": "https://tu-web.es/gracias",
"locale": "es"
}'
# → { "id": "cs_O8t1…", "amount_total": 7800,
# "url": "https://www.listnride.es/api/go/p/BHTm…" }Estado de la sesión: open, booked (con ID de booking) o expired.
| status | Descripción |
|---|---|
| awaiting_payment | El cliente está en el pago, aún sin pagar |
| requested | Pagado o reservado, pendiente de aceptación del arrendador |
| accepted | Aceptado por el arrendador, pago en curso |
| confirmed | Confirmada |
| completed | Alquiler finalizado |
| canceled_by_lister · canceled_by_customer · canceled | Cancelada |
| expired | No pagada o no aceptada a tiempo |
Todas las reservas de tus sesiones de pago, las más recientes primero. Filtra por status o por tu client_reference. Una clave de prueba lista reservas de prueba; una clave real, las reales. Las reservas canceladas incluyen cancellation con el importe y el estado del reembolso.
Cancela una reserva en nombre de tu cliente – con las mismas reglas que en listnride.com: desde 72 horas antes de la recogida se reembolsa todo; por debajo, una parte del alquiler. Se avisa al arrendador, recibes booking.canceled_by_customer y el reembolso vuelve al medio de pago del cliente.
Comprueba antes con GET /v1/bookings/{id}/cancellation: indica si es posible cancelar y el reembolso exacto, sin cambiar nada. Cancelar requiere el scope bookings:cancel, que activamos a petición. Opcional: reason (changed_mind, found_better, timing_issue, price_too_high, personal_reasons, other) y comment.
# 1. Vista previa: ¿se puede cancelar y cuánto recupera el cliente?
curl https://api.listnride.com/v1/bookings/bkg_gr9z…/cancellation \
-H "Authorization: Bearer lnr_live_…"
# → "cancelable": true, "refund": { "amount": 7300 },
# "breakdown": { "rental_refunded_percent": 75 }
# 2. Cancelar
curl -X POST https://api.listnride.com/v1/bookings/bkg_gr9z…/cancel \
-H "Authorization: Bearer lnr_live_…" \
-H "Content-Type: application/json" \
-d '{ "reason": "timing_issue" }'
# → "status": "canceled_by_customer"Añade ?aff=TUCODIGO a cualquier enlace de ListNRide – cualquier página, cualquier idioma. Si el cliente reserva dentro del plazo acordado, la reserva cuenta para ti; gana el último clic. Las reservas de tus sesiones de pago cuentan siempre para ti, incluso sin clic.
La comisión se acuerda de forma individual y se basa en las tarifas que ListNRide gana con la reserva. Cuenta cuando termina el alquiler; si cancela el arrendador, no hay comisión. GET /v1/commissions muestra clics, reservas, nivel y comisión por mes – sin datos de clientes. Se liquida mensualmente mediante nota de abono.
# Enlace de partner – funciona en cualquier página
https://www.listnride.de/berlin/e-bike?aff=NEEDABIKE
# Tu comisión de un mes
curl "https://api.listnride.com/v1/commissions?month=2026-09" \
-H "Authorization: Bearer lnr_live_…"
# → "clicks": 4812, "bookings": 91, "tier": "Stufe 3 · 30 %",
# "commission_net": { "amount": 54120 }, "status": "approved"Webhooks
Cuando cambia el estado de una reserva, enviamos un POST a tu URL de webhook, p. ej. booking.confirmed. Responde con 2xx – si no, reintentamos tras 1 min, 5 min, 30 min y hasta 24 h. Usa ping para probar la conexión; el id del evento no cambia en los reintentos.
Verifica cada mensaje con la cabecera LNR-Signature: t=…,v1=…: HMAC-SHA256 sobre t + "." + body con tu secreto whsec_…. Usa el cuerpo sin modificar y rechaza los mensajes cuyo t tenga más de 5 minutos.
POST https://tu-web.es/hooks/listnride
LNR-Event: booking.confirmed
LNR-Signature: t=1791210000,v1=5f2c…
{
"id": "evt_Qm3xT0bWl2kZ8yQe",
"object": "event",
"type": "booking.confirmed",
"created": "2026-11-02T09:14:03.000Z",
"data": { "object": {
"id": "bkg_gr9z-CSUh4I8UrC9WEwW2Q",
"object": "booking",
"mode": "live",
"status": "confirmed",
"reference": "LNR-w4kKb",
"checkout_session": "cs_O8t1fc2rdEG2GyYTqghvfA",
"ride": "ride_jK6nvpDczET3Adx8AKKn1w",
"from": "2026-11-17T08:00:00.000Z",
"to": "2026-11-18T16:00:00.000Z",
"quantity": 1,
"amount_total": 7800,
"currency": "EUR",
"client_reference": "NB-1042"
} }
}const crypto = require("crypto");
// rawBody: el cuerpo sin modificar, tal como llegó
function verify(rawBody, header, secret) {
const p = Object.fromEntries(header.split(",").map(x => x.split("=")));
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(`${p.t}.${rawBody}`).digest("hex");
return typeof p.v1 === "string" && p.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}
function lnr_verify(string $rawBody, string $header, string $secret): bool {
$p = [];
foreach (explode(",", $header) as $part) {
[$k, $v] = array_pad(explode("=", $part, 2), 2, "");
$p[$k] = $v;
}
if (abs(time() - (int)($p["t"] ?? 0)) > 300) return false;
$expected = hash_hmac("sha256", $p["t"] . "." . $rawBody, $secret);
return hash_equals($expected, $p["v1"] ?? "");
}
// $raw = file_get_contents("php://input");
// lnr_verify($raw, $_SERVER["HTTP_LNR_SIGNATURE"] ?? "", "whsec_…");
import hashlib, hmac, time
# raw_body: el cuerpo sin modificar, tal como llegó
def verify(raw_body: bytes, header: str, secret: str) -> bool:
p = dict(x.split("=", 1) for x in header.split(",") if "=" in x)
if abs(time.time() - int(p.get("t", 0))) > 300:
return False
expected = hmac.new(secret.encode(), p["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, p.get("v1", ""))Errores y límites
Los errores tienen siempre la misma forma. Con el request_id encontramos tu caso rápidamente.
Límites por clave: un límite por minuto y una cuota diaria (cabeceras X-Quota-Limit, X-Quota-Remaining). Una búsqueda se puede paginar como máximo hasta 500 resultados: acótala por lugar, radio, categoría o periodo. No se permite la extracción masiva ni la reventa de los datos.
Reintentos seguros: envía la cabecera Idempotency-Key con POST /v1/checkout/sessions y POST /v1/bookings/{id}/cancel. Un reintento con la misma clave y el mismo contenido devuelve la primera respuesta (cabecera Idempotent-Replayed: true) en lugar de actuar dos veces. Las claves se guardan 24 horas.
| HTTP | code |
|---|---|
| 400 | parameter_missing, parameter_invalid, outside_opening_hours, duration_too_short, duration_too_long, multiple_listers, delivery_unavailable, pagination_limit |
| 401 | missing_api_key, invalid_api_key |
| 403 | missing_scope |
| 404 | resource_missing, route_not_found |
| 409 | ride_unavailable, already_canceled, processing, already_started |
| 429 | rate_limited, daily_quota_exceeded, too_many_failed_attempts – X-RateLimit-*, X-Quota-*, Retry-After |
{ "error": {
"type": "invalid_request_error",
"code": "ride_unavailable",
"message": "The ride is not available for this period and quantity.",
"param": "ride",
"request_id": "req_UqrIw8P5wzwT" } }SDK y herramientas
Clientes oficiales en un solo archivo, sin dependencias – cópialos en tu proyecto. Se encargan de la autenticación, la paginación, los reintentos con 429/5xx, las Idempotency-Keys y la verificación de webhooks.
const { ListNRide } = require("./listnride");
const lnr = new ListNRide({ apiKey: process.env.LNR_API_KEY });
for await (const ride of lnr.rides.all({ lat: 52.52, lng: 13.40, category: "e-bike" })) {
console.log(ride.name, ride.price.per_day);
}
const session = await lnr.checkoutSessions.create({ ride: "ride_…", from: "2026-11-17",
to: "2026-11-19", return_url: "https://your-site.com/thanks" }); // la Idempotency-Key se añade automáticamenteVersiones
La versión forma parte de la ruta. Dentro de /v1 solo añadimos campos – nada se elimina ni se renombra. Los cambios mayores aparecerán como /v2, y /v1 seguirá funcionando al menos 12 meses. La descripción legible por máquina (OpenAPI 3.1) está en /openapi.json y https://api.listnride.com/v1/openapi.json.
Cambios
Idempotency-Key para llamadas de escritura, colección de Postman, clientes oficiales para Node.js, PHP y Python.
Afiliación: enlaces de partner con
?aff=CODEyGET /v1/commissions.GET /v1/bookings, vista previa de cancelación yPOST /v1/bookings/{id}/cancel(scopebookings:cancel).Filtros de búsqueda (precio, altura, accesorios, zona del mapa …), escalas de precio, horarios, condiciones de cancelación, calendario de disponibilidad, opiniones y perfiles de arrendadores.
Lugares, clases de vehículos,
POST /v1/quotes, reserva por clase y cuota diaria por clave.Lanzamiento de v1: bicicletas, categorías, sesiones de pago, reservas, webhooks y modo de prueba.
Solicitar acceso
Cuéntanos tu web y lo que tienes en mente. Primero recibes una clave de prueba – la puesta en real llega tras una breve llamada.
partner@listnride.com