---
name: dubailand-travel-api
description: Integrate with the DubaiLand Travel API for flights, hotels and transfers — search, confirm a price, place and manage orders. Use when writing or debugging code that calls https://api.dubailandtravel.com/partner/v1.
---

Rules that are easy to get wrong:

- Always call `confirm-price` immediately before `POST /orders`, and pass its `price_confirmation_token`.
- Send an `Idempotency-Key` on `POST /orders`, and reuse the SAME key when retrying after a timeout. Never generate a new one for a retry: that books twice.
- `409 TB-PARTNER-3019` is not a refusal: the order is under review and the credit is held. Never place it again; wait for its outcome.
- `409 TB-PARTNER-3020` is not a refusal either: the order is over the credit limit and waiting for our approval. Never place it again; wait for its outcome, or cancel it.
- Send exactly one item per order.
- Develop against a sandbox key (`sk_test_…`). It reaches no supplier and moves no money.
- Branch on the error `code`, not on the message text.
- Never put the client secret in browser or mobile code. Call this API from a server.

# DubaiLand Travel API

Flights, hotels and transfers for businesses, over one JSON API. Orders settle against a prepaid credit balance.

- Base URL: `https://api.dubailandtravel.com/partner/v1`
- Authentication: HTTP Basic, client id as the username and secret as the password. `Authorization: Bearer client_id:secret` also works.
- Environment: decided by the key. `sk_test_…` is sandbox (no supplier, no money); `sk_live_…` is live.
- Currency: everything is in USD. A request naming another currency is refused, not converted.
- Calls that place, cancel or add to an order need an `Idempotency-Key` header: 8 to 64 characters of letters, digits, `.`, `_`, `:` or `-` (a UUID fits). Reuse the same key when retrying; the stored response, error or not, comes back for 24 hours.
- One item per order. Add a room or a car to an existing order with `POST /orders/{reference}/hotels` or `/transfers`.

## The flow

1. **Search** — `POST /flights/search` (or `/hotels/search`, `/transfers/search`). Take an offer `id` from `data.offers`.
2. **Confirm the price** — `POST /flights/offers/{offerId}/confirm-price`. Take `data.price_confirmation_token`. Prices expire in minutes; always do this immediately before ordering.
3. **Order** — `POST /orders` with the offer id, the token and the travellers. Transfers have no confirm step: order the quote directly, sending any 64-character string as `price_confirmation_token`.

```bash
# 1. Search
curl -u "$CLIENT_ID:$CLIENT_SECRET" -X POST https://api.dubailandtravel.com/partner/v1/flights/search \
  -H 'Content-Type: application/json' \
  -d '{"slices":[{"origin":"BGW","destination":"DXB","date":"2026-12-12"}],"passengers":{"adults":1}}'

# 2. Confirm the price
curl -u "$CLIENT_ID:$CLIENT_SECRET" -X POST https://api.dubailandtravel.com/partner/v1/flights/offers/$OFFER_ID/confirm-price

# 3. Order
curl -u "$CLIENT_ID:$CLIENT_SECRET" -X POST https://api.dubailandtravel.com/partner/v1/orders \
  -H 'Content-Type: application/json' -H "Idempotency-Key: $(uuidgen)" \
  -d @order.json
```

`order.json`:

```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-12"
            }
        }
    ],
    "contact": {
        "email": "traveller@example.com",
        "phone": "+9647701234567"
    },
    "partner_reference": "your-own-id"
}
```

## Responses

Success is `{"data": …, "meta": {…}}`. Failure is a problem document, `{"code": "TB-…", "title": …, "detail": …, "status": …, "retryable": …, "action": …}`, with a matching HTTP status, whatever `Accept` header you send. Error codes are stable; branch on `code`, never on the text.

`409 TB-PARTNER-3019` means the supplier did not say whether it created the order: the credit stays held and the order must not be placed again. Its outcome arrives as `order.confirmed` or `order.failed`, or read it back with `GET /orders/{reference}`.

`409 TB-PARTNER-3020` means the order is over your balance and credit limit and is waiting for our approval: nothing is charged, and it must not be placed again. Its outcome arrives as `order.confirmed`, or as `order.failed` (`error_code` `TB-PARTNER-3001`) if it is rejected or not decided in time. You may cancel it while it waits.

A `429` carries `Retry-After`: wait that long rather than retrying immediately.

## Endpoints

### GET /account

The account at a glance

`balances` is null on a sandbox key, which moves no money, rather than a zero that would read as "out of funds".

Response shape (values are placeholders):

