DubaiLand Travel API

Flight and hotel inventory for businesses. Search, book and ticket through one API, settled against a credit balance — no supplier contract, no card processing, no PCI scope of your own.

Quickstart

Three calls from nothing to a confirmed order. Every example works against sandbox.

# 1. Search — take an offer id from data.offers
curl -u "$CLIENT_ID:$CLIENT_SECRET" \
  -X POST https://staging.dubailandtravel.com/partner/v1/flights/search \
  -H 'Content-Type: application/json' \
  -d '{"slices":[{"origin":"BGW","destination":"DXB","date":"2026-12-02"}],"passengers":{"adults":1}}'

# 2. Confirm the price is still real — take data.price_confirmation_token
curl -u "$CLIENT_ID:$CLIENT_SECRET" \
  -X POST https://staging.dubailandtravel.com/partner/v1/flights/offers/$OFFER_ID/confirm-price

# 3. Order it, with the offer id and the token
curl -u "$CLIENT_ID:$CLIENT_SECRET" \
  -X POST https://staging.dubailandtravel.com/partner/v1/orders \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d @order.json

order.json:

{
    "items": [
        {
            "type": "flight",
            "offer_id": "{{offer_id}}",
            "price_confirmation_token": "{{price_confirmation_token}}"
        }
    ],
    "passengers": [
        {
            "type": "ADT",
            "first_name": "Dana",
            "last_name": "Aziz",
            "date_of_birth": "1990-04-02",
            "gender": "F",
            "nationality": "IQ",
            "is_lead": true,
            "document": {
                "type": "passport",
                "number": "A1234567",
                "issuing_country": "IQ",
                "expires_on": "2031-11-02"
            }
        }
    ],
    "contact": {
        "email": "traveller@example.com",
        "phone": "+9647701234567"
    },
    "partner_reference": "your-own-id"
}

Skip the reading

Everything on this page, in the form your tools want. All four are generated from the running API, so they cannot describe an endpoint that does not exist.

ForGet itThen
Postman, Insomnia, Bruno Postman collection Import it, set client_id and client_secret on the collection, send. Every request is pre-filled.
Code generators and SDKs OpenAPI 3.1 (JSON) Feed it to openapi-generator, or to your editor for autocomplete.
Claude, ChatGPT, Cursor, Copilot Reference (Markdown) Paste it into the chat, or give the assistant this URL, and ask it to write your integration.
Claude Code and other agents AI skill (SKILL.md) Save it as .claude/skills/dubailand-travel-api/SKILL.md in your project. The agent then knows this API whenever it is relevant.

AI tools that look for it will also find /llms.txt.

Prices expire. Airline fares are held for minutes, not hours. Always call confirm-price immediately before ordering — an order against a stale offer is refused with TB-HOTEL-1502, not silently repriced. We will never charge you more than the price you confirmed.
Everything here is in USD. Prices, balances, invoices and statements. You are buying wholesale from us and selling to your own customers — converting for them, at your rate, on your margin, is yours to do. A request naming another currency is refused rather than converted, so you never reconcile against a number you did not expect.

Authentication

HTTP Basic: your client id as the username, your secret as the password. A bearer token of the form client_id:secret works identically if your HTTP stack makes Basic awkward.

Authorization: Basic {base64(client_id:secret)}

The secret is shown once, when the key is issued, and is stored hashed — we cannot read it back to you. Lost secrets are replaced by issuing a new key, and both work until you revoke the old one, so a rotation needs no downtime.

Sandbox and live

EnvironmentKey prefixReaches a supplierMoves money
Sandboxsk_test_…NoNo
Livesk_live_…YesYes

The environment is a property of the key, not a header or a base URL. There is no flag to forget: a sandbox key cannot reach real inventory even if you point it at production, and every response echoes meta.environment so a support ticket answers its own first question.

Endpoints

Base URL https://staging.dubailandtravel.com/partner/v1.

Open an endpoint for what to send and what comes back.

POST /flights/search Search flights

Returns offers already priced for the calling partner. A supplier failing degrades the search rather than failing it.

Scope flights.search

Request body

FieldTypeRequired
trip_typestring: one_way, round_trip, multi_city—
slicesarrayYes
slices[].originstringYes
slices[].destinationstringYes
slices[].datestringYes
passengersobjectYes
passengers.adultsintegerYes
passengers.childreninteger—
passengers.infantsinteger—
cabinstring: ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST—
preferencesobject—
preferences.non_stop_onlyboolean—
preferences.airlinesarray—
preferences.exclude_airlinesarray—
preferences.max_priceinteger—
currencystring—

Example

