# Conventions

These rules apply to every product.

## Requests

- Use HTTPS and UTF-8.
- `POST` endpoints take a JSON body and need `Content-Type: application/json`. `GET` endpoints take query parameters.
- Using the wrong method returns `405`. v2 adds an `Allow` header that lists the valid methods.
- Field names are in English. JSON keys are case sensitive, and a few use camelCase (for example `childrenAges` and `DeliveryMethod`), so copy them from the reference.

## Formats

- Dates are `YYYY-MM-DD`, for example `2026-11-17`. Other forms are rejected.
- Countries are ISO 3166-1 alpha-2 codes, for example `AR`, `BR`, `US`. Transfers and Disney also accept the alpha-3 code or the country name and convert it.
- Send amounts, such as `total_amount`, as JSON numbers, for example `120.00`. Do not send them as strings.
- Hotel and Disney responses carry the currency next to each price. v1 transfer responses have no currency field. Agree the billing currency with your account manager. Every v2 price comes with its `currency`.
- v2 uses `snake_case`, timestamps in ISO 8601 UTC, and ISO 4217 currencies.
- Responses are UTF-8 JSON, with two exceptions: voucher endpoints return `application/pdf`, and some catalog endpoints return a bare JSON array (Disney and Universal `getTickets`).

## Success and error responses

A successful response is the data itself, with no wrapper such as `{"data": ...}`. Most object responses include a `request_id`. A few v1 endpoints add `"success": true` and `"code": 200` as plain fields. A v2 success is the resource plus `request_id`.

A v1 error has an HTTP status of 400 or higher and this body:

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

v2 errors have one envelope with a closed catalog of codes: `{"error": {"code", "message", "request_id", "details"}}`. You branch on `error.code`. See [Errors](/developers/guides/errors) for v1 and [v2 errors](/developers/guides/v2-errors) for v2.

## Request ID

Most object responses and most error responses carry a `request_id` inside the JSON. In v1 it is not a response header. In v2 the same id is also in the `X-Request-Id` header of every response, including PDFs. Log it on every call. It is the fastest way for support to find your request.

Some v1 responses have no `request_id`: the catalog arrays of Disney and Universal, PDFs, the `429` response, the success bodies of Universal `ticketConfirm` and `ticketCancelation`, and a few older error shapes. When there is none, log your own `external_reference`, your `Idempotency-Key` and the order id.

## Rate limits

The default limit is 500 requests per minute per credential. Past it you receive `429` with a `Retry-After: 60` header:

```json
{
  "error": "rate_limited",
  "message": "Too many requests for this company. Try again in 60 seconds.",
  "code": 429,
  "limit": 500,
  "window": "60s"
}
```

Wait the number of seconds in `Retry-After` and repeat the same request. The limit is per credential, and each version (v1 and v2) counts its own requests: v2 requests do not use the v1 limit, or the other way round. v1 sends no `X-RateLimit` headers. Successful v2 responses usually carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, but do not rely on them being present. The v2 `429` uses the v2 error envelope with the code `RATE_LIMITED`. If your volume needs a higher limit, ask your account manager.

Quote calls are the ones that tend to add up. Cache catalogs on your side and avoid asking for prices you do not show.

## Timeouts and retries

Set client timeouts that fit the call. As a starting point:

- Reads: 30 seconds.
- v1 hotel search in `full` mode: 180 seconds.
- v1 Universal `ticketConfirm`: at least 240 seconds.
- v1 Disney `ticketConfirm`: at least 400 seconds.
- v2 `POST /bookings` and ticket cancellations: at least 400 seconds.