```json
{
    "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

Response shape (values are placeholders):

```json
{
    "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. Live keys only: a sandbox key moves no money, so it has no statement and is refused with 403 `TB-PARTNER-3009`.

Query: `period`, `currency`

Response shape (values are placeholders):

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

### POST /flights/search

Search flights

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

| Field | Type | Required |
|---|---|---|
| `trip_type` | string: one_way, round_trip, multi_city | no |
| `slices` | array | yes |
| `slices[].origin` | string | yes |
| `slices[].destination` | string | yes |
| `slices[].date` | string | yes |
| `passengers` | object | yes |
| `passengers.adults` | integer | yes |
| `passengers.children` | integer | no |
| `passengers.infants` | integer | no |
| `cabin` | string: ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST | no |
| `preferences` | object | no |
| `preferences.non_stop_only` | boolean | no |
| `preferences.airlines` | array | no |
| `preferences.exclude_airlines` | array | no |
| `preferences.max_price` | integer | no |
| `currency` | string | no |

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

Response shape (values are placeholders):

```json
{
    "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. Returns every offer in the search, with no paging. Reading it again never costs another supplier call or consumes the search rate limit.

Response shape (values are placeholders):

```json
{
    "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.

Response shape (values are placeholders):

```json
{
    "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.

| Field | Type | Required |
|---|---|---|
| `city_code` | string | no |
| `hotel_id` | string | no |
| `latitude` | number | no |
| `longitude` | number | no |
| `radius_km` | integer | no |
| `check_in` | string | yes |
| `check_out` | string | yes |
| `rooms` | integer | no |
| `adults` | integer | no |
| `children_ages` | array | no |
| `currency` | string | no |
| `min_stars` | integer | no |
| `board_type` | string: room_only, breakfast, half_board, full_board, all_inclusive | no |

```json
{
    "city_code": "DXB",
    "check_in": "2026-12-12",
    "check_out": "2026-12-15",
    "rooms": 1,
    "adults": 2
}
```

Response shape (values are placeholders):

```json
{
    "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. Returns every rate in the search, with no paging. Reading it again never costs another supplier call or consumes the search rate limit.

Response shape (values are placeholders):

```json
{
    "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.

Response shape (values are placeholders):

```json
{
    "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 /orders

Place an order Requires an `Idempotency-Key` header.

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. One item per order; add a room or a car afterwards with the hotels and transfers endpoints of the order. A 402 means the balance and credit limit together do not cover it and the account is not set to wait for approval; the body states the shortfall. A 409 `TB-PARTNER-3019` means the supplier did not say whether it created the order: the hold stays and the order must not be placed again. When the balance and credit limit do not cover the order, our team may still approve it: the answer is then a 409 `TB-PARTNER-3020` with status `pending_approval`, and the order must not be placed again. Nothing is charged meanwhile. Its outcome arrives as `order.confirmed`, or as `order.failed` (`TB-PARTNER-3001`) if it is rejected or not decided in time; you may cancel it while it waits.

| Field | Type | Required |
|---|---|---|
| `items` | array | yes |
| `items[].type` | string: flight, hotel, transfer | yes |
| `items[].offer_id` | string | yes |
| `items[].price_confirmation_token` | string | yes |
| `passengers` | array | no |
| `passengers[].type` | string: ADT, CHD, INF | no |
| `passengers[].first_name` | string | yes |
| `passengers[].last_name` | string | yes |
| `passengers[].date_of_birth` | string | no |
| `passengers[].gender` | string: M, F, X | no |
| `passengers[].nationality` | string | no |
| `passengers[].is_lead` | boolean | no |
| `passengers[].document` | object | no |
| `passengers[].document.type` | string | no |
| `passengers[].document.number` | string | no |
| `passengers[].document.issuing_country` | string | no |
| `passengers[].document.expires_on` | string | no |
| `passenger_count` | integer | no |
| `guests` | array | no |
| `guests[].first_name` | string | no |
| `guests[].last_name` | string | no |
| `guests[].is_lead` | boolean | no |
| `special_requests` | string | no |
| `flight_number` | string | no |
| `bags` | integer | no |
| `estimated_arrival_time` | string | no |
| `contact` | object | yes |
| `contact.email` | string | yes |
| `contact.phone` | string | yes |
| `contact.name` | string | no |
| `partner_reference` | string | no |

```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-12"
            }
        }
    ],
    "contact": {
        "email": "traveller@example.com",
        "phone": "+9647701234567"
    },
    "partner_reference": "your-own-id"
}
```

Response shape (values are placeholders):

```json
{
    "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",
            "travel_starts_at_local": "string",
            "travel_timezone": "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

Query: `status`, `created_after`, `page`, `per_page`

Response shape (values are placeholders):

```json
{
    "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",
                "travel_starts_at_local": "string",
                "travel_timezone": "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

Response shape (values are placeholders):

```json
{
    "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",
            "travel_starts_at_local": "string",
            "travel_timezone": "string",
            "created_at": "string",
            "ticketing_deadline_at": "string"
        }
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string"
    }
}
```

### POST /orders/{reference}/cancel

Cancel an order Requires an `Idempotency-Key` header.

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. An order still `pending_approval` was never charged: cancelling it closes it with no penalty and nothing to refund.

| Field | Type | Required |
|---|---|---|
| `reason` | string | no |

Response shape (values are placeholders):

```json
{
    "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",
            "travel_starts_at_local": "string",
            "travel_timezone": "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` header.

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.

| Field | Type | Required |
|---|---|---|
| `offer_id` | string | yes |
| `price_confirmation_token` | string | yes |
| `special_requests` | string | no |
| `estimated_arrival_time` | string | no |

Response shape (values are placeholders):

```json
{
    "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",
            "travel_starts_at_local": "string",
            "travel_timezone": "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` header.

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.

| Field | Type | Required |
|---|---|---|
| `offer_id` | string | yes |
| `flight_number` | string | no |
| `passengers` | integer | no |
| `bags` | integer | no |

Response shape (values are placeholders):

```json
{
    "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",
            "travel_starts_at_local": "string",
            "travel_timezone": "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 /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.

| Field | Type | Required |
|---|---|---|
| `origin_id` | string | yes |
| `destination_id` | string | yes |
| `pickup_at` | string | yes |
| `return_at` | string | no |
| `adults` | integer | yes |
| `children` | integer | no |
| `infants` | integer | no |
| `bags` | integer | no |
| `flight_number` | string | no |
| `currency` | string | no |

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

Response shape (values are placeholders):

```json
{
    "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.

Response shape (values are placeholders):

```json
{
    "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.

Query: `country_code`

Response shape (values are placeholders):

```json
{
    "data": {
        "locations": [
            {}
        ]
    },
    "meta": {
        "request_id": "string",
        "timestamp": "string",
        "environment": "string",
        "count": 0
    }
}
```

## Errors

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