{
    "slices": [
        {
            "origin": "BGW",
            "destination": "DXB",
            "date": "2026-12-02"
        }
    ],
    "passengers": {
        "adults": 1
    }
}

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offers": [
            {
                "id": "string",
                "total": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "base": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "taxes": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "currency": "string",
                "validating_carrier": "string",
                "cabin": "string",
                "fare_type": "string",
                "slices": [
                    "string"
                ],
                "passengers": [
                    "string"
                ],
                "conditions": {
                    "refundable": true,
                    "changeable": true,
                    "seats_remaining": 0,
                    "last_ticketing_date": "string"
                },
                "baggage": {},
                "expires_at": "string"
            }
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "search_id": "string",
        "count": 0,
        "cache_hit": "string",
        "degraded": true
    }
}
GET /flights/search/{searchId}/results Read a held search

Cheap — the offers were computed once. Paging or filtering a result set a partner already has must never cost another supplier call, and must never consume their search rate limit either.

Scope flights.results

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offers": [
            {
                "id": "string",
                "total": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "base": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "taxes": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "currency": "string",
                "validating_carrier": "string",
                "cabin": "string",
                "fare_type": "string",
                "slices": [
                    "string"
                ],
                "passengers": [
                    "string"
                ],
                "conditions": {
                    "refundable": true,
                    "changeable": true,
                    "seats_remaining": 0,
                    "last_ticketing_date": "string"
                },
                "baggage": {},
                "expires_at": "string"
            }
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "search_id": "string",
        "count": 0
    }
}
POST /flights/offers/{offerId}/confirm-price Confirm a price

Mandatory before an order. Airfare moves between search and booking often enough that ordering from a held price is a chargeback waiting to happen — and in B2B it would be the partner absorbing it, which is worse. Returns a token bound to the offer and its final amount. Ordering without one is refused.

Scope flights.price

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offer": {
            "id": "string",
            "total": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "base": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "taxes": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "currency": "string",
            "validating_carrier": "string",
            "cabin": "string",
            "fare_type": "string",
            "slices": [
                "string"
            ],
            "passengers": [
                "string"
            ],
            "conditions": {
                "refundable": true,
                "changeable": true,
                "seats_remaining": 0,
                "last_ticketing_date": "string"
            },
            "baggage": {},
            "expires_at": "string"
        },
        "price_changed": true,
        "previous_total": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        },
        "price_confirmation_token": "string"
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string"
    }
}
POST /hotels/search Search hotels

Returns rates already priced for the calling partner. A supplier failing degrades the search rather than failing it.

Scope hotels.search

Request body

FieldTypeRequired
city_codestring—
hotel_idstring—
latitudenumber—
longitudenumber—
radius_kminteger—
check_instringYes
check_outstringYes
roomsinteger—
adultsinteger—
children_agesarray—
currencystring—
min_starsinteger—
board_typestring: room_only, breakfast, half_board, full_board, all_inclusive—

Example

{
    "city_code": "DXB",
    "check_in": "2026-12-02",
    "check_out": "2026-12-05",
    "rooms": 1,
    "adults": 2
}

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offers": [
            {
                "id": "string",
                "property": {
                    "id": "string",
                    "name": "string",
                    "star_rating": 0,
                    "city_code": "string",
                    "address": "string",
                    "latitude": 0,
                    "longitude": 0,
                    "amenities": [
                        "string"
                    ],
                    "images": [
                        "string"
                    ]
                },
                "room": {
                    "type_code": "string",
                    "description": {},
                    "board_type": "string",
                    "rate_plan": "string"
                },
                "stay": {
                    "check_in": "string",
                    "check_out": "string",
                    "nights": 0,
                    "rooms": 0,
                    "adults": 0,
                    "children_ages": [
                        "integer"
                    ]
                },
                "total": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "taxes_and_fees": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "per_night": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "currency": "string",
                "payment_policy": "string",
                "collected_by_property": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "payable_now": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "cancellation": {
                    "refundable": true,
                    "free_until": "string",
                    "penalty": {
                        "amount": "integer",
                        "currency": "string",
                        "decimals": "integer",
                        "formatted": "string"
                    },
                    "tiers": [
                        {}
                    ],
                    "description": "string"
                },
                "expires_at": "string"
            }
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "search_id": "string",
        "count": "string",
        "cache_hit": "string",
        "expires_at": "string",
        "degraded": true
    }
}
GET /hotels/search/{searchId}/results Read a held search

Cheap — the rates were computed once. Paging or filtering a result set a partner already holds must never cost another supplier call, and must never consume their search rate limit either.

