{"openapi":"3.1.0","info":{"title":"Sterling Travel & Tour — Partner API","version":"1.0.0","description":"Book flights for your customers from your own systems. Create a key on **Organisation → API keys** (shown once), send it as `X-API-Key`. Money is always integer minor units. Writes need `Idempotency-Key`. Each key has its own requests-per-minute limit (`X-RateLimit-Limit` / `X-RateLimit-Remaining`). Flow: search → hold an offer → upload each traveller's passport image (trips abroad) → add the travellers → pay from your organisation's wallet → poll the order until `TICKETED`."},"servers":[{"url":"https://sterling.automatrix.et/v1","description":"This environment"}],"security":[{"ApiKey":[]}],"tags":[{"name":"Flights","description":"Search"},{"name":"Orders","description":"Hold, travellers, pay, status"},{"name":"Account","description":"Bookings and wallet"}],"paths":{"/v1/flights/search":{"post":{"tags":["Flights"],"summary":"Search flights","description":"Offers at your sell price, by currency then cheapest first (up to 200). A trip that starts outside Ethiopia is answered in two currencies: Ethiopian Airlines in ETB, the other airlines in USD — `currencies` lists them and each offer's `price.currency` says which. A company with a travel policy sees `travelPolicy` on each offer.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"Offers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"Invalid search.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders":{"post":{"tags":["Orders"],"summary":"Hold an offer","description":"Locks the price for a short time (`holdExpiresAt`). Refused with 409 when the flight leaves too soon, the offer is gone or your travel policy blocks it (`travel_policy`).","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"A unique value per operation (8–128 characters). A retry with the same key and body returns the first answer.","schema":{"type":"string","minLength":8,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldRequest"}}}},"responses":{"201":{"description":"The held order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cannot be held.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{id}":{"get":{"tags":["Orders"],"summary":"Get an order","description":"Poll after paying until `status` is `TICKETED` (tickets in `fulfilment.tickets`).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{id}/passengers":{"put":{"tags":["Orders"],"summary":"Add the travellers","description":"The airline's booking rules are checked here; a refused traveller answers 409 with the reasons. The booking contact must give a phone number (`contact.phone`); every traveller gives a middle name and every adult a mobile number (`passengers.N.phone`). A birth date is required for every traveller, except an adult on a domestic trip (every airport in Ethiopia). On a trip abroad the typed passport number and expiry date must match the uploaded passport image: otherwise 422 `document_image_mismatch` with the fields in `details.fieldErrors` (`passengers.N.document.number` / `passengers.N.document.expiresOn`).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassengersRequest"}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassengersResponse"}}}},"400":{"description":"Invalid travellers: e.g. no contact phone, or a missing birth date (`details.fieldErrors`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Refused by the booking rules.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Passport image problems: `document_image_required`, `document_image_invalid`, `document_image_not_passport`, `document_image_mismatch` (`details.reason`, `details.fieldErrors`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{id}/passengers/document-image":{"post":{"tags":["Orders"],"summary":"Upload a traveller's passport image","description":"A trip abroad needs an image of every traveller's passport (PDF, JPEG or PNG, 8 MB max). Send the answer's `imageFileId` as `document.imageFileId` when adding the travellers; without one, a trip abroad is refused (422 `document_image_required`). For a trip abroad the image is read on upload: only a passport data page is accepted (a national ID or any other picture answers 422 `document_image_not_passport`; an illegible page 422 `document_image_unreadable` — upload a clearer photo). The number and expiry date read from it must match what you send with the travellers.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"Stored","content":{"application/json":{"schema":{"type":"object","properties":{"imageFileId":{"type":"string","format":"uuid"},"contentType":{"type":"string"},"size":{"type":"integer"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"No file, an unsupported type, or too large; on a trip abroad also `document_image_not_passport` or `document_image_unreadable` (`details.reason`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{id}/pay":{"post":{"tags":["Orders"],"summary":"Pay from your organisation's wallet","description":"Captures the amount due from the wallet in `currency` (409 `insufficient_funds` when short). Booking and ticketing then run in the background — poll the order.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"A unique value per operation (8–128 characters). A retry with the same key and body returns the first answer.","schema":{"type":"string","minLength":8,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayRequest"}}}},"responses":{"201":{"description":"The payment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cannot be paid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bookings":{"get":{"tags":["Account"],"summary":"Your organisation's bookings","parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"A page of bookings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingsPage"}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/wallet":{"get":{"tags":["Account"],"summary":"Your organisation's wallet balances","responses":{"200":{"description":"Wallets per currency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallets"}}}},"401":{"description":"Missing, invalid, revoked or expired API key (`api_key_required` / `api_key_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's owner may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key — wait `Retry-After` seconds (`RATE_LIMITED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Error":{"type":"object","required":["statusCode","code","message"],"properties":{"statusCode":{"type":"integer"},"code":{"type":"string","example":"CONFLICT"},"message":{"type":"string"},"details":{"type":"object","description":"`reason` explains a refusal; validation errors list `fieldErrors`."},"requestId":{"type":"string"}}},"SearchRequest":{"type":"object","required":["slices","passengers"],"properties":{"slices":{"type":"array","minItems":1,"items":{"type":"object","required":["origin","destination","date"],"properties":{"origin":{"type":"string","example":"ADD"},"destination":{"type":"string","example":"NBO"},"date":{"type":"string","format":"date"}}}},"passengers":{"type":"object","properties":{"adults":{"type":"integer","minimum":1},"children":{"type":"integer"},"infants":{"type":"integer"}}},"cabin":{"type":"string","enum":["ECONOMY","PREMIUM_ECONOMY","BUSINESS","FIRST"]}}},"Offer":{"type":"object","properties":{"offerId":{"type":"string","description":"Send back to hold it."},"slices":{"type":"array","items":{"type":"object"}},"price":{"type":"object","properties":{"currency":{"type":"string"},"totalMinor":{"type":"integer"},"decimals":{"type":"integer"}}},"validatingCarrier":{"type":["string","null"]},"refundable":{"type":["boolean","null"]},"expiresAt":{"type":"string","format":"date-time"},"travelPolicy":{"type":"object","properties":{"outcome":{"type":"string","enum":["allow","request","deny"]}}}}},"SearchResponse":{"type":"object","properties":{"offers":{"type":"array","items":{"$ref":"#/components/schemas/Offer"}},"currencies":{"type":"array","items":{"type":"string"},"description":"The currencies of `offers`, ETB first. Never compare prices across currencies."}}},"HoldRequest":{"type":"object","required":["offerId","search"],"properties":{"offerId":{"type":"string"},"search":{"$ref":"#/components/schemas/SearchRequest"}}},"Order":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string","example":"ST7K2P9Q"},"status":{"type":"string","enum":["HELD","AWAITING_PAYMENT","PAID","TICKETED","EXPIRED","CANCELLED","FAILED","NEEDS_OPS"]},"holdExpiresAt":{"type":"string","format":"date-time"},"totalDueMinor":{"type":"integer"},"price":{"type":"object","properties":{"currency":{"type":"string"},"totalMinor":{"type":"integer"}}},"tripApproval":{"type":"string","enum":["NOT_REQUIRED","PENDING","APPROVED","REJECTED"]},"fulfilment":{"type":["object","null"],"properties":{"status":{"type":"string"},"locator":{"type":["string","null"]},"tickets":{"type":"array","items":{"type":"object","properties":{"number":{"type":"string"},"travelerName":{"type":["string","null"]}}}}}}}},"PassengersRequest":{"type":"object","required":["passengers","contact"],"properties":{"passengers":{"type":"array","items":{"type":"object","required":["type","givenName","middleName","surname","gender"],"properties":{"type":{"type":"string","enum":["ADT","CHD","INF"]},"givenName":{"type":"string"},"middleName":{"type":"string"},"surname":{"type":"string"},"gender":{"type":"string","enum":["MALE","FEMALE"]},"birthDate":{"type":"string","format":"date","description":"Required, except for an adult (ADT) on a domestic trip."},"email":{"type":"string"},"phone":{"type":"string","example":"+251911000000","description":"Required for an adult (ADT)."},"document":{"type":"object","properties":{"type":{"type":"string","enum":["PASSPORT","NATIONAL_ID"]},"number":{"type":"string"},"issuingCountry":{"type":"string","example":"ET"},"nationality":{"type":"string","example":"ET"},"expiresOn":{"type":"string","format":"date"},"imageFileId":{"type":"string","format":"uuid","description":"From the passport image upload; required for a trip abroad."}}},"infantOfPosition":{"type":"integer"}}}},"contact":{"type":"object","required":["phone"],"properties":{"email":{"type":"string"},"phone":{"type":"string","example":"+251911000000","description":"Required: the booking contact's phone."},"refused":{"type":"boolean","deprecated":true,"description":"No longer accepted: omit it or send false (`true` answers 400)."}}}}},"PassengersResponse":{"type":"object","properties":{"passengers":{"type":"array","items":{"type":"object"}},"warnings":{"type":"array","items":{"type":"object"}}}},"PayRequest":{"type":"object","required":["currency"],"properties":{"currency":{"type":"string","example":"ETB"}}},"Payment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string"},"status":{"type":"string","example":"CAPTURED"},"amount":{"type":"object","required":["amountMinor","currency"],"properties":{"amountMinor":{"type":"integer","description":"Integer minor units (cents)."},"currency":{"type":"string","example":"ETB"},"decimals":{"type":"integer","example":2}}}}},"BookingsPage":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}}},"Wallets":{"type":"object","properties":{"wallets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"currency":{"type":"string"},"balanceMinor":{"type":"integer"}}}}}}}}}