TixTab API
Read your stock and sales, add purchases from your buying tools and list on TixTab, from your own software. Everything the API does is done the same way the TixTab app does it.
https://api.tixtab.com/v1
The API is switched on per workspace. If Settings → API keys says it isn't on yet, ask TixTab support.
Quickstart
- In TixTab, open Settings → API keys and choose New key. Tick only what your software needs. The key is shown once: store it where your software reads its secrets.
- Call the API from your server with the key in the
Authorizationheader:curl https://api.tixtab.com/v1/events?from_date=2026-10-06 \ -H "Authorization: Bearer $TIXTAB_API_KEY" - Page through results with
next_cursor, and send anIdempotency-Keywith everyPOST.
Authentication
Every request carries a workspace API key, tt_live_…, as a bearer token. A key belongs to one workspace and only ever reads or changes that workspace.
- A key does what its boxes allow, and never more than the person who made it can do right now. If that person loses a box, or their login is removed, the key loses it too.
- Keys expire (30 days to 1 year) and can be limited to IP addresses. Revoking one takes effect within a minute.
- Making or revoking a key emails its maker and the workspace owner.
- The API is for servers. A request sent from a web page (with an
Originheader) is refused, so never put a key in a page, an app you ship, a shared sheet or a chat.
| Box on the key | Opens |
|---|---|
| Events & inventory | GET /events, GET /tickets |
| Import stock | GET and POST /purchase-orders (Edit to add) |
| Listing & unlisting | POST /listing-runs (Edit) |
| Repricing | Lets a listing run change the price of seats that are already live |
| Sales & refunds | GET /sales |
| Delivery | Adds the buyer's email, name and phone to sales |
| Dashboard & performance | GET /summary |
A missing box is a 403 naming it.
Conventions
| Topic | Rule |
|---|---|
| Format | JSON in UTF-8. Times are RFC 3339 in UTC. Ids are integers. |
| Money | Decimal strings ("125.00") in your workspace's currency, named by the response's currency field. |
| Lists | ?limit= (default 100, max 200) and ?cursor=. A list answers {"data": [...], "next_cursor": "…"}, oldest id first; next_cursor is null on the last page. |
| Changes | Every list takes updated_since= (RFC 3339) to fetch only what changed. See Keeping in sync. |
| Writes | Every POST needs an Idempotency-Key header: any unique string up to 200 characters. Sending the same key and body again within 24 hours returns the first answer (with Idempotent-Replayed: true) and does nothing twice. |
Errors
An error is {"error": "…"}. When the problem is with your request (4xx), the message says what to fix. When it's ours (5xx), it carries a ref: quote that to support, because we have already been told about it.
| Status | Meaning |
|---|---|
| 400 | Something in the request is missing or wrong. |
| 401 | No key, a wrong key, or one that was revoked or has expired. |
| 402 | The workspace's subscription doesn't include listing. |
| 403 | The key lacks the box, the address isn't on its allowlist, or the API isn't switched on. |
| 404 | Nothing with that id in your workspace. |
| 409 | The first request with this Idempotency-Key is still running. |
| 422 | This Idempotency-Key was already used for a different request. |
| 429 | Rate limit. Wait the Retry-After seconds. |
Rate limits
Limits are counted across all of TixTab's servers, so spreading requests over connections doesn't raise them.
| What | Limit | Counted per |
|---|---|---|
| Any request | 20 a second | Key |
POST /listing-runs | 1 a minute (dry runs are not counted) | Workspace, across all its keys |
| Requests with a wrong or unknown key | 30 a minute | IP address |
Every answer tells you where you stand in the current window:
| Header | |
|---|---|
RateLimit-Limit | Requests allowed in the window (20). |
RateLimit-Remaining | Requests left in it. |
RateLimit-Reset | Seconds until it starts again. |
Over a limit, the answer is 429 with a Retry-After header in seconds. Nothing was done, so wait that long and send the same request again (with the same Idempotency-Key for a POST).
HTTP/1.1 429 Too Many Requests
Retry-After: 1
RateLimit-Limit: 20
RateLimit-Remaining: 0
RateLimit-Reset: 1
{"error": "Rate limit: at most 20 requests a second per key."}
Staying under them:
- Read with
limit=200andupdated_sinceinstead of re-reading everything (Keeping in sync). - Put every seat you list for an event into one listing run, as several
groups, rather than one run per group. - When
RateLimit-Remainingreaches 0, pause untilRateLimit-Reset. - On a
429or a5xx, retry after a pause that doubles each time (1 s, 2 s, 4 s…), up to about a minute.
Listing runs are slow on purpose: each one reaches TixTab and every channel behind it, and allow up to a few minutes before giving up. Use a timeout of at least 10 minutes on them.
Events
GET/events Events & inventory
Your events. from_date=2026-10-06 leaves out earlier ones.
{
"data": [
{ "id": 1147, "name": "CELINE DION", "date": "2027-05-19T00:00:00Z",
"venue": "PLENITUDE ARENA, NANTERRE", "city": "", "category": "concert",
"created_at": "2026-09-23T21:13:30Z", "updated_at": "2026-09-30T07:50:45Z" }
],
"next_cursor": null
}
GET/events/{id} Events & inventory
One event.
Events are created in the TixTab app. To add stock to an event, find its id here.
Tickets
GET/tickets Events & inventory
One row per seat. Filters: event_id, purchase_order_id, status (available, listed, sold, refunded), updated_since.
{
"data": [
{ "id": 26762, "event_id": 1147, "purchase_order_id": 4743,
"section": "M PA", "row": "20", "seat": "11", "status": "listed",
"cost": "335.41", "listed_price": "2900.00", "restrictions": [],
"listings": { "tixtab": true, "viagogo": false },
"created_at": "2026-06-04T06:41:53Z", "updated_at": "2026-10-06T03:48:13Z" }
],
"next_cursor": "MjY3NjI",
"currency": "USD"
}
listings says where the seat is on sale. listed_price is the last price set on TixTab. A sold seat reads sold with no listings; its delivery is on the sale.
GET/tickets/{id} Events & inventory
One seat: {"ticket": {…}, "currency": "USD"}.
Purchase orders
A purchase order is one purchase you made, with the seats it brought in. Adding one is the same as Add tickets on an event in the app: the seats arrive available, nothing is listed and no sale is recorded.
POST/purchase-orders Import stock · Edit
curl -X POST https://api.tixtab.com/v1/purchase-orders \
-H "Authorization: Bearer $TIXTAB_API_KEY" \
-H "Idempotency-Key: order-TM-123456789" \
-H "Content-Type: application/json" \
-d '{
"event_id": 1147,
"platform": "ticketmaster",
"external_order_id": "123456789",
"purchase_date": "2026-10-06",
"tickets": [
{ "section": "M PA", "row": "20", "seat_from": "11", "seat_to": "12",
"cost_price": "335.41", "account_email": "buyer1@example.com" }
]
}'
| Field | |
|---|---|
event_id | Required. From GET /events. |
platform | Required. Where you bought (e.g. ticketmaster). |
purchase_date | Required. YYYY-MM-DD. |
external_order_id | The seller's order number. A purchase with the same platform and order number is not added twice. |
payment_method, notes | Optional text. |
tickets[] | One entry per run of seats. section, row, seat_from required; seat_to for a range; for general admission leave out seat_to and give quantity. cost_price per seat; currency if it isn't your workspace's (it is converted). account_email: the account the seats sit in. |
Answer 201: {"purchase_order_id": 4790, "event_id": 1147, "created": 2, "skipped": 0, "errors": []}. A seat that already exists is skipped and said in errors.
GET/purchase-orders · /purchase-orders/{id} Import stock
Your purchases: id, platform, external_order_id, total_cost, purchase_date, notes. Their seats: GET /tickets?purchase_order_id=….
Listing runs
A listing run puts seats on sale on TixTab, or changes the price of seats already there. It is the app's List button: one run for one event, on TixTab and every channel behind it that is set up for the event, with the same checks against selling a seat twice.
POST/listing-runs Listing & unlisting · Edit
curl -X POST https://api.tixtab.com/v1/listing-runs \
-H "Authorization: Bearer $TIXTAB_API_KEY" \
-H "Idempotency-Key: list-1147-2026-10-06T08" \
-H "Content-Type: application/json" \
-d '{
"event_id": 1147,
"groups": [
{ "ticket_ids": [26762, 26763], "price": "2900.00" }
],
"split_type": "pairs",
"dry_run": true
}'
| Field | |
|---|---|
event_id | Required. |
groups[] | Required. Seats listed together, side by side, at one price: what you take home per seat, in your workspace's currency. max_display optionally caps how many show at once. |
split_type | How buyers may split a group: no_singles (default: never leave one seat), pairs (in twos), any. split_quantity with pairs. |
ticket_type | mobile (default), eticket or paper. |
missing_only | Only put up seats that aren't on sale yet; change no price. |
publish | false creates the listing on TixTab without putting it on sale (a draft). |
dry_run | Checks everything and changes nothing. Free of the one-a-minute limit. |
Seats already on sale take the new price only if the key holds Repricing; otherwise they keep theirs and the run says so.
{
"dry_run": false,
"event": "CELINE DION · Wed 19 May 2027",
"created": 1,
"updated": 0,
"listed": 2,
"issues": []
}
issues lists any group that didn't go up, with its ticket_ids and the reason. One with an action needs a step in the app first: map_category (choose the section's category) or link_event (link the event).
Group every seat you want to list for an event into one run: runs are limited to one a minute per workspace.
Sales
GET/sales Sales & refunds
One row per seat sold. Filters: event_id, from and to (sale date, YYYY-MM-DD or RFC 3339; to is exclusive), updated_since.
{
"data": [
{ "id": 7877, "ticket_id": 26780, "event_id": 1147, "marketplace": "TixTab",
"order_id": "19C1F0AF", "sale_date": "2026-10-06T02:09:10Z",
"price": "1293.00", "fees": "38.79", "net_profit": "673.33",
"status": "", "cancelled": false, "delivered": false, "delivered_at": null,
"section": "A PA", "row": "8", "seat": "99",
"buyer": { "email": "buyer@example.com", "name": null, "phone": null },
"created_at": "2026-10-06T02:09:10Z", "updated_at": "2026-10-06T02:09:10Z" }
],
"next_cursor": null,
"currency": "USD"
}
marketplace is TixTab, Viagogo, Manual or Other. buyer is present only when the key holds the Delivery box. A cancelled sale stays in the list with cancelled: true.
GET/sales/{id} Sales & refunds
One sale: {"sale": {…}, "currency": "USD"}.
Summary
GET/summary Dashboard & performance
The dashboard's totals. period: 1d, 7d, 30d, 90d, 6m, 1y, or leave it out for all time.
{
"period": "30d", "currency": "USD",
"tickets": { "total": 812, "available": 120, "listed": 410, "sold": 270, "refunded": 12 },
"cost_basis": "251204.50", "revenue": "198340.00", "fees": "5950.20",
"expenses": "1200.00", "net_profit": "61230.75", "roi": "0.4512", "capital_locked": "180400.00"
}
Keeping in sync
To mirror your stock or sales into your own system, read everything once, then every few minutes ask only for what changed:
# first time: walk every page
GET /v1/sales?limit=200
GET /v1/sales?limit=200&cursor=… # until next_cursor is null
# afterwards: what changed since your last run (keep a minute of overlap)
GET /v1/sales?updated_since=2026-10-06T08:00:00Z
Match rows on id, and replace what you hold with what comes back.
Not in v1
- Taking listings down (use Unlist in the app).
- Changing a ticket's details, creating events or recording a sale by hand.
- Webhooks that tell your system about a sale as it happens. Until then, poll
/sales?updated_since=….
TixTab API