Scope hotels.results

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offers": [
            {
                "id": "string",
                "property": {
                    "id": "string",
                    "name": "string",
                    "star_rating": 0,
                    "city_code": "string",
                    "address": "string",
                    "latitude": 0,
                    "longitude": 0,
                    "amenities": [
                        "string"
                    ],
                    "images": [
                        "string"
                    ]
                },
                "room": {
                    "type_code": "string",
                    "description": {},
                    "board_type": "string",
                    "rate_plan": "string"
                },
                "stay": {
                    "check_in": "string",
                    "check_out": "string",
                    "nights": 0,
                    "rooms": 0,
                    "adults": 0,
                    "children_ages": [
                        "integer"
                    ]
                },
                "total": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "taxes_and_fees": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "per_night": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "currency": "string",
                "payment_policy": "string",
                "collected_by_property": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "payable_now": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "cancellation": {
                    "refundable": true,
                    "free_until": "string",
                    "penalty": {
                        "amount": "integer",
                        "currency": "string",
                        "decimals": "integer",
                        "formatted": "string"
                    },
                    "tiers": [
                        {}
                    ],
                    "description": "string"
                },
                "expires_at": "string"
            }
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "search_id": "string",
        "count": 0
    }
}
POST /hotels/offers/{offerId}/confirm-price Confirm a rate

Mandatory before an order, and for more than the price: a cancellation policy that changed between search and booking is a promise we would otherwise make and not keep — and in B2B it is the partner who would have made it to their customer. Returns a token bound to the offer and its final amount. Ordering without one is refused.

Scope hotels.price

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offer": {
            "id": "string",
            "property": {
                "id": "string",
                "name": "string",
                "star_rating": 0,
                "city_code": "string",
                "address": "string",
                "latitude": 0,
                "longitude": 0,
                "amenities": [
                    "string"
                ],
                "images": [
                    "string"
                ]
            },
            "room": {
                "type_code": "string",
                "description": {},
                "board_type": "string",
                "rate_plan": "string"
            },
            "stay": {
                "check_in": "string",
                "check_out": "string",
                "nights": 0,
                "rooms": 0,
                "adults": 0,
                "children_ages": [
                    0
                ]
            },
            "total": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "taxes_and_fees": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "per_night": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "currency": "string",
            "payment_policy": "string",
            "collected_by_property": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "payable_now": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "cancellation": {
                "refundable": true,
                "free_until": "string",
                "penalty": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "tiers": [
                    {
                        "from": "string",
                        "amount_minor": "integer"
                    }
                ],
                "description": "string"
            },
            "expires_at": "string"
        },
        "price_changed": true,
        "previous_total": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        },
        "policy_changed": true,
        "price_confirmation_token": "string"
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string"
    }
}
POST /transfers/search Quote a transfer

Returns every vehicle an operator will put on the journey, already priced for the calling partner and already filtered to those that can actually carry the party.

Scope transfers.search

Request body

FieldTypeRequired
origin_idstringYes
destination_idstringYes
pickup_atstringYes
return_atstring—
adultsintegerYes
childreninteger—
infantsinteger—
bagsinteger—
flight_numberstring—
currencystring—

Example

{
    "origin_id": "{{origin_id}}",
    "destination_id": "{{destination_id}}",
    "pickup_at": "2026-12-02 14:30",
    "adults": 2
}

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offers": [
            {
                "id": "string",
                "journey": {
                    "origin": {
                        "id": "string",
                        "name": "string",
                        "type": "string",
                        "address": "string",
                        "city_name": "string",
                        "country_code": "string",
                        "iata_code": "string",
                        "latitude": "number",
                        "longitude": "number"
                    },
                    "destination": {
                        "id": "string",
                        "name": "string",
                        "type": "string",
                        "address": "string",
                        "city_name": "string",
                        "country_code": "string",
                        "iata_code": "string",
                        "latitude": "number",
                        "longitude": "number"
                    },
                    "pickup_at": "string",
                    "pickup_timezone": "string",
                    "return_pickup_at": "string",
                    "duration_minutes": 0,
                    "distance_km": 0
                },
                "vehicle": {
                    "class": "string",
                    "description": {},
                    "max_passengers": 0,
                    "max_bags": 0,
                    "inclusions": [
                        "string"
                    ]
                },
                "total": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "currency": "string",
                "payment_policy": "string",
                "collected_by_driver": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "payable_now": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "cancellation": {
                    "refundable": true,
                    "free_until": "string",
                    "penalty": {
                        "amount": "integer",
                        "currency": "string",
                        "decimals": "integer",
                        "formatted": "string"
                    },
                    "description": "string"
                },
                "expires_at": "string"
            }
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "search_id": "string",
        "count": "string",
        "cache_hit": "string",
        "expires_at": "string",
        "degraded": true,
        "unserved": "string"
    }
}
GET /transfers/search/{searchId}/results Read a held quote

