API Conventions
Patterns and standards that apply across all TTG API v3 endpoints. Read this first — individual endpoint docs assume familiarity with these conventions.
Response Envelope
All responses use a consistent envelope:
Success:
Success (list):
Error:
The top-level key is always data or error, never both. Parse the HTTP status code first (2xx vs 4xx/5xx), then read the body.
Money Format
All monetary values use integer minor units with an ISO 4217 currency code. No floating-point, no strings.
The number of decimal places depends on the currency:
Use your platform’s i18n library to determine the scale from the currency code — don’t hardcode / 100.
Client examples:
- JavaScript:
new Intl.NumberFormat(locale, { style: "currency", currency }).format(amount / 10 ** scale) - iOS:
NumberFormatterwithcurrencyCodeset - Android:
Currency.getInstance(currency).defaultFractionDigitsfor scale
Currency
Every price can be shown and charged in a local currency. The default is the event’s base currency.
The rule: reads take a currency query parameter (display); writes take a currency body field (state).
Browsing. On occurrences and inventory-items, send ?currency= (ISO 4217) to see prices in that currency; omit it for the base currency. This affects display only. Events carry no prices, so GET /api/v3/events takes no currency.
A cart owns its currency. A cart is created in a currency — base by default, or the body’s currency when supported. The cart’s current currency is total.currency on the response; options.currencies lists the currencies it can be priced in.
- Quote:
GET /api/v3/carts/{cartId}?currency=EURreturns the cart priced in euros, without changing the cart. Read it again without the parameter and you get the cart’s own currency back. - Switch:
PATCH /api/v3/carts/{cartId}with{ "currency": "EUR" }changes the currency the cart is priced and settled in. Later reads come back in euros with no parameter. Send the event’s base currency to go back to base prices.
Prices are always current. Every amount is worked out from the exchange rate at the time you read the cart, so a total can move while the cart is open. Read the cart again just before checkout and send that amount as expectedTotal.
Orders confirm the cart. POST /api/v3/orders settles in the cart’s currency — the order call takes no currency input, so switch currency on the cart (PATCH) beforehand. expectedTotal must equal the cart total exactly, both amount and currency.
Asking for a currency the retailer doesn’t sell the event in — or a three-letter code that isn’t a real ISO 4217 currency — returns 400 CURRENCY_NOT_SUPPORTED; a value that isn’t a three-letter code at all returns 400 INVALID_FIELD_VALUE. A currency the retailer does sell but that can’t be priced right now (no current exchange rate) returns 503 CURRENCY_TEMPORARILY_UNAVAILABLE — retry shortly. This applies wherever a currency is accepted: browsing, cart create (POST), cart quote (GET), and cart switch (PATCH). Asking for nothing prices in the cart’s own currency — the base currency unless the cart was created in or switched to another one. A requested currency is never silently swapped for base.
Example — a London show (base GBP), customer paying in euros: create the cart, PATCH it with { "currency": "EUR" }, read the cart to get the current euro total, then place the order with expectedTotal set to it — e.g. { "amount": 4899, "currency": "EUR" }.
IDs
All IDs are opaque strings. Never parse, construct, or derive meaning from an ID — treat them as tokens you receive from one endpoint and pass to another. The format may change without notice.
Stable IDs — persist across sessions and are safe to store:
- Events, Occurrences, Carts, Orders, Tickets
Ephemeral IDs — valid only for the current session; re-fetch inventory to get fresh ones:
- Inventory Items, Price Options
Pagination
All v3 list endpoints — events, occurrences, inventory-items, locations — use limit/offset pagination and return a pagination block. Page until you’ve read pagination.total results.
The response includes a pagination object:
To iterate all pages: increment offset by limit until offset >= total.
Timezones
Occurrence times (startsAt, endsAt) use the venue’s local timezone with UTC offset:
Use the venue.timezone field (IANA identifier, e.g., America/New_York) if you need an IANA timezone for calendar integration (Apple Calendar, Google Calendar). For display, the offset embedded in the timestamp is sufficient — no timezone manipulation required.
System timestamps (createdAt, expiresAt) use UTC:
Nullability
Fields are nullable only when absence carries meaning:
availabilityLevel: null— no availability signal; display the occurrence normallyfromPrice: null— occurrence is sold out or not yet on saleendsAt: null— event has no known end timelistPrice: null— no discount exists (or display is contractually prohibited)barcode: null— barcode not yet available (delayed delivery venues)totalTaxes: null— taxes not disclosed in this market
When a nullable field is null, handle it explicitly in your UI — don’t display “null” or “$0.00”.
Optional fields that carry no information are omitted entirely rather than sent as null. For example, maxDiscountPercentage is absent (not null) when no discount exists.
Forward Compatibility
The API may add new fields, enum values, or error codes at any time without a version bump. To avoid breaking:
- Ignore unknown fields — don’t fail on unexpected keys in response objects
- Handle unknown enum values — use a default/fallback case for enums like
status,_type, and errorcode - Don’t switch on error messages — use
typeandcodefor programmatic handling;messageis for display only and may change