A shorter timeout gives you a timeout on a booking that is still running. Repeat it with the same idempotency value, never a new one. The v2 guide has a table per call: [Booking flow](/developers/guides/v2-booking-flow#client-timeouts).

| Situation | What to do |
|---|---|
| `429` | Wait `Retry-After` and repeat the request. |
| `500`, `502`, `503`, network error, timeout on a read | Retry with backoff, for example after 1, 3, 10 and 30 seconds, then stop. |
| Timeout or network error on a confirmation | Repeat it with the same idempotency value: `external_reference` or `X-Idempotency-Key` in v1, `Idempotency-Key` in v2. Never use a new one. On v1 Hotels, see the exception in [Idempotency](#idempotency). |
| `400`, `401`, `403`, `404`, `405`, `410` | Do not retry. Fix the request or ask support. |
| `409` with `ORDER_CONFIRMATION_IN_PROGRESS` (v1) or `BOOKING_IN_PROGRESS` (v2) | Wait a few seconds and repeat the same request with the same reference or key. |
| v2 `202` with `booking.status: "pending"` | Do not book again. Read `GET /bookings/{booking_id}` until it is `confirmed` or `failed`. |

## Idempotency

v1 and v2 protect a booking from being made twice in different ways. v1 uses `external_reference` or `X-Idempotency-Key`. v2 uses the `Idempotency-Key` header only, and it is required. The two are independent: v2 never reads a v1 reference, and v1 never reads a v2 key.

### v2

`POST /api/v2/bookings` requires `Idempotency-Key`, up to 191 bytes. Use a new value for each booking, for example a UUID, and repeat the same value, with the same body, when you retry.

- Same key and same body, after the first attempt finished: `200` with the original booking and `"idempotent_replay": true`. Nothing is booked again.
- Same key while the first attempt is still running: `409 BOOKING_IN_PROGRESS`. Wait and repeat.
- Same key with another body: `409 IDEMPOTENCY_KEY_REUSED`.
- If the result is unknown: `202` with `booking.status: "pending"`. A repeat with the same key answers the same `202` until `GET /bookings/{booking_id}` shows `confirmed` or `failed`.
- A request that fails without creating a booking frees the key, so you can repeat it.
- `external_reference` is only your own number. It is optional, up to 50 bytes (ASCII recommended), and can repeat across bookings. It does not protect against duplicates, and v2 has no search by reference.
- A test credential keeps its own keys, and `Idempotency-Key` works the same as in production.

### v1

Every v1 confirmation endpoint accepts your own reference:

| Product | Endpoint |
|---|---|
| Transfers | `transferConfirm` |
| Hotels | `bookingConfirm` |
| Universal | `ticketConfirm` |
| Disney | `ticketConfirm` |

Send it in the body as `external_reference`, or in the `X-Idempotency-Key` header. If you send both, the body wins. Use a value that is unique per booking in your system, such as your order id, and keep it under 50 characters. A longer value is cut to 50.

How it behaves:

- First request: the booking is created and the reference is tied to the new order.
- Same reference again, after the first one finished: you get `200` with the same order and `"idempotent_replay": true`. Nothing is booked again.
- Same reference while the first request is still running: you get `409` with `ORDER_CONFIRMATION_IN_PROGRESS`. Wait a few seconds and repeat the same reference.
- If the booking fails, the reference is released and you can repeat it.
- The reference is unique per account and per product. The same value on a transfer and on a hotel creates two separate bookings.

```json
{
  "success": true,
  "code": 200,
  "id_order": 168001,
  "pdf_voucher": "https://example.com/voucher.pdf",
  "idempotent_replay": true
}
```

Hotels: `bookingConfirm` looks up the reference before it checks the rates again. A repeat of a booking that exists returns it with `200` and `"idempotent_replay": true`, in the same shape as the first answer, even if the `rate_key` has expired since. A credential in test mode does not get this shortcut. Never repeat with another reference.

Replay details: on Universal the replayed `id_order` is a string, like on a new booking. On Disney and Hotels the replay carries the same fields as the first answer.

If you send no reference, a repeated confirmation is not protected and can create a second booking. There is no endpoint to look an order up by your reference, in v1 or in v2, so send one every time and keep the order id.

The body field `reference` is different. It is stored with the order for your own tracking and never prevents a duplicate.

Sandbox note: v1 transfers in sandbox ignore `external_reference`, so you can repeat the same request as many times as you need. In v2 the sandbox honors `Idempotency-Key`.

## Caching

Some responses are safe to cache on your side and say so with `Cache-Control: private, max-age=3600`: the transfer catalog and the hotel content endpoints. Cache them for up to one hour. Follow the `Cache-Control` header of each response.

- v1: the catalogs and hotel content above have `max-age=3600`, and the transfer and Universal vouchers have `private, max-age=300`. Do not cache v1 responses that send no `Cache-Control`.
- v2: the catalogs and hotel content have `max-age=3600`, and the availability of transfers and tickets has `max-age=300`. Everything else, bookings, hotel searches, hotel room content and vouchers included, is `private, no-store`.

## Compression

Hotel search responses can be several megabytes. Send `Accept-Encoding: gzip` to receive them compressed. Most HTTP clients decompress automatically. v2 compresses every JSON response of 1 KB or more when you send that header.

## Document language

Transfer and hotel voucher PDFs are generated in the language set on your credential: `es`, `en` or `pt`. Ask your account manager to change it. A voucher downloaded later keeps the language it had when you booked. Universal and Disney vouchers use the park's own format and language.

## Next

- [Errors](/developers/guides/errors) lists every error and how to react to it.
- [Sandbox](/developers/guides/sandbox) explains test credentials.