Cheap — the quotes were computed once. Re-reading a result set a partner already holds must never cost another operator call, and must never consume their search rate limit either.

Scope transfers.results

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "offers": [
            {
                "id": "string",
                "journey": {
                    "origin": {
                        "id": "string",
                        "name": "string",
                        "type": "string",
                        "address": "string",
                        "city_name": "string",
                        "country_code": "string",
                        "iata_code": "string",
                        "latitude": "number",
                        "longitude": "number"
                    },
                    "destination": {
                        "id": "string",
                        "name": "string",
                        "type": "string",
                        "address": "string",
                        "city_name": "string",
                        "country_code": "string",
                        "iata_code": "string",
                        "latitude": "number",
                        "longitude": "number"
                    },
                    "pickup_at": "string",
                    "pickup_timezone": "string",
                    "return_pickup_at": "string",
                    "duration_minutes": 0,
                    "distance_km": 0
                },
                "vehicle": {
                    "class": "string",
                    "description": {},
                    "max_passengers": 0,
                    "max_bags": 0,
                    "inclusions": [
                        "string"
                    ]
                },
                "total": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "currency": "string",
                "payment_policy": "string",
                "collected_by_driver": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "payable_now": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "cancellation": {
                    "refundable": true,
                    "free_until": "string",
                    "penalty": {
                        "amount": "integer",
                        "currency": "string",
                        "decimals": "integer",
                        "formatted": "string"
                    },
                    "description": "string"
                },
                "expires_at": "string"
            }
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "search_id": "string",
        "count": 0
    }
}
GET /transfers/locations Places we can be driven to

Transfers are quoted between identifiers from an operator's catalogue, not between addresses — so without this endpoint a partner has nothing valid to put in a search. It is part of the product rather than a convenience.

Scope transfers.locations

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "locations": [
            {}
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "count": 0
    }
}
POST /orders Place an order

Requires an `Idempotency-Key`. Draws the total from the partner's balance, creates the order with the supplier, and returns the confirmation. A refusal at the supplier releases the hold immediately — the partner is never out of pocket for a booking that did not happen. A 402 means the balance and credit limit together do not cover it, and the body states the shortfall.

Scope orders.create · Idempotency-Key header required

Request body

FieldTypeRequired
itemsarrayYes
items[].typestring: flight, hotel, transferYes
items[].offer_idstringYes
items[].price_confirmation_tokenstringYes
passengersarray—
passengers[].typestring: ADT, CHD, INF—
passengers[].first_namestringYes
passengers[].last_namestringYes
passengers[].date_of_birthstring—
passengers[].genderstring: M, F, X—
passengers[].nationalitystring—
passengers[].is_leadboolean—
passengers[].documentobject—
passengers[].document.typestring—
passengers[].document.numberstring—
passengers[].document.issuing_countrystring—
passengers[].document.expires_onstring—
guestsarray—
guests[].first_namestring—
guests[].last_namestring—
guests[].is_leadboolean—
special_requestsstring—
flight_numberstring—
bagsinteger—
estimated_arrival_timestring—
contactobjectYes
contact.emailstringYes
contact.phonestringYes
contact.namestring—
partner_referencestring—

Example

{
    "items": [
        {
            "type": "flight",
            "offer_id": "{{offer_id}}",
            "price_confirmation_token": "{{price_confirmation_token}}"
        }
    ],
    "passengers": [
        {
            "type": "ADT",
            "first_name": "Dana",
            "last_name": "Aziz",
            "date_of_birth": "1990-04-02",
            "gender": "F",
            "nationality": "IQ",
            "is_lead": true,
            "document": {
                "type": "passport",
                "number": "A1234567",
                "issuing_country": "IQ",
                "expires_on": "2031-11-02"
            }
        }
    ],
    "contact": {
        "email": "traveller@example.com",
        "phone": "+9647701234567"
    },
    "partner_reference": "your-own-id"
}

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "order": {
            "reference": "string",
            "partner_reference": "string",
            "status": "string",
            "type": "string",
            "total": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "charged_to_balance": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "contact": {
                "email": "string",
                "phone": "string",
                "name": "string"
            },
            "passengers": [
                {
                    "first_name": "string",
                    "last_name": "string",
                    "type": "string",
                    "document_last4": "string"
                }
            ],
            "items": [
                {
                    "id": 0,
                    "type": "string",
                    "status": "string",
                    "total": {
                        "amount": "integer",
                        "currency": "string"
                    },
                    "hotel": "string",
                    "confirmation_number": "string",
                    "check_in": "string",
                    "check_out": "string"
                }
            ],
            "travel_starts_at": "string",
            "created_at": "string",
            "ticketing_deadline_at": "string"
        }
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "balance": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        }
    }
}
GET /orders List orders

