3. API Reference
Book
POST /api/trade/book
Places a hotel booking order that reserves a room against a specific rate package.
This is the terminating call of the hotel booking flow. It creates a confirmed reservation with the supplier and covers pre-paid account deduction, order creation and the platform's reliability mechanisms.
Critical Prerequisites:
- SessionId (REQUIRED in Header): must be supplied so the booking process can be tracked
- Must be called after CheckAvail: guarantees the rate package is still available
- JWT Authentication: an Authorization header carrying a Bearer token is required
Required Parameters:
- customerReferenceNo: the booking's reference number
- ratePkgId: the rate package id being booked
- holder: holder details for the booking
- guests: guest details for the booking
- sessionId (in Header): session id used to track the booking process
Booking Flow Integration:
- Search: call HotelRates to retrieve available room options
- Select: the user picks one rate package
- Verify: call CheckAvail to re-confirm availability and price
- Book: call Book to place the final reservation - this step
- Confirm: query the order status to verify the booking succeeded
Idempotency:
- Requests are idempotent on customerReferenceNo
- A duplicate request carrying the same identifiers returns the already-created order
- This is what prevents accidental double bookings
- A request under a different customerReferenceNo whose content (hotel, dates, rooms and guests) matches a booking still active on your account is refused with
100001003:msgnames that booking, and nothing is sent to the supplier. It stays refused for as long as that booking is active, however long you wait: find it with QueryOrders, or cancel it first
Enterprise Reliability Features:
- Order status tracking and confirmation
- Session management for booking context
- Transaction rollback when a call fails
Response Features:
- Confirmed booking details
- Supplier reference numbers
- Order tracking information
Error Handling:
- Timeout errors when the upstream or supplier is slow
- Reliability mechanisms covering booking failures
- Order rollback on critical failures
Common Use Cases:
- Final confirmation of a hotel reservation
Request headers
| Header | Required | Description |
|---|---|---|
Client-Request-Timestamp |
No | Time the client began sending the request, as a unix timestamp. |
Currency |
No | Currency the rates should be returned in, as an ISO 4217 code (https://en.wikipedia.org/wiki/ISO_4217). |
IP |
No | Client IP address. Both IPv4 and IPv6 are accepted. |
Language |
No | IETF BCP 47 language tag (https://masonreview.com/posts/ietf-bcp-47-language-tags/), for example "en" or "en-US". Defaults to "en-US". |
Market |
No | Point-of-sale market for this call, as an ISO 3166-1 alpha-2 country code. Required only when your account carries a market restriction: an allow-list cannot admit a market you have not stated, so such an account refuses calls that omit it. |
Request-Id |
No | Identifies this single request. Must not be reused across requests. |
Session-Id |
Yes | Recommended on every client request, and mandatory throughout the booking flow. |
Test |
No | Test flags, expressed as key-value pairs such as "hotel=HC1&scenario=priceChange". When the server does not recognise the value, the call behaves exactly as if no Test header had been sent. |
Timeout-Milliseconds |
No | Client-specified timeout. The server makes a best effort to return data before it elapses. |
Trace-Id |
No | Correlates a group of related requests for tracing. |
User-Ref |
No | Your own identifier for the person on whose behalf this call is made. Required only once named users have been authorised on your account, after which every call must identify one and is authorised against that user's scopes as well as the key's. |
Authorization |
Yes | Must start with "Bearer ". The token comes from the Ticket response. The Ticket call itself does not need this header. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
customerReferenceNo |
string | Yes | Your own reference for this booking, and the idempotency key for the endpoint — retrying with the same value returns the original order instead of creating a second one. A UUID is the usual choice. |
ratePkgId |
string | Yes | RatePkgId identifying the rate being booked. Obtained from a HotelList or HotelRates response, and only resolvable within the session that produced it — send that session's id in the Session-Id header. |
holder |
Holder | Yes | Holder contains the booking contact information |
holder.firstName |
string | Yes | |
holder.lastName |
string | Yes | |
holder.email |
string | No | |
holder.phone |
Phone | No | |
holder.phone.countryCode |
string | No | AE |
holder.phone.countryNumber |
integer | No | 971 |
holder.phone.number |
string | No | 525757249 |
guests[] |
array |
Yes | Guests contains the list of guests for this room |
guests[].roomIndex |
integer | Yes | Assigned room index for this guest, starts from 1 |
guests[].firstName |
string | Yes | First name of this guest |
guests[].lastName |
string | Yes | Last name of this guest |
guests[].nationalityCode |
string | No | Nationality code of this guest |
guests[].age |
integer | No | Age of this guest, only matters for children |
guests[].isChild |
boolean | No | Indicates if this guest is a child, determined by clients themselves |
guests[].gender |
enum | No | Optional. Sent to the supplier when their contract asks for it (requiredGuestFields includes gender). Matched case-insensitively, and M / F are accepted as shorthand, because a partner's PMS emits whatever it emits. Anything outside the listed values is REFUSED rather than mapped to UNSPECIFIED — guessing a traveller's gender to satisfy a supplier's contract is worse than telling you the value was not understood. Allowed: MALE, FEMALE, OTHER, UNSPECIFIED. |
guests[].dateOfBirth |
string | No | Optional. ISO YYYY-MM-DD. Required only when the supplier's contract asks for it (requiredGuestFields includes dateOfBirth), and for a child or infant, where an age alone does not identify the birthday the hotel checks at the desk. age remains accepted in its place. |
guests[].passportNumber |
string | No | Optional, max 50 characters. Required only when the supplier's contract asks for it (requiredGuestFields includes passportNumber). Encrypted at rest and never returned in full — order reads show the last four characters only. |
callbackUrl |
string | No | Optional. Registers an outbound order-status webhook for this order: every later status change — and every amendment — is POSTed to this address as an OrderStatusWebhook, signed with your credential's webhook secret. Omit it and no callbacks are sent for the order. OrderStatusWebhook, not OrderStatusCallback: the outbound body carries a fourth event, order_modified, that the inbound /api/callback/order route does not accept, so the two directions have separate schemas. Size your handler's event enum on the outbound one. Must be https. That is a deliberate constraint rather than a transport preference — the delivery carries the signed order payload — and plain http, a private, loopback or shared (100.64.0.0/10) address, a host name that only exists on a private network (localhost, a single-label name, *.internal, *.local), or a non-URL is refused as 100000400 / ERR.PARAM naming callbackUrl. The field was accepted and dropped before a dispatcher existed, so no configuration that was ever actually delivered to is refused by this. A host name is checked again at delivery. It is resolved before every attempt, an attempt whose host resolves to a non-public address is not sent, and redirects are never followed. |
Example request
{
"customerReferenceNo": "9d1f4b2a-7c60-4f3e-9a58-1b0c2d3e4f50",
"ratePkgId": "rp_8f3c1a7e6b2d40f9a51c",
"holder": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phone": {
"countryCode": "AE",
"countryNumber": 971,
"number": "525757249"
}
},
"guests": [
{
"roomIndex": 1,
"firstName": "John",
"lastName": "Doe",
"nationalityCode": "US",
"isChild": false
},
{
"roomIndex": 1,
"firstName": "Jane",
"lastName": "Doe",
"nationalityCode": "US",
"isChild": false
}
]
}Response
Successful calls return the standard envelope. code is 0 on success — check it rather than the HTTP status alone, because some business failures are delivered with HTTP 200.
| Field | Type | Required | Description |
|---|---|---|---|
code |
integer | No | |
msg |
string | No | |
data |
hotel.trade.protocol.BookResp | No | |
data.hotelOrder |
hotel.supplier.domain.HotelOrder | No | |
data.hotelOrder.status |
OrderStatus | Yes | OrderStatus indicates the current status of the order Allowed: 0, 1, 2, 3, 4, 5. |
data.hotelOrder.statusRemark |
string | No | |
data.hotelOrder.checkIn |
string | Yes | CheckIn is the check-in date, defaults to current date |
data.hotelOrder.checkOut |
string | Yes | CheckOut is the check-out date, defaults to 7 days from now |
data.hotelOrder.nightCount |
integer | No | Number of nights for the stay |
data.hotelOrder.roomCount |
integer | No | CheckOut is the check-out date for the booking |
data.hotelOrder.bookingTime |
string | Yes | BookingTime is when the booking was created |
data.hotelOrder.hotelConfirmNo |
string | No | HotelConfirmNo is the confirmation number from the hotel |
data.hotelOrder.holder |
Holder | Yes | Holder contains the person who made the booking |
data.hotelOrder.holder.firstName |
string | Yes | |
data.hotelOrder.holder.lastName |
string | Yes | |
data.hotelOrder.holder.email |
string | No | |
data.hotelOrder.holder.phone |
Phone | No | |
data.hotelOrder.platformReferenceNo |
string | Yes | PlatformReferenceNo is the internal platform order identifier |
data.hotelOrder.customerReferenceNo |
string | No | CustomerReferenceNo is the unique order identifier from the customer |
data.hotelOrder.commissionableRate |
Money | No | used for commission calculation |
data.hotelOrder.commissionableRate.currency |
string | Yes | Currency code, e.g. "USD". |
data.hotelOrder.commissionableRate.amount |
number | Yes | Monetary amount, e.g. "14.50". |
data.hotelOrder.netRate |
Money | Yes | |
data.hotelOrder.netRate.currency |
string | Yes | Currency code, e.g. "USD". |
data.hotelOrder.netRate.amount |
number | Yes | Monetary amount, e.g. "14.50". |
data.hotelOrder.grossRate |
Money | No | |
data.hotelOrder.grossRate.currency |
string | Yes | Currency code, e.g. "USD". |
data.hotelOrder.grossRate.amount |
number | Yes | Monetary amount, e.g. "14.50". |
data.hotelOrder.respectGrossRate |
boolean | No | You should respect GrossRate if RespectGrossRate is true; default as false |
data.hotelOrder.cancelTime |
string | No | CancelTime is when the booking was cancelled, if applicable |
data.hotelOrder.cancelReason |
string | No | CancelReason contains the reason if the order was cancelled |
data.hotelOrder.refundedPrice |
Money | No | RefundedPrice is the amount that has been refunded to the customer |
data.hotelOrder.refundedPrice.currency |
string | Yes | Currency code, e.g. "USD". |
data.hotelOrder.refundedPrice.amount |
number | Yes | Monetary amount, e.g. "14.50". |
data.hotelOrder.supplier |
integer | No | |
data.hotelOrder.supplierReferenceNo |
string | Yes | SupplierReferenceNo is the unique order identifier from the supplier // Supplier identifies which supplier processed this order |
data.hotelOrder.hotel |
OrderHotelInfo | No | hotel-specific information |
data.hotelOrder.hotel.hotelId |
string | No | |
data.hotelOrder.hotel.destinationId |
string | No | index |
data.hotelOrder.hotel.name |
I18N | No | hotel name |
data.hotelOrder.hotel.rating |
number | No | Rating, hotel rating, range [0.0, 5.0] |
data.hotelOrder.hotel.address |
I18N | No | Localised string set. Keys are fixed language codes. |
data.hotelOrder.hotel.latlngCoordinator |
LatlngCoordinator | No | |
data.hotelOrder.hotel.logoURL |
string | No | |
data.hotelOrder.rooms[] |
array |
No | detailed information for each booked room |
data.hotelOrder.rooms[].roomTypeId |
string | Yes | standardized roomTypeId, e.g. "R001" |
data.hotelOrder.rooms[].roomTypeName |
I18N | Yes | standardized room type name; if not recognized, we'll give a default name like "Standard Room" |
data.hotelOrder.rooms[].hotelId |
string | Yes | hotelId, duplicate of parent HotelId for indicating identified room |
data.hotelOrder.rooms[].rates[] |
array<hotel.supplier.domain.RoomRatePkg> | No | core structure, room offers under this room |
data.hotelOrder.rooms[].ratePkgId |
string | Yes | used as key input for checkAvail & book APIs |
data.hotelOrder.rooms[].refundableMode |
RefundableMode | No | Refundability of the rate package: full = RefundableModeFully - free cancellation partial = RefundableModePartially - partial cancellation no = RefundableModeNo - not refundable Allowed: full, partial, no. |
data.hotelOrder.rooms[].refundableUntil |
string | No | RefundableMode is valid until RefundableUntil |
data.hotelOrder.rooms[].cancelFees[] |
array<hotel.supplier.domain.ComputedCancelPolicyItem> | No | |
data.hotelOrder.rooms[].originalRoomNaming |
OriginalRoomNaming | No | origin room naming fields from supplier |
data.hotelOrder.rooms[].rate |
Rate | Yes | single room price for multiple nights |
data.hotelOrder.rooms[].totalRate |
Rate | Yes | total price for multiple rooms (and multiple nights) |
data.hotelOrder.rooms[].rateComment |
string | No | |
data.hotelOrder.rooms[].includesPackaging |
boolean | No | Whether this is a packaged product (e.g., flight+hotel combo) |
data.hotelOrder.rooms[].checkIn |
string | Yes | CheckIn is the check-in date, defaults to current date |
data.hotelOrder.rooms[].checkOut |
string | Yes | CheckOut is the check-out date, defaults to 7 days from now |
data.hotelOrder.rooms[].board |
Board | No | Board info - Standard meal plan following liteapi standard |
data.hotelOrder.rooms[].tax |
Tax | No | Taxes and fees, generally collected by hotels on behalf of the government, are fixed costs and do not participate in the price increase during the distribution process. Therefore, they are listed separately. |
data.hotelOrder.rooms[].roomIndex |
integer | No | room index |
data.hotelOrder.rooms[].guests[] |
array |
No | information about all guests in this room |
data.hotelOrder.rooms[].refundInfo[] |
array<hotel.supplier.domain.OrderRoomRefundInfo> | No | |
code |
integer | No | |
msg |
string | No |
Example response
{
"code": 0,
"msg": "string",
"data": {
"hotelOrder": {
"status": 1,
"statusRemark": "aborted",
"checkIn": "2026-01-01",
"checkOut": "2026-01-03",
"nightCount": 0,
"roomCount": 0,
"bookingTime": "2026-01-01T10:00:00Z",
"hotelConfirmNo": "123456",
"holder": {
"email": "johndoe@example.com",
"firstName": "John",
"lastName": "Doe",
"phone": {
"countryCode": "1",
"number": "1234567890"
}
},
"platformReferenceNo": "1234567890",
"customerReferenceNo": "1234567890",
"commissionableRate": {
"currency": "USD",
"amount": 0
},
"netRate": {
"currency": "USD",
"amount": 0
},
"grossRate": {
"currency": "USD",
"amount": 0
},
"respectGrossRate": false,
"cancelTime": "2026-01-02T10:00:00Z",
"cancelReason": "Customer requested cancellation",
"refundedPrice": {
"amount": 99,
"currency": "USD"
},
"supplier": 20000001,
"supplierReferenceNo": "string",
"hotel": {
"hotelId": "string",
"destinationId": "0192f3a7-5c41-7e88-b3d2-9a1c4e6f0b57",
"name": {
"en": "Jumeirah Beach Hotel"
},
"rating": 4.5,
"address": {
"en": "Millenium Al Barsha"
},
"latlngCoordinator": {
"google": {},
"gaode": {}
},
"logoURL": "https://cdn.example.com/hotel/461850557_1.jpg"
},
"rooms": [
{
"roomTypeId": "string",
"roomTypeName": {},
"hotelId": "string",
"rates": [],
"ratePkgId": "string",
"refundableMode": "full",
"refundableUntil": "2006-01-02T15:04:05Z07:00",
"cancelFees": [],
"originalRoomNaming": {},
"rate": {},
"totalRate": {},
"rateComment": "string",
"includesPackaging": false,
"checkIn": "2026-01-01",
"checkOut": "2026-01-03",
"board": {},
"tax": {},
"roomIndex": 0,
"guests": [],
"refundInfo": []
}
]
}
}
}Failure responses
| HTTP | Meaning |
|---|---|
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 429 | Too Many Requests |
| 504 | Upstream or supplier timeout |
Every failure uses the { code, msg } envelope. See Error handling for the full business-code table.
Code samples
# $SESSION_ID is the sessionId returned by hotelList (data.basic.sessionId).
curl -X POST https://api-test.ttdbooking.com/api/trade/book \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TICKET" \
-H "Session-Id: $SESSION_ID" \
-H "Request-Id: $(uuidgen)" \
-d '{
"customerReferenceNo": "9d1f4b2a-7c60-4f3e-9a58-1b0c2d3e4f50",
"ratePkgId": "rp_8f3c1a7e6b2d40f9a51c",
"holder": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phone": {
"countryCode": "AE",
"countryNumber": 971,
"number": "525757249"
}
},
"guests": [
{
"roomIndex": 1,
"firstName": "John",
"lastName": "Doe",
"nationalityCode": "US",
"isChild": false
},
{
"roomIndex": 1,
"firstName": "Jane",
"lastName": "Doe",
"nationalityCode": "US",
"isChild": false
}
]
}'Try it
Try it
POST /api/trade/book
