> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.todaytixgroup.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.todaytixgroup.com/_mcp/server.

# Migration: Availability to Occurrences

Guide for moving from the Encore API's `GET /api/v4/availability/products/{productId}/...` endpoint to `GET /api/v3/events/{eventId}/occurrences` in the TTG Storefront API v3, the newer API. The two APIs number their versions separately.

> For the full v4 inventory reference, see [Inventory API](https://developers.todaytixgroup.com/reference/inventory-overview) on the legacy portal.

## Endpoint Comparison

| | Legacy (v4) | New (v3) |
|---|---|---|
| **Method** | `GET` | `GET` |
| **Path** | `/api/v4/availability/products/{productId}/quantity/{quantity}/from/{fromDate}/to/{toDate}` | `/api/v3/events/{eventId}/occurrences` |
| **Auth** | None — the optional `affiliateId` header only picks the retailer | `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform` headers |
| **Filtering** | Date range (`YYYYMMDD`) and quantity in URL path | Optional `quantity`, `startDate`, `endDate` query parameters |
| **Pagination** | Not built in | `limit` / `offset` with a `pagination` block |

## What Changed

### Headers

Legacy availability needed no key. v3 needs the three headers in [Authentication & Headers](/guides/getting-started#authentication--headers) on every call.

### Pagination

The response is a page, not the whole list: `{ data, filters, pagination }`. See [Pagination](/guides/api-conventions#pagination).

### Terminology

| Legacy (v4) | New (v3) | Notes |
|---|---|---|
| Product | Event | Same concept — a show or experience |
| `productId` | `eventId` | Usually the same value. Some older products have a different TodayTix id, which the legacy API looked up for you: check each id with `GET /api/v3/events/{eventId}`, or ask your TTG contact for the list |
| Availability | Occurrence | Each date/time slot is now an "occurrence" |
| `datetime` | `startsAt` / `endsAt` | ISO 8601 with venue timezone offset |
| `minPrice` + `currency` | `fromPrice` | Both in minor units; v3 puts them in one object |

### Quantity and Date Range Are Now Optional Query Parameters

In v4, `quantity`, `from`, and `to` were required path segments. In v3, they are optional query parameters:

| v4 (path) | v3 (query param) | Notes |
|---|---|---|
| `/quantity/{quantity}` | `?quantity=2` | Optional. Pricing and `availabilityLevel` reflect that many tickets together; see [Party Size](/guides/api-conventions#party-size). |
| `/from/{fromDate}` (`YYYYMMDD`) | `?startDate=2026-03-15` | Optional. Inclusive start date (ISO 8601 `YYYY-MM-DD`). Interpreted in the event's local timezone. |
| `/to/{toDate}` (`YYYYMMDD`) | `?endDate=2026-03-31` | Optional. Inclusive end date (ISO 8601 `YYYY-MM-DD`). Interpreted in the event's local timezone. |

Two more optional parameters have no v4 equivalent: `currency` prices the dates in another currency (see [Currency](/guides/api-conventions#currency)), and `voucherCode` shows the dates a presale or access code unlocks (see [Vouchers](/guides/purchase-flow#vouchers)).

All parameters can be combined freely or omitted entirely:

```
# Legacy
GET /api/v4/availability/products/123/quantity/2/from/20260315/to/20260331

# Equivalent to v4 call with all filters
GET /api/v3/events/123/occurrences?quantity=2&startDate=2026-03-15&endDate=2026-03-31

# Only filter by date range
GET /api/v3/events/123/occurrences?startDate=2026-03-15&endDate=2026-03-31

# Only filter by quantity
GET /api/v3/events/123/occurrences?quantity=2

# No filters — the first page of upcoming occurrences
GET /api/v3/events/123/occurrences
```

### Money Format

Both use minor units. Legacy sent the price and its currency as two fields; v3 puts them in one object.

Legacy (v4):

```json
{ "minPrice": 8500, "currency": "USD" }
```

New (v3):

```json
{ "fromPrice": { "amount": 8500, "currency": "USD" } }
```

All money in v3 uses this shape, in minor units (cents for USD, pence for GBP). Get the scale from the currency code. See [Money Format](/guides/api-conventions#money-format).

### Availability Status

| Legacy (v4) | New (v3) Equivalent |
|---|---|
| Available dates returned | `fromPrice` is non-null |
| Sold-out and off-sale dates excluded | Included, with `availabilityLevel: "SOLD_OUT"` and `fromPrice: null` — render as sold out or hide, your choice. Some events and retailers leave these dates out; see [Browse Occurrences](/guides/purchase-flow#2-browse-occurrences) |

Each occurrence also carries `availabilityLevel`, as legacy did: `LOW` (tickets are running out), `SOLD_OUT`, or `null` (no signal — display the occurrence normally). New values may be added over time; treat any unrecognized value as no signal.

### Discount Information

Legacy only flagged a discount, with `discount` and `offer` set to `true` or `false`. v3 says how big it is:

- `maxDiscountPercentage` — Best available % discount (e.g., `29` = "Up to 29% off")
- `maxDiscountAmount` — Best available amount discount (e.g., `{ amount: 5000, currency: "USD" }` = "Save up to $50")

These fields are optional — omitted when no discount exists. `maxDiscountPercentage` is also omitted on events that don't show savings as a percentage.

## Response Shape

### Legacy (v4)

```json
{
  "request": { ... },
  "response": [
    {
      "datetime": "2026-03-15T19:30:00-0400",
      "largestLumpOfTickets": 8,
      "minPrice": 8500,
      "currency": "USD",
      "discount": true,
      "offer": true,
      "availabilityLevel": null
    }
  ],
  "context": null
}
```

### New (v3)

```json
{
  "data": [
    {
      "id": "456",
      "admissionType": "TIMED",
      "startsAt": "2026-03-15T19:30:00-04:00",
      "endsAt": "2026-03-15T22:30:00-04:00",
      "availabilityLevel": null,
      "fromPrice": { "amount": 8500, "currency": "USD" },
      "maxDiscountPercentage": 29,
      "orderLimit": { "min": 1, "max": 8 },
      "venue": {
        "id": "UmljaGFyZCBSb2RnZXJzIFRoZWF0cmU=",
        "name": "Richard Rodgers Theatre",
        "address": {
          "line1": "226 W 46th St", "city": "New York",
          "state": "NY", "postalCode": "10036", "country": "US"
        },
        "timezone": "America/New_York"
      }
    },
    {
      "id": "457",
      "admissionType": "TIMED",
      "startsAt": "2026-03-16T19:30:00-04:00",
      "endsAt": null,
      "availabilityLevel": "SOLD_OUT",
      "fromPrice": null,
      "orderLimit": { "min": 1, "max": 8 },
      "venue": {
        "id": "UmljaGFyZCBSb2RnZXJzIFRoZWF0cmU=",
        "name": "Richard Rodgers Theatre",
        "address": {
          "line1": "226 W 46th St", "city": "New York",
          "state": "NY", "postalCode": "10036", "country": "US"
        },
        "timezone": "America/New_York"
      }
    }
  ],
  "filters": [],
  "pagination": { "limit": 50, "offset": 0, "total": 2 }
}
```

Key differences:
- The list sits in `data`, not `response`, and comes one page at a time
- Each occurrence has an `id` you'll need for subsequent calls (inventory, cart)
- Each occurrence names its `venue`
- `startsAt` replaces `datetime`
- `endsAt` is nullable (some events have no fixed end time)
- A date with nothing on sale comes back with `fromPrice: null` instead of being left out
- `availabilityLevel` says why nothing is buyable (`SOLD_OUT`) or that stock is running out (`LOW`)
- `orderLimit` gives the `min` and `max` ticket quantity one order may contain. It is the event's order limit, repeated on each occurrence so you can bound the quantity selector without loading the event

## Migration Checklist

1. Send `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform` on every call
2. Replace `productId` with `eventId`. Most are the same number; for some older products ask your TTG contact for the v3 id
3. Update the endpoint URL from v4 path to `GET /api/v3/events/{eventId}/occurrences`
4. Move `quantity` from URL path to `?quantity=` query parameter (now optional)
5. Move the date range from the URL path to `?startDate=` and `?endDate=` (now optional), and change `YYYYMMDD` to `YYYY-MM-DD`
6. Read the price from `fromPrice.amount` and `fromPrice.currency` instead of `minPrice` and `currency`
7. Store the occurrence `id` — you need it for inventory browsing and cart creation
8. Decide how to present unavailable occurrences — any date with nothing to buy comes back with `availabilityLevel: "SOLD_OUT"` and `fromPrice: null`, unless the event or your retailer leaves such dates out (see [Browse Occurrences](/guides/purchase-flow#2-browse-occurrences)). To match the legacy behavior (which excluded every date with nothing to buy), filter out occurrences where `fromPrice` is null
9. Page with `limit` / `offset` until `offset >= pagination.total`
10. Optionally display discount badges using `maxDiscountPercentage` or `maxDiscountAmount`

## Next Steps

After migrating the browse step, continue with the full [Purchase Flow](/guides/purchase-flow):

1. **Browse Inventory** — `GET /api/v3/events/{eventId}/occurrences/{occurrenceId}/inventory-items`
2. **Create Cart** — `POST /api/v3/carts`
3. **Checkout** — `POST /api/v3/orders`