Scope orders.read

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "orders": [
            {
                "reference": "string",
                "partner_reference": "string",
                "status": "string",
                "type": "string",
                "total": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "charged_to_balance": {
                    "amount": 0,
                    "currency": "string",
                    "decimals": 0,
                    "formatted": "string"
                },
                "contact": {
                    "email": "string",
                    "phone": "string",
                    "name": "string"
                },
                "passengers": [
                    {
                        "first_name": "string",
                        "last_name": "string",
                        "type": "string",
                        "document_last4": "string"
                    }
                ],
                "items": [
                    {
                        "id": "integer",
                        "type": "string",
                        "status": "string",
                        "total": {},
                        "hotel": "string",
                        "confirmation_number": "string",
                        "check_in": "string",
                        "check_out": "string"
                    }
                ],
                "travel_starts_at": "string",
                "created_at": "string",
                "ticketing_deadline_at": "string"
            }
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "page": 0,
        "per_page": 0,
        "total": 0
    }
}
GET /orders/{reference} Read an order

Scope orders.read

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "order": {
            "reference": "string",
            "partner_reference": "string",
            "status": "string",
            "type": "string",
            "total": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "charged_to_balance": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "contact": {
                "email": "string",
                "phone": "string",
                "name": "string"
            },
            "passengers": [
                {
                    "first_name": "string",
                    "last_name": "string",
                    "type": "string",
                    "document_last4": "string"
                }
            ],
            "items": [
                {
                    "id": 0,
                    "type": "string",
                    "status": "string",
                    "total": {
                        "amount": "integer",
                        "currency": "string"
                    },
                    "hotel": "string",
                    "confirmation_number": "string",
                    "check_in": "string",
                    "check_out": "string"
                }
            ],
            "travel_starts_at": "string",
            "created_at": "string",
            "ticketing_deadline_at": "string"
        }
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string"
    }
}
POST /orders/{reference}/cancel Cancel an order

Returns the penalty the supplier actually charged and credits the remainder back to the partner's balance. On a prepaid model a refund is a ledger entry rather than a gateway call that can fail, which is the single biggest operational advantage of settling this way.

Scope orders.cancel · Idempotency-Key header required

Request body

FieldTypeRequired
reasonstring—

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "order": {
            "reference": "string",
            "partner_reference": "string",
            "status": "string",
            "type": "string",
            "total": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "charged_to_balance": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "contact": {
                "email": "string",
                "phone": "string",
                "name": "string"
            },
            "passengers": [
                {
                    "first_name": "string",
                    "last_name": "string",
                    "type": "string",
                    "document_last4": "string"
                }
            ],
            "items": [
                {
                    "id": 0,
                    "type": "string",
                    "status": "string",
                    "total": {
                        "amount": "integer",
                        "currency": "string"
                    },
                    "hotel": "string",
                    "confirmation_number": "string",
                    "check_in": "string",
                    "check_out": "string"
                }
            ],
            "travel_starts_at": "string",
            "created_at": "string",
            "ticketing_deadline_at": "string"
        },
        "refunded_to_balance": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        },
        "penalty": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        }
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "balance": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        }
    }
}
POST /orders/{reference}/hotels Add a hotel to an order

Requires an `Idempotency-Key`: this changes what the partner owes, and a retry must not book the room twice. The room is reserved and confirmed on its own. A property refusing leaves the flight exactly as it was — that is the whole reason items carry independent lifecycles — and comes back as a 409 on the room rather than as a failure of the order.

Scope orders.add_hotel · Idempotency-Key header required

Request body

FieldTypeRequired
offer_idstringYes
price_confirmation_tokenstringYes
special_requestsstring—
estimated_arrival_timestring—

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "order": {
            "reference": "string",
            "partner_reference": "string",
            "status": "string",
            "type": "string",
            "total": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "charged_to_balance": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "contact": {
                "email": "string",
                "phone": "string",
                "name": "string"
            },
            "passengers": [
                {
                    "first_name": "string",
                    "last_name": "string",
                    "type": "string",
                    "document_last4": "string"
                }
            ],
            "items": [
                {
                    "id": 0,
                    "type": "string",
                    "status": "string",
                    "total": {
                        "amount": "integer",
                        "currency": "string"
                    },
                    "hotel": "string",
                    "confirmation_number": "string",
                    "check_in": "string",
                    "check_out": "string"
                }
            ],
            "travel_starts_at": "string",
            "created_at": "string",
            "ticketing_deadline_at": "string"
        }
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "balance": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        }
    }
}
POST /orders/{reference}/transfers Add a transfer to an order

