# Migrating from v1

API v2 is stable since 2026-10-02 and covers transfers, tickets (Universal and Disney) and hotels. The v1 endpoints for these products keep working and have no end date. Nobody has to migrate. Use v2 for new integrations, or move when the new flow is worth it to you.

v1 and v2 use the same Bearer credential, and each version has its own rate limit per credential. See the [API v2 overview](/developers/guides/v2-overview) for v2. The v1 guides are [Transfers](/developers/guides/transfers), [Hotels](/developers/guides/hotels), [Universal](/developers/guides/universal) and [Disney](/developers/guides/disney).

v1 and v2 are independent. Nothing in v2 changes how v1 works, and v1 keeps its own idempotency rules.

> **If you send one confirm per product with the same order number.** Today you may call `transferConfirm` or `ticketConfirm` several times with the same `external_reference`, once per product. In v2 you can still repeat `external_reference`: it is only your own number now, up to 50 bytes (ASCII recommended), and it is not unique. But each booking needs its own `Idempotency-Key` header, a new value every time, for example a UUID. v1 protects against duplicates with `X-Idempotency-Key` or `external_reference`. v2 uses `Idempotency-Key` only, and it is required. If you reuse a key with another body, you get `409 IDEMPOTENCY_KEY_REUSED`.

This page has one section per product: [Transfers](#transfers), [Universal](#universal-v1-to-v2), [Disney](#disney-v1-to-v2) and [Hotels](#hotels-v1-to-v2).

## Transfers

### Call by call

| Step | v1 | v2 |
|---|---|---|
| Catalog | `POST /api/v1/transfer/getCatalog` | `GET /api/v2/transfers/products` |
| Prices | `POST /api/v1/transfer/getPrice`, one product | `POST /api/v2/transfers/availability`, up to 20 products |
| Check | none | `POST /api/v2/offers/check`, optional |
| Book | `POST /api/v1/transfer/transferConfirm` | `POST /api/v2/bookings` |
| Voucher | `GET /api/v1/transfer/pdf?order_id=...` | `GET /api/v2/bookings/{booking_id}/voucher` |
| Cancel | `POST /api/v1/transfer/transferCancelation` | `POST /api/v2/bookings/{booking_id}/cancel` |
| Find by your id | none | none: keep the `booking_id` |

### What changes

- **Offers replace `total_amount`.** In v1 you send the price back as `total_amount`. In v2 you send the `offer_id`. The price, the date and the passenger counts are inside it.
- **One call for many products and days.** v1 prices one product at a time. v2 takes up to 20 products and 62 days.
- **The catalog says what to collect.** v2 transfers carry `booking_requirements` on the product, and tickets on the brand (`GET /tickets/brands`). In v1 you read the transfer guide.
- **The idempotency key is a header.** In v2 `POST /bookings` requires `Idempotency-Key`, one new value per booking. `external_reference` stays as your own number: optional and repeatable. v1 uses `external_reference` or `X-Idempotency-Key` for this. A call that ends with an unknown result answers `202` with `booking.status: "pending"`, and you read `GET /bookings/{booking_id}` until it is `confirmed` or `failed`.
- **Errors have a closed `code`.** v1 returns `error` (a label), `message`, `code` (the HTTP status) and `request_id`. v2 returns `error.code` from a fixed catalog. Status codes change too: a price change is `400 Price mismatch` in v1 and `409 PRICE_CHANGED` in v2, and v2 gives you the new offer in the error. See [v2 errors](/developers/guides/v2-errors).
- **A currency comes with the price.** v1 responses have no currency field. v2 returns `currency` next to `total`.
- **The booking is a resource.** v1 returns `id_order` and a voucher link. v2 returns the whole booking with `booking_id`, `status` and its items, and the same shape comes back when you read it.
- **Cancellation has a deadline.** In v2 a transfer can be cancelled until `cancellation.deadline` of the item. After it, the cancel answers `409 NOT_CANCELLABLE`. v1 has no such deadline.

### Field equivalences

| v1 | v2 |
|---|---|
| `id_product` | `product_id` |
| `adults`, `children`, `infants` | `pax.adult`, `pax.child`, `pax.infant` |
| `from_date`, `to_date` | `from`, `to` |
| `calendar[].price` | `results[].days[].total` |
| `calendar[].available: false` | The day is left out |
| `total_amount` | `offer_id` |
| `pax[0].name`, `pax[0].surname` | `items[].travelers[0].first_name`, `last_name` |
| `pax[0].phone`, `pax[0].nationality` | `items[].travelers[0].phone`, `nationality` |
| `transfer_data` | `items[].logistics` (keys depend on the product) |
| `id_order` | `booking_id` |
| `pdf_voucher` | `booking.voucher.url` |

In v1 the trip details are free-form keys such as `origin_type`, `hour` and `minutes`. In v2 `logistics` keys come from `booking_requirements`, the time is one `flight_time` field in `HH:MM`, and keys the product does not use are rejected. v2 takes exactly one traveler, the lead. The other passengers are counted in the offer and are not listed.

### Migration path

1. Load `GET /api/v2/transfers/products` and build the booking form from `booking_requirements`.
2. Replace `getPrice` with `availability` and keep the `offer_id` of the day your customer picks.
3. Replace `transferConfirm` with `POST /bookings`. Send a new `Idempotency-Key` with each booking. You can keep sending your own number as `external_reference`.
4. Store `booking_id` next to your own id, and switch voucher and cancel to the v2 paths.
5. Handle `PRICE_CHANGED` by showing `details.current_offer`.

The [Transfers guide](/developers/guides/v2-transfers) and the [Booking flow](/developers/guides/v2-booking-flow) describe the v2 side. The [Booking flow reference](/developers/reference/v2/bookings) and the [Transfers reference](/developers/reference/v2/transfers) have every field.

## Universal v1 to v2

Universal and Disney share the v2 ticket flow, described in [Tickets](/developers/guides/v2-tickets). Nothing forces you to move: the Universal v1 endpoints stay as they are.

### Call by call

| Step | v1 | v2 |
|---|---|---|
| Catalog | `POST /api/v1/universal/getTickets` | `GET /api/v2/tickets/products` |
| Prices | `POST /api/v1/universal/getTicketPrice`, one `plu` | `POST /api/v2/tickets/availability`, up to 20 products |
| Check | none | `POST /api/v2/offers/check`, optional |
| Book | `POST /api/v1/universal/ticketConfirm` | `POST /api/v2/bookings` |
| Voucher | `GET /api/v1/universal/pdf?order_id=...` | `GET /api/v2/bookings/{booking_id}/voucher` |
| Cancel | `POST /api/v1/universal/ticketCancelation` | `POST /api/v2/bookings/{booking_id}/cancel` |

### Field equivalences

| v1 | v2 |
|---|---|
| `plu`, or `plu_ad` and `plu_ch` | `product_id` (ours). The codes are in `external_id` and `external_ids` |
| `adults`, `children` | `pax.adult`, `pax.child` in availability. In the booking, one traveler per ticket with its `role` |
| `from_date`, `to_date` | `from`, `to` |
| `eventResults[].totalPriceWithTax` | `unit_prices` per category and `total` for the party |
| `eventResults[]` with `eventDateTime` | `days[]`, or `days[].time_slots[]` for products with slots |
| `date`, `event_time` | Inside the `offer_id` |
| `total_amount` | `offer_id` |
| `DeliveryMethod` `"92"` | `options.delivery_method` `eticket` |
| `DeliveryMethod` `"53"` | `options.delivery_method` `kiosk_voucher` |
| `pax[].name`, `pax[].surname` | `items[].travelers[].first_name`, `last_name` |
| `pax[].type` | `items[].travelers[].role` |
| `pax[].nationality` | `items[].travelers[].nationality`, required for the lead traveler |
| `pax[].date` | Not asked for Universal. Disney still asks for `birth_date` |
| `pax[].email`, `pax[].phone`, `pax[].age` | Not used in v2 (ignored) |
| `external_reference` or `X-Idempotency-Key` | The `Idempotency-Key` header (required). `external_reference` is optional data |
| `id_order` | `booking_id` |
| `pdf_voucher` | `booking.voucher.url`, which needs your Bearer credential |

### What changes

- **The brand lists what to send.** In v2 `booking_requirements.travelers.fields` of the brand (`GET /tickets/brands`) lists what the tickets need. For Universal it is `first_name` and `last_name` of every traveler and the `nationality` of the lead. It does not ask for the birth date.
- **The delivery method has names.** `92` and `53` become `eticket` and `kiosk_voucher`. When the product and your credential allow both, `delivery_method` is required. If your credential is allowed only one, only that one is listed and it is applied when you omit it.
- **Offers replace `total_amount`.** The offer holds the price, the date, the time slot and the quantities. v1 returned `Price changed.` as a `400`. v2 answers `409 PRICE_CHANGED` with the current offer in `details.current_offer`.
- **One call for many products and days.** v1 priced one `plu` at a time.
- **A price per category.** `unit_prices` gives one price per category, and `total` adds them up.
- **Time slots are offers.** Each slot of a day has its own `offer_id`.
- **Partial answers.** If a product cannot be priced, `availability` answers the others and marks the failed one with an `error` and `partial: true`.
- **The booking is a resource.** The same shape comes back when you read it. A cancellation that only works for part of the booking answers `409 CANCELLATION_PARTIAL` with the state of each item. v1 answered `400 Can't cancel order` without saying which ones.
- **The voucher needs your credential.** v2 gives a download URL that takes your Bearer header. While `voucher.status` is `pending`, it answers `409 VOUCHER_NOT_READY`.
- **Errors have a closed `code`.** See [v2 errors](/developers/guides/v2-errors).

### Migration path

1. Load `GET /api/v2/tickets/brands` and `GET /api/v2/tickets/products`. Map your `plu` values to `product_id` through `external_id` and `external_ids`.
2. Build the traveler form from the `booking_requirements` of the brand: names for everyone and the nationality of the lead.
3. Replace `getTicketPrice` with `availability`. Keep the `offer_id` of the day, or the slot, your customer picks.
4. Replace `ticketConfirm` with `POST /bookings`. Send a new `Idempotency-Key` with each booking, your own number as `external_reference` if you want, and the `delivery_method` when the product requires it. Set a client timeout of at least 400 seconds. If you time out, repeat with the same `Idempotency-Key`: you get `409 BOOKING_IN_PROGRESS` while it runs, then the result. Never book again with a new key.
5. Store `booking_id` next to your own id, and switch voucher and cancel to the v2 paths.
6. Handle `PRICE_CHANGED` by showing `details.current_offer`.

## Disney v1 to v2

The v2 ticket flow is the same as for Universal. The steps are in [Tickets](/developers/guides/v2-tickets).

### Call by call

| Step | v1 | v2 |
|---|---|---|
| Catalog | `POST /api/v1/disney/getTickets` | `GET /api/v2/tickets/products` |
| Prices | `POST /api/v1/disney/getTicketPrice`, one product code | `POST /api/v2/tickets/availability`, up to 20 products |
| Check | none | `POST /api/v2/offers/check`, optional |
| Book | `POST /api/v1/disney/ticketConfirm` | `POST /api/v2/bookings` |
| Voucher | `GET /api/v1/disney/pdf?order_id=...` | `GET /api/v2/bookings/{booking_id}/voucher` |
| Cancel | `POST /api/v1/disney/ticketCancelation` | `POST /api/v2/bookings/{booking_id}/cancel` |

### Field equivalences

| v1 | v2 |
|---|---|
| `product_id` (`T0001907`), one per adult and child | `external_ids.adult` and `external_ids.child` of one `product_id` |
| `product_id_adult`, `product_id_child` | One `product_id` for both categories |
| `brand` in `getTickets` | `brand` query parameter of `GET /tickets/products` |
| `adults`, `children` | `pax.adult`, `pax.child` in availability |
| `from_date`, `to_date` | `from`, `to` |
| `results[].price` | `unit_prices` per category and `total` for the party |
| `date` | Inside the `offer_id` |
| `total_amount` | `offer_id` |
| `pax[].name`, `pax[].surname` | `items[].travelers[].first_name`, `last_name` |
| `pax[].type` | `items[].travelers[].role` |
| `pax[].birthdate` | `items[].travelers[].birth_date` |
| `pax[].nationality`, `pax[].phone` | `items[].travelers[].nationality`, `phone` (optional) |
| `external_reference` or `X-Idempotency-Key` | The `Idempotency-Key` header (required). `external_reference` is optional data |
| `id_order` | `booking_id` |
| `pdf_voucher` | `booking.voucher.url` |

### What changes

- **One product for adult and child.** v1 lists the adult ticket and the child ticket as two codes of the same `family`. In v2 they are one product with two categories, and their codes are in `external_ids`. A product has a price for each category you ask for.
- **The travelers are the same four fields.** Name, birth date and nationality are required, and `phone` stays optional. The field names change: `name` and `surname` become `first_name` and `last_name`, `birthdate` becomes `birth_date`, and `type` becomes `role`.
- **Offers replace `total_amount`.** v1 answered `400 Price changed.` when the total moved. v2 answers `409 PRICE_CHANGED` with the current offer in `details.current_offer`, and nothing is booked.
- **One call for many products and days.** v1 priced one product code at a time.
- **Partial answers.** If a product cannot be priced, `availability` answers the others and marks the failed one with an `error` and `partial: true`.
- **Cancellation reports each item.** A cancellation that only works for part of the booking answers `409 CANCELLATION_PARTIAL` with the state of each item.
- **Errors have a closed `code`.** See [v2 errors](/developers/guides/v2-errors).

### Migration path

1. Load `GET /api/v2/tickets/brands` and `GET /api/v2/tickets/products`, with `brand` when you only sell one. Map your product codes through `external_ids`.
2. Build the traveler form from the `booking_requirements` of the brand.
3. Replace `getTicketPrice` with `availability`. Keep the `offer_id` of the day your customer picks.
4. Replace `ticketConfirm` with `POST /bookings`, with a new `Idempotency-Key` for each booking.
5. Store `booking_id` next to your own id, and switch voucher and cancel to the v2 paths.
6. Handle `PRICE_CHANGED` by showing `details.current_offer`.

The [Tickets reference](/developers/reference/v2/tickets) and the [Booking flow reference](/developers/reference/v2/bookings) have every field.

## Hotels v1 to v2

The v2 hotel flow is described in [Hotels](/developers/guides/v2-hotels). Nothing forces you to move: the v1 hotel endpoints stay as they are. Disney resort hotels are part of the same flow in v2, with `requires_check: true` on their offers.

### Call by call

| Step | v1 | v2 |
|---|---|---|
| Destinations | `GET /api/v1/hotel/content/destinations` | `GET /api/v2/hotels/destinations` |
| Geography tree | `GET /api/v1/hotel/content/locations` | None. Destinations carry `country`, `division` and `parent` |
| Content | `GET /api/v1/hotel/content/hotelData` and `hotelDetails` | `GET /api/v2/hotels/{hotel_id}` with `sections` |
| Start a search | `POST /api/v1/hotel/distribution/hotelSearch` | `POST /api/v2/hotels/availability` |
| Poll | The same `hotelSearch` call, with the whole body and `id_search` | `GET /api/v2/hotels/availability/{search_id}`, no body |
| Check | `POST /api/v1/hotel/distribution/hotelCheckPrice` | `POST /api/v2/offers/check`, optional |
| Book | `POST /api/v1/hotel/distribution/bookingConfirm` | `POST /api/v2/bookings` |
| Read | `GET /api/v1/hotel/distribution/bookingDetail?id_orden=...` | `GET /api/v2/bookings/{booking_id}` |
| Confirmation number | `GET /api/v1/hotel/distribution/bookingHcn?id_orden=...` | `hotel_confirmation` in the booking, and `voucher?variant=hcn` |
| Voucher | `pdf_voucher` in the answers | `GET /api/v2/bookings/{booking_id}/voucher` |
| Cancel | `POST /api/v1/hotel/distribution/bookingCancelation` | `POST /api/v2/bookings/{booking_id}/cancel` |
| Find by your id | None | None: keep the `booking_id` |

### Field equivalences

| v1 | v2 |
|---|---|
| `id_location` (one) | `destination_ids` (a list) |
| `from_date`, `to_date` | `check_in`, `check_out` |
| `occupancies[].adults`, `childrenAges` | `rooms[].adults`, `children_ages` |
| `mode: full` | no equivalent: poll every `poll_after_seconds` until `status` is `completed` |
| `id_search` (a number, in the body) | `search_id` (a string, in the path) |
| `finish` | `status` (`running` or `completed`) |
| `isCached` | `cached` |
| `page`, `limit` | `offset`, `limit`, with `total` and `next_offset` in the answer |
| `hotels[].id_hotel`, `hotel_name` | `hotels[].hotel_id`, `name` (our ids) |
| `hotel_rating`, `hotel_chain`, `hotel_type` | `star_rating`, `chain`, `property_type`, always typed |
| `hotels[].occupancies[]` | `hotels[].slots[]` |
| `rates[]` | `offers[]` |
| `rate_key`, `rateKey`, `ratekey` | `offer_id` |
| `id_room`, `room_name` | `room.room_id`, `room.name` |
| `board.id`, `board.name` | `board.code` (a closed list), `board.name` |
| `price` | `total` (the price of one room) |
| `price_total`, `room_quantity` | `rate_total` and `rate_group`: the rooms of one rate share a `rate_group` |
| `non_refundable`, `cancelation_fees[]`, `cancellationFees[]` | `cancellation_policy`: `refundable`, `free_until`, `penalties[{from, amount}]` |
| `fees[]` | `hotel_fees[]`, each with `basis` and `payable_at_hotel` |
| `remarks` (a string in the search, a list in the check) | `remarks`, always a list |
| `adults_only`, `adults_min_age` | The same names |
| `bookingConfirm` `ratekey[]` | `items[].offer_id`, one per room |
| `bookingHolder` and `guests[]` | `items[].travelers[]`, as the offer asks (`booking_requirements`) |
| `external_reference` or `X-Idempotency-Key` header | The `Idempotency-Key` header (required). `external_reference` is optional data, and you can repeat it |
| `id_orden` | `booking_id` |
| `hcn_status` and `rooms[].hcn` | `hotel_confirmation.status` and `numbers[]` |
| `pdf_voucher` | `booking.voucher.url` and `booking.voucher.hcn.url`, which need your Bearer credential |

`provider` and `providers[]` are not in v2.

### What changes

- **A search is two calls with the same vocabulary.** v1 repeats the whole body to poll. v2 polls with the `search_id` alone, and the `search_id` belongs to your company: another company's id answers `404 SEARCH_NOT_FOUND`. Polling every `poll_after_seconds` replaces `mode: full`.
- **One offer per room.** The `price` of v1 meant different things depending on the step. In v2 each offer is one room, `total` is the price of that room, and a booking costs the sum of the `total` of its offers. A grouped rate is the same offer in each of its slots with one `rate_group`.
- **The offer carries what it needs.** You send the `offer_id`, not the rate key repeated for each room of a grouped rate. The `offer_id` is valid for one credential. It has an `expires_at` only when the offer has a fixed end time, such as the Disney resort hotels after the check. Without it the offer is still good only for a short time after the search. When it is too old, the booking answers `OFFER_EXPIRED`.
- **The price is accepted by the offer.** v1 books at the price valid at confirmation and tells you nothing. v2 compares the current price with the offer: if it moved by 0.01 or more you get `409 PRICE_CHANGED` with the current offer, and nothing is booked. The check is optional, except that an offer with `requires_check: true` is checked inside the booking.
- **Travelers depend on the offer.** In v1 you send a holder, and for some hotels the other guests too. In v2 the offer lists what it needs. A hotel that needs one holder takes it in the first item. A hotel that needs every guest takes each room with its own guests and the age of each child. v2 asks only for the fields the offer lists, with no email. Some offers ask for more than v1 did: the Disney resort hotels ask the nationality of every guest.
- **Closed error codes.** A rejected rate is `409 OFFER_EXPIRED` or `409 NO_AVAILABILITY` instead of three `400` texts. A cancellation the hotel refuses is `409 CANCELLATION_REJECTED` instead of `400`. A date too close is `400 VALIDATION_ERROR` with `DATE_NOT_BOOKABLE`. See [v2 errors](/developers/guides/v2-errors).
- **Typed policy and fees.** The penalties have one shape, a date and time in UTC, for the room. Fees that are paid at the hotel are in their own list and never in `total`.
- **Hotel confirmation number.** It is in `hotel_confirmation` of the booking, and you download the voucher with it by `?variant=hcn`. The standard voucher never has the number. There is no link-credential: the voucher needs your Bearer header, and a cancelled booking answers `410 BOOKING_CANCELLED`.
- **The booking is a resource.** `currency` is the one of the booking and is not fixed to `USD`. An unknown result answers `202` with `status: "pending"`. A retry with the same `Idempotency-Key` answers the same `202`, so it cannot book twice, and `GET /bookings/{booking_id}` tells you when it is `confirmed` or `failed`.
- **Content is typed.** Descriptions, facilities and images come in one shape, and the fields that did not map to it are not exposed.

### Migration path

1. Load `GET /api/v2/hotels/destinations` and map your `id_location` values to `destination_id`.
2. Replace `hotelSearch` with `POST /hotels/availability`, and the polling with `GET /hotels/availability/{search_id}`.
3. Build your results from `slots[].offers[]`. Keep the `offer_id` of the offer the guest picks in each slot.
4. Build the traveler form after the pick, from `booking_requirements` of that offer.
5. Replace `bookingConfirm` with `POST /bookings`, one item per room, with a new `Idempotency-Key` for each booking.
6. Handle `PRICE_CHANGED` by showing `details.current_offer`, and `202 pending` by reading the booking by its `booking_id`.
7. Store `booking_id` next to your own id. Switch the voucher, the confirmation number and the cancellation to the v2 paths.

The [Hotels reference](/developers/reference/v2/hotels) and the [Booking flow reference](/developers/reference/v2/bookings) have every field.
