Where Am I now ?

The "breadcrumbs" element is always present, on top of the page.

It shows you where you are on the lab.

It enables you to reach any page on the lab.

What OTP 2 can express, and how to expose it to travellers

R&D review, September 2026. Scope: the OTP 2 transmodel trip query as deployed at BMC (OTP 2.9 for the two recipes since 2026-09-10; the older planner-v2 stays on 2.6), the planner-v2-1 recipe that exposes it one to one, and the mode-selection mockups. Legend for the "exposed" columns: yes = available today · partly = with a limitation · no = not available. The "→ snippet" buttons put the example in the query string on the right.

1. The engine's model in one paragraph

A trip is [access → transit → egress] computed together with [direct]. Each street slot takes one StreetMode; the transit part takes a set of TransportMode (optionally narrowed by submodes). On top of that, OTP accepts who may operate the legs (banned / whitelisted authorities, lines, rental networks), where the trip must pass (pass-through points, via trips), when (date-time, arrive-by, search window, paging), for whom (wheelchair, speeds, reluctances, slacks, transfer limits) and how many results (numTripPatterns, itinerary filters). Everything below is a way of asking a traveller one of those questions.

2. Street modes: the ten values and where they may go

StreetModeMeaningaccessdirectegressIn the presets?
footwalkyesyesyesdefault
bicycleown bike, kept for the whole tripyesyesyesBike
bike_parkown bike, left at the stopyesyesnoBike & park
bike_rentalshared bike (docked or free floating)yesyesyesRental bike
scooter_rentalshared scooteryesyesyesRental scooter (secondary), Scooter mode in 4a/4b
carown car, door to doornoyesnoCar (2 queries), Just drive
car_parkown car, park & rideyesyesnoCar P&R (direct foot, see the filter section)
car_pickuptaxi, kiss & ride, drop-offyesyesyescustom only (asymmetric)
car_rentalshared caryesyesyesRental car
carpoolshared ride with a driver (OTP 2.9, no carpool data in the BMC graph yet)yesyesyesnone, custom only
flexibleon-demand transport (flex data)yesyesyesOn-demand transport (secondary)

Gaps this table showed, now addressed in the mockups:

  • Scooter: Rental scooter preset, and a Scooter mode (rental only) in the toggle designs 4a/4b.
  • Kiss & ride / taxi and other asymmetric trips are deliberately not presets: they are what custom is for (three selects, one query). Rental modes already fall back to walking when no vehicle is around (verified live: bike_rental ×3 gave foot → rail → foot → bicycle(rented) → foot), and car_pickup only needs a direct foot to keep its transit shapes.
  • Return trips: the two park presets carry the note "return trip: reverse the shape, pick the vehicle up at the same stop". A UI responsibility, OTP cannot enforce it.
  • Also added: Transit only (omit directMode), Just drive and Just cycle (transportModes= empty), On-demand transport (where flex data exists).
  • Rental presets show two shapes from one query, exactly like rental bike did live.

3b. The transit-vs-street filter changes the query count

Verified live on the BMC OTP2 (2026-09-09, Grand-Place → Meiser, next morning 8:00):

QueryResult
bicycle / bicycle / bicycle5 itineraries: the pure bike trip and 4 bike → rail → bike. One query gives both shapes.
foot / car / foot1 itinerary: car only. The transit alternatives are gone.
foot / car / foot + itineraryFiltersDebug=listAll48 itineraries: the car trip plus the transit ones tagged transit-vs-street-filter.
car_park / car / foot1 itinerary: car only, no park & ride shape.
car_park / foot / foot4 itineraries: car → bus → walk park & ride shapes plus a walk.
car_pickup / foot / footdrop-off shapes (foot → car → foot → bus → foot) plus transit.
bike_park / bicycle / footbike direct plus bike → rail park shapes.
scooter_rental ×3, Antwerp Centraal → MAS5 itineraries: foot → scooter(lime) direct and scooter → tram, bus → scooter shapes. One query, both shapes.
scooter_rental ×3, Brusselswalking and transit only: no scooter feed in the Brussels part of the graph (Voi bikes are there, Lime scooters only in Antwerp).

OTP's itinerary filter (removeTransitWithHigherCostThanBestOnStreetOnly, on by default, server-wide) drops every transit itinerary that costs more than the best street-only one. A fast direct mode (car, taxi) therefore hides the transit shapes of the same query; bike does not, at city distances.

  • Car (car trip and transit alternatives) is 2 queries: foot / foot / foot and foot / car / foot with transportModes= empty; the car preset does both in one document.
  • Park & ride and dropped off keep foot as direct mode, otherwise the direct car kills the shape the preset is about.
  • One-query alternative: itineraryFiltersDebug=listAll returns the filtered itineraries tagged in systemNotices; a client can keep them and hide the tag. Product-grade version: a keepTransitAlternatives flag that maps to listAll and strips the notices server side.
  • Data caveat: in Brussels scooter_rental and car_rental returned no rental legs, OTP silently falls back to walking; scooters do show up in Antwerp (Lime). A preset should say so when its vehicle is not available; the rental network lists are how a customer would scope that.

