> 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: Search to Events

Guide for moving from the Encore API's `GET /api/v4/search` endpoint to `GET /api/v3/events` in the TTG Storefront API v3, the newer API. The two APIs number their versions separately.

> For the full v4 search reference, see [Search](https://developers.todaytixgroup.com/reference/get_api-v4-search) on the legacy portal.

## Endpoint Comparison

| | Legacy (v4) | New (v3) |
|---|---|---|
| **Method** | `GET` | `GET` |
| **Path** | `/api/v4/search` | `/api/v3/events` |
| **Auth** | None — the optional `affiliateId` header only picks the retailer | `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform` headers |
| **Scope** | Free-text query across all products | Events the retailer sells in one market, with an optional free-text `query` |
| **Pagination** | Not built in | `limit` / `offset` with a `pagination` block |

## What Changed

### From a global search to a retailer catalog you can search

The v4 endpoint took a `query` string and searched across every product. The v3 endpoint returns the events your retailer is set up to sell, scoped to one market, and takes an optional `query` to narrow that list.

`locationId` is required and picks the market (e.g. New York, London). Get the IDs from `GET /api/v3/locations`.

`query` is optional. It matches against event name, venue, category, cast and search keywords, up to 50 characters. Omit it, or send it blank, to list the whole catalog for the market.

```
# Legacy — text search
GET /api/v4/search?query=hamilton

# New — the retailer's events in one market
GET /api/v3/events?locationId=1

# New — the same list, narrowed by a search term
GET /api/v3/events?locationId=1&query=hamilton
```

### Authentication

Legacy search needed no key; the optional `affiliateId` header only picked the retailer. v3 needs the three headers in [Authentication & Headers](/guides/getting-started#authentication--headers) on every call. The events you get back are the ones your retailer can sell.

### Pagination

v3 lists come one page at a time, with a `pagination` block that says when to stop. See [Pagination](/guides/api-conventions#pagination).

### Terminology

| Legacy (v4) | New (v3) | Notes |
|---|---|---|
| Product | Event | Same concept — a show or experience |
| `venue.id` | `location` | Not a rename — `location` is the **market** (e.g. New York), not the venue. Venue detail comes back per-occurrence, not on the event. |
| `type` | `productType` | Upper-case enum values: `ATTRACTION`, `BUS_TOUR`, `GIFT_CARD`, `SHOW`, `STREAMING` |
| `bookingStarts` / `bookingEnds` | `startDate` / `endDate` | The run's date range (`YYYY-MM-DD`) |
| `onSale` | (implicit) | Only events the retailer can sell are listed; `fromPrice: null` means nothing is on sale right now |

## Response Shape

### Legacy (v4)

```json
{
  "id": 123,
  "name": "Hamilton",
  "type": "show",
  "venue": { "id": "789" },
  "onSale": "true",
  "bookingStarts": "2026-03-01T00:00:00Z",
  "bookingEnds": "2026-12-31T00:00:00Z"
}
```

### New (v3)

```json
{
  "data": [
    {
      "id": "123",
      "name": "Hamilton",
      "productType": "SHOW",
      "slug": "hamilton-broadway",
      "fromPrice": { "amount": 8900, "currency": "USD" },
      "maxDiscountPercentage": 25,
      "orderLimit": { "min": 1, "max": 8 },
      "startDate": "2026-03-01",
      "endDate": "2026-12-31",
      "location": { "id": "1", "name": "New York", "country": "US", "slug": "nyc" }
    }
  ],
  "pagination": { "limit": 50, "offset": 0, "total": 1 }
}
```

Key differences:
- `id` is an opaque string — pass it to `GET /api/v3/events/{eventId}/occurrences`, never parse it (see [IDs](/guides/api-conventions#ids))
- `startDate` and `endDate` are both nullable — an open-ended run returns `null` for either. Handle both explicitly.
- The market is a `location` object with `id`, `name`, `country`, and `slug`, not a bare `venue.id`
- `slug` is the event's URL path segment on the TodayTix website; it can be `null` for events without one
- `name` is the event's full title — use it on the product card
- `fromPrice` is the lowest regular ticket price on sale across the event's occurrences (its dates and times), as a `Money` object in the event's base currency (see [Currency](/guides/api-conventions#currency)). It is `null` when no regular tickets are on sale (sold out, off sale, or not yet on sale).
- `maxDiscountPercentage` is the best discount on sale as a whole number (`25` means "Save 25%"). It is omitted, not `null`, when there is no discount or the event does not present savings as a percentage.
- `orderLimit` gives the `min` and `max` ticket quantity a single order may contain. It is set per event and applies to every occurrence; each occurrence also carries the same `orderLimit`.

## Migration Checklist

1. Send `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform` on every call; drop `affiliateId`
2. Look up the market you sell with `GET /api/v3/locations`, then pass it as `?locationId=`
3. Keep passing your search term as `query`, now alongside `locationId`
4. Treat `id` as an opaque string — store it, don't parse it
5. Read `startDate` / `endDate` instead of `bookingStarts` / `bookingEnds`; both can be `null`
6. Render product cards from `name`, `fromPrice` and `maxDiscountPercentage`, and bound the quantity selector with `orderLimit`
7. Page with `limit` / `offset` until `offset >= pagination.total`

## Next Steps

After listing events, continue with the full [Purchase Flow](/guides/purchase-flow):

1. **Browse Occurrences** — `GET /api/v3/events/{eventId}/occurrences` (see [Availability → Occurrences](/migration/availability-to-occurrences))
2. **Browse Inventory** — `GET /api/v3/events/{eventId}/occurrences/{occurrenceId}/inventory-items`
3. **Create Cart** — `POST /api/v3/carts`
4. **Checkout** — `POST /api/v3/orders`