# Migrating from v1

API v2 is in preview 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 the rate limit is shared between them. 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).

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 | `GET /api/v2/bookings?external_reference=...` |

### 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 90 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.
- **`external_reference` is required.** It is optional in v1.
- **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.

### Field equivalences

| v1 | v2 |
|---|---|
| `id_product` | `product_id` |
| `adults`, `children`, `infants` | `travelers.adult`, `travelers.child`, `travelers.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 the same `external_reference` you use in v1.
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` | `travelers.adult`, `travelers.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`, now required |
| `pax[].date` | `items[].travelers[].birth_date`, now required |
| `pax[].email`, `pax[].phone`, `pax[].age` | Not used in v2 (ignored). The age comes from `birth_date` |
| `external_reference` | `external_reference`, required |
| `id_order` | `booking_id` |
| `pdf_voucher` | `booking.voucher.url`, which needs your Bearer credential |

### What changes

- **Every traveler needs a birth date and nationality.** v1 asked for less. In v2 `booking_requirements.travelers.fields` of the brand (`GET /tickets/brands`) lists what the tickets need, and each traveler sends `first_name`, `last_name`, `birth_date` and `nationality`.
- **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, with birth date and nationality for everyone.
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 the same `external_reference` you use in v1, and the `delivery_method` when the product requires it.
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` | `travelers.adult`, `travelers.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` | `external_reference`, required |
| `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 the same `external_reference` you use in v1.
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 | `GET /api/v2/bookings?external_reference=...` |

### 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` (optional) | `external_reference`, required |
| `X-Idempotency-Key` header | `external_reference` is required and has precedence. The `Idempotency-Key` header is only checked for length |
| `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: the answer never says which source a rate comes from.

### 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 source gives a real expiry, such as the Disney resort hotels after the check. Without it the offer works while its search exists, about a day, and then 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 live 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 for no email or country of the holder: only the fields the offer lists.
- **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 at the source answers `502` and keeps your reference tied, so a retry answers `409 BOOKING_IN_PROGRESS` instead of booking twice.
- **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 the same `external_reference` you use in v1.
6. Handle `PRICE_CHANGED` by showing `details.current_offer`, and `409 BOOKING_IN_PROGRESS` by reading the booking by reference.
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.