Requires an `Idempotency-Key`: this changes what the partner owes, and a retry must not put a second car on the road. The car is booked on its own. An operator refusing leaves the flight exactly as it was and comes back as a 410 on the transfer rather than a failure of the order.

Scope orders.add_transfer · Idempotency-Key header required

Request body

FieldTypeRequired
offer_idstringYes
flight_numberstring—
passengersinteger—
bagsinteger—

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "order": {
            "reference": "string",
            "partner_reference": "string",
            "status": "string",
            "type": "string",
            "total": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "charged_to_balance": {
                "amount": 0,
                "currency": "string",
                "decimals": 0,
                "formatted": "string"
            },
            "contact": {
                "email": "string",
                "phone": "string",
                "name": "string"
            },
            "passengers": [
                {
                    "first_name": "string",
                    "last_name": "string",
                    "type": "string",
                    "document_last4": "string"
                }
            ],
            "items": [
                {
                    "id": 0,
                    "type": "string",
                    "status": "string",
                    "total": {
                        "amount": "integer",
                        "currency": "string"
                    },
                    "hotel": "string",
                    "confirmation_number": "string",
                    "check_in": "string",
                    "check_out": "string"
                }
            ],
            "travel_starts_at": "string",
            "created_at": "string",
            "ticketing_deadline_at": "string"
        }
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "balance": {
            "amount": 0,
            "currency": "string",
            "decimals": 0,
            "formatted": "string"
        }
    }
}
GET /account The account at a glance

Scope account.read

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "partner": {
            "name": "string",
            "status": "string",
            "account_id": "string"
        },
        "credential": {
            "label": "string",
            "environment": "string",
            "scopes": [
                "string"
            ],
            "expires_at": "string"
        },
        "services": [
            "string"
        ],
        "usage_this_period": {
            "period": "string",
            "calls": 0,
            "remaining": 0
        },
        "balances": [
            "string"
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string"
    }
}
GET /account/usage Usage for a closed or current period

Scope account.usage

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "period": "string",
        "calls": 0,
        "billable_calls": 0,
        "overage_calls": 0,
        "errors": 0,
        "p95_duration_ms": 0
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "billable": true
    }
}
GET /account/statement Every movement on the account in a period, with a running balance

The running balance is what makes this reconcilable line by line rather than only in total — a partner whose closing figure disagrees with ours needs to find WHERE, not merely THAT.

Scope account.statement

No request body.

Response

The shape of a successful reply. Values are placeholders.

