# Tickets

This guide covers what is specific to tickets: theme park tickets, multi-day passes, express passes and special events. The steps every product shares (the offer, the check, booking, reading, cancelling, the voucher and the errors) are in the [Booking flow](/developers/guides/v2-booking-flow) guide. Field-by-field detail is in the [Tickets reference](/developers/reference/v2/tickets) and the [Booking flow reference](/developers/reference/v2/bookings).

| Step | Call | What carries to the next step |
|---|---|---|
| 1 | `GET /api/v2/tickets/brands` | what to collect from travelers, per brand |
| 2 | `GET /api/v2/tickets/products` | `product_id` and `booking_options` |
| 3 | `POST /api/v2/tickets/availability` | `offer_id` |
| 4 | The [Booking flow](/developers/guides/v2-booking-flow) | |

## 1. List brands

```bash
curl "https://api.avantetravel.com/api/v2/tickets/brands" \
  -H "Authorization: Bearer YOUR_CREDENTIAL"
```

A brand is the resort or seller of a ticket. Everything the tickets of one brand have in common is here, once: the traveler fields and the age categories. You build your form from the brand, not from each product. A brand appears only if your credential can sell at least one of its products right now. A credential with no access to any ticket integration gets `403 FORBIDDEN`.

Each brand has:

- `code` and `name`: `UOR` (Universal Orlando Resort), `WDW` (Walt Disney World), `DLR` (Disneyland Resort), `UPR` (United Parks & Resorts) or `OTKT` (other tickets). The list can grow.
- `products`, how many products you can sell in it. Brands with more products come first.
- `age_categories`, the categories you price and book by: `adult` and `child`, each with its age range. The list can be empty when the brand has no age ranges, and `role` is still `adult` or `child`. Infants under 3 do not need a ticket and are not part of a ticket booking.
- `booking_requirements`, what to send for every traveler. `travelers.mode` is `all_travelers`: one entry per person, each with a `role`. `fields` lists the keys, each with `required` and `applies_to` (`lead`, `adult`, `child` or `all`), and `age_validation` says whether the birth date must fit the age category of the traveler (`strict`) or is not checked against it (`none`).

The API asks only for what each integration needs, so the list is different for each brand:

| Brand | What it asks for |
|---|---|
| Universal (`UOR`) | `first_name` and `last_name` of every traveler, and `nationality` of the lead. No birth date. |
| Disney (`WDW`, `DLR`) | `first_name`, `last_name`, `nationality` and `birth_date` of every traveler, checked against the age category |
| United Parks (`UPR`) | `first_name` and `last_name` of every traveler, and `nationality` of the lead only |
| Other tickets (`OTKT`, our own products) | `first_name`, `last_name`, `nationality` and `birth_date` of every traveler, the same as our website |

Build your form from `fields`, not from this table. A field the brand does not list is not required. If you send it, it must be valid.

## 2. List products

```bash
curl "https://api.avantetravel.com/api/v2/tickets/products?brand=UOR" \
  -H "Authorization: Bearer YOUR_CREDENTIAL"
```

A ticket your credential cannot sell is not listed. Every product has a `brand` code from step 1. Filter with `?brand=UOR` or `?brand=WDW,DLR`.

Products appear only while their integration is enabled for your account and on your credential. A product whose integration is turned off is not listed and gets no offers; bookings already made can still be read, cancelled and their voucher downloaded.

Each product carries:

- `product_id`, our id. Use it in `availability`.
- `external_id` and `external_ids`, the codes the ticket has outside our platform. Use them to map and reconcile. When adult and child are sold under different codes, `external_ids` has one per category (`adult`, `child`). Both are `null` only for tickets with no code outside our platform.
- `days`, the days of admission, and `usage_window_days`, the days counted from the first visit in which all of them must be used. A 4-day ticket with a window of 7 must be used within 7 days of the first visit. `null` means no window.
- `validity`, the dates the product is sold for, and `event_date`, the one date of a special event.
- `included_venues`, `time_slots` (whether it sells by time slot) and `currency`.
- `details`, typed by `details.kind`. Read `kind` first and ignore the kinds you do not know.
- `booking_options`, the choices that belong to this product alone. It is `{}` when there are none.

Travelers and age categories are not on the product: they come from its brand.

`booking_options` holds the choices to make at booking time. Today that is `delivery_method`:

- `eticket` is an instant e-ticket.
- `kiosk_voucher` is a voucher collected at a kiosk.

When the product and your credential allow both, `values` has two entries and `required` is `true`: you must send the option. Your credential can be allowed only one of them. Then only that one is listed, and it is applied if you leave the option out.

The catalog changes rarely. Both responses have `Cache-Control: private, max-age=3600`.

## 3. Ask for availability

```bash
curl -X POST "https://api.avantetravel.com/api/v2/tickets/availability" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{
    "product_ids": [415, 331],
    "from": "2026-11-10",
    "to": "2026-11-12",
    "pax": { "adult": 2, "child": 1 }
  }'
```

- One call covers up to 20 products and a range of 62 days, both ends counted. More answers `400 VALIDATION_ERROR`. These limits can change, and we tell you before they do.
- `pax` gives the quantity per category. Only `adult` and `child` are priced, with 1 to 50 people in total. A Disney booking takes at most 40 tickets.
- The response has one entry in `results` per requested product, in request order.

