Bookings
The Bookings API gives one view of a customer's bookings across bus, ferry, train and flight. It lets agents and customers list and search their own bookings without calling each transport-specific endpoint separately.
Overview
The Bookings API currently provides:
- An activity feed of the caller's bookings across all modes, newest first, with cursor pagination
- A lookup that finds bookings by payment reference, ticket ID, any known reference, passenger name or passenger phone number
- One
BookingSummaryshape for every mode, with a small mode-specific block (bus,trainorflight) and enough references to deep-link into the mode-specific ticket endpoints - Booking-level status covering every ticket in the booking (
PENDING_PAYMENT,PROCESSING,CONFIRMED,PARTIALLY_CANCELLED,CANCELLED)
Both endpoints are read-only and never return passenger contact details, identity numbers, payment information or provider payloads.
Authentication
Both endpoints are protected Safiri Booking API endpoints and require JWT authentication. Obtain a JWT token by logging in through the /api/logon endpoint.
For detailed information on authenticating and obtaining a token, see the Authentication guide.
This guide describes access for agent and customer accounts. You can see only bookings bought with your own account. A passenger on a booking made by an agent or another customer will not find it through their own account. An unknown reference and a reference belonging to another account both return an empty list. These access rules apply to every request and every page of the feed.
Headers
Required:
Authorization: Bearer <JWT_TOKEN>- Your JWT authentication token
Every response is sent with Cache-Control: private, no-store.
Endpoints
Activity Feed
Returns the caller's bookings across every selected mode, newest booking first, one page at a time.
Endpoint: GET /api/bookings/activity
Example Request:
GET /api/bookings/activity?limit=20
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | string | No | Page size. Whole digits only; the value is clamped to 1..100 (so 0 becomes 1 and 1000 becomes 100). Defaults to 50. Anything else returns 400 BOOKINGS_LIMIT_INVALID. |
cursor | string | No | Opaque cursor copied from the previous page's nextCursor. Omit it (or send an empty string) for the first page. A cursor that cannot be decoded or validated returns 400 BOOKINGS_CURSOR_INVALID. |
kinds | string | No | Comma-separated subset of BUS, FERRY, TRAIN, FLIGHT. Tokens are trimmed, upper-cased and de-duplicated; the response echoes them in canonical order. Defaults to all four. An unknown token or an empty list returns 400 BOOKINGS_KINDS_INVALID. |
ownerUuid | string | No | Optional own-account UUID; omitting it gives the same scope. Another account's UUID returns 403 FORBIDDEN_ACCESS_PRIVILEGE. An invalid UUID returns 400 BOOKINGS_OWNER_INVALID. |
Pagination
- The cursor is opaque. Store and return it verbatim; do not build or edit one.
- Keep requesting pages with the returned
nextCursoruntilnextCursorisnull. There is no total count. - A page may contain fewer than
limititems even when more pages exist. Never treat a short page as the last page; continue untilnextCursorisnull. kindsmay change between pages. The cursor only records where the caller is in time, not which modes were requested.- If you send
ownerUuid, keep it unchanged on every page. A cursor does not grant access to another account's bookings. - Items are ordered by
bookedAtdescending, then bykindin the orderBUS,FERRY,TRAIN,FLIGHT, then bybookingKeyascending. Every booking is returned exactly once across the pages of one walk, including bookings that share a timestamp across the page boundary. - An invalid or modified cursor returns
400 BOOKINGS_CURSOR_INVALID. Start again without a cursor if you cannot reuse the value returned by the API.
Success Response
The example below shows bookings belonging to the authenticated account.
{
"items": [
{
"id": "FLIGHT:F:r:4f5c3d2e-9a1b-4c8d-8e7f-0a1b2c3d4e5f",
"kind": "FLIGHT",
"bookingKey": "F:r:4f5c3d2e-9a1b-4c8d-8e7f-0a1b2c3d4e5f",
"status": "CONFIRMED",
"statusDetail": "ISSUED",
"bookedAt": "2026-01-06T09:15:00.000Z",
"reference": {
"paymentReference": "FLT1234567890123456",
"ticketId": "6be55963-070f-4389-bc83-2a9300dbf9f4",
"pnr": "X7K9LM",
"reservationId": "4f5c3d2e-9a1b-4c8d-8e7f-0a1b2c3d4e5f",
"ticketNumbers": ["0021234567890", "0021234567891"]
},
"operator": { "uuid": null, "tradingName": "Precision Air" },
"journeyType": "RETURN",
"journey": {
"from": "DAR",
"to": "JRO",
"departureDate": "2026-01-20",
"departureTime": "06:30",
"arrivalDate": "2026-01-20",
"arrivalTime": "07:45"
},
"legs": [
{
"direction": "OUTBOUND",
"from": "DAR",
"to": "JRO",
"departureDate": "2026-01-20",
"departureTime": "06:30",
"arrivalDate": "2026-01-20",
"arrivalTime": "07:45"
},
{
"direction": "RETURN",
"from": "JRO",
"to": "DAR",
"departureDate": "2026-01-24",
"departureTime": "18:10",
"arrivalDate": "2026-01-24",
"arrivalTime": "19:25"
}
],
"passengers": {
"count": 2,
"cancelledCount": 0,
"names": ["Asha Juma", "Juma Ali"],
"seats": []
},
"price": { "amount": "640000.00", "currency": "TZS" },
"bookingUserType": "ONLINE",
"bookingChannel": "MOBILE_APP",
"flight": {
"reservationId": "4f5c3d2e-9a1b-4c8d-8e7f-0a1b2c3d4e5f",
"bookingReference": "X7K9LM",
"airlineCodes": ["PW"],
"flightNumbers": ["PW411", "PW418"],
"validatingCarrier": "PW",
"issuanceStatus": "ISSUED"
}
},
{
"id": "TRAIN:T:p:SGR1234567890123456",
"kind": "TRAIN",
"bookingKey": "T:p:SGR1234567890123456",
"status": "CONFIRMED",
"statusDetail": "PAID",
"bookedAt": "2026-01-05T10:00:00.000Z",
"reference": {
"paymentReference": "SGR1234567890123456",
"ticketId": null,
"pnr": "ABC123",
"reservationId": null,
"ticketNumbers": ["TRC0001", "TRC0002"]
},
"operator": {
"uuid": "9f1c2b3a-4d5e-4f60-8a71-b2c3d4e5f607",
"tradingName": "TRC"
},
"journeyType": "ONE_WAY",
"journey": {
"from": "Dar es Salaam",
"to": "Dodoma",
"departureDate": "2026-01-10",
"departureTime": "07:00",
"arrivalDate": "2026-01-10",
"arrivalTime": "11:00"
},
"legs": [
{
"direction": "OUTBOUND",
"from": "Dar es Salaam",
"to": "Dodoma",
"departureDate": "2026-01-10",
"departureTime": "07:00",
"arrivalDate": "2026-01-10",
"arrivalTime": "11:00"
}
],
"passengers": {
"count": 2,
"cancelledCount": 0,
"names": ["Asha Juma", "Juma Ali"],
"seats": ["A 12", "A 13"]
},
"price": { "amount": "98000.00", "currency": "TZS" },
"bookingUserType": "ONLINE",
"bookingChannel": "MOBILE_APP",
"train": {
"pnr": "ABC123",
"trainName": "SGR Express",
"trainNumber": "T101",
"coach": "A",
"providerMode": "LIVE"
}
},
{
"id": "FERRY:B:p:9122601041430987",
"kind": "FERRY",
"bookingKey": "B:p:9122601041430987",
"status": "PENDING_PAYMENT",
"statusDetail": "UNPAID",
"bookedAt": "2026-01-04T14:30:00.000Z",
"reference": {
"paymentReference": "9122601041430987",
"ticketId": "AZM-2601-04431",
"pnr": null,
"reservationId": null,
"ticketNumbers": []
},
"operator": {
"uuid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"tradingName": "Azam Marine"
},
"journeyType": "ONE_WAY",
"journey": {
"from": "Dar es Salaam",
"to": "Zanzibar",
"departureDate": "2026-01-08",
"departureTime": "09:30",
"arrivalDate": "2026-01-08",
"arrivalTime": "11:30"
},
"legs": [
{
"direction": "OUTBOUND",
"from": "Dar es Salaam",
"to": "Zanzibar",
"departureDate": "2026-01-08",
"departureTime": "09:30",
"arrivalDate": "2026-01-08",
"arrivalTime": "11:30"
}
],
"passengers": {
"count": 1,
"cancelledCount": 0,
"names": ["Asha Juma"],
"seats": ["A12"]
},
"price": { "amount": "35000.00", "currency": "TZS" },
"bookingUserType": "ONLINE",
"bookingChannel": "MOBILE_APP",
"bus": { "ticketId": "AZM-2601-04431", "ferryOption": "PASSENGER" }
},
{
"id": "BUS:B:p:9122601030805123",
"kind": "BUS",
"bookingKey": "B:p:9122601030805123",
"status": "PARTIALLY_CANCELLED",
"statusDetail": "PARTIALLY_CANCELLED",
"bookedAt": "2026-01-03T08:05:00.000Z",
"reference": {
"paymentReference": "9122601030805123",
"ticketId": "KLM-2601-03118",
"pnr": null,
"reservationId": null,
"ticketNumbers": []
},
"operator": {
"uuid": "7a8b9c0d-1e2f-4a3b-9c4d-5e6f7a8b9c0d",
"tradingName": "Kilimanjaro Express"
},
"journeyType": "ONE_WAY",
"journey": {
"from": "Dar es Salaam",
"to": "Arusha",
"departureDate": "2026-01-07",
"departureTime": "06:00",
"arrivalDate": "2026-01-07",
"arrivalTime": "16:30"
},
"legs": [
{
"direction": "OUTBOUND",
"from": "Dar es Salaam",
"to": "Arusha",
"departureDate": "2026-01-07",
"departureTime": "06:00",
"arrivalDate": "2026-01-07",
"arrivalTime": "16:30"
}
],
"passengers": {
"count": 3,
"cancelledCount": 1,
"names": ["Asha Juma", "Juma Ali", "Neema Said"],
"seats": ["12", "13", "14"]
},
"price": { "amount": "135000.00", "currency": "TZS" },
"bookingUserType": "ONLINE",
"bookingChannel": "MOBILE_APP",
"bus": { "ticketId": "KLM-2601-03118", "ferryOption": null }
}
],
"nextCursor": "eyJ2IjoxLCJ0IjoxNzY3NDI3NTAwMDAwLCJ4IjpbIkI6cDo5MTIyNjAxMDMwODA1MTIzIl19",
"metadata": { "limit": 20, "kinds": ["BUS", "FERRY", "TRAIN", "FLIGHT"] }
}
Response Fields
| Field | Type | Description |
|---|---|---|
items | array | Booking Summary Objects, newest first. May be empty on the last page. |
nextCursor | string or null | Cursor for the next page, or null when there are no more bookings. |
metadata.limit | number | The page size actually applied after clamping. |
metadata.kinds | array | The kinds actually applied, in canonical order (BUS, FERRY, TRAIN, FLIGHT). |
Booking Lookup
Finds bookings that match one selector across every selected mode. Use it for support search and for resolving a reference a customer reads out.
Endpoint: GET /api/bookings/lookup
Example Requests:
GET /api/bookings/lookup?paymentReference=SGR1234567890123456
GET /api/bookings/lookup?ticketId=TRC0001&kinds=TRAIN
GET /api/bookings/lookup?reference=X7K9LM
GET /api/bookings/lookup?phoneNumber=0714503163
GET /api/bookings/lookup?phoneNumber=714503163&countryCode=255
GET /api/bookings/lookup?name=Asha%20Juma&from=2026-01-01&to=2026-01-31
Query Parameters
Exactly one selector must be present (non-empty after trimming). Sending none, or more than one, returns 400 BOOKINGS_SELECTOR_INVALID.
| Parameter | Type | Required | Description |
|---|---|---|---|
paymentReference | string | Selector | Safiri payment reference of the booking (912… bus and ferry, SGR… train, FLT… flight). |
ticketId | string | Selector | Ticket number as printed on the ticket: bus or ferry ticketId, train TRC ticket number, flight ticket ID or IATA e-ticket number. |
reference | string | Selector | Catch-all. Matches everything paymentReference and ticketId match, plus the train PNR, the flight booking reference (PNR) and airline record locators. |
name | string | Selector | Passenger name, case-insensitive substring match. |
phoneNumber | string | Selector | Passenger phone number in any common shape (see Phone numbers). |
countryCode | string | No | Calling code digits (for example 255) used to interpret a phoneNumber that has no + prefix. Ignored for other selectors. |
from | string | No | Lower bound on bookedAt, YYYY-MM-DD, inclusive from 00:00 UTC of that day. |
to | string | No | Upper bound on bookedAt, YYYY-MM-DD, inclusive of the whole day (the query uses the next day's 00:00 UTC as an exclusive bound). |
kinds | string | No | As in the Activity Feed. Only the selected modes are searched. |
ownerUuid | string | No | As in the Activity Feed. Use only your own account UUID, or omit it. |
Selectors
Reference-style selectors (paymentReference, ticketId, reference) are trimmed and upper-cased, then must match ^[A-Z0-9-]{4,40}$; otherwise the response is 400 BOOKINGS_REFERENCE_INVALID. Matching is exact on the normalised value; there is no prefix or fuzzy matching. When the value does not already start with the bus payment prefix (912), the prefixed form is also tried against bus and ferry payment references, so a customer who reads out a bare bus number is still found.
| Selector | Bus / Ferry | Train | Flight |
|---|---|---|---|
paymentReference | Safiri payment reference, with or without the 912 prefix | Safiri payment reference | Safiri payment reference |
ticketId | Ticket number, or payment number without the 912 prefix | TRC ticket number | Ticket ID or e-ticket number |
reference | Payment reference or ticket number | Payment reference, PNR or ticket number | Payment reference, ticket ID, booking reference, airline record locator or e-ticket number |
name | Passenger name | Passenger name | Passenger name |
phoneNumber | Passenger phone number | Passenger phone number | Passenger phone number |
Examples per mode:
| Mode | paymentReference | ticketId | reference also finds |
|---|---|---|---|
| Bus / Ferry | 9122601030805123 | KLM-2601-03118 | either of the two |
| Train | SGR1234567890123456 | TRC0001 (TRC ticket number) | ABC123 (PNR) |
| Flight | FLT1234567890123456 | 0021234567890 (IATA e-ticket number) | X7K9LM (booking reference), airline record locators |
A match on any ticket returns its whole booking, including every passenger and leg, subject to the caller's access scope. A reference belonging to another account returns no results.
Phone numbers
phoneNumber must be 6 to 20 characters made of digits, spaces, +, (, ) and -. It is parsed into a calling code and a national number by trying, in order:
- A value that starts with
+(for example+255714503163) is parsed exactly as given and no other shape is tried. - Otherwise, when
countryCodeis supplied: the digits as an international number if they already start with that code (255714503163withcountryCode=255), then+<countryCode>followed by the national digits with any leading0removed (0714503163or714503163withcountryCode=255). - Otherwise, or when those fail: the same two shapes with Safiri's default calling code
255.
The first shape that parses as a valid number wins; if none does, the response is 400 BOOKINGS_PHONE_INVALID.
Name search
name is trimmed and internal whitespace is collapsed. It must be 2 to 60 characters made of letters (any script), digits, spaces, apostrophes, dots and hyphens; otherwise the response is 400 BOOKINGS_NAME_INVALID. The search is a case-insensitive substring match over passenger names. It matches passengers, not buyers.
Date window
fromandtoare optional. Each must be a real calendar date inYYYY-MM-DD;fromlater thantoreturns400 BOOKINGS_WINDOW_INVALID.- When both bounds are given, the span may be at most 92 days. A window with only
fromis open-ended. - The applied window is returned in
metadata.window(nullwhen none), withtoexpressed as the exclusive upper bound.
Result rules
- At most 25 bookings are returned, newest first (same ordering as the feed).
metadata.truncatedistruewhen the results may be incomplete; narrow the search with a tighter date window orkindswhen it is set. - Each item carries
matchedOn, the field family that produced the hit:PAYMENT_REFERENCE,TICKET_NUMBER,PNR,PHONEorNAME. AticketIdsearch that finds a bus booking through its912-prefixed payment reference still reportsTICKET_NUMBER, because that value is the ticket number; onlypaymentReferenceandreferencesearches reportPAYMENT_REFERENCE. - Each item carries
owned, which istruefor bookings returned to agent and customer accounts. - A search that finds nothing, or finds only bookings the caller may not see, returns
200withitems: []. The endpoint never returns404. - A search that cannot finish in time returns
400 BOOKINGS_LOOKUP_TIMEOUT; narrow it and retry.
Success Response
The example below shows a booking belonging to the authenticated agent.
{
"items": [
{
"id": "TRAIN:T:p:SGR1234567890123456",
"kind": "TRAIN",
"bookingKey": "T:p:SGR1234567890123456",
"status": "CONFIRMED",
"statusDetail": "PAID",
"bookedAt": "2026-01-05T10:00:00.000Z",
"reference": {
"paymentReference": "SGR1234567890123456",
"ticketId": null,
"pnr": "ABC123",
"reservationId": null,
"ticketNumbers": ["TRC0001", "TRC0002"]
},
"operator": {
"uuid": "9f1c2b3a-4d5e-4f60-8a71-b2c3d4e5f607",
"tradingName": "TRC"
},
"journeyType": "ONE_WAY",
"journey": {
"from": "Dar es Salaam",
"to": "Dodoma",
"departureDate": "2026-01-10",
"departureTime": "07:00",
"arrivalDate": "2026-01-10",
"arrivalTime": "11:00"
},
"legs": [
{
"direction": "OUTBOUND",
"from": "Dar es Salaam",
"to": "Dodoma",
"departureDate": "2026-01-10",
"departureTime": "07:00",
"arrivalDate": "2026-01-10",
"arrivalTime": "11:00"
}
],
"passengers": {
"count": 2,
"cancelledCount": 0,
"names": ["Asha Juma", "Juma Ali"],
"seats": ["A 12", "A 13"]
},
"price": { "amount": "98000.00", "currency": "TZS" },
"bookingUserType": "AGENT",
"bookingChannel": "API",
"train": {
"pnr": "ABC123",
"trainName": "SGR Express",
"trainNumber": "T101",
"coach": "A",
"providerMode": "LIVE"
},
"matchedOn": "TICKET_NUMBER",
"owned": true
}
],
"metadata": {
"selector": "ticketId",
"truncated": false,
"kinds": ["TRAIN"],
"window": null
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
items | array | Booking Summary Objects extended with matchedOn and owned. At most 25, newest first. |
items[].matchedOn | string | PAYMENT_REFERENCE, TICKET_NUMBER, PNR, PHONE or NAME. |
items[].owned | boolean | Whether the calling account is the buyer of the booking. |
metadata.selector | string | The selector used: paymentReference, ticketId, reference, name or phoneNumber. |
metadata.truncated | boolean | true when the results may be incomplete. Narrow the search before trying again. |
metadata.kinds | array | The kinds actually searched, in canonical order. |
metadata.window | object or null | { "from": ISO string or null, "to": ISO string or null } actually applied to bookedAt; to is exclusive. null when none. |
Booking Summary Object
Every item returned by both endpoints has this shape. Exactly one of bus, train or flight is present, chosen by kind (FERRY uses bus). Text fields are trimmed and empty text is returned as null. Dates are YYYY-MM-DD; times use HH:mm.
| Field | Type | Description |
|---|---|---|
id | string | kind + : + bookingKey. Stable across requests and safe to use as a list key. |
kind | string | BUS, FERRY, TRAIN or FLIGHT. |
bookingKey | string | Stable per-booking key (see Booking keys). |
status | string | Booking-level status (see Status). |
statusDetail | string or null | The mode-specific status that decided status. |
bookedAt | string | ISO 8601 timestamp when the booking was created. This is the feed's sort key. |
reference.paymentReference | string or null | Safiri payment reference. |
reference.ticketId | string or null | Bus/ferry ticket ID or flight ticket ID; always null for train. |
reference.pnr | string or null | Train PNR or flight booking reference; always null for bus/ferry. |
reference.reservationId | string or null | Flight reservation ID; null for other modes. |
reference.ticketNumbers | array | Train TRC ticket numbers or flight e-ticket numbers, distinct, at most 10; empty for bus/ferry. |
operator.uuid | string or null | Transit agency UUID; null for flights. |
operator.tradingName | string or null | Operator name. For flights: the single airline of the itinerary, otherwise the validating carrier code. |
journeyType | string | ONE_WAY or RETURN. Bus and ferry are always ONE_WAY. |
journey | object or null | The outbound leg without direction: from, to, departureDate, departureTime, arrivalDate, arrivalTime. |
legs | array | One entry per direction, OUTBOUND first, each with direction plus the journey fields. Bus and ferry always have one leg. |
passengers.count | number | Passengers on the booking (see the per-mode table). |
passengers.cancelledCount | number | Passengers whose ticket is cancelled. |
passengers.names | array | Distinct passenger names, at most 5. |
passengers.seats | array | Distinct seat labels, at most 10. Always empty for flights. |
price.amount | string or null | Booking total as a decimal string with two decimals (for example "98000.00"). null when any amount is not numeric or currencies are mixed. |
price.currency | string or null | ISO currency code, or null when currencies are mixed. |
bookingUserType | string or null | Who made the booking (for example ONLINE, AGENT). |
bookingChannel | string or null | Channel the booking was made through. |
bus | object | Present for BUS and FERRY (see below). |
train | object | Present for TRAIN (see below). |
flight | object | Present for FLIGHT (see below). |
Passenger contact details, identity documents and payment details are not included in these responses.
Status
status summarises all tickets in the booking. Cancellation takes precedence:
all tickets cancelled gives CANCELLED, while a mix of cancelled and active
tickets gives PARTIALLY_CANCELLED.
| Mode | Status behaviour | statusDetail |
|---|---|---|
| Bus / Ferry | Unpaid active tickets give PENDING_PAYMENT; paid active tickets give CONFIRMED. | CANCELLED, PARTIALLY_CANCELLED, UNPAID, PAID |
| Train | Reserved tickets awaiting issuance give PENDING_PAYMENT; issued tickets give CONFIRMED. Unfinished reservations are not listed. Check the issuance endpoint for payment progress. | CANCELLED, PARTIALLY_CANCELLED, RESERVED, PAID |
| Flight | Bookings awaiting payment give PENDING_PAYMENT; issuance or refund work gives PROCESSING; issued tickets give CONFIRMED. | The applicable flight reservation status, or PARTIALLY_CANCELLED |
status | Meaning |
|---|---|
PENDING_PAYMENT | The booking is awaiting payment or, for trains, issuance confirmation. |
PROCESSING | Paid, but ticket issuance or a refund is still in progress (flights only). |
CONFIRMED | Every ticket is paid and issued. |
PARTIALLY_CANCELLED | Some, but not all, tickets of the booking are cancelled. |
CANCELLED | Every ticket of the booking is cancelled. |
Per-mode blocks
bus (also used for FERRY)
| Field | Type | Description |
|---|---|---|
ticketId | string or null | The bus or ferry ticket ID (same value as reference.ticketId). |
ferryOption | string or null | The ferry option stored on the ticket (PASSENGER, STUDENT, CAR, MOTORCYCLE, ...) when the operator is a marine operator; otherwise null. |
train
| Field | Type | Description |
|---|---|---|
pnr | string or null | TRC PNR (same value as reference.pnr). |
trainName | string or null | Train name. |
trainNumber | string or null | Train number. |
coach | string or null | Railway car name of the first ticket. |
providerMode | string | LIVE for real TRC bookings, FAKE for simulated ones. Defaults to LIVE. |
flight
| Field | Type | Description |
|---|---|---|
reservationId | string or null | Safiri flight reservation ID (same value as reference.reservationId). |
bookingReference | string or null | Airline booking reference / PNR (same value as reference.pnr). |
airlineCodes | array | Distinct marketing carrier codes across all segments. |
flightNumbers | array | Distinct flight numbers across all segments. |
validatingCarrier | string or null | Validating carrier code. |
issuanceStatus | string or null | The reservation's issuance status. |
How each mode is summarised
| Field | Bus / Ferry | Train | Flight |
|---|---|---|---|
kind | BUS or FERRY | TRAIN | FLIGHT |
legs | One OUTBOUND leg | OUTBOUND and, when booked, RETURN | One entry per itinerary, in itinerary order |
passengers.count | Passenger count | Passenger count, without counting a return journey twice | Distinct passenger count |
passengers.names | Up to five distinct passenger names | Up to five distinct outbound passenger names | Up to five distinct passenger names |
passengers.seats | Up to ten seat labels | Up to ten outbound car and seat labels | Empty |
price | Total for all passengers | Total for all passengers and journey legs | Total for all passengers across the whole itinerary |
operator | Bus or ferry operator | TRC | The airline or validating carrier; uuid is null |
Booking keys
bookingKey is an opaque identifier for a booking. Store it exactly as returned;
do not construct one or interpret its components. Use id as a stable list key
and the reference fields with the ticket endpoints below.
Deep links
Use the references on the summary to open the full ticket documents through the mode-specific endpoints:
kind | Endpoint |
|---|---|
BUS, FERRY | GET /api/ticket?paymentReference=<reference.paymentReference> (see Bus Booking) |
TRAIN | GET /api/trip/train/tickets?paymentReference=<reference.paymentReference> (see Train Booking) |
FLIGHT | GET /api/flight/tickets?paymentReference=<reference.paymentReference> or GET /api/flight/tickets?ticketId=<reference.ticketId> (see Flight Booking) |
Limitations
- Deleted bookings are not listed. A cancelled train or flight booking may no longer appear in either endpoint. Retain the references returned when the booking was created.
- Ownership is the buyer. Agent and customer accounts see bookings bought with their account only. Passengers on agent bookings cannot find those bookings through their own accounts.
- Order is booking time, not travel time. The feed is sorted by
bookedAt; a ticket for tomorrow bought last month sorts below a ticket bought today for next year. Sort client-side byjourney.departureDateif you need an upcoming-trips view. - Flight price counts each passenger once. The flight total covers the whole itinerary, including a return journey. Train and bus totals cover every passenger and journey leg.
- Passenger names and seats may be abbreviated.
passengers.countandpassengers.cancelledCountare exact. The summary includes at most five distinct names and ten seat labels; use the ticket endpoint for complete details. - Reference matching is exact and upper-cased. Values are upper-cased before matching, so identifiers stored in lower case cannot be found through the reference selectors; use the mode-specific endpoints for those.
- Name search matches passengers, not buyers, and is a substring match with no transliteration or fuzzy matching.
- Train payment and issuance progress requires the issuance endpoint. A train booking may remain
PENDING_PAYMENTafter payment while confirmation is outstanding, including after the payment deadline. UseGET /api/trip/train/ticket/{paymentReference}/issuancefor the latest progress; the feed label alone does not mean the customer should pay again. - Short feed pages are normal (see Pagination); do not infer the end of the feed from a page size.
Response handling
Responses use Cache-Control: private, no-store; do not cache them in a shared
cache. If a search returns BOOKINGS_LOOKUP_TIMEOUT, narrow the date window or
use a more specific reference before retrying.
Error Responses
When a bookings request fails, the API returns a standard error object:
{
"error": { "name": "bookings" },
"errorCode": "BOOKINGS_SELECTOR_INVALID",
"errorMessage": "Provide exactly one of paymentReference, ticketId, reference, name, phoneNumber",
"status": 400
}
| HTTP | errorCode | When |
|---|---|---|
| 400 | BOOKINGS_LIMIT_INVALID | limit is not a string of digits. |
| 400 | BOOKINGS_CURSOR_INVALID | cursor is invalid or has been modified. Use the value returned by the API. |
| 400 | BOOKINGS_KINDS_INVALID | kinds contains an unknown token or no tokens at all. |
| 400 | BOOKINGS_OWNER_INVALID | ownerUuid is not shaped like a UUID. |
| 400 | BOOKINGS_SELECTOR_INVALID | Lookup received no selector, or more than one. |
| 400 | BOOKINGS_REFERENCE_INVALID | paymentReference, ticketId or reference is not 4 to 40 letters, digits or hyphens. |
| 400 | BOOKINGS_NAME_INVALID | name is not 2 to 60 letters, digits, spaces, apostrophes, dots or hyphens. |
| 400 | BOOKINGS_PHONE_INVALID | phoneNumber has the wrong shape or does not parse as a valid number with any of the tried calling codes. |
| 400 | BOOKINGS_WINDOW_INVALID | from or to is not a valid YYYY-MM-DD date, from is after to, or a bounded window spans more than 92 days. |
| 400 | BOOKINGS_LOOKUP_TIMEOUT | The search could not finish in time. errorMessage is Narrow the search window. |
| 401 | PROTECTED_RESOURCE | No authenticated user on the request. |
| 403 | FORBIDDEN_ACCESS_PRIVILEGE | The requested ownerUuid belongs to another account. |
| 500 | INTERNAL_SERVER_ERROR | Any other failure. The message is fixed (Booking activity request failed or Booking lookup request failed). |