Appearance
Best practices
A one-page checklist for a fast, complete and fair-use integration. Each point links to the page with the details.
Offer the whole category, not one ticket
A category (a section of the venue) is shared by all suppliers. One event usually has several tickets in the same category, each from a different supplier, with its own price and stock. If you link a product on your side to a single ticket_id, you only see that one supplier. When it sells out or changes price, your product shows "sold out" while the same seats are still available from other suppliers.
Do this instead:
Map your product to
event_id+category_id+sub_category, never to aticket_id.Fetch all available tickets of that combination, from all suppliers:
/v1/tickets?event_id=<event_id>&category_id=<category_id>&ticket_status=available&stock=gt:0Pick the ticket to sell at checkout time, for example the cheapest one with enough
stockfor the requested quantity (check the ticket flags too).Make the reservation with the
ticket_idyou picked.
Only the ticket_id is supplier-specific. Do not store it as a long-lived reference: new tickets for the same category appear all the time.
See Find events & tickets for how to group tickets.
Fetch many items in one request with in
Use the in operator instead of one request per item. This is faster for you and keeps you far away from the rate limit.
/v1/tickets?event_id=in:[<event_id_1>,<event_id_2>,<event_id_3>]&ticket_status=available&stock=gt:0
/v1/categories?category_id=in:[<category_id_1>,<category_id_2>]
/v1/events?sport_type=in:[soccer,formula1]&date_stop=ge:2026-10-21Rules:
- Separate values with a comma. Spaces around values are ignored.
- One filter value can be at most 2000 characters. With our identifiers that is about 50 IDs per
inlist. Split longer lists over several requests. - The full URL must stay under 8 KB.
not_inis available too, see Filtering & sorting.
Avoid the "N+1" pattern, for example one /v1/categories/{id} call per ticket. Fetch the categories of the whole event with /v1/categories?event_id=<event_id> in one call.
Filter as much as you can on our side
- Always add a date filter, for example
date_stop=ge:2026-10-21on events and tournaments. Otherwise you also get historical data. - On tickets, filter on
ticket_status=availableandstock=gt:0unless you really need the unavailable ones. - Only request the sports, tournaments or events you sell.
Use pagination
List endpoints return 50 items per page by default. Increase page_size to reduce the number of requests.
Do not build the next page URL yourself. The pagination.next_page field holds the complete URL of the next page, with all your filters, page_size and page already in it. Call that URL until next_page is empty (""):
json
{
"pagination": {
"total_size": 120,
"page_size": 50,
"page_number": 1,
"next_page": "https://api.xs2event.com/v1/tickets?event_id=<event_id>&ticket_status=available&page_size=50&page=2",
"previous_page": ""
}
}Cache what is stable, never cache ticket availability
Our responses do not carry HTTP cache headers (Cache-Control, ETag), so cache on your side.
| Resource | Cache locally | Refresh |
|---|---|---|
| Sports, tournaments, teams, venues | Yes | Monthly |
| Categories | Yes | Weekly |
| Events | Yes | Daily |
| Tickets (price, stock, status) | No | Fetch when needed |
Always fetch the current tickets right before you show prices or availability in checkout, and before you make a reservation.
For a daily sync, fetch only what changed with the
updatedfilter. Use the date of your last sync inYYYY-MM-DDformat (a time is not supported):/v1/events?updated=ge:<last_sync_date> /v1/categories?updated=ge:<last_sync_date>
More details are on Rate limits & caching.
Use webhooks instead of polling orders
Subscribe to webhooks to be notified of order and fulfilment changes. If you poll, filter on the status you need (for example logistic_status=completed, see E-tickets & e-invoices) instead of fetching all orders again.
Handle errors and limits
- A 429 means you exceed the rate limit: wait and retry with an increasing delay. Do not retry in a tight loop.
- A 400 means the request does not match the schema: an unknown query parameter, a wrong type or enum value, or a missing body field. The
errorsobject in the response names each field. Fix the request, do not retry it unchanged. - A 422 means the request is well-formed but cannot be processed. This is either a filter value we cannot parse (for example a value longer than 2000 characters, or a date that is not
YYYY-MM-DD), or a business rule, such asout-of-stock,pairs-only,invalid-guest-dataorreservation-expired. Business rule errors carry atypefield: use it to decide what to do, for example pick another ticket onout-of-stock. See Errors & validation. - Make the reservation before the booking, and handle an expired reservation by making a new one. See Booking flow.
Next steps
- Find events & tickets: the full catalogue flow
- Filtering & sorting: all operators
- Rate limits & caching: limits and update intervals