# Errors

When a call fails, the HTTP status is 400 or higher and the body is a JSON object with four fields.

```json
{
  "error": "Price mismatch",
  "message": "The total_amount does not match the current price. Please call getPrice again.",
  "code": 400,
  "request_id": "3f8a9b7c1d2e4f5a6b7c8d9e0f1a2b3c"
}
```

| Field | What it is | Safe to parse? |
|---|---|---|
| `error` | A short label for the kind of error. | Yes. Branch on it. |
| `message` | A sentence for a person. It can change without notice. | No. Log it, show it to a developer, do not match on it. |
| `code` | The HTTP status, repeated in the body. | Yes. |
| `request_id` | The id of this request. | Keep it and send it to support. |

Decide what to do from the pair of HTTP status and `error`. Look at the status first and use `error` when you need more detail.

A confirmation that does not return `200` does not give you an order id. The one case where you cannot know the result is a timeout on your side. Then repeat the call with the same `external_reference`. Never use another reference for the repeat. See [Conventions](/developers/guides/conventions#idempotency).

## Older response shapes

Transfers and Hotels use the envelope above on every error, apart from `429`. Universal and Disney use it on most errors, but some errors were defined before the envelope and have fewer fields:

- A `403` for a missing product permission on Universal and Disney has only `message`.
- Some Universal confirmation errors, such as `Price changed.` and `No availability for the selected date.`, have `error` and `code` but no `message` and no `request_id`.
- `Can't cancel this ticket.` on Universal and Disney is returned as `{"message": "Can't cancel this ticket."}` with status `400`.
- One Universal error label is in Spanish: `Faltan datos requeridos`, status `400`, returned when a required field is empty.
- When the supplier refuses a Universal booking, `error` carries the supplier's text. Use the status `400` and treat it as a rejection.
- The `429` response has `limit` and `window` and no `request_id`.

Always branch on the HTTP status first, so that these shapes do not break your handler.

## HTTP status codes

| Status | Meaning | What to do |
|---|---|---|
| 200 | Success. A replay of an idempotent confirmation is also `200`, with `idempotent_replay: true`. | Continue. |
| 400 | The request is wrong or cannot be done: bad JSON, missing field, wrong format, price changed, no availability, date not bookable, pax problem, order not cancellable. | Fix the request. Do not retry unchanged. |
| 401 | Missing credential. | Send `Authorization: Bearer <credential>`. |
| 403 | Credential without the product, with a mismatch, or from an IP that is not allowed. | See [Authentication](/developers/guides/authentication). |
| 404 | The product or the order does not exist, or it is not yours. | Check the id. Do not retry. |
| 405 | Wrong HTTP method. | Use the method in the reference. |
| 409 | Conflict: a confirmation with the same reference is still running, or a voucher is not ready yet. | Wait and repeat the same call. |
| 410 | The order was cancelled and its voucher is no longer valid. | Stop asking for that voucher. |
| 429 | Rate limit. | Wait `Retry-After` seconds. |
| 500 | Unexpected error on our side. The `message` includes a reference. | Retry with backoff. Send the `request_id` if it persists. |
| 502 | A supplier did not answer. | Retry with backoff. |
| 503 | The product, its pricing or a supplier service is temporarily unavailable. | Retry later with a longer backoff. |

The API does not return `422` or `504`. Problems in the content of a request arrive as `400`.

## Errors you will see often

The API reference for each product lists every label. These are the ones that shape your integration.

| Status | `error` | Products | Meaning and action |
|---|---|---|---|
| 400 | `Invalid JSON format` | all | The body is not a JSON object. |
| 400 | `Missing required parameters`, `Missing required fields` | all | A required field is missing. The `message` names it. |
| 400 | `Invalid date format`, `Invalid date`, `Invalid date range` | all | A date is not `YYYY-MM-DD`, is in the past, or the range is wrong. |
| 400 | `Price mismatch` | Transfers | `total_amount` is not the current price. Call `getPrice` again. |
| 400 | `Price changed.` | Universal, Disney | Same as above. Call `getTicketPrice` again. |
| 400 | `Date not bookable` | Transfers, Hotels | The date is too close. The `message` gives the earliest date. |
| 400 | `Date not available` | Transfers, Disney | The date is blocked or outside the product's validity. |
| 400 | `No availability for the selected date.` | Universal, Disney | Choose another date or product. |
| 400 | `Capacity exceeded` | Transfers | More passengers than the product allows. |
| 400 | `Pax count mismatch`, `Invalid pax entry` | Transfers, Universal, Disney | Passenger list does not match the counts, or a passenger lacks a required field. |
| 400 | `Age mismatch` | Disney | The age on the visit date does not match the passenger `type`. |
| 400 | `RateKey expired`, `Rate not bookable` | Hotels | Search again and choose another rate. |
| 400 | `Booking rejected`, `Cancellation rejected` | Hotels | The hotel supplier refused. Nothing changed. |
| 400 | `Cancellation not allowed`, `Transfer not found`, `No active reservation found.` | Transfers, Hotels | The order is not yours, or there is nothing active to cancel. |
| 404 | `Product not found` | Transfers, Universal, Disney | The product id is wrong, or it is not for sale. |
| 404 | `ORDER_NOT_FOUND` | voucher endpoints | The order does not exist or is not yours. The API gives the same answer in both cases. |
| 404 | `Booking not found` | Hotels | Same, for a hotel booking. |
| 409 | `ORDER_CONFIRMATION_IN_PROGRESS` | all confirmations | Repeat the same `external_reference` in a few seconds. Never create a new one. |
| 409 | `VOUCHER_NOT_GENERATED` | voucher endpoints | The voucher is not ready. Try again in a minute. |
| 410 | `ORDER_CANCELLED` | voucher endpoints | The order was cancelled. |
| 429 | `rate_limited` | all | See [Conventions](/developers/guides/conventions#rate-limits). |
| 500 | `Internal error` | all | Retry with backoff, then contact support with the `request_id`. |
| 502 | `External API error` | Hotels, Universal, Disney | A supplier did not answer. Retry with backoff. |
| 503 | `Provider unavailable` | Transfers, Hotels, Universal, Disney | Selling this product is paused. Nothing was created. Retry later. |
| 503 | `Pricing unavailable` | all products with prices | Pricing for your account is not available right now. On a confirmation, nothing was created. Contact support if it persists. |
| 503 | `Service unavailable` | Universal | A supplier service is not available. Retry in a few seconds. |
| 503 | `Cancellation unavailable` | Disney | The cancellation service is not available. Retry later. |

## Handling errors in code

```python
resp = call_api(...)

if resp.status == 200:
    handle(resp.json())
elif resp.status == 429:
    sleep(int(resp.headers.get("Retry-After", 60)))
    retry_same_request()
elif resp.status == 409 and body.get("error") == "ORDER_CONFIRMATION_IN_PROGRESS":
    sleep(3)
    retry_with_same_external_reference()
elif resp.status in (500, 502, 503):
    retry_with_backoff()
else:
    # 400, 401, 403, 404, 405, 410: retrying the same request will not help
    log(resp.status, body.get("error"), body.get("request_id"))
    raise
```

## Report an error

If you see an `error` that is not listed, or one you cannot explain, send support the `request_id`. See [Support](/developers/guides/support).