3. Trip shapes and the unset slots: no polyfill

OTP 2 defaults the three street modes to foot when the modes input is omitted altogether, and leaves the missing ones unset when some are given (an unset slot produces no itinerary of that shape). A first version of planner-v2-1 polyfilled the missing slots with foot; that was dropped: the API keeps the OTP 2 semantics and only rejects the combinations that cannot return anything.

Traveller intentWhat to sendResult
OTP 2 defaultsno mode parameter at allfoot / foot / foot
Transit only, no door to door itineraryaccessMode=foot&egressMode=foot, no directModetransit itineraries only (verified live)
Door to door onlydirectMode=bicycle, nothing elseno transit at all, the direct itinerary only
Street only, with transit explicitly offdirectMode=car&transportModes=direct itinerary only
Transit modes without a first/last miletransportModes=rail alone400: accessMode and egressMode required with transportModes
One of access / egress aloneaccessMode=bike_rental alone400: they go together

The preset recipe follows the same model: its transit-only preset leaves the direct slot unset. No special value such as none is needed.

4. Transit modes and submodes

OTP offersplanner-v2-1MockupsSensible exposure
transportModes bus / tram / metro / railyesrestrict (view 1b)Checkboxes in a settings screen, rarely per search. Default: all.
transportSubModes (e.g. rail: local, regionalRail, longDistance, international)nonoWorth exposing for rail: "no IC/ICE", "regional trains only" are real requests (fares). Needs a look at which submodes the BMC graph actually carries.
Empty transportModes = no transityesvia the "just drive / just cycle" presetsA "no transit" switch next to the mode row, or the direct-only presets.

5. Operators, lines, networks: the "who" dimension

OTP offersplanner-v2-1UX patternVerdict
banned.authoritiesyes (bannedAgencies, BMC ids)settings: operator checkboxes (TEC, De Lijn, STIB-MIVB, SNCB)keep
whiteListed.authoritiesyes, whiteListedAgencies (BMC ids)"only SNCB" for a partner appdone, verified live (only STIB-MIVB lines came back)
banned.lines / whiteListed.linesyes, bannedLines / whiteListedLines (OTP ids, nmbssncb:gr:nmbssncb:1064)"avoid line 71" after a disruption, or a partner that only sells some linesdone, verified live (the banned line left the results)
banned.quays / quaysHardno, on purpose"avoid this station"not implemented in OTP 2.9 either (the schema says so in the field description); nothing to expose until the engine does
banned.serviceJourneysyes, bannedServiceJourneys (OTP ids, nmbssncb:gt:nmbssncb:…)hide one specific tripdone, verified live
banned.rentalNetworks / whiteListed.rentalNetworksyes, bannedRentalNetworks / whiteListedRentalNetworks (GBFS network ids: voi, villo, lime, cambio)"I have a Villo subscription, not Dott"done, verified live: whitelisting voi gave Voi legs only, banning it switched to Villo
filters (select / not, on lines, authorities, transport modes)no, on purposea JSON object, and when present OTP ignores banned, whiteListed and transportModes (schema description); the separate params above express the same without JSON
relaxTransitGroupPriorityyes, relaxTransitGroupPriorityRatio (≥ 1) and relaxTransitGroupPriorityConstant (seconds)keep competing operators in the resultsdone; the OTP Cost scalar must be sent as a string in variables (an integer makes OTP throw)

6. Places: from, to, and in between

OTP offersplanner-v2-1UX patternVerdict
from / to as coordinatesyes, fromPlace / toPlace, requiredgeocoderkeep
from / to as a place id (stop, station)works, undocumented: fromPlaceId / toPlaceId next to the required coordinates take precedence (OTP keeps the coordinates as fallback if the place is unknown). Kept out of the doc so fromPlace / toPlace stay plainly required (OpenAPI cannot express "one of these two"); documented the day a consumer needs it"from Brussels-Central", departure boards, station pagesverified live: itineraries start with the train, no walking leg
passThroughPointsyes, repeat the param once per point, comma separated alternative place ids per point"via Gare du Midi"done, verified live: every itinerary passed the quay
viaTrip (separate query: stops with min/max slack, per segment modes)nomulti-stop trips, "drop the kids then work"out of scope for v2-1 (different query), worth a planner-via recipe later

7. Time

OTP offersplanner-v2-1UX patternVerdict
dateTime, arriveByyesdepart at / arrive bykeep
searchWindowyesrarely user-facingkeep for integrators
pageCursor (next / previous)yes, one cursor in both recipes (the preset recipe wraps the cursors of its queries in it, 9c)"earlier" / "later" buttonskeep: the one-cursor story is the main UX win over planner-v2
timetableViewyestimetable vs "best trips" toggleexpose in advanced settings only
bookingTimenoon-demand bookingonly with flexible

8. The traveller profile: settings that outlive one search

These map naturally to a profile screen (saved once) rather than to the search form; the user preferences of the mockups are exactly that.

