Multi-Modal Bike Directions API
Routing
Multi-Modal Bike Directions API
Get multi-step routes combining walking, bike-share, and personal bikes
POST
Multi-Modal Bike Directions API
Overview
Get comprehensive routing options that integrate bike-share availability. Returns multiple route alternatives including:- Personal Bike: Direct cycling route
- Docked Bike-Share: Walk to station → cycle → walk to destination. Pickup and drop-off stations are always in the same system — the drop-off is the nearest dock in the pickup station’s system.
- Docked E-Bike: Same shape as docked bike-share, but the origin station is the nearest one with at least one e-bike available, and the cycling leg is recalculated at a higher e-bike speed. Marked with
is_electric: trueon the route option. Systems whose fleet is e-bike-only (e.g. Lime London) produce only this variant, never a classic docked option. - Dockless Bike-Share: Walk to bike → cycle to destination (per provider)
Authentication
This endpoint requires an API key. Include it in theAuthorization header:
Request Body
array
required
Starting point as
[longitude, latitude]array
required
Ending point as
[longitude, latitude]boolean
Optional. Changes how the response is fanned out:
- Omitted (recommended for current clients). The server returns both
supersafe=trueandsupersafe=falsePersonal routes in a single response, tagged per-route (see thesupersafefield on each route alternative below). When the two variants are geometrically identical (short trips, or trips already entirely on safe roads), only thesupersafe=trueroute is returned — so clients may receive a single Personal route even withsupersafeomitted. Other option types (Docked, E-bike, Dockless) are returned once at the default regime (true) — they involve walking legs and rental lookups, so toggling for them still requires a refetch. The client filters the Personal pair locally when the user flips their SuperSafe toggle. - Provided (back-compat for older app builds). Every option type — Personal, Docked, E-bike, Dockless — is computed under exactly that regime and only that regime is returned. This matches the pre-multi-variant contract.
number
Optional event ID. If provided and the event has a route, includes the event route as a non-reroutable final cycling segment. Exception: when the referenced event has
is_solo_share = true, the host’s polyline is not appended — the response returns the same shape as a non-event request (direct origin → destination), with event metadata attached for context. Solo shares are live broadcasts, so a joining guest routes straight to the destination on their own optimal path instead of being detoured through the host’s start point.number
Optional destination ID for enriched destination information in response
string
Optional display name for the destination (used if
destination_id not provided). Stored on the analytics row as destination_name.string
Optional display name for the origin waypoint. Recorded verbatim on the analytics row as
origin_name. The mobile/web planner forwards the literal string "Current location" when the origin is the device GPS fix — passing it lets us split that funnel from named-origin searches in analytics.string
Optional wall-clock departure time the rider plans to leave, as
"YYYY-MM-DDTHH:mm" with no timezone offset (e.g. "2026-06-03T21:00"). The server interprets it in the route city’s timezone to decide day/night routing: when the picked time lands in the dark window (30 minutes after sunset to 30 minutes before sunrise at the origin) and the city has street-lighting data (London today), every option is tagged nighttime: true and the Personal option fans out its night variants (see the nighttime field below). A departure shortly before the dark window also triggers the night fan-out when the ride is expected to still be going at nightfall: the server estimates the ride’s duration as the origin→destination straight-line distance × 1.4 detour factor at cycling speed (15 km/h), and if departure + estimate lands inside the dark window at the destination, the Personal option fans out night-first exactly as if it were already night (a “dusk-crossing” ride). Cities without lighting data never go nighttime. Omit to time the route to now. An unparseable value returns 400.The departure time also drives time-based avoidances: operators can mark a road as avoided on a recurring weekly window (e.g. a Saturday street market) or a one-time date range (e.g. a festival closure; may span multiple days). When the departure falls inside such a window, every cycling leg (personal and bike-share, both SuperSafe and Direct) routes around the road; walking legs are unaffected. These windows never change the map’s safety coloring (routing-time only). One-time windows that are active at the departure time — or open within the next 60 minutes — and lie near a returned option’s cycling legs are additionally surfaced on that option as disruptions (see below), so clients can explain the detour.Example: "2026-06-03T21:00".string
Optional “navigate to parking” mode:
"ebike" or "regular". Any other value returns 400. When set, the Personal (“My bike”) option only changes shape — it becomes a cycling leg from the origin to the nearest qualifying parking spot near the destination, followed by a walking leg to the destination. The cycling leg’s endLabel pill is "Park bike"."ebike"searches designated rental e-bike parking bays (ebike_parking = true— the borough council bay datasets synced daily by refresh-parking). Use this when the rider is on a rental e-bike (e.g. a Lime) that must be parked in a bay. Gated by the destination borough’s rule: a bay is returned only when the destination falls inside a borough that mandates bay parking (ebike_parking_rule = 'bay_mandatory', surfaced asdestination_parkingbelow). In a free-floating borough — where you may park anywhere — no bay is returned and the route stays direct, so the rider is never sent to a designated bay in a neighbouring borough. The borough rule is reported indestination_parkingeither way."regular"searches regular cycle parking racks/stands (TfL survey + NYC DOT data). Not gated by the borough rule (a rack is never mandated) — always the nearest rack within radius.
"regular", or for "ebike" in a bay_mandatory borough, when no qualifying spot exists within 1,500 m the Personal option falls back to the plain direct cycling shape and carries parking: null — never an error. Clients should render a “no parking found near destination” notice off that field. For "ebike" in a free_floating borough the route is intentionally direct with parking: null; use destination_parking to tell the two apart (park-anywhere vs no-bay-found).Event interplay: when event_id resolves a real event route (the event has a route and is not a solo share), parking_type is ignored — the ride ends at the event, not a parking spot. Solo shares route direct and DO honor parking_type.string
Optional rental e-bike operator (London):
"lime" or "forest". Any other value returns 400. Only meaningful with parking_type: "ebike" — ignored otherwise.London’s operators serve different, non-identical borough sets (recorded in helloapp_area_operator_coverage). When operator is set, the e-bike bay search is restricted to bays inside a borough the operator serves. So when the destination is in a borough the operator doesn’t serve, the nearest legal bay sits at the operator’s coverage edge — the Personal option cycles to that bay and walks the rest, and destination_parking.covered is false. When the destination borough is served, behaviour is unchanged (nearest operator bay in a bay_mandatory borough; direct route in a free_floating one).Response
Returns an envelope{ search_id, routes, destination_parking } where routes is an array of route alternatives, each containing a sequence of steps.
number | null
Identifier of the analytics row written for this request (
helloapp_route_search.id). Pass this back when persisting a route to helloapp_routehistory.route_search_id so the search → ride funnel can be computed. May be null if the server failed to persist the analytics row; in that case the routes are still valid — analytics writes are best-effort.object | null
The destination borough’s rental e-bike parking rule. Set only when the request used
parking_type: "ebike" and the destination fell inside a bounded area (a London borough). null for "regular" / no parking_type, or when the destination is in no bounded area (outside London)."bay_mandatory"— the borough requires ending a hire in a designated bay; the Personal option ends at the returnedparkingbay (or falls back to direct withparking: nullif none is within 1,500 m)."free_floating"— you may park anywhere in the borough; no bay is returned and the Personal option stays direct. Render this as “park anywhere in ” rather than a “no parking found” error.
operator, two more fields are present:operator— the operator ("lime"|"forest") the coverage was resolved for.covered— whether that operator serves this borough. Whenfalse, the operator can’t legally end a hire here, so the Personal option ends at a bay inside the operator’s covered area (up to 5,000 m away) and walks the remaining distance to the destination. Render as “Outside area — park at and walk the rest”.
array
Array of route alternative objects
Request Example
Basic Multi-modal Route
cURL
JavaScript
With Super-Safe Routing and Event
JavaScript
Python
Response Example
Route Types Explained
Personal Bike Route
- Direct cycling route from origin to destination
- No walking segments unless event route is involved
- Always appears first in results
Personal Bike Route with Parking (parking_type set)
- Step 1: Cycle from origin to the nearest qualifying parking spot within 1.5km of the destination (
"Park bike"pill at the transition) - Step 2: Walk from the parking spot to the destination
- The chosen spot is returned in the option’s
parkingfield;parking: nullmeans nothing was found and the option fell back to the direct shape parking_type: "ebike"→ designated rental e-bike bays only;"regular"→ racks/stands only- Ignored for event routes (except solo shares); all other option types are unchanged
Docked Bike-Share Route (GBFS/TFL)
- Step 1: Walk from origin to nearest station with bikes available
- Step 2: Cycle from origin station to destination station
- Step 3: Walk from destination station to final destination
- Only included if suitable stations are found within 1.5km
Docked E-Bike Route
- Same three-step shape as classic docked bike-share.
- Origin station is the nearest one with
num_ebikes_available > 0. Destination station logic is unchanged (any open dock). - Cycling-leg duration is recalculated at the e-bike speed (6.11 m/s / ~22 km/h) instead of the regular cycling speed.
- Marked with
is_electric: true. Name is${system_name} E-bike(e.g."Citi Bike NYC E-bike"). - Included only when at least one nearby station within 1.5km has an e-bike available; otherwise omitted entirely.
Dockless Bike-Share Route
- Step 1: Walk from origin to nearest available bike
- Step 2: Cycle from bike location to destination
- One route per provider (Lime, Spin, etc.)
- Only included if bikes are found within 1.5km
Event Integration
Whenevent_id is provided and the event has a defined route:
- All cycling segments route to the event’s starting point
- An additional non-reroutable cycling step is added for the event route
- Event details are included in each route alternative
- Final walking segment (for docked bike-share) goes to nearest station to event endpoint
Solo shares
Events flaggedis_solo_share = true are live broadcasts rather than coordinated rides. For these, the multi-modal response returns the same shape as a non-event request — Personal is one cycling leg from origin to destination, docked is the standard walk → cycle → walk, dockless is walk → cycle. The host’s polyline is not added as a step, and no leg is marked non-reroutable. The event field is still populated on each option so clients can label the route as “joining ‘s ride.” The host’s actual route stays visible on the event page itself, where clients render it from event.route_polyline directly.
Step-level semantics
Each leg’sroute.routes[0].legs[0].steps follows the same turn-by-turn shape as the directions endpoint. In particular:
step.distanceis the length of the road after the maneuver — to render “in X metres, ”, use the previous step’sdistance.step.maneuver.locationequalsstep.geometry.coordinates[0].step.maneuver.bearing_beforeis the heading at the end of the previous step’s geometry (approaching the maneuver);bearing_afteris the heading at the start of this step’s geometry. On a curved approach these are not equal — do not assume continuity between consecutive steps.- The final step of the final leg’s instruction is
"Arrive at destination". - Each step carries the same
voiceInstructions[]/bannerInstructions[](incl. the stackedsub) as the directions endpoint. These are generated per leg independently — the “then” lookahead never crosses a leg boundary, because every leg ends in its own arrive step.
Notes
- Routes are automatically filtered to remove alternatives >3x slower than the personal bike route
- Bike availability is fetched in real-time from GBFS feeds and provider APIs — except for systems with
has_live_availability: false(e.g. Lime London’s Hackney bays), which have no availability feed at all: those options assume unlimited capacity and clients must warn that availability isn’t live - Distance calculations use the Haversine formula
- Walking speed assumed: 1.4 m/s (5 km/h)
- Cycling speed assumed: 4.17 m/s (15 km/h)
- E-bike cycling speed assumed: 6.11 m/s (22 km/h) — applied only to options with
is_electric: true - Maximum bike search radius: 1500 meters
- Results are ordered: Personal first (twice when the request omits
supersafe:supersafe=truethensupersafe=false— collapsing to once when the two variants are geometrically identical; once otherwise), followed by Docked bike-share (classic), Docked bike-share (e-bike, when available), and Dockless bike-share. Docked/E-bike/Dockless are always single-regime per response — at the regime the caller pinned, ortrueif they didn’t. Clients filter on thesupersafefield to render one Personal variant at a time.
Error Responses
object
Invalid coordinates
object
No viable routes found
object
Rate limit exceeded. Includes a
Retry-After response header with the number of seconds until the current window closes.object
Server error
Rate Limits
Requests are rate-limited. Authenticated requests (valid user session) are allowed a higher rate than anonymous requests. Multi-modal is subject to a stricter rate limit than directions because each request runs multiple internal routing queries. When the limit is exceeded, the response is429 Too Many Requests with a Retry-After header indicating the number of seconds to wait before retrying, and a matching retry_after_seconds field in the body. Clients should honor Retry-After.
Performance
- Typical response time: 800-1500ms
- Bike availability queries: ~100-200ms
- Routing segments computed in parallel
- Results cached when possible