Download OpenAPI specification:
Journey planning and fares: journeys with their fare options, saved journeys, legs and fares, rovers, seasons, travelcards, ticket and restriction validity, bike reservations and train loading.
Every path is under /odyssey.
Successes don't share one shape: some routes answer {"status": "success", "data": ...}, others {"data": ...}
or {"response": ...}, each with links. Each operation shows its own. A failure is
{"status": "error", "code": "...", "message": "..."}, for a 4xx as well as a 5xx; an error that isn't one of
the service's own has no code.
Values in data that are links to other resources are returned resolved in links, keyed by the link. Odyssey's
own links (/odyssey/...) are always resolved. Other links (/station/2968) are resolved through the link
service, and only as far as the include parameter says. A link that can't be resolved doesn't fail the
request: the response gets an errors list instead.
Plans outward (and, with inwardDate, inward) journeys between two locations and finds the fare options for
them. The response is not in the usual {"status": "success", "data": ...} form: it has response,
pagination, links and messages at the top level.
Journeys, legs, fare options and fares are given as links (/odyssey/journey/..., /odyssey/fare-option/...)
and are all in links. They are also saved for about a day, so they can be fetched again with
GET /odyssey/journey/{id}, /odyssey/leg/{id}, /odyssey/fare-option/{id} and /odyssey/fare/{id}.
Other links (/station/..., /toc/..., /ticket-type/..., /delivery/...) are resolved through link-service
only when include is given.
Dates and times are UK local time. A date and time in the past is moved to now, unless the tenant (or
showAlreadyDepartedTrains) shows departed trains.
Many parameters that are not about the search itself override tenant settings or are for debugging; they are marked so.
| tenant | string Example: tenant=rsm The tenant, or give it as the |
| subtenant | string The subtenant, or give it as the |
| include | string Which links to other services to resolve into |
| requestId | string The request ID, when there's no |
| sessionId | string The session ID, when there's no |
| origin required | string Example: origin=EUS Where the outward journey starts, as an NLC, CRS or TIPLOC code. An unknown code is a 400 ( |
| destination required | string Example: destination=MAN Where the outward journey ends, as an NLC, CRS or TIPLOC code. |
| via | string Example: via=CRE Locations the journeys must pass, as comma-separated NLC, CRS or TIPLOC codes (or a repeated parameter). At most 3. |
| avoid | string Locations the journeys must not pass, as for |
| stopAt | string Example: stopAt=PBO,YRK Locations the journeys' trains must call at (pick up or set down), as for |
| tocs | string Example: tocs=VT,NT Comma-separated 2-character TOC codes; each journey must use at least one of them. Not with |
| excludedTocs | string Example: excludedTocs=CS Comma-separated 2-character TOC codes the journeys must not use. Only the shape is checked, so an unknown code does nothing. Not with |
| excludedModes | string Example: excludedModes=bus,replacement-bus Comma-separated leg modes the journeys must not use: train, bus, replacement bus, walk, tube, tram, funicular, ferry, cable car, metro, transfer. Case-insensitive; |
| minWait | integer >= 0 Example: minWait=10 The shortest wait at a change, in minutes. |
| maxWait | integer >= 0 Example: maxWait=60 The longest wait at a change, in minutes. Not less than |
| maxChanges | integer [ 0 .. 9 ] Example: maxChanges=2 The most changes a journey can have. |
| adults | integer >= 0 Default: 0 Example: adults=1 The number of adults. 0 when not given. Adults and children together can't be more than the tenant's maximum group size (9 by default). |
| children | integer >= 0 Default: 0 The number of children. 0 when not given. |
| bikes | integer >= 0 Default: 0 The number of bikes. Not more than the passengers. |
| pets | integer >= 0 Default: 0 The number of pet-friendly compartments (Caledonian Sleeper). |
| railcards | string Example: railcards=YNG,YNG Comma-separated railcard codes, one for each railcard (a code can repeat). An unknown code is a 400 ( |
| promoCode | string A promo code for a discount. |
| outwardDate required | string <date> Example: outwardDate=2025-05-21 The outward date. |
| outwardDepartAt | string Example: outwardDepartAt=09:00 Outward journeys departing at or after this time ( |
| outwardDepartAfter | string The same as |
| outwardDepartBy | string Outward journeys departing at or before this time. |
| outwardArriveAt | string Outward journeys arriving at or after this time. |
| outwardArriveBy | string Outward journeys arriving at or before this time. |
| inwardDate | string Example: inwardDate=2025-05-23 The inward date, for a return search; |
| inwardDepartAt | string Inward journeys departing at or after this time. |
| inwardDepartAfter | string The same as |
| inwardDepartBy | string Inward journeys departing at or before this time. |
| inwardArriveAt | string Inward journeys arriving at or after this time. |
| inwardArriveBy | string Inward journeys arriving at or before this time. |
| returnDate | string Deprecated The old name of |
| horizon | integer >= 1 Example: horizon=12 How many hours of trains to look through. More than 27 only for a one-way search. By default the planner tries the tenant's horizons in turn (6, then 27 hours, unless the tenant sets others). Larger groups can have a lower limit ( |
| limit | integer Default: 5 Example: limit=5 The most journeys in each direction. Above the tenant's maximum (100 by default), the maximum. |
| firstClass | boolean Default: true
|
| standardClass | boolean Default: true
|
| singles | boolean Default: true
|
| returns | boolean Default: true
|
| advance | boolean Default: true
|
| ticketCategory | string Example: ticketCategory=off-peak Only fares in these categories, comma-separated: anytime, off-peak, advance. |
| fareSetters | string Example: fareSetters=CAL,SCS Only fares from these fare setters (or fare setter groups), comma-separated. By default the tenant's. |
| ticketType | string Example: ticketType=SOS,SDS Only fares with these ticket type codes, comma-separated. Replaces the tenant's ticket type list. |
| routeCode | string Example: routeCode=00000 Only fares with these route codes, comma-separated. |
| allFares | boolean All fares for all journeys, with the reasons fares aren't valid ( |
| licenseeType | string^[0-9]{1,4}$ Example: licenseeType=1 The RCS licensee type ID (1 to 4 digits) the fare options' delivery methods are for, added to |
| licenseeCode | string^(\*|[A-Za-z0-9]{1,20})$ Example: licenseeCode=HO The RCS licensee code (1 to 20 letters or digits), or |
| licenseeChannel | string^[0-9]{1,4}$ Example: licenseeChannel=6 The RCS channel ID (1 to 4 digits), added to the delivery links as |
| useSplits | boolean Tenant setting. Split tickets. Never in the |
| useSplitsForEmptyResults | boolean Tenant setting. Split tickets for journeys with no other fares. |
| splitPattern | string Only split tickets with these patterns, comma-separated. |
| fareAlgorithm | string Enum: "fast" "thorough" Tenant setting. How fare options are made. Another value is a 500. |
| showPassingPoints | boolean Tenant setting. Calling points where the train doesn't stop too. Always in the |
| showAlreadyDepartedTrains | boolean Tenant setting. Keep a date and time in the past instead of moving it to now. |
| showSlowExpensiveTrains | boolean Tenant setting. Keep journeys that are both slower and dearer than others. |
| showEmptyFastTrains | boolean Tenant setting. Keep fast journeys with no fares. |
| overlapPagingResults | boolean Tenant setting. The earlier and later pages start at the first and last journey's time instead of a minute before or after. |
| useRoomCountSelector | boolean Tenant setting. Caledonian Sleeper room counts. |
| useProductAvailabilityMatrix | boolean Tenant setting. Adds |
| useBookabilityMatrix | boolean Tenant setting. Adds |
| fixChildOriginalPrice | boolean Tenant setting. |
| journeyLimitMultiplier | integer Tenant setting. How many journeys to ask the planner for, as a multiple of |
| journeyLimitMin | integer Tenant setting. The fewest journeys to ask the planner for. |
| journeyLimitMax | integer Tenant setting. The most journeys to ask the planner for. |
| preValidationResultMultiplier | integer Tenant setting. How many journeys to keep before fare validation, as a multiple of |
| extraFareOptionJourneys | integer Default: 4 How many extra journeys to look for fare options on. |
| debug | boolean Default: false Debugging. Adds |
| noCache | boolean Default: false Debugging. Don't use cached results. |
| journey | string Example: journey=/journey/141:141:EUS_MAN_C21856 Debugging. Only these journeys, as journey links (with or without |
| fare | string Example: fare=/fare/1072-0438-00000-SOS-000-1----141-1-0--19300 Debugging. Only this fare, as a fare link. |
| availability | string Enum: "full" "none" Debugging. Treat every fare as fully available ( |
| fareOptionFactory | string Debugging. The fare option generator to prefer. An unknown one is a 400 ( |
| patterns | string Debugging. Passed to the journey planner. |
| patternSource | string Debugging. Passed to the journey planner. |
| showAllSplits | boolean Default: false Debugging. All split tickets, not only the cheapest. |
| suppressSalesFor | string Debugging. Treat sales as suppressed for journeys on these trains (comma-separated train UIDs). |
| maxMixedCalSleeperJourneys | integer Debugging. The most mixed Caledonian Sleeper and ScotRail journeys. |
| X-Tenant | string The tenant, for internal callers. |
| X-Subtenant | string The subtenant, for internal callers. |
| X-Token-Data | string The customer's token data. For a B2B tenant, the customer's travel policy (from customer-service) can turn off first class and set a maximum price per ticket. |
| X-Request-Id | string <= 36 characters The request ID, in fare options' |
| X-Session-Id | string <= 36 characters The session ID, in fare options' |
{- "response": {
- "outward": [
- "/odyssey/journey/141:141:EUS_MAN_C21856"
], - "inward": [ ],
- "fares": {
- "/odyssey/journey/141:141:EUS_MAN_C21856": {
- "outward": [
- "/odyssey/fare-option/141:/fare/1072-0438-00000-SOS-000-1----141-1-0--19300-1-0-1c46"
], - "inward": [ ],
- "return": [ ]
}
}, - "faresNotForSale": {
- "/odyssey/journey/141:141:EUS_MAN_C21856": {
- "outward": [ ],
- "inward": [ ],
- "return": [ ]
}
}, - "addons": {
- "outward": {
- "origin": { },
- "destination": {
- "plusbus": [
- "/odyssey/fare-option/addon/141:/fare/0438-J819-00009-PBD-000-1----141-1-0--490-1-0"
]
}
}
}, - "availability": {
- "/odyssey/journey/141:141:EUS_MAN_C21856": {
- "/ticket-type/SOS": {
- "/route/00000": 9
}
}
}, - "legAvailability": {
- "/odyssey/journey/141:141:EUS_MAN_C21856": {
- "/odyssey/leg/141:EUS_MAN_C21856": {
- "/ticket-type/SOS": {
- "/route/00000": {
- "seats": null,
- "outcome": "NOT_CHECKED"
}
}
}
}
}, - "splitSavings": { }
}, - "pagination": {
- "outward": {
- "current": "origin=1444&destination=2968&outwardDate=2025-05-21&outwardDepartAt=09%3A00&adults=1&children=0",
- "earlier": "origin=1444&destination=2968&outwardDate=2025-05-21&outwardDepartBy=09%3A19&adults=1&children=0",
- "later": "origin=1444&destination=2968&outwardDate=2025-05-21&outwardDepartAt=09%3A21&adults=1&children=0"
}
}, - "links": {
- "/odyssey/journey/141:141:EUS_MAN_C21856": {
- "id": "/odyssey/journey/141:141:EUS_MAN_C21856",
- "origin": "/station/EUS",
- "destination": "/station/MAN",
- "departure": "2025-05-21T09:20:00",
- "arrival": "2025-05-21T11:28:00",
- "realTime": {
- "departure": "2025-05-21T09:20:00",
- "departureType": "scheduled",
- "arrival": "2025-05-21T11:28:00",
- "arrivalType": "scheduled"
}, - "scheduledDeparture": "2025-05-21T09:20:00",
- "scheduledArrival": "2025-05-21T11:28:00",
- "legs": [
- "/odyssey/leg/141:EUS_MAN_C21856"
], - "disruptions": "/disruption/2025-05-21T09:20:00/VT_EUS_MAN_C21856",
- "isReservationCompulsory": false,
- "isReservationPossible": true,
- "isOvertaken": false,
- "overtakingJourneysCount": 0,
- "status": "ready",
- "boardingTime": null,
- "alightingTime": null,
- "distance": 184
}, - "/odyssey/fare-option/141:/fare/1072-0438-00000-SOS-000-1----141-1-0--19300-1-0-1c46": {
- "totalPrice": 19300,
- "labels": [ ],
- "hasETicket": "/has-e-ticket/1072-0438-1444-2968-00000-SOS-19300-0-1",
- "delivery": "/delivery/params/1072-0438-1444-2968-00000-SOS-19300-0-1?date=2025-05-21T09:20:00",
- "supplements": [ ],
- "fares": [
- {
- "adults": 1,
- "children": 0,
- "fare": "/odyssey/fare/1072-0438-00000-SOS-000-1----141-1-0--19300",
- "validFrom": "2025-05-21T00:01:00",
- "validUntil": "2025-05-22T04:29:59"
}
], - "validUntil": "2025-05-22T04:29:59",
- "createdAt": "2025-05-20T14:02:11",
- "requestId": "odyssey-7a2e-7c3b-9d41-0242ac120002",
- "sessionId": "odyssey-7a2e-7c3b-9d41-0242ac121c46"
}, - "/odyssey/fare/1072-0438-00000-SOS-000-1----141-1-0--19300": {
- "origin": "/station/1072",
- "destination": "/station/0438",
- "route": "/route/00000",
- "price": 19300,
- "originalPrice": 19300,
- "ticketType": "/ticket-type/SOS",
- "restrictionCode": null,
- "fareSetter": "VTC",
- "railcard": null,
- "statusCode": "000",
- "xLondon": 0,
- "permittedDestinationStations": "/station/0438/permitted-stations/1072/00000",
- "permittedOriginStations": "/station/1072/permitted-stations/0438/00000",
- "discount": null,
- "groups": [
- "1072-0438-00000-SOS"
]
}
}, - "messages": [ ]
}The same endpoint as /odyssey/fares-search.
| tenant | string Example: tenant=rsm The tenant, or give it as the |
| include | string Which links to other services to resolve into |
| origin required | string Example: origin=TBW CRS, NLC or TIPLOC code. |
| destination required | string Example: destination=BTN CRS, NLC or TIPLOC code. |
| outwardDate required | string <date> Example: outwardDate=2026-11-02 The outward date, |
| inwardDate | string <date> Example: inwardDate=2026-11-03 The inward date, |
| returnDate | string <date> Another name for |
| adults required | integer >= 0 Example: adults=2 Number of adults. Not validated. |
| children required | integer >= 0 Example: children=1 Number of children. Not validated. |
| cycles | integer >= 0 Default: 0 Number of bikes. |
| railcards | string Example: railcards=YNG Comma-separated railcard codes; repeat a code for more than one railcard of the type. Railcards the tenant doesn't allow are ignored. |
| firstClass | string Default: "true" Enum: "true" "false"
|
| standardClass | string Default: "true" Enum: "true" "false"
|
| singles | string Default: "true" Enum: "true" "false"
|
| returns | string Default: "true" Enum: "true" "false"
|
| advance | string Default: "true" Enum: "true" "false"
|
| ticketType | string Example: ticketType=SDS Comma-separated ticket type codes. Only fares with these ticket types. |
| routeCode | string Example: routeCode=00073 Comma-separated route codes. Only fares with these routes. |
| ticketCategory | string Enum: "advance" "off-peak" "anytime" Example: ticketCategory=off-peak Only fares with ticket types of this category. One value only: a comma-separated list, or an unknown value, is ignored. |
| tocs | string Example: tocs=SN,GX Comma-separated fare setter (TOC) codes. Only fares set by these. |
| maxPrice | integer Example: maxPrice=5000 Highest total price of a fare option, in pence. |
| maxGroupSize | integer Passed through as given (a string), not the tenant's maximum as on |
| allFares | string Example: allFares=true The allFares mode: every fare, with the reasons it isn't valid or available. |
| promoCode | string Example: promoCode=SUMMER26 A promo code to apply to the fares, where it can be. |
| userGroupId | integer The user group, for user group discounts. |
| licenseeType | string^[0-9]{1,4}$ Example: licenseeType=1 RCS licensee type id for the delivery links, as on |
| licenseeCode | string^(\*|[A-Za-z0-9]{1,20})$ Example: licenseeCode=HO RCS licensee (business) code, or |
| licenseeChannel | string^[0-9]{1,4}$ Example: licenseeChannel=6 RCS channel id for the delivery links. Up to 4 digits. The tenant's when not given. |
| X-Tenant | string The tenant, for internal callers. |
{- "data": {
- "outward": [
- "/odyssey/fare-option/306:/fare/5268-I444-00777-SDS-000-0----303-306-0--2350-1-0-a1b2"
], - "inward": [ ],
- "return": [
- "/odyssey/fare-option/306:/fare/5268-I444-00777-SDR-000-0----303-306-0--2620-1-0-a1b2"
]
}, - "links": {
- "/odyssey/fare-option/306:/fare/5268-I444-00777-SDS-000-0----303-306-0--2350-1-0-a1b2": {
- "totalPrice": 2350,
- "labels": [
- "anytime"
], - "hasETicket": "/has-e-ticket/5268-I444-5268-I444-00777-SDS-2350-0-1?date=2026-11-02T00%3A00%3A00",
- "delivery": "/delivery/params/5268-I444-5268-I444-00777-SDS-2350-0-1?date=2026-11-02T00%3A00%3A00",
- "supplements": [ ],
- "fares": [
- {
- "adults": 1,
- "children": 0,
- "fare": "/odyssey/fare/5268-I444-00777-SDS-000-0----303-306-0--2350",
- "validFrom": "2026-11-02T00:01:00",
- "validUntil": "2026-11-03T04:29:59"
}
], - "validUntil": "2026-11-03T04:29:59",
- "createdAt": "2026-10-30T10:15:00",
- "requestId": "odyssey-7c1e-4d2f-9a6b-0e5f3c8da1b2",
- "sessionId": "odyssey-7c1e-4d2f-9a6b-0e5f3c8da1b2"
}
}
}Season ticket fares between two stations for one passenger, by season duration. Each fare is in links
(the same shape as a /odyssey/fare/{id}), and is saved so that /odyssey/fare/{id} returns it.
For a B2B tenant, the user's travel policy (from customer-service, with the request's headers) must allow seasons; if it doesn't, the request is a 403.
400 codes: no-tenant, tenant-not-found, missing-start-date, invalid-start-date, missing_property,
invalid-location-code, invalid-passenger-type, invalid-duration, railcard-not-found.
| tenant | string Example: tenant=rsm The tenant, or give it as the |
| include | string Which links to other services to resolve into |
| origin required | string Example: origin=TBW CRS, NLC or TIPLOC code. |
| destination required | string Example: destination=CHX CRS, NLC or TIPLOC code. |
| startDate required | string <date> Example: startDate=2026-11-02 The first day of the season, |
| passengerType required | string Enum: "adult" "child" Example: passengerType=adult One adult or one child. |
| duration | string Example: duration=0100,0304,1200 Comma-separated season durations, each |
| railcards | string Example: railcards=NGC Comma-separated railcard codes. |
| railcard | string Deprecated Old name for |
| ticketType | string Example: ticketType=PSS Comma-separated ticket type codes. Only fares with these ticket types. |
| routeCode | string Example: routeCode=00000 Comma-separated route codes. Only fares with these routes. |
| promoCode | string Example: promoCode=SUMMER26 A promo code to apply to the fares, where it can be. |
| noCache | string
|
| flatten | string Default: "false" Enum: "true" "false"
|
| X-Tenant | string The tenant, for internal callers. |
{- "response": {
- "fares": {
- "1200": [
- "/odyssey/fare/1072-0418-00000-PSS-000-0----303-306-0-12,0-493700"
], - "0007": [
- "/odyssey/fare/1072-0418-00000-7DS-000-0----303-306-0-0,7-12340"
]
}, - "zoneSets": [ ],
- "addons": {
- "origin": {
- "plusbus": [
- "/odyssey/fare/1072-J809-00000-PB7-000-0----303-306-0--2050"
]
}, - "destination": { }
}
}, - "links": {
- "/odyssey/fare/1072-0418-00000-7DS-000-0----303-306-0-0,7-12340": {
- "origin": "/station/1072",
- "destination": "/station/0418",
- "route": "/route/00000",
- "price": 12340,
- "originalPrice": 12340,
- "ticketType": "/ticket-type/7DS",
- "restrictionCode": null,
- "fareSetter": null,
- "railcard": null,
- "statusCode": "000",
- "xLondon": 0,
- "permittedDestinationStations": "/station/0418/permitted-stations/1072/00000",
- "permittedOriginStations": "/station/1072/permitted-stations/0418/00000",
- "discount": null,
- "season": {
- "duration": {
- "months": 0,
- "days": 7
}, - "stockColour": "blue",
- "comparisonPrice": 14800
}, - "groups": [ ]
}
}
}The same search as /odyssey/season-search, with the same parameters and errors, but each fare is wrapped in
a fare option with one fare use, for one adult or one child as passengerType says. The fare options have
hasETicket and delivery links, dated startDate. The add-ons are fare options too.
flatten has no effect: fares is always by duration.
400 codes: no-tenant, tenant-not-found, missing-start-date, invalid-start-date, missing_property,
invalid-location-code, invalid-passenger-type, invalid-duration, railcard-not-found.
| tenant | string Example: tenant=rsm The tenant, or give it as the |
| include | string Which links to other services to resolve into |
| origin required | string Example: origin=TBW CRS, NLC or TIPLOC code. |
| destination required | string Example: destination=CHX CRS, NLC or TIPLOC code. |
| startDate required | string <date> Example: startDate=2026-11-02 The first day of the season, |
| passengerType required | string Enum: "adult" "child" Example: passengerType=adult One adult or one child. |
| duration | string Example: duration=0100,0304,1200 Comma-separated season durations, each |
| railcards | string Example: railcards=NGC Comma-separated railcard codes. |
| railcard | string Deprecated Old name for |
| ticketType | string Example: ticketType=PSS Comma-separated ticket type codes. Only fares with these ticket types. |
| routeCode | string Example: routeCode=00000 Comma-separated route codes. Only fares with these routes. |
| promoCode | string Example: promoCode=SUMMER26 A promo code to apply to the fares, where it can be. |
| noCache | string
|
| X-Tenant | string The tenant, for internal callers. |
{- "response": {
- "fares": {
- "0007": [
- "/odyssey/fare-option/306:/fare/1072-0418-00000-7DS-000-0----303-306-0-0,7-12340-1-0"
]
}, - "zoneSets": [ ],
- "addons": {
- "origin": {
- "plusbus": [
- "/odyssey/fare-option/306:/fare/1072-J809-00000-PB7-000-0----303-306-0--2050-1-0"
]
}, - "destination": { }
}
}, - "links": {
- "/odyssey/fare-option/306:/fare/1072-0418-00000-7DS-000-0----303-306-0-0,7-12340-1-0": {
- "totalPrice": 12340,
- "labels": [ ],
- "hasETicket": "/has-e-ticket/1072-0418-1072-0418-00000-7DS-12340-0-1?date=2026-11-02T00%3A00%3A00",
- "delivery": "/delivery/params/1072-0418-1072-0418-00000-7DS-12340-0-1?date=2026-11-02T00%3A00%3A00",
- "supplements": [ ],
- "fares": [
- {
- "adults": 1,
- "children": 0,
- "fare": "/odyssey/fare/1072-0418-00000-7DS-000-0----303-306-0-0,7-12340",
- "validFrom": "2026-11-02T00:01:00",
- "validUntil": "2026-11-09T04:29:59"
}
], - "validUntil": "2026-11-09T04:29:59",
- "createdAt": "2026-10-30T10:15:00"
}
}
}Standalone Travelcard fare options (London zones) for a date and set of passengers: one fare option for each
travelcard origin, destination and ticket type that has fares for all the passengers. Each fare option and
its fares are in links and are saved, so /odyssey/fare-option/{id} and /odyssey/fare/{id} return them.
The fare options have no hasETicket or delivery links.
400 codes: no-tenant, tenant-not-found, missing_property, invalid-date, railcard-not-found.
| tenant | string Example: tenant=rsm The tenant, or give it as the |
| include | string Which links to other services to resolve into |
| date required | string <date> Example: date=2026-11-02 The day of travel, |
| adults required | integer >= 0 Example: adults=1 Number of adults. Not validated beyond being present. |
| children | integer >= 0 Default: 0 Example: children=2 Number of children. |
| railcards | string Example: railcards=YNG,DIS Comma-separated railcard codes; repeat a code for more than one railcard of the type. No |
| promoCode | string Example: promoCode=SUMMER26 A promo code to apply to the fares, where it can be. |
| ticketType | string Example: ticketType=ODT Comma-separated ticket type codes. Only fares with these ticket types. |
| X-Tenant | string The tenant, for internal callers. |
{- "response": {
- "fares": [
- "/odyssey/fare-option/306:/fare/1072-0035-01000-ODT-000-0----303-306-0--1500-1-0"
]
}, - "links": {
- "/odyssey/fare-option/306:/fare/1072-0035-01000-ODT-000-0----303-306-0--1500-1-0": {
- "totalPrice": 1500,
- "labels": [ ],
- "supplements": [ ],
- "fares": [
- {
- "adults": 1,
- "children": 0,
- "fare": "/odyssey/fare/1072-0035-01000-ODT-000-0----303-306-0--1500",
- "validFrom": "2026-11-02T00:01:00",
- "validUntil": "2026-11-03T04:29:59"
}
], - "validUntil": "2026-11-03T04:29:59",
- "createdAt": "2026-10-30T10:15:00"
}
}
}Rover options for one rover code, start date and set of passengers: at most two, one for first class and one
for standard class, each the cheapest combination of rover prices for the passengers. The rover options and
their rovers are saved for 2 days, so /odyssey/rover-option/{id} and /odyssey/rover/{id} return them.
The origin of every rover is the tenant's retailer location; the destination is the rover's accounting location.
links always has the rover options and their rovers, whatever include says: they're read from the
cache, not through the link service.
An unknown rover code, or one not on sale on startDate, gives an empty list.
400 codes: no-tenant, tenant-not-found, tenant-no-retailer-location, invalid-date,
railcard-not-found, invalid-adults, invalid-children.
| tenant | string Example: tenant=rsm The tenant, or give it as the |
| include | string Which links to other services to resolve into |
| roverCode required | string Example: roverCode=14R The rover (or ranger) code. |
| startDate required | string <date> Example: startDate=2026-11-02 The first day of validity, |
| adults required | integer >= 0 Example: adults=2 Number of adults, a whole number. |
| children | integer >= 0 Default: 0 Example: children=1 Number of children, a whole number. |
| railcards | string Example: railcards=DIS Comma-separated railcard codes. Each code once per railcard; no |
| X-Tenant | string The tenant, for internal callers. |
{- "response": {
- "fares": [
- "/odyssey/rover-option/1072-I662-14R-115720--2026306-1072-I662-14R-115720--2026306",
- "/odyssey/rover-option/1072-I662-14R-77815--2026306-1072-I662-14R-77815--2026306"
]
}, - "links": {
- "/odyssey/rover-option/1072-I662-14R-77815--2026306-1072-I662-14R-77815--2026306": {
- "fares": [
- {
- "adults": 1,
- "children": 0,
- "fare": "/odyssey/rover/1072-I662-14R-77815--2026306"
}, - {
- "adults": 1,
- "children": 0,
- "fare": "/odyssey/rover/1072-I662-14R-77815--2026306"
}
]
}, - "/odyssey/rover/1072-I662-14R-77815--2026306": {
- "origin": "/station/1072",
- "destination": "/station/I662",
- "restrictionCode": "",
- "isFirstClass": false,
- "price": 77815,
- "railcard": null,
- "type": "/rover-type/14R",
- "validityPeriod": {
- "from": "2026-11-02T00:00:00",
- "until": "2026-11-15T23:59:59"
}
}
}
}A rover option that /odyssey/rover-search returned in the last 2 days, read from the cache.
An unknown or expired ID is a 404. Query parameters don't change which one is found.
| id required | string Example: 1072-I662-14R-77815--2026306-1072-I662-14R-77815--2026306 The rover IDs of the option's rovers, joined with |
| include | string Which links to other services to resolve into |
{- "data": {
- "fares": [
- {
- "adults": 1,
- "children": 0,
- "fare": "/odyssey/rover/1072-I662-14R-77815--2026306"
}, - {
- "adults": 0,
- "children": 1,
- "fare": "/odyssey/rover/1072-I662-14R-51355--2026306"
}
]
}, - "links": {
- "/odyssey/rover/1072-I662-14R-77815--2026306": {
- "origin": "/station/1072",
- "destination": "/station/I662",
- "restrictionCode": "",
- "isFirstClass": false,
- "price": 77815,
- "railcard": null,
- "type": "/rover-type/14R",
- "validityPeriod": {
- "from": "2026-11-02T00:00:00",
- "until": "2026-11-15T23:59:59"
}
}, - "/odyssey/rover/1072-I662-14R-51355--2026306": {
- "origin": "/station/1072",
- "destination": "/station/I662",
- "restrictionCode": "",
- "isFirstClass": false,
- "price": 51355,
- "railcard": null,
- "type": "/rover-type/14R",
- "validityPeriod": {
- "from": "2026-11-02T00:00:00",
- "until": "2026-11-15T23:59:59"
}
}
}
}A rover that /odyssey/rover-search returned in the last 2 days, read from the cache.
An unknown or expired ID is a 404. Query parameters don't change which one is found.
| id required | string Example: 1072-I662-14R-77815--2026306
|
| include | string Which links to other services to resolve into |
{- "data": {
- "origin": "/station/1072",
- "destination": "/station/I662",
- "restrictionCode": "",
- "isFirstClass": true,
- "price": 50950,
- "railcard": "/railcard/DIS",
- "type": "/rover-type/14R",
- "validityPeriod": {
- "from": "2026-11-02T00:00:00",
- "until": "2026-11-15T23:59:59"
}
}, - "links": { }
}When a restriction allows travel, by day and time band, with the restriction's National Rail text and its rules. Used to show a fare's restriction code and its calendar.
A known restriction code always gets its text and rules. Its calendar (hourly) is for the given origin and
destination, so it's only there when their fares have the restriction. Otherwise restriction.calendarOmitted
says why (for example NT on the Carlisle to Newcastle part of a Manchester to Newcastle split ticket): ask with
the fare's own origin and destination.
With a railcardCode, the railcard's restrictions also limit the calendar. Without a restriction code or a
railcard, every time band is valid. The response has no links.
| origin required | string Example: origin=MAN CRS or NLC. A group's member stations count too. |
| destination required | string Example: destination=NCL CRS or NLC. |
| date required | string <date> Example: date=2026-11-02 The first day of the calendar (YYYY-MM-DD). |
| restrictionCode | string Example: restrictionCode=NT The restriction code, any case. |
| railcardCode | string Example: railcardCode=NEW A railcard, any case, whose restrictions for all fares are added to the calendar. An unknown one is ignored. |
| days | integer [ 1 .. 366 ] Default: 28 Example: days=28 How many days the calendar has, a whole number from 1 to 366. |
| isInward | string Example: isInward=true
|
{- "status": "success",
- "data": {
- "hourly": {
- "2026-11-02": {
- "8": [
- 2,
- 0
], - "11": [
- 0,
- 0
], - "14": [
- 0,
- 0
], - "17": [
- 1,
- 0
], - "20": [
- 0,
- 0
]
}
}, - "text": {
- "name": "NT",
- "applicableDays": {
- "title": "Applicable Days",
- "html": "<p>Monday to Friday</p>"
}, - "outwardDirection": {
- "title": "Outward Travel",
- "html": "<p>Not valid on trains departing before 09:30.</p>"
}, - "returnDirection": {
- "title": "Return Travel",
- "html": "<p>No restrictions.</p>"
}
}, - "info": {
- "out": {
- "id": "NT_O",
- "changeAllowed": true,
- "legRestrictions": [ ],
- "allowedTrains": [ ],
- "allowedOperators": [ ]
}, - "ret": {
- "id": "NT_R",
- "changeAllowed": true,
- "legRestrictions": [ ],
- "allowedTrains": [ ],
- "allowedOperators": [ ]
}
}, - "debugText": "Dates\n=====\n- 01/01-12/31 (Mon,Tue,Wed,Thu,Fri)\n...",
- "restriction": {
- "text": {
- "name": "NT",
- "applicableDays": {
- "title": "Applicable Days",
- "html": "<p>Monday to Friday</p>"
}, - "outwardDirection": {
- "title": "Outward Travel",
- "html": "<p>Not valid on trains departing before 09:30.</p>"
}, - "returnDirection": {
- "title": "Return Travel",
- "html": "<p>No restrictions.</p>"
}
}, - "info": {
- "out": {
- "id": "NT_O",
- "changeAllowed": true,
- "legRestrictions": [ ],
- "allowedTrains": [ ],
- "allowedOperators": [ ]
}, - "ret": {
- "id": "NT_R",
- "changeAllowed": true,
- "legRestrictions": [ ],
- "allowedTrains": [ ],
- "allowedOperators": [ ]
}
}, - "debugText": "Dates\n=====\n- 01/01-12/31 (Mon,Tue,Wed,Thu,Fri)\n..."
}, - "railcard": { }
}
}How full the tenant's operator's trains around now are, from the journey planner. Only some tenants have loading; for the others the planner's 400 is passed on. Times are local, to the minute.
The tenant is sent on to the planner as the X-Tenant header and the tenant parameter; the planner uses the
header when there are both. The response has no resolved links: links is the planner's own.
| tenant | string Example: tenant=rsm The tenant, or give it as the |
| X-Tenant | string The tenant, for internal callers. |
{- "status": "success",
- "data": [
- {
- "rid": "202610127119776",
- "uid": "G19776",
- "toc": "GX",
- "departure": "2026-10-12T06:14",
- "arrival": "2026-10-12T07:14",
- "stops": [
- {
- "crs": "BTN",
- "departure": "2026-10-12T06:14",
- "averageLoading": 25,
- "observedLoading": {
- "1": 10,
- "2": 20
}
}, - {
- "crs": "HSK",
- "departure": "2026-10-12T06:23"
}
], - "coaches": {
- "1": {
- "class": "Standard",
- "toiletType": "Accessible",
- "toiletStatus": "Unknown"
}, - "2": {
- "class": "Standard",
- "toiletType": "Standard",
- "toiletStatus": "Unknown"
}
}
}
], - "links": { }
}How full one train is, by train UID, from the journey planner: the latest run it has for the UID. The same
shape as an item of /odyssey/loading. The planner gets no tenant: tenant is only logged. The response has
no resolved links.
| tuid required | string^[A-Za-z0-9]{1,16}$ Example: P89003 The Darwin train UID. |
| tenant | string Example: tenant=rsm The tenant, or give it as the |
{- "status": "success",
- "data": {
- "rid": "202610128089003",
- "uid": "P89003",
- "toc": "SE",
- "departure": "2026-10-12T08:01",
- "arrival": "2026-10-12T08:04",
- "stops": [
- {
- "crs": "CHX",
- "departure": "2026-10-12T08:01",
- "observedLoading": {
- "A1": 0,
- "A2": 2
}
}, - {
- "crs": "WAE",
- "departure": "2026-10-12T08:04",
- "averageLoading": 9,
- "observedLoading": {
- "A1": 0,
- "A2": 2
}
}
], - "coaches": {
- "A1": {
- "class": "Standard",
- "toiletType": "None",
- "toiletStatus": "Unknown"
}, - "A2": {
- "class": "Standard",
- "toiletType": "Standard",
- "toiletStatus": "None"
}
}
}, - "links": { }
}