Connector API · v1
API reference
One REST API for every supplier on Supplier.Travel. Your system calls Connector. Connector calls each supplier your account can use, normalises the answers into one contract and keeps the booking record. Supplier connections, credentials and protocols are operated by Supplier.Travel, so nothing in this reference changes when a supplier is added.
| Environment | Base URL | Keys |
|---|---|---|
| Sandbox | https://sandbox.connector.supplier.travel | Sandbox plan keys; test inventory; bookings are never charged |
| Production | https://connector.supplier.travel | Growth and Enterprise plan keys |
All requests and responses are JSON over HTTPS (TLS 1.2+). Contracts are versioned per vertical (/hotels/v1, /flights/v1). Additive fields can appear at any time, so ignore fields you don't know. Breaking changes ship only as a new major version.
Quickstart
- Create an agency account. It starts on the free Sandbox plan.
- In the console, open API keys and create a key. Copy it once; it is never shown again.
- Search hotels:
curl https://sandbox.connector.supplier.travel/hotels/v1/search \
-H "Authorization: Bearer $CONNECTOR_KEY" \
-H "Content-Type: application/json" \
-d '{
"checkin": "2026-12-10", "checkout": "2026-12-12",
"rooms": [{ "adults": 2 }],
"destination": { "city_name": "Dubai", "country_code": "AE" },
"currency": "USD", "guest_nationality": "AE"
}'
Then prebook a rates[].id from the response and book the prebook. The booking flow walks through each step.
Authentication
Send your API key as a bearer token (or as X-API-Key):
Authorization: Bearer stk_9Kd2ZwBe…
- Keys belong to your agency. They inherit its supplier access, plan and markup.
- Create one key per system (booking engine, staging, back office) so you can revoke one without touching the others. Revocation is immediate.
- Keep keys server-side. Never embed them in browsers or mobile apps.
- A missing, unknown or revoked key returns
401 UNAUTHENTICATED.
Conventions
Money
Every amount is an integer in the currency's minor unit. decimals is the exponent: USD 2, KWD/OMR/BHD 3, JPY 0. Divide by 10^decimals for display. Nothing is hidden: total = supplier_total + platform_fee + markup.
"price": {
"currency": "USD", "decimals": 2,
"base": 17829, // supplier base before included taxes
"taxes": 3922, // taxes and fees included in supplier_total
"supplier_total": 21751,
"platform_fee": 0,
"markup": 544, // your account markup
"total": 22295
}
pay_at_property[] lists mandatory fees that are not in total and are collected by the hotel, such as tourism fees. Show them before checkout.
Identifiers
Ids are opaque strings such as nuitee~lp1b30d1, pb_7sptjqj2j3qre5iy or bk_vnio3epwl2pccmcx. Store them as text; rate ids can exceed 1,000 characters, so they always travel in JSON bodies.
Dates and times
Stay dates are YYYY-MM-DD. Timestamps are RFC 3339 in UTC (2026-12-08T10:00:00Z).
Partial results
Searches go to all your suppliers in parallel under one deadline. Every search response includes supplier_status[], one entry per supplier, with an outcome: ok, no_results, timeout, error, circuit_open, overloaded or skipped. partial: true means at least one supplier did not answer completely. A failed supplier never contributes results, so "no availability" and "supplier unavailable" are always distinguishable.
Request ids
Every response carries X-Request-Id. Send your own ([A-Za-z0-9._-]{1,64}) to correlate logs, and quote it when contacting support.
Limits
| Limit | Value |
|---|---|
| Request body | 64 KB |
| Search deadline | Up to 12 s end to end; slow suppliers are reported as timeout |
| Booking deadline | Up to 90 s; see unknown outcomes |
| Prebook validity | 20 minutes |
| API keys per agency | 20 active |
| Rate limits | Per plan. 503 SUPPLIER_RATE_LIMITED with retryable: true means back off and retry |
Hotels — booking flow
- Destination
GET /hotels/v1/destinations?q=: autocomplete for cities, hotels and landmarks. Each suggestion carries a ready-madedestinationobject. - Search
POST /hotels/v1/search: priced offers across every supplier available to you, stored as a search session. The response holds page 1, facets for filters, and asearch_id. - Refine
GET /hotels/v1/searches/{search_id}: filter, sort and page the session for 20 minutes without calling suppliers again. - Hotel page
GET /hotels/v1/hotels/{id}and…/reviews: photos, room types, facilities, policies and guest reviews. - Prebook
POST /hotels/v1/prebooks: the supplier confirms price and conditions, valid for 20 minutes. - Book
POST /hotels/v1/bookingswith anIdempotency-Key. The agency net is charged to your account. - Service: list, retrieve, refresh, change the lead guest, quote and cancel.
price_recheck_required: true. Always prebook, and charge the traveller the prebook price.Destinations
{ "suggestions": [
{ "type": "city", "id": "city:AE:Dubai", "name": "Dubai", "subtitle": "United Arab Emirates",
"country_code": "AE", "hotel_count": 8209, "destination": { "city_name": "Dubai", "country_code": "AE" } },
{ "type": "place", "id": "place:ChIJ…", "name": "Dubai Marina", "subtitle": "Dubai, AE",
"destination": { "place_id": "ChIJ…" } },
{ "type": "hotel", "id": "hotel:nuitee~lp1b30d1", "name": "Rove Dubai Marina", "subtitle": "Dubai, AE",
"destination": { "hotel_ids": ["nuitee~lp1b30d1"] } } ] }
Matching ignores case and accents. Cities and hotels come from our synced master data and are instant; areas and landmarks come from the supplier. Pass a suggestion's destination unchanged into search. limit is 1–25, default 10.
Search
{
"checkin": "2026-12-10",
"checkout": "2026-12-13",
"rooms": [ { "adults": 2, "children_ages": [5] } ],
"destination": { "country_code": "AE", "city_name": "Dubai" },
"currency": "USD",
"guest_nationality": "AE",
"max_hotels_per_supplier": 100,
"max_rates_per_hotel": 20,
"sort": "recommended",
"page_size": 20,
"filters": { "star_ratings": [4, 5], "refundable_only": true }
}
| Field | Rules |
|---|---|
checkin, checkout | Check-in from today to 12 months ahead; 1–30 nights |
rooms[] | 1–8 rooms. adults 1–8; children_ages 0–17, up to 6 per room |
destination | Exactly one of: {country_code, city_name}; {place_id} from destinations; {hotel_ids: […]} (up to 200); {latitude, longitude, radius_m} (1,000–50,000 m); {iata_code} |
currency | ISO 4217 |
guest_nationality | ISO 3166-1 alpha-2 of the lead guest. It changes price and availability, so send the real value |
max_hotels_per_supplier | 1–200, default 30 |
max_rates_per_hotel | 1–50, default 10 |
sort | recommended (default), price_asc, price_desc, stars_desc, review_desc, distance_asc |
page_size | 1–100, default 20 |
filters | Optional; see filters. Star rating and minimum score are also sent to suppliers that support them |
suppliers | Optional subset of supplier ids; default is every hotel supplier available to you |
Response:
{
"request_id": "req_8d96fda5", "service": "hotels", "schema_version": "1.0",
"generated_at": "2026-10-09T05:25:10Z",
"search_id": "srch_054c05d48b0b6fe4899f0acb", "expires_at": "2026-10-09T05:45:10Z",
"nights": 3, "partial": false,
"supplier_status": [ { "supplier_id": "nuitee", "outcome": "ok", "duration_ms": 11821, "offer_count": 169 } ],
"sort": "recommended", "filters": { … }, "page": 1, "page_size": 20,
"total_offers": 109, "total_hotels": 169,
"facets": {
"star_ratings": { "0": 7, "3": 19, "4": 43, "5": 100 },
"board_types": { "room_only": 147, "breakfast": 30, "half_board": 2 },
"refundable": 109, "price_min": 17800, "price_max": 533400, "currency": "USD",
"property_types": [ { "id": 204, "name": "Hotels", "count": 131 } ],
"chains": [ { "id": 1763, "name": "Jumeirah", "count": 6 } ]
},
"offers": [ {
"id": "nuitee~lp40986", "supplier_id": "nuitee", "price_recheck_required": true,
"hotel": { "id": "nuitee~lp40986", "name": "Holiday Inn Express Dubai Internet City", "star_rating": 3,
"review_score": 8.4, "review_count": 4289, "address": "Knowledge Village",
"city": "Dubai", "country_code": "AE", "latitude": 25.1042, "longitude": 55.1662,
"distance_m": 483, "chain": "Holiday Inn Express", "property_type": "Hotels",
"thumbnail": "https://…", "main_photo": "https://…" },
"from_price": { "currency": "USD", "decimals": 2, "base": 23518, "taxes": 0,
"supplier_total": 23518, "platform_fee": 0, "markup": 706, "total": 24224 },
"rates": [ {
"id": "nuitee~3gAYonJz…", "name": "Standard King Room",
"board_code": "RO", "board_name": "Room Only", "board_type": "room_only",
"rooms": [ { "room_number": 1, "name": "Standard King Room", "adults": 2, "children_ages": [5],
"max_occupancy": 3, "room_type_id": "1321525043" } ],
"cancellation": { "refundable": true, "free_cancellation_until": "2026-12-08T10:00:00Z",
"penalties": [ { "from": "2026-12-08T10:00:00Z", "amount": 23518, "currency": "USD" } ] },
"pay_at_property": [ { "description": "Tourism fee", "amount": 816, "currency": "USD" } ],
"price": { … }, "price_per_night": 8075, "suggested_retail": 27100,
"payment_type": "prepaid", "perks": ["Free WiFi"]
} ]
} ]
}
total_hotelscounts the whole session;total_offerscounts what matches the current filters. Facets always describe the whole session, so filter options never disappear while the user narrows down.recommendedranks by guest score weighted by review volume, star rating, price relative to the result set, and flexibility. It is deterministic for a session.distance_mis measured from the search point, or for city searches from the median position of the results (robust to mis-geocoded hotels).board_typenormalises supplier meal codes toroom_only,breakfast,half_board,full_board,all_inclusiveorother.board_codeandboard_namestay as the supplier sent them.price_per_nightisprice.total ÷ nightsin minor units, for display only.room_type_idjoins a rate torooms[].idon the hotel page for photos, beds and size.cancellation.refundableisfalsefor non-refundable rates and absent when the supplier did not say. Penalties apply fromfromonwards.suggested_retailis the supplier's recommended public price. Selling below it publicly can breach rate-parity terms.
Refine a search
Returns the same response shape for the stored session, with no supplier calls, so it answers in milliseconds. Every filter is a query parameter; lists are comma-separated. A session belongs to the account that created it and expires at expires_at (20 minutes); after that you get 410 SEARCH_EXPIRED and should search again.
| Filter | Meaning |
|---|---|
star_ratings | Any of 1–5; 0 matches unrated properties |
min_review_score | 0–10 |
board_types | Keeps hotels with at least one rate on these meal plans, and only those rates |
refundable_only | true keeps refundable rates only |
price_min, price_max | Total stay of the cheapest matching rate, in minor units of the search currency |
hotel_name | Case-insensitive contains |
chain_ids, property_type_ids, facility_ids | Ids from facets or reference data |
max_distance_m | Upper bound on distance_m |
sort, page, page_size | As in search |
Hotel page
Returns every hotel field plus description (plain text), images[], facilities[], checkin_from, checkout_by, phone, important_information, reviews_url, and:
"rooms": [ { "id": "1321525043", "name": "Standard King Room with Sofa Bed", "size_sqm": 26,
"max_adults": 2, "max_children": 2, "max_occupancy": 3,
"beds": [ { "type": "Extra-large double bed", "quantity": 1, "size": "181-210 cm wide" } ],
"amenities": ["Free WiFi", "Rain shower", …], "views": ["City view"], "images": [ { "url": "https://…" } ] } ],
"policies": { "children_allowed": true, "pets_allowed": false, "parking": "free" }
Content is cached for 24 hours.
{ "hotel_id": "nuitee~lp40986", "total": 4289, "average_score": 9.5,
"reviews": [ { "score": 10, "name": "Ahmad", "country": "AE", "traveller_type": "solo traveller",
"date": "2026-09-19T00:00:00Z", "headline": "…", "pros": "…", "cons": "…", "language": "en-gb" } ] }
total is the property's lifetime review count. average_score averages only the reviews returned; show the hotel's review_score as the headline score. limit is 1–100. Reviews are cached for 6 hours.
Master data
Static content synced daily from suppliers, for building your own catalogue, mapping and filters. Hotels carry ids, name, address, coordinates, stars, review score and count, chain, property type, facility ids and photos, and are paged up to 1,000 at a time with a total. Cities include hotel_count. Reference data lists countries, facilities, chains and property_types with the ids used by filters. Only suppliers available to your account are included. Prices and availability are never cached: they always come from search.
Prebook
{ "rate_id": "nuitee~3gAYonJz…" }
201 Created returns the prebook: id (pb_…), hotel_id, checkin, checkout, rooms[], board, cancellation, pay_at_property[], price, suggested_retail, terms, expires_at and three change flags: price_changed, cancellation_changed and board_changed. If any flag is true, show the new terms before booking. A rate that has gone returns 409 SUPPLIER_REJECTED_REQUEST; search again.
Book
Requires an Idempotency-Key header: 8–128 characters of A-Z a-z 0-9 . _ : -. Create one per checkout and store it with your order, so every retry of that checkout reuses it.
Idempotency-Key: order-4815-attempt-1
{
"prebook_id": "pb_7sptjqj2j3qre5iy",
"client_reference": "agency-ref-1001",
"holder": { "first_name": "Ana", "last_name": "Lee", "email": "ana@example.com", "phone": "+971500000000" },
"guests": [ { "room_number": 1, "first_name": "Ana", "last_name": "Lee", "remarks": "late arrival" } ]
}
Send exactly one lead guest per prebooked room. Use legal names: letters, spaces and ' . -, up to 50 characters. client_reference is your own id; it is echoed back and searchable.
Payment. Rates are payment_type: "prepaid": the agency net (price.supplier_total + price.platform_fee) is held on your account when the booking starts, charged when the supplier confirms and released if it fails. Your markup is never charged; you collect it from the traveller. Without enough available credit the call returns 402 INSUFFICIENT_CREDIT before any supplier call.
| HTTP | status | What to do |
|---|---|---|
| 201 | confirmed | Done. Show hotel_confirmation_code and supplier_booking_id |
| 202 | unknown | The supplier didn't answer in time and the booking may exist. Poll GET …?refresh=true. Never rebook with a new key. The amount stays on hold until resolved |
| 422 | failed | The supplier did not book; error explains why and the hold is released. Prebook again to retry |
| 200 | any | Replay of the same key and body: the original booking, without a new supplier call |
| 402 | — | INSUFFICIENT_CREDIT: top up or ask for a higher credit limit |
| 422 | — | IDEMPOTENCY_KEY_REUSED: same key with a different body. ACCOUNT_CURRENCY_MISMATCH: book in your account currency |
| 409 | — | PREBOOK_ALREADY_USED: another booking already used this prebook |
| 410 | — | PREBOOK_EXPIRED: prebook again |
Retrieve and list
The first form returns the stored booking. refresh=true re-reads it from the supplier; for an unknown booking it looks up our reference at the supplier, and after 30 minutes with no match the booking becomes failed. The list returns {bookings, total, page, page_size}, newest first, for API and console bookings alike. q matches the booking id, your reference, the supplier reference, the hotel and guest names. page_size is 1–500.
Change the lead guest
{ "holder": { "first_name": "Tess", "last_name": "Lee", "email": "tess@example.com" }, "remarks": "name correction" }
Sends a name change to the supplier and returns the booking with a new entry in amendments[] (status: "requested"). Suppliers process it asynchronously; refresh to see the result. Dates, rooms and rates cannot be amended: cancel and rebook. Only confirmed bookings can be amended (409 NOT_AMENDABLE).
Cancel
{ "booking_id": "bk_23bkvteay6qvpika", "cancellable": true, "free_cancellation_until": "2026-11-18T08:01:00Z",
"fee": 0, "refund": 20102, "currency": "USD", "as_of": "2026-10-08T11:22:37Z",
"note": "Estimate from the cancellation policy agreed at booking; the supplier confirms the final fee." }
Show the quote before the user confirms. The fee is the latest penalty that applies now.
Cancels a confirmed booking under the supplier's policy. The response is the booking with status: "cancelled" and cancellation_result {cancelled_at, fee, refund, currency}. The refund is credited to your account (never more than was charged). Refund your markup under your own terms. A repeat cancel is a no-op. An unknown booking returns 409 NOT_CANCELLABLE until a refresh resolves it.
Account and statement
{ "account": { "id": "org_t8e1akym0r_h", "currency": "USD", "credit_limit": 1000000, "balance": -20102,
"held": 0, "available": 979898, "updated_at": "…" },
"entries": [ { "id": "le_61b9…", "at": "…", "type": "charge", "amount": -20102, "currency": "USD",
"booking_id": "bk_23bk…", "note": "booking confirmed", "actor": "api:stk_Q3pX7aLm", "balance_after": -20102 } ] }
available = credit_limit + balance − held. Entry types are topup, adjustment, charge and refund; holds appear in held. actor is the console user or api: plus the key prefix that made the booking.
Flights — search beta
{
"slices": [ { "origin": "DXB", "destination": "LHR", "departure_date": "2027-02-15" } ],
"passengers": { "adults": 1, "children": 0, "infants": 0 },
"cabin_class": "economy",
"currency": "AED",
"market": "AE",
"max_offers_per_supplier": 50
}
One slice is one-way, two make a return trip, and more make multi-city (up to 6). The response uses the same envelope with offers[]. Each offer has itineraries[].segments[] (carriers, flight number, airports, local times, cabin, booking class, aircraft), fare (brand, fare basis, refundable, changeable, baggage), price and expires_at. Flight booking is on the roadmap.
Suppliers
Lists the suppliers available to your account, their services and their health:
{ "suppliers": [ { "id": "nuitee", "name": "Nuitee", "kind": "nuitee", "api_version": "liteapi-v3.0",
"services": ["hotels"], "circuit_state": "closed" } ] }
circuit_state is closed when healthy and open while a supplier is temporarily skipped after repeated failures. Which suppliers you see depends on your plan and on each supplier's distribution settings, and can change without any change on your side.
Liveness check; no authentication.
Objects
Booking
| Field | Description |
|---|---|
id | Connector booking id (bk_…). Use it for every follow-up call |
status | pending → confirmed | failed | unknown; confirmed → cancelled |
supplier_id, supplier_booking_id | Who fulfils it and their reference |
hotel_confirmation_code | Code the hotel recognises, when provided |
client_reference | Your reference, echoed |
hotel_id, hotel_name, checkin, checkout, rooms[], board_name | Stay details from the prebook |
holder, guests[] | As sent |
price, cancellation, pay_at_property[] | Commercial terms as prebooked |
cancellation_result | Fee and refund after cancellation |
error | {code, message} when failed or unknown |
booked_by | Console user email, or api: plus the API key prefix |
account_charge | {status, amount, refunded, currency}; status is held, charged, released or refunded |
amendments[] | {at, type, status, detail} for change requests |
events[] | Timeline {at, type, detail, actor}: created, confirmed, failed, unknown, reconciled, refreshed, amended, cancelled |
Errors
{ "error": { "code": "INVALID_REQUEST", "message": "request failed validation",
"request_id": "req_…", "fields": [ { "field": "checkout", "message": "must be after checkin" } ] } }
Branch on code, never on message. retryable: true marks errors that are safe to retry with backoff.
| HTTP | code | Meaning |
|---|---|---|
| 400 | MALFORMED_JSON, IDEMPOTENCY_KEY_REQUIRED | Fix the request |
| 401 | UNAUTHENTICATED | Missing, unknown or revoked API key |
| 402 | INSUFFICIENT_CREDIT | Not enough available credit for the booking net |
| 404 | NOT_FOUND | Unknown id, or one that belongs to another account |
| 409 | SUPPLIER_REJECTED_REQUEST | Supplier refused: rate gone, invalid offer, booking not confirmed |
| 409 | PREBOOK_ALREADY_USED, NOT_CANCELLABLE, NOT_AMENDABLE | State conflict |
| 410 | PREBOOK_EXPIRED, SEARCH_EXPIRED | Prebook or search again |
| 413 | BODY_TOO_LARGE | Body over 64 KB |
| 415 | UNSUPPORTED_MEDIA_TYPE | Send Content-Type: application/json |
| 422 | INVALID_REQUEST, NO_ELIGIBLE_SUPPLIERS, IDEMPOTENCY_KEY_REUSED, ACCOUNT_CURRENCY_MISMATCH | See fields[] or the message |
| 502 | SUPPLIER_UNAVAILABLE, SUPPLIER_BAD_RESPONSE, SUPPLIER_AUTH_FAILED | Supplier-side problem; retry later or try another rate |
| 503 | SUPPLIER_RATE_LIMITED, SUPPLIER_CIRCUIT_OPEN | Back off and retry |
| 504 | SUPPLIER_TIMEOUT | Retry reads; never blindly retry a booking |
Go-live checklist
- API keys live on your servers only; one key per system.
- When
partialis true, tell users some suppliers did not respond. - Results use
search_idrefinement for filters, sorting and paging instead of new searches. - Checkout shows total, board, cancellation policy and
pay_at_propertyfees from the prebook. - Show the cancellation quote before cancelling, and keep enough account credit for your booking volume.
- One
Idempotency-Keyper checkout, stored with the order and reused on retry. - On
202 unknown, pollGET …?refresh=true(for example every 20 s for 5 minutes) before telling the traveller anything. - Store both
idandsupplier_booking_id; useidfor follow-ups. - Log
X-Request-Idwith every call.
Changelog
| Date | Change |
|---|---|
| 2026-10 | Hotels v1, additive: search sessions with filters, facets, sorting and paging (GET /hotels/v1/searches/{id}); destinations autocomplete; room types, policies and reviews; master-data content API; booking list filters; lead-guest amendment; cancellation quote; prepaid account with statement. New fields on search, rates and bookings. Existing fields unchanged. |
| 2026-10 | Console-issued API keys; supplier distribution rules now apply to every request. No contract changes. |
| 2026-10 | Hotels v1: search, content, prebook, idempotent booking, refresh and cancel. |
| 2026-10 | Flights v1 search (beta). |
Questions? developers@supplier.travel · Quote your X-Request-Id.