# Universal

Universal Orlando tickets. You read the catalog, ask for the price of a product for a range of dates, book with the passenger list, and download the voucher.

This guide explains the flow. Field-by-field detail is in the [Universal reference](/developers/reference/v1/universal).

| Step | Endpoint | Gives you |
|---|---|---|
| 1 | `POST /api/v1/universal/getTickets` | Products and their `plu` codes |
| 2 | `POST /api/v1/universal/getTicketPrice` | Price and availability by date for one `plu` |
| 3 | `POST /api/v1/universal/ticketConfirm` | `id_order` and the voucher link |
| 4 | `GET /api/v1/universal/pdf` | The voucher PDF again, at any time |
| 5 | `POST /api/v1/universal/ticketCancelation` | Cancels the order |

All calls need your Bearer credential. Send it in the header. See [Authentication](/developers/guides/authentication).

## 1. List the catalog

```bash
curl -X POST "https://api.avantetravel.com/api/v1/universal/getTickets" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The response is a JSON array with one object per product. It has no prices.

```json
[
  {
    "plu": "110117001386",
    "productName": "Example Express Pass",
    "allowedDeliveryMethods0": "92",
    "allowedDeliveryMethods1": "53",
    "numberOfDays": 1,
    "isParkToPark": "0",
    "ageValue": "",
    "isLimitedExpress": "1",
    "isUnlimitedExpress": "0",
    "residencyRequirement": "",
    "isThemeParkAccess": "1",
    "themeParkAccessNames0": "USF",
    "isGradEventAccess": "0"
  }
]
```

- `plu` is the product code you use in the next calls.
- The flags, such as `isParkToPark`, are the strings `"1"` and `"0"`.
- `allowedDeliveryMethods0`, `allowedDeliveryMethods1` and so on list the delivery methods the product accepts. You need one of them at confirmation. See [Delivery method](#delivery-method).
- The API does not filter by park. Each product declares its own access, for example in `themeParkAccessNames0`.

The catalog changes rarely, so cache it.

## 2. Read the price by date

```bash
curl -X POST "https://api.avantetravel.com/api/v1/universal/getTicketPrice" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{
    "plu": "110117001386",
    "from_date": "2026-06-15",
    "to_date": "2026-06-20"
  }'
```

```json
{
  "eventResults": [
    {
      "eventId": 12345,
      "eventDateTime": "2026-06-15T08:00:00-04:00",
      "capacityAvailable": 500,
      "totalPriceWithTax": 135.0
    }
  ],
  "request_id": "3f8a9b7c1d2e4f5a6b7c8d9e0f1a2b3c"
}
```

- Each row of `eventResults` is an available date (and time slot, if the product has them), with the capacity left and the price.
- `totalPriceWithTax` is the price of one ticket for your account. The price is per ticket, not per order.
- A product with no availability returns an empty `eventResults`. No availability can also come as `success: false` with no `eventResults`. Treat both as no availability.
- Ask for one `plu` at a time.
- Prices change. A price has no guaranteed lifetime. Ask right before you confirm.

## 3. Book

```bash
curl -X POST "https://api.avantetravel.com/api/v1/universal/ticketConfirm" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{
    "plu": "110117001386",
    "adults": 2,
    "children": 1,
    "date": "2026-06-15",
    "total_amount": 405.00,
    "DeliveryMethod": "92",
    "external_reference": "MY-SYSTEM-9999",
    "pax": [
      { "name": "John", "surname": "Doe", "type": "adult" },
      { "name": "Jane", "surname": "Doe", "type": "adult" },
      { "name": "Kid", "surname": "Doe", "type": "child" }
    ]
  }'