{
    "data": {
        "period": "string",
        "currency": "string",
        "opening_balance": {},
        "closing_balance": {},
        "credits": {},
        "debits": {},
        "entries": [
            "string"
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string"
    }
}

Generated from the running API: the fields, the scope each key is checked against and the response shapes are read from the code, not written here by hand.

Responses

Two shapes, and only two. Success:

{
  "data": { … },
  "meta": { "request_id": "…", "timestamp": "…", "environment": "sandbox" }
}

Failure, as an RFC 9457 problem document:

{
  "type": "https://…/errors/TB-PARTNER-3001",
  "title": "Insufficient balance",
  "detail": "This booking exceeds the available balance and credit limit on this account.",
  "status": 402,
  "code": "TB-PARTNER-3001",
  "action": "top_up",
  "retryable": false,
  "request_id": "…"
}
Branch on code and action, never on the message. Messages are localised into Arabic, English and Kurdish Sorani and will be reworded. Codes are permanent. action tells your integration what to do without it having to know what every code means.

Idempotency

Every endpoint that books or moves money requires an Idempotency-Key header — a value you generate, unique per logical operation.

Replaying a key returns the original response rather than repeating the work. This is what makes a network timeout safe to retry: without it, a request that timed out after we created a booking would create a second one when you tried again, and you would be charged for both.

Keys are held for 24 hours. Reuse one with a different body and the call is refused.

Webhooks

An order's state changes long after the request that created it — a ticket issues, a carrier moves a flight, a reconciler resolves an ambiguous order. Subscribe rather than poll.

EventMeaning
order.confirmedThe supplier accepted the order. Some multi-item orders confirm partially; the payload says which.
order.ticketedTicket numbers exist. This is the point at which the passenger can travel.
order.failedThe supplier refused the order. Nothing was created and any credit hold has been released.
order.cancelledThe order was cancelled. The payload carries the penalty that was actually applied.
order.needs_attentionThe order is ambiguous at the supplier and a human is resolving it. The credit hold is KEPT. Do not retry.

Subscriptions accept wildcards — order.* follows the family without you enumerating events we have not invented yet. Registering no filter means everything.

Verifying the signature

Every delivery carries X-Signature: HMAC-SHA256 of the raw request body using your endpoint secret. Verify it before you trust a payload — a webhook URL is otherwise an unauthenticated endpoint anyone who learns it can post fabricated order confirmations to.

// Raw bytes, not the parsed JSON: re-encoding changes key order and
// unicode escaping, and the signature will not match.
$expected = hash_hmac('sha256', $request->getContent(), $endpointSecret);

if (! hash_equals($expected, $request->header('X-Signature'))) {
    abort(400);
}

Retries

A non-2xx is retried with backoff at 10 seconds, 1 minute, 10 minutes and 1 hour. A 4xx is not retried — an unchanged body will be rejected identically. An endpoint that fails twenty times consecutively is disabled; deliveries are still recorded and can be replayed once you fix it.

Deduplicate on id. We retry, so the same event can arrive twice. Treat the envelope's id as seen-or-not and you will never act on one twice.

Try it

Make a real call from this page. Your credentials stay in this browser tab — the request goes straight to the endpoint from here, and nothing is stored or proxied.

Sandbox keys only. This console refuses anything that is not a sk_test_ key. A live key typed into a page is a live key in your browser history, and sandbox answers every question you have at this stage.
The response appears here.

Sandbox scenarios

You can reach the happy path by accident. What you cannot exercise without help is the handful of ways an order fails — and those are exactly the paths your support team will live in. Set the lead passenger's surname to a trigger:

SurnameWhat happens
SANDBOXSOLDOUTSupplier refuses the order. Nothing is created and the credit hold is released.
SANDBOXPRICECHANGEThe fare changed between pricing and ordering. The order is refused.
SANDBOXTIMEOUTThe supplier never responds. The order goes to manual review and the credit hold is KEPT — do not retry.
SANDBOXNOTICKETOrder confirms with a PNR, but no ticket is issued automatically.
SANDBOXSCHEDULECHANGEOrder confirms, then the carrier changes the departure time.
SANDBOXPENALTYCancelling this order costs a penalty instead of being free.
Build against SANDBOXTIMEOUT before you go live. It is the only outcome where the money is neither spent nor free, and an integration that retries blindly on it will double-book a real passenger.

Rate limits

Requests are rate limited per key, and search carries its own tighter limit because every miss costs a supplier call. Reading back a held result set does not touch it — paging through your own results is not a new search.

A 429 carries Retry-After; honour it rather than tightening your retry loop. If your traffic needs a higher limit, ask your account manager.

Balance and statements

Orders settle against a USD credit balance you top up in advance by bank transfer — there is no card in the booking path. A booking reserves before it commits and releases if the supplier refuses, so you are never out of pocket for an order that never existed.

GET /account returns your balance and your usage. GET /account/statement returns every movement for a month with a running balance, so a disagreement can be traced to a line rather than a total.

Versioning

partner/v1 is the contract. Adding a field, an endpoint or an error code does not move it — so parse leniently: ignore fields you do not recognise rather than failing on them. Anything that would break a working integration gets a new version and an overlap, announced in advance.

Error catalogue

Every failure this API can return, grouped by what it is about, with the status it returns and the action your integration should take. Copy shown in English; the same codes carry Arabic and Kurdish Sorani.

Requests 12 codes
CodeHTTPActionRetryMeaning
TB-CORE-1001 400 correct_input No We could not read that request
TB-CORE-1002 422 correct_input No Some details need correcting
TB-CORE-1003 404 none No Not found
TB-CORE-1004 403 none No Not allowed
TB-CORE-1005 429 wait Yes Too many requests
TB-CORE-1007 426 update_app No Please update the app
TB-CORE-1008 400 correct_input No Missing request identifier
TB-CORE-1009 409 none No This request was already submitted
TB-CORE-1010 409 wait Yes Still working on that
TB-CORE-1011 400 correct_input No Currency not available
TB-CORE-1012 400 correct_input No Language not available
TB-CORE-1013 405 none No Not supported
Authentication 6 codes
CodeHTTPActionRetryMeaning
TB-AUTH-1102 401 reauthenticate No Your session has expired
TB-AUTH-1105 403 contact_support No This account is suspended
TB-AUTH-1106 400 correct_input Yes That code is incorrect
TB-AUTH-1107 410 retry No That code has expired
TB-AUTH-1108 429 wait Yes Too many codes requested
TB-AUTH-1114 401 reauthenticate No Security check failed
Search 5 codes
CodeHTTPActionRetryMeaning
TB-SRCH-1301 422 correct_input No Choose a departure date in the future
TB-SRCH-1302 422 correct_input No Check your return date
TB-SRCH-1303 422 correct_input No Choose two different places
TB-SRCH-1306 422 correct_input No Too many passengers
TB-SRCH-1307 422 correct_input No Each infant needs an adult
Flights 4 codes
CodeHTTPActionRetryMeaning
TB-FLIGHT-1401 410 restart_search No This fare is no longer available
TB-FLIGHT-1402 410 restart_search No This offer has expired
TB-FLIGHT-1403 409 none No The price for this flight has changed
TB-FLIGHT-1406 422 none No Please confirm the price first
Hotels 6 codes
CodeHTTPActionRetryMeaning
TB-HOTEL-1501 410 restart_search No This room is no longer available
TB-HOTEL-1502 409 restart_search No The rate has changed
TB-HOTEL-1503 409 none No This property cannot confirm the booking
TB-HOTEL-1504 422 correct_input No Check your dates
TB-HOTEL-1505 422 correct_input No Too many guests for these rooms
TB-HOTEL-1507 422 correct_input No The room needs a name
Account and balance 15 codes
CodeHTTPActionRetryMeaning
TB-PARTNER-3001 402 top_up No Insufficient balance
TB-PARTNER-3002 403 contact_support No Account suspended
TB-PARTNER-3003 409 none No This top-up has already been processed
TB-PARTNER-3004 401 check_credentials No Invalid API credentials
TB-PARTNER-3005 403 check_scopes No This key is not permitted to do that
TB-PARTNER-3006 429 back_off Yes Rate limit exceeded
TB-PARTNER-3007 402 upgrade_plan No Monthly quota exhausted
TB-PARTNER-3008 403 upgrade_plan No Product not included in this plan
TB-PARTNER-3009 403 use_live_key No Sandbox keys cannot do that
TB-PARTNER-3010 402 top_up No No balance in this currency
TB-PARTNER-3011 422 correct_input No This API settles in USD
TB-PARTNER-3012 403 contact_support No This service is not active on your account
TB-PARTNER-3013 429 back_off Yes Daily limit reached
TB-PARTNER-3014 422 correct_input No This order exceeds your per-booking limit
TB-PARTNER-3015 409 contact_support No No permitted supplier can fulfil this
Transfers 2 codes
CodeHTTPActionRetryMeaning
TB-TRANSFER-1601 410 restart_search No This transfer is no longer available
TB-TRANSFER-1602 422 correct_input No Too many passengers or bags
Orders 10 codes
CodeHTTPActionRetryMeaning
TB-BOOK-1701 410 restart_search No Your booking hold has expired
TB-BOOK-1704 409 contact_support No This booking cannot be changed right now
TB-BOOK-1706 422 contact_support No This fare cannot be cancelled
TB-BOOK-1708 202 wait No Your booking needs a quick review
TB-BOOK-1709 409 wait Yes This booking is being updated
TB-BOOK-1712 409 none No A booking needs at least one item
TB-BOOK-1713 409 restart_search No This booking can no longer be changed
TB-BOOK-1714 409 restart_search No That price is in a different currency
TB-BOOK-1716 409 contact_support No This booking needs a human to issue the ticket
TB-BOOK-1715 409 restart_search No The supplier could not confirm this order
Travellers 9 codes
CodeHTTPActionRetryMeaning
TB-PAX-1801 422 correct_input No Name must match the travel document
TB-PAX-1802 422 correct_input No Use Latin characters for names
TB-PAX-1803 422 correct_input No This passport expires before travel
TB-PAX-1804 422 correct_input No Passport expires soon after travel
TB-PAX-1805 422 correct_input No Date of birth does not match the passenger type
TB-PAX-1806 422 correct_input No Check the infant age
TB-PAX-1807 422 correct_input No Passenger count does not match the fare
TB-PAX-1808 422 correct_input No Check the document number
TB-PAX-1809 422 correct_input No Contact details needed
Suppliers 8 codes
CodeHTTPActionRetryMeaning
TB-PROV-3001 503 retry Yes This airline's system is unavailable
TB-PROV-3002 504 wait Yes The airline is taking longer than usual
TB-PROV-3003 502 contact_support Yes Something went wrong on our side
TB-PROV-3004 429 wait Yes We're very busy right now
TB-PROV-3005 500 contact_support No Something went wrong on our side
TB-PROV-3006 409 correct_input No The airline did not accept these details
TB-PROV-3007 500 contact_support No Something went wrong on our side
TB-PROV-3008 503 wait Yes This option is temporarily unavailable
System 1 code
CodeHTTPActionRetryMeaning
TB-SYS-5001 500 contact_support Yes Something went wrong on our side

Machine-readable contract: openapi.json. Generate a client with openapi-generator, or mock the whole API offline with npx @stoplight/prism-cli mock openapi.json.