TixTab APIv1 Your API keys

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.

Base URL
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

  1. 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.
  2. Call the API from your server with the key in the Authorization header:
    curl https://api.tixtab.com/v1/events?from_date=2026-10-06 \
      -H "Authorization: Bearer $TIXTAB_API_KEY"
  3. Page through results with next_cursor, and send an Idempotency-Key with every POST.

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 Origin header) is refused, so never put a key in a page, an app you ship, a shared sheet or a chat.
Box on the keyOpens
Events & inventoryGET /events, GET /tickets
Import stockGET and POST /purchase-orders (Edit to add)
Listing & unlistingPOST /listing-runs (Edit)
RepricingLets a listing run change the price of seats that are already live
Sales & refundsGET /sales
DeliveryAdds the buyer's email, name and phone to sales
Dashboard & performanceGET /summary

A missing box is a 403 naming it.

Conventions

TopicRule
FormatJSON in UTF-8. Times are RFC 3339 in UTC. Ids are integers.
MoneyDecimal 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.
ChangesEvery list takes updated_since= (RFC 3339) to fetch only what changed. See Keeping in sync.
WritesEvery 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.

StatusMeaning
400Something in the request is missing or wrong.
401No key, a wrong key, or one that was revoked or has expired.
402The workspace's subscription doesn't include listing.
403The key lacks the box, the address isn't on its allowlist, or the API isn't switched on.
404Nothing with that id in your workspace.
409The first request with this Idempotency-Key is still running.
422This Idempotency-Key was already used for a different request.
429Rate 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.

WhatLimitCounted per
Any request20 a secondKey
POST /listing-runs1 a minute (dry runs are not counted)Workspace, across all its keys
Requests with a wrong or unknown key30 a minuteIP address

Every answer tells you where you stand in the current window:

Header
RateLimit-LimitRequests allowed in the window (20).
RateLimit-RemainingRequests left in it.
RateLimit-ResetSeconds 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=200 and updated_since instead 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-Remaining reaches 0, pause until RateLimit-Reset.
  • On a 429 or a 5xx, 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_idRequired. From GET /events.
platformRequired. Where you bought (e.g. ticketmaster).
purchase_dateRequired. YYYY-MM-DD.
external_order_idThe seller's order number. A purchase with the same platform and order number is not added twice.
payment_method, notesOptional 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.

The event must be linked to TixTab once, in the app (open the event and list it, or link it on the listing screen). After that every run goes through the API.

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_idRequired.
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_typeHow buyers may split a group: no_singles (default: never leave one seat), pairs (in twos), any. split_quantity with pairs.
ticket_typemobile (default), eticket or paper.
missing_onlyOnly put up seats that aren't on sale yet; change no price.
publishfalse creates the listing on TixTab without putting it on sale (a draft).
dry_runChecks 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=….