```

| Field | Rule |
|---|---|
| `plu` | The product code. If the product has separate adult and child codes, send `plu_ad` and `plu_ch` instead. You need at least one of `plu`, `plu_ad` and `plu_ch`. |
| `adults`, `children` | Integers from 0 to 50. You need at least one passenger. |
| `date` | The date of the service, `YYYY-MM-DD`. |
| `event_time` | Optional. The time slot, when the product has slots. |
| `total_amount` | The total price you expect. See below. |
| `DeliveryMethod` | Required. See [Delivery method](#delivery-method). The key is written with capital letters. |
| `pax` | One object per passenger. The count must be exactly `adults + children`. |
| `external_reference` | Your reference. Always send it. See [Conventions](/developers/guides/conventions#idempotency). |

Each passenger needs `name`, `surname` and `type` (`adult` or `child`). You can also send `email`, `phone`, `nationality`, `date` and `age`.

### Total amount

Prices are not held. The call quotes again at booking time and compares the result with your `total_amount`. If it differs by 0.01 or more you get `400` with `Price changed.`, and nothing is booked. The price is applied per ticket, so:

```text
total_amount = adults × adult ticket price + children × child ticket price
```

Use the `totalPriceWithTax` you read from `getTicketPrice` for each code you send, and read it right before you confirm. If there is no availability for the date you get `400` with `No availability for the selected date.`.

### Delivery method

`DeliveryMethod` must be one of the methods the product lists in `allowedDeliveryMethods`. The two usual ones are:

- `92`: the tickets are issued immediately.
- `53`: voucher delivery. Your account needs voucher sales enabled. If a product offers both `92` and `53` and your account does not sell vouchers, `53` is refused and you must use `92`.

A method that the product does not list returns `400 Delivery method not supported`, and the `message` lists the ones that are allowed. A refused voucher method returns `400 Delivery method not available`.

### The response

```json
{
  "success": true,
  "code": 200,
  "id_order": "58740",
  "pdf_voucher": "https://api.avantetravel.com/archivos/api/universal/pdf/Ab12Cd34Ef56.pdf"
}
```

- Keep `id_order`. You need it to cancel and to ask for the voucher again.
- The success body has no `request_id`. Keep your own `external_reference` and the `id_order` in your logs.
- `pdf_voucher` is a link that can be opened without a credential. Treat it as private. The link works even if the file is not ready yet.

If the booking is refused you get `400`, and `error` has the reason. Nothing is booked, and you can repeat the booking.

### If the request times out

A confirmation can take up to about three minutes. Use a client timeout of at least 240 seconds. If you time out, repeat the request with the same `external_reference`: you never book twice. You get the same order with `"idempotent_replay": true` (`id_order` is a string, as in the first answer), or `409 ORDER_CONFIRMATION_IN_PROGRESS` if the first one is still running. Do not use a new reference. If you did not send a reference, a repeat can book twice. See [Conventions](/developers/guides/conventions#idempotency).

## 4. Download the voucher

```bash
curl "https://api.avantetravel.com/api/v1/universal/pdf?order_id=58740" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -o voucher.pdf
```

| Status | `error` | Meaning |
|---|---|---|
| 404 | `ORDER_NOT_FOUND` | The order does not exist or is not yours. |
| 410 | `ORDER_CANCELLED` | The order was cancelled. The voucher is not valid. |
| 409 | `VOUCHER_NOT_GENERATED` | The order has no tickets yet. Try again shortly. |
| 500 | `Failed to generate PDF` | The file could not be created. Retry, then contact support. |

## 5. Cancel

```bash
curl -X POST "https://api.avantetravel.com/api/v1/universal/ticketCancelation" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{ "id_order": 58740 }'
```

A successful cancellation returns `{"success": true}` and cancels every ticket of the order.

An order can hold several tickets. If one of them cannot be cancelled, the ones already cancelled stay cancelled, and the answer is `400` with `Can't cancel order`. The answer does not say which ones were cancelled. Contact support with your `id_order`.

| Status | `error` or `message` | Meaning |
|---|---|---|
| 400 | `Can't cancel this ticket.` | The order is not yours, or it is not active. This answer has only a `message` field. |
| 400 | `Ticket not found.` | The order has no Universal ticket that can be cancelled. |
| 400 | `Can't cancel order` | The cancellation was refused, or only some tickets were cancelled. |

## Errors to handle

| Status | `error` | Action |
|---|---|---|
| 400 | `Price changed.` | Ask for the price again and confirm with the new total. |
| 400 | `No availability for the selected date.` | Choose another date or product. |
| 400 | `Missing Product ID` | Send `plu`, or `plu_ad` or `plu_ch`. |
| 400 | `Pax count mismatch`, `Invalid pax entry` | Make the passenger list match the counts, with `name`, `surname` and `type` in each. |
| 400 | `Ticket not found.` | The `plu` does not match a product that is on sale (a product that exists but is not on sale gets the same answer). Check it against the catalog. |
| 404 | `Product not found` | In `getTicketPrice`, the `plu` does not match a product for sale. |
| 502 | `External API error` | The request could not be completed in time. Retry with backoff. |
| 502 | `External API unreachable`, `Invalid response from external API` | In `getTickets`, the catalog could not be read right now. Retry with backoff. |
| 503 | `Service unavailable` | The service is not available right now. Retry in a few seconds. |

The response to a missing required field in `getTicketPrice` has the label `Faltan datos requeridos`, in Spanish. The status is `400` and `message` is `Empty fields received`. See [Errors](/developers/guides/errors) for the shape of the Universal answers.

## What not to do

- Do not reuse a price from an earlier query. Ask right before you confirm.
- Do not retry a timed-out confirmation with a new `external_reference`.
- Do not rely on the `pdf_voucher` link as the only copy. You can ask for the voucher again by `id_order`.

## Next

- [Universal reference](/developers/reference/v1/universal) has every field and response.
- [Sandbox](/developers/guides/sandbox) explains how to test a booking.
