# 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:**
1. **Search**: call HotelRates to retrieve available room options
2. **Select**: the user picks one rate package
3. **Verify**: call CheckAvail to re-confirm availability and price
4. **Book**: call Book to place the final reservation - this step
5. **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

**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<Guest> | 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 is POSTed to this address as an OrderStatusCallback, signed with your credential's webhook secret. Omit it and no callbacks are sent for the order. 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 or loopback address, 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. |


### Example request

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

_No fields._


## 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](../guides/error-handling) for the full business-code table.