```json
{
  "results": [
    {
      "product_id": 415,
      "currency": "USD",
      "days": [
        {
          "date": "2026-11-10",
          "availability": "available",
          "unit_prices": { "adult": 641.3, "child": 622.6 },
          "total": 1905.2,
          "offer_id": "OFFER_ID_FROM_THIS_RESPONSE",
          "expires_at": null
        },
        { "date": "2026-11-12", "availability": "sold_out" }
      ]
    }
  ],
  "request_id": "3f8a9b7c1d2e4f5a6b7c8d9e0f1a2b3c"
}
```

- `unit_prices` is the price of one ticket for each category you asked for. `total` is the sum of each unit price, rounded to 2 decimals, times its quantity. It is the price of the whole party.
- `availability` is `available`, `limited` (few left) or `sold_out`. Only `available` and `limited` days carry prices and an `offer_id`.
- A date with nothing to sell is left out: outside the validity, or not the date of a special event.
- `expires_at` is `null` on ticket offers. To know whether a price still holds, call the check; the booking re-quotes it live anyway. See the [Booking flow](/developers/guides/v2-booking-flow#1-the-offer).
- The response has `Cache-Control: private, max-age=300`.

### Time slots

A product with `time_slots: true` returns, for each day, one offer per slot in `time_slots[]`. The prices, `total` and `offer_id` are in each slot, not on the day.

```json
{
  "date": "2026-11-10",
  "availability": "available",
  "time_slots": [
    {
      "time": "09:30",
      "availability": "available",
      "unit_prices": { "adult": 135.0, "child": 135.0 },
      "total": 405.0,
      "offer_id": "OFFER_ID_FOR_THIS_SLOT",
      "expires_at": null
    }
  ]
}
```

`time` is `HH:MM`, local to the venue.

### Partial results

If a product cannot be priced right now, its result has `days: []` and an `error` with `code: PROVIDER_ERROR`. The other products are answered as usual, and the response carries `partial: true`. If the integration behind a product is switched off, its result carries `code: PROVIDER_UNAVAILABLE` instead, also with `partial: true`. Retry the failed products.

```json
{
  "product_id": 331,
  "currency": "USD",
  "days": [],
  "error": { "code": "PROVIDER_ERROR", "message": "This product could not be priced right now. Ask again." }
}
```

If every requested product fails with `PROVIDER_ERROR`, the whole call answers `502 PROVIDER_ERROR`. If all of them are switched off, it answers `503 PROVIDER_UNAVAILABLE`. When every product fails but not all for the same reason, the answer is `200` with `partial: true`. A `partial` response is the only v2 answer that mixes outcomes, and it is a read: nothing was booked.

An unknown product and a product your credential cannot sell answer the same `404 PRODUCT_NOT_FOUND`, for the whole call. You never get a partial list for this case.

### Where the price comes from

- Universal prices are requested live when you call `availability`. If the live answer does not arrive in time, we answer from the last stored price for that product and day.
- Disney prices come from a stored copy that we keep up to date, or are requested live.
- Other tickets are priced from our own table by date. What `availability` returns is what we sell.

Whatever the source, the price is checked live again when you book. The stored price is never the price you are charged.

## 4. The ticket item of a booking

The booking is the one described in the [Booking flow](/developers/guides/v2-booking-flow#3-book). A ticket item has these specifics:

```json
{
  "offer_id": "OFFER_ID",
  "travelers": [
    { "role": "adult", "first_name": "Ana", "last_name": "Example", "nationality": "AR" },
    { "role": "adult", "first_name": "Luis", "last_name": "Example" },
    { "role": "child", "first_name": "Sol", "last_name": "Example" }
  ],
  "options": { "delivery_method": "eticket" }
}
```

- `offer_id` is the only thing that travels from availability. The date, the time slot, the quantities and the price are inside it. You do not send them again.
- `travelers` has one entry per ticket, every traveler named, each with its `role` (`adult` or `child`). The count per `role` must match the offer (`pax`). The example above is a Universal ticket. A Disney ticket, and one of our own tickets (`OTKT`), also needs the `birth_date` and the `nationality` of everyone, as the brand lists.
- `options.delivery_method` is required when the product lists more than one value.
- Tickets take no `logistics`.
- A price that moved answers `409 PRICE_CHANGED` with a fresh offer in `details.current_offer`, and nothing is created. Show the new price and book again with its `offer_id`.

In the booking answer, `date` of a multi-day ticket is the first day, and each item has `pax`, `unit_prices` per category and the `options` you sent. The product carries `external_id` and `external_ids`. Reading a ticket booking returns the count per category, not the list of names.

Cancelling cancels the whole booking. Most tickets give you the voucher at once. Disney tickets can publish it later: while `voucher.status` is `pending`, the download answers `409 VOUCHER_NOT_READY`, so retry later. With `none` there is no voucher, and the download answers `409 VOUCHER_NOT_AVAILABLE`. A cancelled booking answers `410 BOOKING_CANCELLED`.

A Universal booking can take up to about 120 seconds. Set your client timeout for `POST /bookings` to 130 seconds, and send the `Idempotency-Key` header. If the source does not answer, the booking answers `202` with `status: "pending"`: read it with `GET /bookings/{booking_id}`. See the [Booking flow](/developers/guides/v2-booking-flow#if-the-result-is-unknown).

## Next

The rest is the [Booking flow](/developers/guides/v2-booking-flow) guide: the check, booking and retries, reading, cancelling, the voucher, test credentials and the errors to handle. The full list of codes is in [v2 errors](/developers/guides/v2-errors).
