Skip to main content

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 BookingSummary shape for every mode, with a small mode-specific block (bus, train or flight) 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.

note

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​

ParameterTypeRequiredDescription
limitstringNoPage 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.
cursorstringNoOpaque 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.
kindsstringNoComma-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.
ownerUuidstringNoOptional 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 nextCursor until nextCursor is null. There is no total count.
  • A page may contain fewer than limit items even when more pages exist. Never treat a short page as the last page; continue until nextCursor is null.
  • kinds may 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 bookedAt descending, then by kind in the order BUS, FERRY, TRAIN, FLIGHT, then by bookingKey ascending. 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​

FieldTypeDescription
itemsarrayBooking Summary Objects, newest first. May be empty on the last page.
nextCursorstring or nullCursor for the next page, or null when there are no more bookings.
metadata.limitnumberThe page size actually applied after clamping.
metadata.kindsarrayThe 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.

ParameterTypeRequiredDescription
paymentReferencestringSelectorSafiri payment reference of the booking (912… bus and ferry, SGR… train, FLT… flight).
ticketIdstringSelectorTicket number as printed on the ticket: bus or ferry ticketId, train TRC ticket number, flight ticket ID or IATA e-ticket number.
referencestringSelectorCatch-all. Matches everything paymentReference and ticketId match, plus the train PNR, the flight booking reference (PNR) and airline record locators.
namestringSelectorPassenger name, case-insensitive substring match.
phoneNumberstringSelectorPassenger phone number in any common shape (see Phone numbers).
countryCodestringNoCalling code digits (for example 255) used to interpret a phoneNumber that has no + prefix. Ignored for other selectors.
fromstringNoLower bound on bookedAt, YYYY-MM-DD, inclusive from 00:00 UTC of that day.
tostringNoUpper 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).
kindsstringNoAs in the Activity Feed. Only the selected modes are searched.
ownerUuidstringNoAs 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.

SelectorBus / FerryTrainFlight
paymentReferenceSafiri payment reference, with or without the 912 prefixSafiri payment referenceSafiri payment reference
ticketIdTicket number, or payment number without the 912 prefixTRC ticket numberTicket ID or e-ticket number
referencePayment reference or ticket numberPayment reference, PNR or ticket numberPayment reference, ticket ID, booking reference, airline record locator or e-ticket number
namePassenger namePassenger namePassenger name
phoneNumberPassenger phone numberPassenger phone numberPassenger phone number

Examples per mode:

ModepaymentReferenceticketIdreference also finds
Bus / Ferry9122601030805123KLM-2601-03118either of the two
TrainSGR1234567890123456TRC0001 (TRC ticket number)ABC123 (PNR)
FlightFLT12345678901234560021234567890 (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:

  1. A value that starts with + (for example +255714503163) is parsed exactly as given and no other shape is tried.
  2. Otherwise, when countryCode is supplied: the digits as an international number if they already start with that code (255714503163 with countryCode=255), then +<countryCode> followed by the national digits with any leading 0 removed (0714503163 or 714503163 with countryCode=255).
  3. 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 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​

  • from and to are optional. Each must be a real calendar date in YYYY-MM-DD; from later than to returns 400 BOOKINGS_WINDOW_INVALID.
  • When both bounds are given, the span may be at most 92 days. A window with only from is open-ended.
  • The applied window is returned in metadata.window (null when none), with to expressed as the exclusive upper bound.

Result rules​

  • At most 25 bookings are returned, newest first (same ordering as the feed). metadata.truncated is true when the results may be incomplete; narrow the search with a tighter date window or kinds when it is set.
  • Each item carries matchedOn, the field family that produced the hit: PAYMENT_REFERENCE, TICKET_NUMBER, PNR, PHONE or NAME. A ticketId search that finds a bus booking through its 912-prefixed payment reference still reports TICKET_NUMBER, because that value is the ticket number; only paymentReference and reference searches report PAYMENT_REFERENCE.
  • Each item carries owned, which is true for bookings returned to agent and customer accounts.
  • A search that finds nothing, or finds only bookings the caller may not see, returns 200 with items: []. The endpoint never returns 404.
  • 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​

FieldTypeDescription
itemsarrayBooking Summary Objects extended with matchedOn and owned. At most 25, newest first.
items[].matchedOnstringPAYMENT_REFERENCE, TICKET_NUMBER, PNR, PHONE or NAME.
items[].ownedbooleanWhether the calling account is the buyer of the booking.
metadata.selectorstringThe selector used: paymentReference, ticketId, reference, name or phoneNumber.
metadata.truncatedbooleantrue when the results may be incomplete. Narrow the search before trying again.
metadata.kindsarrayThe kinds actually searched, in canonical order.
metadata.windowobject 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.

FieldTypeDescription
idstringkind + : + bookingKey. Stable across requests and safe to use as a list key.
kindstringBUS, FERRY, TRAIN or FLIGHT.
bookingKeystringStable per-booking key (see Booking keys).
statusstringBooking-level status (see Status).
statusDetailstring or nullThe mode-specific status that decided status.
bookedAtstringISO 8601 timestamp when the booking was created. This is the feed's sort key.
reference.paymentReferencestring or nullSafiri payment reference.
reference.ticketIdstring or nullBus/ferry ticket ID or flight ticket ID; always null for train.
reference.pnrstring or nullTrain PNR or flight booking reference; always null for bus/ferry.
reference.reservationIdstring or nullFlight reservation ID; null for other modes.
reference.ticketNumbersarrayTrain TRC ticket numbers or flight e-ticket numbers, distinct, at most 10; empty for bus/ferry.
operator.uuidstring or nullTransit agency UUID; null for flights.
operator.tradingNamestring or nullOperator name. For flights: the single airline of the itinerary, otherwise the validating carrier code.
journeyTypestringONE_WAY or RETURN. Bus and ferry are always ONE_WAY.
journeyobject or nullThe outbound leg without direction: from, to, departureDate, departureTime, arrivalDate, arrivalTime.
legsarrayOne entry per direction, OUTBOUND first, each with direction plus the journey fields. Bus and ferry always have one leg.
passengers.countnumberPassengers on the booking (see the per-mode table).
passengers.cancelledCountnumberPassengers whose ticket is cancelled.
passengers.namesarrayDistinct passenger names, at most 5.
passengers.seatsarrayDistinct seat labels, at most 10. Always empty for flights.
price.amountstring or nullBooking total as a decimal string with two decimals (for example "98000.00"). null when any amount is not numeric or currencies are mixed.
price.currencystring or nullISO currency code, or null when currencies are mixed.
bookingUserTypestring or nullWho made the booking (for example ONLINE, AGENT).
bookingChannelstring or nullChannel the booking was made through.
busobjectPresent for BUS and FERRY (see below).
trainobjectPresent for TRAIN (see below).
flightobjectPresent 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.

ModeStatus behaviourstatusDetail
Bus / FerryUnpaid active tickets give PENDING_PAYMENT; paid active tickets give CONFIRMED.CANCELLED, PARTIALLY_CANCELLED, UNPAID, PAID
TrainReserved 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
FlightBookings awaiting payment give PENDING_PAYMENT; issuance or refund work gives PROCESSING; issued tickets give CONFIRMED.The applicable flight reservation status, or PARTIALLY_CANCELLED
statusMeaning
PENDING_PAYMENTThe booking is awaiting payment or, for trains, issuance confirmation.
PROCESSINGPaid, but ticket issuance or a refund is still in progress (flights only).
CONFIRMEDEvery ticket is paid and issued.
PARTIALLY_CANCELLEDSome, but not all, tickets of the booking are cancelled.
CANCELLEDEvery ticket of the booking is cancelled.

Per-mode blocks​

bus (also used for FERRY)

FieldTypeDescription
ticketIdstring or nullThe bus or ferry ticket ID (same value as reference.ticketId).
ferryOptionstring or nullThe ferry option stored on the ticket (PASSENGER, STUDENT, CAR, MOTORCYCLE, ...) when the operator is a marine operator; otherwise null.

train

FieldTypeDescription
pnrstring or nullTRC PNR (same value as reference.pnr).
trainNamestring or nullTrain name.
trainNumberstring or nullTrain number.
coachstring or nullRailway car name of the first ticket.
providerModestringLIVE for real TRC bookings, FAKE for simulated ones. Defaults to LIVE.

flight

FieldTypeDescription
reservationIdstring or nullSafiri flight reservation ID (same value as reference.reservationId).
bookingReferencestring or nullAirline booking reference / PNR (same value as reference.pnr).
airlineCodesarrayDistinct marketing carrier codes across all segments.
flightNumbersarrayDistinct flight numbers across all segments.
validatingCarrierstring or nullValidating carrier code.
issuanceStatusstring or nullThe reservation's issuance status.

How each mode is summarised​

FieldBus / FerryTrainFlight
kindBUS or FERRYTRAINFLIGHT
legsOne OUTBOUND legOUTBOUND and, when booked, RETURNOne entry per itinerary, in itinerary order
passengers.countPassenger countPassenger count, without counting a return journey twiceDistinct passenger count
passengers.namesUp to five distinct passenger namesUp to five distinct outbound passenger namesUp to five distinct passenger names
passengers.seatsUp to ten seat labelsUp to ten outbound car and seat labelsEmpty
priceTotal for all passengersTotal for all passengers and journey legsTotal for all passengers across the whole itinerary
operatorBus or ferry operatorTRCThe 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.

Use the references on the summary to open the full ticket documents through the mode-specific endpoints:

kindEndpoint
BUS, FERRYGET /api/ticket?paymentReference=<reference.paymentReference> (see Bus Booking)
TRAINGET /api/trip/train/tickets?paymentReference=<reference.paymentReference> (see Train Booking)
FLIGHTGET /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 by journey.departureDate if 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.count and passengers.cancelledCount are 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_PAYMENT after payment while confirmation is outstanding, including after the payment deadline. Use GET /api/trip/train/ticket/{paymentReference}/issuance for 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
}
HTTPerrorCodeWhen
400BOOKINGS_LIMIT_INVALIDlimit is not a string of digits.
400BOOKINGS_CURSOR_INVALIDcursor is invalid or has been modified. Use the value returned by the API.
400BOOKINGS_KINDS_INVALIDkinds contains an unknown token or no tokens at all.
400BOOKINGS_OWNER_INVALIDownerUuid is not shaped like a UUID.
400BOOKINGS_SELECTOR_INVALIDLookup received no selector, or more than one.
400BOOKINGS_REFERENCE_INVALIDpaymentReference, ticketId or reference is not 4 to 40 letters, digits or hyphens.
400BOOKINGS_NAME_INVALIDname is not 2 to 60 letters, digits, spaces, apostrophes, dots or hyphens.
400BOOKINGS_PHONE_INVALIDphoneNumber has the wrong shape or does not parse as a valid number with any of the tried calling codes.
400BOOKINGS_WINDOW_INVALIDfrom or to is not a valid YYYY-MM-DD date, from is after to, or a bounded window spans more than 92 days.
400BOOKINGS_LOOKUP_TIMEOUTThe search could not finish in time. errorMessage is Narrow the search window.
401PROTECTED_RESOURCENo authenticated user on the request.
403FORBIDDEN_ACCESS_PRIVILEGEThe requested ownerUuid belongs to another account.
500INTERNAL_SERVER_ERRORAny other failure. The message is fixed (Booking activity request failed or Booking lookup request failed).