OTP offersplanner-v2-1UX pattern
wheelchairAccessibleyesaccessibility switch; also should drive foot speed and stairs avoidance (not exposed by OTP transmodel)
walkSpeed, bikeSpeedyes"walking pace: slow / normal / fast" presets mapping to m/s, never a raw number
walkReluctance, waitReluctanceyes"less walking" / "less waiting" sliders, or three radio presets
maximumTransfers, maximumAdditionalTransfers, transferPenalty, transferSlackyes"fewer transfers" preference, "I need more time to transfer" (accessibility)
boardSlack*, alightSlack*yes, json or split per transit modeops tuning, not traveller UI
bicycleOptimisationMethod, triangleFactorsyes"safest / flattest / fastest route" for cyclists (Bike preset only)
useBikeRentalAvailabilityInformationyes"only stations with bikes available now" switch
maxDirectDurationForMode, maxAccessEgressDurationForModeboth yes, json or split per street mode (maxAccessEgressDurationFoot=PT10M; no …Car variant, car is direct only)"walk at most 15 min to the stop", the single most requested preference in planner UIs. Caveat from OTP itself: both are performance caps that can only lower the server defaults (45 min access/egress, 4 h direct), a higher value is an OTP error (verified live: PT10H for foot gives "Invalid duration for mode WALK. The value 10h is not greater than the default 45m"), and they are hard cut-offs: a 5 min foot cap on a suburban origin returns no itinerary at all (noTransitConnection) where the uncapped search had five. Pair the control with an empty-result fallback; the soft lever stays walkReluctance
accessEgressPenaltynoops tuning
ignoreRealtimeUpdates, include*Cancellationsyesdebug / ops, hide from travellers
numTripPatterns, itineraryFilters.*numTripPatterns and debug yes, cost limits no; in the preset recipe numTripPatterns is a budget split between the queries (9d)integrators only
locale (OTP side, for names)BMC locale handles translationskeep ours

9. Is the option set complete? Check against the intents

Traveller intentCovered byNote
Walk & transitall optionsdefault
Own bike / own car, kept all the waypresets, togglesown car = 2 queries (filter section)
Own vehicle left at the stop (P&R, B&R)presets, "park" togglereturn-trip reversal missing everywhere
Shared bike / carpresets, "rent" togglenetwork choice: subscriptions (1a)
Shared scootersecondary preset, 4a/4b moderental only; no scooter data near the test origin
Dropped off (taxi, kiss & ride)customdirect must stay foot
Different first and last milepark presets, otherwise customrental modes walk by themselves when nothing is available, so bike_rental both sides already covers "walk, then a rental bike"
Street only, no transitJust drive, Just cycletransportModes= empty
Transit onlysecondary preset, planner-v2-1 by omitting directModeunset slots section
On-demand transportsecondary presetonly where flex data exists
Multi-stop, vianot expressibleseparate recipe
Avoid an operator / line / station / tripoperators, lines, trips, rental networks (ban and whitelist)stations: not implemented by OTP 2.6
AccessibilityAPI yes, mockups yes (1a user preferences)global settings screen, applies to every query
Comfort preferences (walking, transfers, pace, caps)API yes, mockups yes (1a: 16 controls in collapsed categories, each one parameter)subscriptions = the ten GBFS networks of the manifest OTP 2 reads + the four GTFS operators, mapped to whiteListedRentalNetworks / whiteListedAgencies only when the traveller asks to hide the rest, since the OTP 2 trip query has no "preferred" operator, only banned / whiteListed

9b. The preset recipe: planner-v2-1-preset

Same parameters as planner-v2-1 minus accessMode, directMode, egressMode (transportModes stays: it narrows the transit part of every query of the preset), plus:

paramrole
modePresetone of the ids below, default public-transit; expands to one or several trip queries in a single OTP2 GraphQL document (planMany), every other parameter shared
pageCursorthe same parameter as in planner-v2-1: one opaque cursor pages the whole preset, whatever its number of queries (9c)
numTripPatternsthe same parameter too, read as a budget for the transit part of the merged list and split between the transit queries (9d)

The response adds modePreset (id, label in the requested locale, queries keys, skippedQueries on the next pages) and the cursor pair of planner-v2-1 (plan.nextPageCursor / plan.previousPageCursor, also cursors.trip), itineraries merged and sorted like planner-v2.

idqueries (access / direct / egress)
public-transitwalk: foot / foot / foot
bikebike: bicycle / bicycle / bicycle
carwalk: foot / foot / foot · carOnly: foot / car / foot, no transit
bike-park-ridebikePark: bike_park / bicycle / foot
car-park-ridecarPark: car_park / foot / foot
bike-rentalbikeRental: bike_rental / bike_rental / bike_rental
car-rentalcarRental: car_rental / car_rental / car_rental
scooter-rentalscooter: scooter_rental / scooter_rental / scooter_rental
transit-onlytransitOnly: foot / (unset) / foot
car-onlycarOnly: foot / car / foot, no transit
bike-onlybikeOnly: foot / bicycle / foot, no transit
on-demandflexible: flexible / flexible / flexible
bike-multimodalbike: bicycle / bicycle / bicycle · bikePark: bike_park / bicycle / foot · bikeRental: bike_rental / bike_rental / bike_rental
car-multimodalcarOnly: foot / car / foot, no transit · carPark: car_park / foot / foot · carRental: car_rental / car_rental / car_rental
multimodalwalk: foot / foot / foot · bike: bicycle / bicycle / bicycle · bikePark: bike_park / bicycle / foot · bikeRental: bike_rental / bike_rental / bike_rental · scooter: scooter_rental / scooter_rental / scooter_rental · carOnly: foot / car / foot, no transit · carPark: car_park / foot / foot · carRental: car_rental / car_rental / car_rental · carPickup: car_pickup / foot / foot · flexible: flexible / flexible / flexible (demo only, about 17 s live)

