> 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: Baskets to Carts

Guide for moving from the Encore API's `PATCH /api/v1/baskets` flow to the `POST /api/v3/carts` flow in the TTG Storefront API v3, the newer API. The two APIs number their versions separately.

> For the full v1 basket reference, see [Baskets](https://developers.todaytixgroup.com/reference/patch_app_v1_basket_upsertbasket_v1) on the legacy portal.

## Endpoint Comparison

| | Legacy (v1) | New (v3) |
|---|---|---|
| **Create / update** | `PATCH /api/v1/baskets` | `POST /api/v3/carts` |
| **Read** | `GET /api/v1/baskets/{reference}` | `GET /api/v3/carts/{cartId}` |
| **Add items** | Re-send the whole basket | `POST /api/v3/carts/{cartId}/items` (extras only, one or more per request) |
| **Change an item's quantity** | Re-send the whole basket | `PATCH /api/v3/carts/{cartId}/items/{itemId}` (extras only) |
| **Apply a discount** | `PATCH /api/v1/baskets/{reference}/applyPromotion` | `POST /api/v3/carts/{cartId}/vouchers` |
| **Remove an item** | `DELETE /api/v1/baskets/{reference}/reservations/{reservationId}` | `DELETE /api/v3/carts/{cartId}/items/{itemId}` (extras only); a new cart to change seats |
| **Empty the basket** | `PATCH /api/v1/baskets/{reference}/clear` | `DELETE /api/v3/carts/{cartId}` |
| **Auth** | None — `channelId` in the body only picks the retailer | `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform` headers |

## What Changed

### Create with `POST`, not upsert with `PATCH`

Both APIs make the ID on the server. In v1 you sent `PATCH /api/v1/baskets` without a `reference` to create a basket, then sent the returned `reference` back to change it. In v3 you `POST` the items you want and the server returns a cart `id`.

```
# Legacy — PATCH with no reference creates; channelId picks the retailer
PATCH /api/v1/baskets
{ "channelId": "...", "reservations": [ ... ] }

# New — POST items, server returns the cart
POST /api/v3/carts
{ "items": [ ... ] }
```

### Headers

Legacy baskets needed no key. `channelId` in the body only picked the retailer. Drop `checksum`; v3 has no equivalent. v3 needs the three headers in [Authentication & Headers](/guides/getting-started#authentication--headers) on every call.

### From reservations to items

Legacy grouped tickets into `reservations`, each with a `productId`, a `quantity`, and opaque `aggregateReference` item tokens that bundled the price inside them. v3 takes a flat `items` list. Each item names the occurrence (date and time), the inventory item, and the price option separately.

```json
{
  "items": [
    {
      "occurrenceId": "456",
      "inventoryItemId": "v1_eyJwcm92aWRlcklkIjoxMDAwLCJzZWF0SWQiOiJPUkNIRVNUUkEtQS0xMDEifQ",
      "priceOptionId": "eyJwcm92aWRlckNvZGUiOiJGVUxMUFJJQ0UiLCJob2xkQ29kZSI6IjE1NzM5In0",
      "quantity": 1
    }
  ]
}
```

| Legacy (v1) | New (v3) | Notes |
|---|---|---|
| `reservations[].productId` | `items[].occurrenceId` | v3 reserves against a specific occurrence, not a product |
| `aggregateReference` (price baked in) | `inventoryItemId` + `priceOptionId` | Price is chosen by option, not encoded in the token |
| `reservations[].quantity` | `items[].quantity` | Per item; assigned seats are always `1` |
| `channelId` | `X-TT-Retailer` header | Picks the retailer; must be one your key was issued for |

Get `inventoryItemId` and `priceOptionId` from the inventory step (see [Areas → Inventory Items](/migration/areas-to-inventory-items)). All items must belong to the same occurrence.

### The hold timer is explicit

Legacy baskets returned `expiredAt`, and `status` turned `expired` once the hold ran out. v3 returns `expiresAt` (UTC) on the cart — show a countdown. When it passes, the hold is released and `GET /api/v3/carts/{cartId}` returns `410 CART_EXPIRED` (see [Cart state](/guides/error-handling#cart-state)). Create a new cart to continue. To release a hold early, `DELETE /api/v3/carts/{cartId}` (idempotent, returns `204`; `404` for an unknown cart, `409 CART_ALREADY_ORDERED` for one that became an order).

### Discounts move to a voucher endpoint

Legacy applied a `coupon` inline on the basket or through `applyPromotion`. v3 takes the code as `voucherCode` when the cart is created, or through a dedicated pair:

```
POST   /api/v3/carts/{cartId}/vouchers        { "code": "SUMMER20" }
DELETE /api/v3/carts/{cartId}/vouchers/{code}
```

The discount shows up in the cart's `receiptLines[]` and `total`. Which route to use, and why an access code must go on `POST /carts`, is in [Vouchers](/guides/purchase-flow#vouchers).

### Changing seats means a new cart; changing extras does not

In v3 a cart's seats and GA are fixed once created — to change them, create a new cart and pass the old cart's id as `previousCartId`: once the new cart is created, the same request attempts to release the old hold so the customer isn't left holding two sets of inventory. The release is best-effort and never fails the create — an old cart that is unknown, expired, or already turned into an order is left alone, and the old hold then simply times out on its own.

*Extras* are added, changed and dropped in place on a live cart, with `POST`, `PATCH` and `DELETE` on `/api/v3/carts/{cartId}/items` — see [Add Extras](/guides/purchase-flow#5-add-extras-optional).

### Money format

```json
// Legacy basket money
{ "value": 8500, "currency": "USD", "decimalPlaces": 2 }

// New cart total
{ "amount": 8500, "currency": "USD" }
```

Both are minor units; v3 drops `decimalPlaces` — derive the scale from the currency. A cart owns its currency: it's set at creation (base, or the body's `currency` when supported) and switched with `PATCH /api/v3/carts/{cartId}` `{ "currency": "EUR" }`. See [Currency](/guides/api-conventions#currency).

### Totals come from the server

Don't compute the total yourself. Read `total` and render `receiptLines[]` in order — each line has a `sign` (`positive` = charge, `negative` = credit). You'll pass `total` straight through as `expectedTotal` at checkout.

## Response Shape

### New (v3)

```json
{
  "data": {
    "id": "6aac14d3-5826-4da8-98b6-9e68c28629d6",
    "expiresAt": "2026-03-15T19:40:00Z",
    "total": { "amount": 18500, "currency": "USD" },
    "items": [ ... ],
    "receiptLines": [ ... ],
    "options": {
      "currencies": ["USD", "EUR"],
      "extras": [ ... ],
      "paymentMethods": [ { "method": "INVOICE" } ]
    }
  }
}
```

Key differences:
- `id` is the server-generated cart ID — stable. Store it on your server only: it reads the order (see [Keep the cart ID](/guides/purchase-flow#keep-the-cart-id))
- `expiresAt` is the hold deadline in UTC — drive a countdown from it
- `options.extras` lists add-ons you can attach; it is an empty list when the event offers none
- `options.currencies` lists the currencies the cart can be priced in
- `options.paymentMethods` lists the methods the cart can be paid with; pick one at checkout (see [Paying](/guides/purchase-flow#paying))

## Migration Checklist

1. Replace `PATCH /api/v1/baskets` with `POST /api/v3/carts`; let the server return the `cartId`
2. Drop `channelId`; send `X-TT-API-Key`, `X-TT-Retailer` and `X-TT-Platform` on every call
3. Convert `reservations` to a flat `items` list keyed by `occurrenceId`, `inventoryItemId`, and `priceOptionId`
4. Create the cart with items from one occurrence; an extra with its own schedule can name its own (see [Add Extras](/guides/purchase-flow#5-add-extras-optional))
5. Read `expiresAt` instead of `expiredAt` and show a countdown; handle `410 CART_EXPIRED` by creating a new cart
6. Move coupons to voucher codes on `POST /api/v3/carts/{cartId}/vouchers`
7. Replace single-reservation removal with "create a new cart with `previousCartId`", which makes a best-effort release of the old hold in the same request; use `DELETE` to release a hold without creating a new cart
8. Read `total` and `receiptLines[]` from the server — don't recompute; drop `decimalPlaces`

## Next Steps

With a cart held, continue the [Purchase Flow](/guides/purchase-flow):

1. **Add Extras (optional)** — `POST /api/v3/carts/{cartId}/items`
2. **Checkout** — `POST /api/v3/orders` (see [Checkout → Orders](/migration/checkout-to-orders))