
# Error Handling

This guide explains how integrators should read and react to errors returned by the TTDbooking OpenAPI.

## HTTP Status Code vs Business Error Code

Errors are reported on **two separate layers**:

1. **HTTP Status Code** — the transport-level result of the request (200, 400, 401, etc.)
2. **Business Error Code** — an application-level code carried in the response body

**Important**: Treat the body's `code` field as authoritative. Reading the HTTP status alone is not enough.

## HTTP Status Code Specification

| HTTP Status | Meaning               | Description                         |
| ----------- | --------------------- | ----------------------------------- |
| **200**     | Success               | Request processed successfully      |
| **400**     | Bad Request           | Request parameter validation failed |
| **401**     | Unauthorized          | Invalid or expired token            |
| **403**     | Forbidden             | No access to resource               |
| **404**     | Not Found             | Requested resource not found        |
| **429**     | Too Many Requests     | Exceeded request frequency limit    |
| **500**     | Internal Server Error | Internal server error               |
| **504**     | Gateway Timeout       | Request processing timeout          |

## Business Error Code Specification

Business error codes are structured as: `[x][xx][xx][xxxx]`

| Error Code    | HTTP Status | Meaning               | Description                                       |
| ------------- | ----------- | --------------------- | ------------------------------------------------- |
| **0**         | 200         | Success               | Request successful                                |
| **100000400** | 400         | Parameter Error       | Request parameter validation failed               |
| **100000401** | 401         | Authentication Failed | Invalid or expired token                          |
| **100000403** | 403         | Permission Denied     | No access to resource                             |
| **100000404** | 404         | Not Found             | Requested resource not found                      |
| **100000429** | 429         | Rate Limited          | Exceeded request frequency limit                  |
| **100000500** | 500         | System Error          | Internal server error                             |
| **100000504** | 504         | Timeout               | Request processing timeout                        |
| **100001003** | 500         | Duplicate Error       | Duplicate resource or operation                   |
| **100001004** | 500         | Dependency Error      | External dependency failure                       |
| **100001005** | 400         | Page Size Exceeded    | Page size exceeds maximum limit                   |
| **100001006** | 400         | Not Implemented       | Feature not implemented                           |
| **100001007** | 400         | Expired               | Resource or token expired                         |
| **100001008** | 400         | Not Matched           | Data does not match expected format               |
| **100001111** | 200         | ARI Changed           | Availability/rate information changed             |
| **100001112** | 200         | Credit Limit          | Reserved — not currently emitted (see note below) |

> **`100001112` (Credit Limit) is reserved and not currently emitted.** No code
> path returns it today, so do not branch on it. A booking that would exceed a
> credit limit is currently refused as a `100000400` parameter error.

> **`100001003` (Duplicate) from Book: read `msg` before retrying.** When a
> booking with identical content — the same hotel, dates, rooms (rate and
> occupancy) and guests — is still active on your account under a different
> `customerReferenceNo`, Book is refused with this code and `msg` names that
> booking's platform reference. Nothing was sent to the supplier, and an
> identical Book is refused for as long as that booking is active, however long
> you wait: retrieve it with `QueryOrders` (`platformReferenceNos`), or cancel it
> first if the stay has to be booked again. (A request whose optional
> `Idempotency-Key` header matches one still executing is also answered
> `100001003`; its `msg` says so, and it can be retried once the first request
> completes.)

## Error Response Format

Every error is returned in the same envelope:

```json
{
  "code": 100000400,
  "msg": "param error"
}
```

## Success Response Format

A successful call adds a `data` payload to that same envelope:

```json
{
  "code": 0,
  "msg": "Success",
  "data": {
    // your response data
  }
}
```

## Error Handling Best Practices

1. Branch your logic on `body.code`; reserve HTTP status codes for transport-level failures.
2. Business failures arrive with the matching HTTP status from the table above, so a 4xx or 5xx does not mean the transport failed — read the `code` to tell a parameter error from a system error.
3. The one deliberate exception today is `100001111` (ARI Changed): it arrives as HTTP 200 carrying a non-zero `code`, because the request itself succeeded and the answer is "the price moved". Check `code` even on a 200 before treating `data` as bookable.
4. Drive retry decisions from the business error code rather than from the HTTP status alone.

## Handling Non-Final Booking Status (Book API)

A common integration question: if a booking comes back with `status` of `1` (Confirming) or `0` (Unknown), should the request be rejected?

Short answer: **do not reject immediately**.

When a Book call returns `HTTP 200`, `code=0`, and `data.hotelOrder.status` is `1` or `0`, proceed as follows.

1. Treat the response as valid and persist the identifiers:

- `customerReferenceNo`
- `supplierReferenceNo` (if present)

2. Resolve the outcome by calling `QueryOrders` — use `customerReferenceNos` where possible.

3. Interpret the status values with this rule:

- `1 (Confirming)`: still processing; not a final state.
- `0 (Unknown)`: indeterminate; must be verified.
- `2 (Confirmed)`: success.
- `3 (Cancelled)`, `4 (Failed)`, `5 (CancelFailed)`: final, unsuccessful.

4. Poll for up to roughly 10 minutes — for example immediately, then at 30s, 1m, 2m, and backing off from there.

5. Never re-submit a second booking for the same business intent while verification is still running.

6. If the status has not reached a final value once the polling window closes, leave the order pending and reach out to support with the stored references.

## Handling Cancel Responses (Cancel API)

`data.status` on a Cancel response is the order's status after the call.

- `3 (Cancelled)`: the cancellation is complete. `serviceFee`, when present, is what the supplier charged.
- `2 (Confirmed)` with `code=0`: the cancellation was **accepted but is not final**. The supplier confirms it later, or it has been handed to our operations team to arrange with the property. No `serviceFee` is returned because no charge is known yet. Do not treat the order as cancelled: poll `QueryOrders` until it reports `3`, or wait for the `order_cancelled` callback.

An order that cannot be cancelled online — a reason is required, check-in or the cancellation cutoff has passed, or the contract accepts no API cancellations — is refused with `100000400`. `msg` says which, and nothing is sent to the supplier. A supplier that refuses the cancellation, or cannot be reached, is answered with `100001004`; the order is unchanged and still live.

## Support

For unresolved orders or questions about a specific error code, contact **integrations@ttdbooking.com**.

Full API reference: **developer.ttdbooking.com/hotel-api/docs**

Environments:

- Production: `api.ttdbooking.com`
- Test: `api-test.ttdbooking.com`