Labels exist in en, fr, nl, de. Verified live: car-multimodal merges car, park & ride and rental car itineraries from one document; transit-only returns transit itineraries and no walk-only one: leaving the direct slot unset is the transit-only mechanism, in both recipes.

  • The merge (planner-v2's mergeOtp2TripResponses) dedupes on every leg's mode + aimed times + end points (or otp leg id for transit legs): two queries of a preset returning the same itinerary (bike-multimodal: the direct bicycle trip of the own bike and the parked bike queries) keep one. Fixed 2026-09-10: the key was overridden by the legs' own id, null on street legs in OTP 2.9, so planner-v2 and the presets showed the duplicate.
  • In a planner-v2 style fan out, the plain transit query of the multimodal profile sends modes: { transportModes } without access/egress modes, which OTP2 treats as no access at all: that query returns zero itineraries (verified live with the same variables the production builder sends). Flagged as a separate task.

9c. One cursor for many queries: how the preset recipe pages

What the client sees: planner-v2-1-preset pages exactly like planner-v2-1. One pageCursor parameter in, plan.nextPageCursor / plan.previousPageCursor (also cursors.trip) out, whatever the number of OTP 2 queries behind the preset. "Earlier" and "Later" are one link each, the client never learns the query keys. Try it: then page with the Earlier / Later buttons of the planner page.

What OTP 2 gives us (verified live on the staging OTP 2.9, 2026-09-10, Brussels Central → Leuven, next day 08:00)

QueryCursorGiven the cursor of the transit queryConsequence
foot / foot / foot (transit)a next and a prev cursor, window 60 min on page 1 then 40 minpage 2: 08:31 → 09:11, page 3: 08:43 → 09:26, "prev": 07:43 → 08:20pages by itself
foot / car / foot, transportModes= empty (street only)none: nextPageCursor null, metadata null0 itinerariescannot be paged, and must not receive a cursor
two transit queries of one preset (bike and bikeParkRide)one pair each, windows that drift apart (40 min vs 60 min on page 2)not tried: a cursor carries the "page cut" of its own query (departure, arrival, transfers and cost of the last itinerary of the page), another query would drop or repeat itinerariesone OTP cursor per transit query, no sharing

An OTP 2 cursor is a base64 token, readable once decoded: 2|NEXT_PAGE|2026-09-11T06:31:42Z||40m|STREET_AND_ARRIVAL_TIME|false|<cut departure>|<cut arrival>|1|3804|41045|. The direct street search still runs on the next pages (OTP's RoutingWorker always calls it), but the paging filter removes the street itinerary because it sorts before the cut: inside one query OTP itself treats the door to door trip as a first page thing.

The policy

RuleWhy
One opaque cursor per response: base64url of { v, preset, dir, cursors: { <queryKey>: <otp cursor> }, dateTime }the client keeps one contract for both recipes; the query keys and the number of queries can change without breaking anyone; it is versioned (v) and bound to its modePreset (another preset is a 400)
Every transit query pages with its own OTP cursorthe only correct use of an OTP cursor
A transit query without a cursor in the bundle reruns from dateTime, the start of the page\'s time window (OTP metadata.nextDateTime / prevDateTime of the first transit query)an empty page or a preset that changed between two deploys keeps that query in step with the others instead of restarting it from page 1 (verified live: the rerun query answered 08:41 → 09:17 next to the paged ones)
Street-only queries (transportModes: []: the direct car or bike trip) run on the first page only and are listed in modePreset.skippedQueriesOTP has no traffic: the car trip at 08:44 is the car trip at 08:00; rerunning it is a wasted query and a row that jumps around the list; and it is what OTP does inside one query
Presets without transit (-only, or transportModes= empty) return no cursornothing to page

Why this and not…

  • One JSON parameter keyed by query key (the first version, pageCursors): the client had to know which preset runs which queries, the contract changed with every preset edit, a single pageCursor was refused on multi query presets, and it was JSON in a query string.
  • Give the transit cursor to every query: proven to return nothing for the street-only queries (table above) and wrong for the other transit queries (the page cut belongs to one query).
  • Re-date the street-only queries on every page: works (verified live, car at 08:44) but buys nothing, see the policy.
  • Force the same searchWindow on every query to align the pages: OTP takes the window from the cursor when paging, and a shared window would still not share the cut.
  • Only allow paging on single query presets (what the mockups assumed): it made the multi query options second class for no engine reason.

What stays imperfect

  • The transit queries of a preset do not cover one clean time span per page: OTP sizes the window per query (live: page 2 of bike-multimodal was 08:42 → 09:15 for the own bike and 08:29 → 08:37 for the parked bike). The merged list is still sorted, and planner-v2 lives with the same drift.
  • The dedupe across queries (leg modes + times + coordinates) cannot dedupe across pages.
  • The cursor is about 350 characters per transit query (the OTP token is most of it); a three query preset is under 900 characters, fine for a query string.

For the UX options

Multi query presets (car, bike-multimodal, car-multimodal, the toggle designs) page like single query ones, so their remaining cost is the query count: about 2 s for car and 7 s for bike-multimodal live, against 1 to 2 s for one query. Option pages that used to warn about "as many cursors as queries" now only weigh latency; how many rows a multi query preset shows is the budget rule of 9d.

Source

  • Backend: src/utils/api/otp/preset-page-cursor.ts (encode, decode, build), parsePlannerV21PresetParams in src/utils/api/routing/parse-planner-v2-1-params.ts (which queries run on a page), routingPlannerV21PresetFactory.ts (the response), the pageCursor description of the preset OpenAPI.
  • Lab and demo mirror: packages/data-services/src/routing/planner-v2-1/preset-page-cursor.ts and planner-v2-1.server.ts, same format and policy.

9d. How many rows: a budget per shape

What the client sees: numTripPatterns keeps its name and its meaning of "at most this many transit itineraries", but on the preset recipe it is the budget of the merged list, whatever the number of OTP 2 queries behind the preset. Omitted, the recipe picks a sane default; the split is reported in modePreset.numTripPatterns. Try it:

The problem

OTP's numItineraries is per query. Passed through unchanged, a preset returned it once per query (three times 5 for bike-multimodal), and omitted it was OTP's server value of 50 per query, tamed only by the search window (12 to 15 rows live). The obvious fix, cutting the merged list to N after the merge, is wrong with paging: an OTP cursor encodes the page cut at the last itinerary OTP returned, so a row trimmed from page 1 never shows up on page 2 either. The only safe knob is what each query asks OTP for.

The policy

RuleWhy
numTripPatterns is the budget of the transit part of the merged first pageone number, one meaning, whatever the preset
Street-only queries (transportModes: [], the direct car or bike trip) always ask for 1, on top of the budgetOTP returns one direct trip for them at most; they do not compete with the transit shapes
The transit queries split the budget in preset order (the bare vehicle first, the variants after): floor(N / t) each, the remainder to the first ones, never below 2two is the least a query with its own direct trip plus a transit alternative needs, and it keeps OTP's dynamic search window from collapsing
Omitted: 4 for one transit query (the planner-v2-1 default too), 3 per transit query otherwisefour rows is the single query default we wanted; three per shape is the diversity floor, two reads thin once one of them is the direct trip
The split covers the whole preset and is reported in modePreset.numTripPatternsthe next pages ask each query for the same count as the first one (the cursor rule already requires identical parameters), and the client sees what it asked for without knowing the rule
PresetQueriesOmittednumTripPatterns=4numTripPatterns=8
public-transit1 transit448
cartransit + car4 + 14 + 18 + 1
bike-multimodal3 transit3, 3, 32, 2, 2 (floor)3, 3, 2
car-multimodalcar + 2 transit1 + 3, 31 + 2, 21 + 4, 4
multimodalcar + 9 transit (demo)1 + 9 × 31 + 9 × 21 + 9 × 2

Why a budget per shape and not a global top N

  • Paging, as above: a cut after the merge loses rows for good.
  • A global cut by arrival time lets one shape crowd the others out: plain transit usually arrives before a park & ride trip, so "Car, all options" would show no park & ride at all. A quota per shape is a diversity guarantee, which is what a multi query preset is for.
  • Over-fetching and trimming (ask for more, keep N) has the same paging problem and costs OTP time for rows that are thrown away.

Caveats

  • numTripPatterns is a maximum for OTP, not a target: a short search window returns fewer, and the dedupe across queries can lower the total (the direct bike of the own bike and parked bike queries counts once).
  • A transit query's own direct trip counts inside its quota (own bike: one bike trip plus bike & rail alternatives).
  • Small quotas mean short pages: OTP sizes the search window to reach the quota, so page 2 of a quota of 2 can cover a quarter of an hour. Earlier / Later still work, they just move in smaller steps.
  • planner-v2-1, the single triplet recipe, defaults to 4 as well: the one default the recipe injects over OTP's semantics (OTP's own is 50, too many for a planner ui). Pass numTripPatterns explicitly for anything else.

Source

  • Backend: src/utils/api/otp/preset-num-trip-patterns.ts (the split and the defaults, with its test), applied in parsePlannerV21PresetParams, reported by routingPlannerV21PresetFactory.ts, described on the preset's numTripPatterns parameter in the OpenAPI.
  • Lab and demo mirror: packages/data-services/src/routing/planner-v2-1/preset-num-trip-patterns.ts, used by planner-v2-1.server.ts.

10. Recommendations, in order

  1. API, small: done on 2026-09-09: whiteListedAgencies, bannedLines / whiteListedLines, bannedServiceJourneys, bannedRentalNetworks / whiteListedRentalNetworks, fromPlaceId / toPlaceId (undocumented), passThroughPoints, relaxTransitGroupPriorityRatio / Constant, maxAccessEgressDurationForMode (+ per-mode split), all as separate params (no JSON), in both recipes (enforced by a test), transportModes included in the preset recipe since 2026-09-10. Still open: keepTransitAlternatives (one-query car preset, maps to listAll and strips the notices).
  2. API, medium: the two recipes run on OTP 2.9 since 2026-09-10 (via replaces passThroughPoints, bikePreferences groups the bike settings, carpool is a new street mode, greenways is gone from the bike optimisation methods); rail transportSubModes once we know what the graph carries; filters deliberately not exposed (JSON, and it disables the banned/whitelisted params).
  3. UX, presets: done in the mockups (scooter, transit only, just drive, just cycle, on-demand; asymmetric trips stay in custom); the return-trip reversal for park presets is still only a note, it needs a real design.
  4. UX, structure: split the form in two: trip intent (mode presets or toggles, per search, one query) and traveller profile (operators, rental networks, accessibility, pace, max walk, transfers; saved once, applied to every query). Done in the mockups: option 1 is Global settings with 1a user preferences and 1b routing settings; both are pieces of the profile, not an alternative to the presets.
  5. Keep the one-query rule: anything that needs several triples (custom with lists, both toggles checked) is a client concern (parallel calls shown as tabs, not merged) or a multimodal preset of the preset recipe.
Command Palette
?
LabData/en/data
ApidateAPI params in:BMC v1BMC v2OTP 1ApidateTimeAPI params in:BMC v1BMC v2OTP 1OTP 2OTP 2.9
LabDataGtfs/en/data/gtfs
LabDataGtfs RT/en/data/gtfs-rt
LabDataGbfs/en/data/gbfs
LabDataRedis/en/data/redis
LabMigrating to OTP2Mode selection UX1a User preferences/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UX4a Rent / park toggles/en/planner/migrating-to-otp2/mode-ux
LabDataGtfsAgencies/en/data/gtfs/feed/agencies/
LabDataGtfsRoutes/en/data/gtfs/feed/routes/
LabDataGtfsTrips/en/data/gtfs/feed/trips/
LabDataGtfsStops/en/data/gtfs/feed/stops/
LabDataGtfsTrip Patterns/en/data/gtfs/trip-patterns
LabDataGtfsGtfs Archives/en/data/gtfs/archives
LabDataGtfs RTAlerts/en/data/gtfs-rt/feed/alerts
LabDataGtfs RTTrip Updates/en/data/gtfs-rt/feed/trip-updates
LabDataGbfsAgencies/en/data/gbfs/agencies
LabDataGbfsStations/en/data/gbfs/stations
LabDataGbfsStation Statuses/en/data/gbfs/station-statuses
LabDataGbfsFree Floating Bikes/en/data/gbfs/free-floating-bikes
LabDataGbfsMap/en/data/gbfs/map
LabDataRedisredisCacher/gtfsRtAlertsDeLijn/en/data/redis/redisCacher/gtfsRtAlertsDeLijn
LabDataRedisredisCacher/gtfsRtAlertsEurostar/en/data/redis/redisCacher/gtfsRtAlertsEurostar
LabDataRedisredisCacher/gtfsRtAlertsNmbsSncb/en/data/redis/redisCacher/gtfsRtAlertsNmbsSncb
LabDataRedisredisCacher/gtfsRtAlertsStibMivb/en/data/redis/redisCacher/gtfsRtAlertsStibMivb
LabDataRedisredisCacher/gtfsRtAlertsTec/en/data/redis/redisCacher/gtfsRtAlertsTec
LabDataRedisredisCacher/gtfsRtAlertsTecRaw/en/data/redis/redisCacher/gtfsRtAlertsTecRaw
LabDataRedisredisCacher/gtfsRtTripUpdatesDeLijn/en/data/redis/redisCacher/gtfsRtTripUpdatesDeLijn
LabDataRedisredisCacher/gtfsRtTripUpdatesEurostar/en/data/redis/redisCacher/gtfsRtTripUpdatesEurostar
LabDataRedisredisCacher/gtfsRtTripUpdatesNmbsSncb/en/data/redis/redisCacher/gtfsRtTripUpdatesNmbsSncb
LabDataRedisredisCacher/gtfsRtTripUpdatesStibMivb/en/data/redis/redisCacher/gtfsRtTripUpdatesStibMivb
LabDataRedisredisCacher/gtfsRtTripUpdatesTec/en/data/redis/redisCacher/gtfsRtTripUpdatesTec
LabDataRedisredisCacher/gtfsRtTripUpdatesDeLijnRaw/en/data/redis/redisCacher/gtfsRtTripUpdatesDeLijnRaw
LabDataRedisredisCacher/gtfsRtTripUpdatesEurostarRaw/en/data/redis/redisCacher/gtfsRtTripUpdatesEurostarRaw
LabDataRedisredisCacher/gtfsRtTripUpdatesNmbsSncbRaw/en/data/redis/redisCacher/gtfsRtTripUpdatesNmbsSncbRaw
LabDataRedisredisCacher/gtfsRtTripUpdatesTecRaw/en/data/redis/redisCacher/gtfsRtTripUpdatesTecRaw
LabDataRedisredisCacher/pointsByLinesStibMivb/en/data/redis/redisCacher/pointsByLinesStibMivb
LabDataRedisredisCacher/waitingTimesStibMivb/en/data/redis/redisCacher/waitingTimesStibMivb
LabSearch/en/search
LabPlan/en/planner-home
LabPlanPlanner v1/en/planner-tec-app
LabPlanPlanner v2/en/planner-v2-bmc
LabPlanOTP 1/en/planner
LabPlanOTP 2/en/planner-v2
LabPlanOTP 2.9/en/planner-otp-2-9
LabPlanPlanner v2.1/en/planner-v2-1
LabPlanPlanner v2.1 preset/en/planner-v2-1-preset
LabPlanMigrating to OTP2/en/planner/migrating-to-otp2
LabPlanMigrating to OTP2Get started/en/planner/migrating-to-otp2
LabMigrating to OTP2Get startedHow the engine thinks/en/planner/migrating-to-otp2
LabMigrating to OTP2Get startedTwo recipes/en/planner/migrating-to-otp2
LabMigrating to OTP2Get startedOn these pages/en/planner/migrating-to-otp2
LabPlanMigrating to OTP2Mode selection UX/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UX1 Global settings/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UX1b Restrict modes/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UX2 Presets + more + custom/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UX2b Multimodal presets/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UX3 Open listbox/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UX4b Vehicle switch/en/planner/migrating-to-otp2/mode-ux
LabMigrating to OTP2Mode selection UXPros & cons/en/planner/migrating-to-otp2/mode-ux
LabPlanMigrating to OTP2Query matrix/en/planner/migrating-to-otp2/query-matrix
LabPlanMigrating to OTP2Coverage review/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewStreet modes/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewTransit-vs-street filter/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewUnset slots/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewTransit modes/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewWho: operators, lines/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewWhere: places/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewWhen: time/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewTraveller profile/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewIntents check/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewPreset recipe/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewOne cursor, many queries/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewBudget per shape/en/planner/migrating-to-otp2/coverage-review
LabMigrating to OTP2Coverage reviewRecommendations/en/planner/migrating-to-otp2/coverage-review
LabMap/en/map
LabMapMap Base/en/map/base-light
LabMapMap Transit/en/map/transit-light
LabMapMap Transit (via proxy)/en/map/transit-light-proxy
LabDeparture/en/departure
LabDoc/en/doc
LabDocPlanner Cases Library/en/doc/planner-lib
LabDocUi/Ux/en/doc/user-interface
LabDocUi/UxPlanner/en/doc/user-interface/planner/
LabUi/UxPlannerInstant | Duration | Distance/en/doc/user-interface/planner/
LabUi/UxPlannerItinerary Time Overview/en/doc/user-interface/planner/
LabUi/UxPlannerLeg Overview/en/doc/user-interface/planner/
LabUi/UxPlannerItinerary Overview/en/doc/user-interface/planner/
LabUi/UxPlannerLeg Detail/en/doc/user-interface/planner/
LabDocUi/UxDeparture/en/doc/user-interface/departure
LabDocUi/UxInput/en/doc/user-interface/input/
LabHelp/en/help
ApifromPlaceAPI params in:BMC v1BMC v2OTP 1OTP 2OTP 2.9ApitoPlaceAPI params in:BMC v1BMC v2OTP 1OTP 2OTP 2.9ApiarriveByAPI params in:BMC v1BMC v2OTP 1OTP 2OTP 2.9ApitimeAPI params in:BMC v1BMC v2OTP 1ApibannedAgenciesAPI params in:BMC v1OTP 1OTP 2OTP 2.9ApibannedRoutesAPI params in:BMC v1OTP 1ApiwalkSpeedAPI params in:BMC v1OTP 1OTP 2OTP 2.9ApiwheelchairAPI params in:BMC v1BMC v2OTP 1ApinumItinerariesAPI params in:BMC v1BMC v2OTP 1ApimodeAPI params in:BMC v1BMC v2OTP 1ApialightSlackAPI params in:BMC v1BMC v2OTP 1ApiboardSlackAPI params in:BMC v1BMC v2OTP 1ApilocaleAPI params in:BMC v1OTP 1ApishowIntermediateStopsAPI params in:BMC v1OTP 1ApimaxWalkDistanceAPI params in:BMC v1OTP 1ApistartTransitStopIdAPI params in:BMC v1OTP 1ApistartTransitTripIdAPI params in:BMC v1OTP 1ApiwaitReluctanceAPI params in:BMC v1OTP 1OTP 2OTP 2.9ApiwalkReluctanceAPI params in:BMC v1OTP 1OTP 2OTP 2.9ApiroutingProfileAPI params in:BMC v2ApipageCursorsAPI params in:BMC v2ApibannedStopsAPI params in:OTP 1ApibannedStopsHardAPI params in:OTP 1ApibannedTripsAPI params in:OTP 1ApibatchAPI params in:OTP 1ApibikeBoardCostAPI params in:OTP 1ApibikeSpeedAPI params in:OTP 1OTP 2OTP 2.9ApibikeSwitchCostAPI params in:OTP 1ApidisableAlertFilteringAPI params in:OTP 1ApidisableRemainingWeightHeuristicAPI params in:OTP 1ApigeoidElevationAPI params in:OTP 1ApiignoreRealtimeUpdatesAPI params in:OTP 1OTP 2OTP 2.9ApiintermediatePlacesAPI params in:OTP 1ApimaxHoursAPI params in:OTP 1ApimaxPreTransitTimeAPI params in:OTP 1ApimaxTransfersAPI params in:OTP 1ApiminTransferTimeAPI params in:OTP 1ApinonpreferredTransferPenaltyAPI params in:OTP 1ApioptimizeAPI params in:OTP 1ApiotherThanPreferredRoutesPenaltyAPI params in:OTP 1ApipreferredAgenciesAPI params in:OTP 1ApipreferredRoutesAPI params in:OTP 1ApireverseOptimizeOnTheFlyAPI params in:OTP 1ApitransferPenaltyAPI params in:OTP 1OTP 2OTP 2.9ApitriangleSafetyFactorAPI params in:OTP 1ApitriangleSlopeFactorAPI params in:OTP 1ApitriangleTimeFactorAPI params in:OTP 1ApiunpreferredAgenciesAPI params in:OTP 1ApiunpreferredRoutesAPI params in:OTP 1ApiuseRequestedDateTimeInMaxHoursAPI params in:OTP 1ApiwaitAtBeginningFactorAPI params in:OTP 1ApiwalkBoardCostAPI params in:OTP 1ApitimetableViewAPI params in:OTP 2OTP 2.9ApinumTripPatternsAPI params in:OTP 2OTP 2.9ApimaximumTransfersAPI params in:OTP 2OTP 2.9ApimaximumAdditionalTransfersAPI params in:OTP 2OTP 2.9ApisearchWindowAPI params in:OTP 2OTP 2.9ApiincludePlannedCancellationsAPI params in:OTP 2OTP 2.9ApiincludeRealtimeCancellationsAPI params in:OTP 2OTP 2.9ApitransferSlackAPI params in:OTP 2OTP 2.9ApiboardSlackDefaultAPI params in:OTP 2OTP 2.9ApiboardSlackListAPI params in:OTP 2OTP 2.9ApialightSlackDefaultAPI params in:OTP 2OTP 2.9ApialightSlackListAPI params in:OTP 2OTP 2.9ApibicycleOptimisationMethodAPI params in:OTP 2OTP 2.9ApitransportModesAPI params in:OTP 2OTP 2.9ApiegressModeAPI params in:OTP 2OTP 2.9ApidirectModeAPI params in:OTP 2OTP 2.9ApiaccessModeAPI params in:OTP 2OTP 2.9ApiitineraryFiltersDebugAPI params in:OTP 2OTP 2.9ApipageCursorAPI params in:OTP 2OTP 2.9ApiwheelchairAccessibleAPI params in:OTP 2OTP 2.9ApitriangleFactorsSafetyAPI params in:OTP 2OTP 2.9ApitriangleFactorsSlopeAPI params in:OTP 2OTP 2.9ApitriangleFactorsTimeAPI params in:OTP 2OTP 2.9ApiuseBikeRentalAvailabilityInformationAPI params in:OTP 2OTP 2.9ApimaxDirectDurationForModeAPI params in:OTP 2OTP 2.9ApifiltersAPI params in:OTP 2OTP 2.9ApirelaxTransitGroupPriorityAPI params in:OTP 2OTP 2.9ApiwhiteListedAgenciesAPI params in:ApibannedLinesAPI params in:ApiwhiteListedLinesAPI params in:ApibannedServiceJourneysAPI params in:ApibannedRentalNetworksAPI params in:ApiwhiteListedRentalNetworksAPI params in:ApipassThroughPointsAPI params in:ApirelaxTransitGroupPriorityRatioAPI params in:ApirelaxTransitGroupPriorityConstantAPI params in:ApimaxAccessEgressDurationForModeAPI params in:ApimodePresetAPI params in:
DocOTP 1Full Open API Documentation
DocOTP 2Full Open API Documentation
DocOTP 2.9Full Open API Documentation
DocPlanner v1Full Open API Documentation
DocPlanner v2Full Open API Documentation
DocPlanner v2.1Full Open API Documentation
DocPlanner v2.1 presetFull Open API Documentation