Purchase Flow
Five-step flow to browse, reserve, and purchase tickets via the TTG API, plus an optional step to add extras to the cart.
This page documents direct v3 API integration. For TTG-hosted checkout, see Whitelabel Checkout. For agent/reseller flows on the v1/v4 stack, see Powered by TodayTix Agent APIs.
1. List Events
Returns a paginated list of events the authenticated retailer has access to. The locationId query parameter is required — it filters events by market (e.g., New York, London). Add query to narrow the list to events matching a search term.
Use limit and offset for pagination. Each event includes id, name, startDate, endDate, and location.
2. Browse Occurrences
Returns dates with pricing summaries, sold-out dates included. Use availabilityLevel to drive the calendar UI: SOLD_OUT (nothing to buy on this date — fromPrice is also null), LOW (tickets are running out), or null (no signal — display normally). An occurrence with a null fromPrice is never buyable.
The discount fields (maxDiscountPercentage or maxDiscountAmount) represent the best available discount across all price points. They may come from a different price point than fromPrice.
3. Browse Inventory
Returns purchasable items. Use inventoryVariant._type to determine the type:
For full details on each type, attributes, and pricing, see Inventory Types.
Pricing: sellPrice is the all-in price customers pay (fees included). listPrice appears only when a discount exists — display as strikethrough.
Example response (trimmed):
4. Create Cart
Reserves inventory and starts a hold timer. All items must be from the same occurrence. The cart is created in a currency — the event’s base currency unless the body names a currency — and carries it on the response. To pay in another currency, PATCH the cart before checkout (see Currency).
The response includes expiresAt (UTC) — display a countdown. When the cart expires, inventory is released and GET /api/v3/carts/{cartId} returns 410 CART_EXPIRED. Create a new cart to continue.
To release held inventory early: DELETE /api/v3/carts/{cartId} (idempotent, returns 204).
5. Add Extras (optional)
Some events offer extras — add-ons such as merchandise, drinks, or parking. The cart response lists them under options.extras, each in the same inventory-item shape as seats and GA (see Inventory Types). The key is absent when the event offers none.
Add them to the cart in one request, each with its inventory item ID, one of its price option IDs and a quantity:
Returns 201 with the updated cart. Each extra appears in items[] with an Extra inventory variant, and in the cart total and receiptLines[]. The request is held as a unit: either every extra is added, or nothing changes and one error comes back. When one item is at fault (a malformed field, a price option that is not an extra’s, an occurrence the cart cannot take), the error’s param names it, for example items[1].priceOptionId. A refusal of the summed quantity — over a cap or the remaining availability — or by the venue’s system names no item. Two items naming the same extra and price option, or an item naming an extra already on the cart, merge into one line with the quantities summed.
Changing your mind
An extra already on the cart is addressed by the id of the line it came back as:
Both return 200 with the cart repriced. items[].id names the line, not the inventory — the same extra at two price options is two lines, each addressable on its own, so changing one leaves the other alone.
quantityis the number to end up holding, up or down from the number held now. It is at least1: to drop the item, sendDELETErather thanPATCHwith0.- Raising a quantity is subject to the same limits as adding: above
maxQuantityPerOrderreturns400, and a quantity that can no longer be held returns409 INVENTORY_EXHAUSTED. DELETEis idempotent: anitemIdthe cart does not hold returns the current cart.PATCHon one returns404withdetails.resourceTypeofcartItem.- Dropping the last extra leaves the customer’s tickets and hold expiry untouched.
Rules:
- Only extras can be added to, changed on, or removed from an existing cart. An
itemIdnaming a seat or GA item returns400. To change seats, create a new cart; to hold nothing at all,DELETE /api/v3/carts/{cartId}. occurrenceIdis the occurrence the item is for. Most extras run at the cart’s performance — send the cart’s occurrence. An extra with its own schedule (a guided tour, parking) runs at its own times — send the occurrence the customer picked for it. Any other occurrence returns400.- Quantity is capped by the lowest of
quantityAvailable, the item’smaxQuantityPerOrder, and the price option’smaxQuantityPerOrder. Asking for more returns400. When several items in one request name the same extra, the cap applies to their summed quantity. - If the extras can no longer be held by the time the request reaches the venue’s system, the request returns
409 INVENTORY_EXHAUSTED. If the cart has expired, it returns410 CART_EXPIRED— create a new cart.
6. Checkout
expectedTotal is the cart’s total, echoed back exactly — same amount, same currency. GET /api/v3/carts/{cartId} returns:
Send that object straight through as expectedTotal (as above) — don’t recompute it. A value that doesn’t match returns 400 with code EXPECTED_TOTAL_MISMATCH.
The order settles in the cart’s currency; the POST /orders call takes no currency input, so to pay in another currency PATCH the cart first (see Currency).
If the customer is checking out on a device you can see — a browser or a mobile app — describe it with the optional X-TT-Device-Data header, as JSON:
Sending it scores card payments for fraud on more signal, so fewer genuine customers are declined.
Integrating server-to-server? If there’s no end-user device in the request path, leave the header off. It’s optional and the order places normally without it.
On success, tickets are in items[].tickets[] — each with a barcode (value, format, optional imageUrl).
Receipt Rendering
Both cart and order responses include receiptLines[]. Render them in order — do not compute totals client-side.
Each line has sign: positive = charge, negative = credit/discount. All total.amount values are positive integers.
Common Errors
For the full error format and retry strategy, see Error Handling.