TicketHero

API documentation

Run your bookings from your own systems.

Read products, availability and bookings. Create, change, cancel and refund bookings. Set how many places are on sale. Hear about every event the moment it happens. Included for every venue.

Getting started

Create a key in the staff admin under API and send it with every request:

curl https://tickethero.co.uk/api/v1/products \
  -H "Authorization: Bearer th_live_..."

Requests and responses are JSON. Money is in pence. Dates are written as YYYY-MM-DD and times as HH:MM in the venue’s local time; timestamps are ISO 8601 in UTC.

Keys and access

A key belongs to one venue and can never reach another venue’s data. There are two kinds:

  • Read only keys can use every GET endpoint. Use these for reporting, your CRM and email tools.
  • Read and write keys can also create, change, cancel and refund bookings and set capacity. Only give one to a system you trust, and revoke it in the admin if it is ever exposed.

We store only a fingerprint of each key, so a key is shown once, when you create it.

Errors

An error returns a status code and a body you can show to a person:

{ "error": { "code": "full", "message": "10:30 on 2026-12-12 has no places left." } }
CodeStatusMeaning
unauthorised401The key is missing or not valid.
forbidden403The key can only read, and this call changes something.
not_found404No product, start time or booking matches.
invalid400Something in the request is wrong. The message says what.
full409That start time has no places left.
conflict409The booking is cancelled or checked in, so it cannot be changed.

Endpoints

GET/api/v1/products

List products

Your venue and what it sells: each experience with its slug, ticket types, prices by price band, extras and party rules. The slug and the ticket and extra key values are what you send when creating a booking.

GET/api/v1/availability

Read availability

Every start time with the places released, booked, being paid for and still available. Narrow it with from and to.

GET /api/v1/availability?from=2026-12-12&to=2026-12-13

{
  "sessions": [
    { "date": "2026-12-12", "time": "10:00", "priceBand": "peak",
      "released": 6, "booked": 4, "held": 1, "available": 1 }
  ]
}

PUT/api/v1/availability

Set capacityNeeds a read and write key

Set how many places are on sale for a whole date, or for one start time if you send time. The booking flow on your website updates straight away.

We never go below what is already booked or being paid for, and never above what a start time can hold (maximum). The answer shows what each start time ended up as, so check released rather than assuming your number was used.

PUT /api/v1/availability
{ "date": "2026-12-12", "time": "10:00", "released": 8 }

{
  "sessions": [
    { "date": "2026-12-12", "time": "10:00", "released": 8, "maximum": 10,
      "booked": 4, "held": 1, "available": 3, "changed": true }
  ]
}

GET/api/v1/bookings

List bookings

Confirmed bookings, newest first. Use since to fetch only bookings confirmed after a moment, and limit for up to 500 at a time (100 if you leave it out). Add status=cancelled to list cancelled bookings instead. more tells you whether there are older ones beyond the limit.

GET /api/v1/bookings?since=2026-12-01T09:00:00Z&limit=50

{ "bookings": [ {
    "reference": "TH-7K4Q2M",
    "status": "confirmed",
    "product": "Santa's Workshop",
    "date": "2026-12-12",
    "time": "10:30",
    "partySize": 4,
    "tickets": [
      { "key": "child", "label": "Child (2 to 12)", "quantity": 2,
        "unitPence": 2900, "totalPence": 5800 }
    ],
    "extras": [],
    "feePence": 150,
    "totalPence": 7550,
    "currency": "GBP",
    "customer": { "firstName": "Sam", "lastName": "Example",
      "email": "sam@example.com", "marketingConsent": true },
    "source": { "utmSource": "google", "utmMedium": "cpc" },
    "guests": [ { "name": "Alex", "age": "6", "interests": "Dinosaurs" } ],
    "confirmedAt": "2026-12-01T10:14:03.000Z",
    "checkedInAt": null,
    "changedAt": null,
    "cancelledAt": null,
    "cancelReason": null,
    "refundedPence": 0,
    "refunds": []
  } ], "more": false }

GET/api/v1/bookings/{reference}

Get a booking

One booking, confirmed or cancelled, in the shape shown above.

POST/api/v1/bookings

Create a bookingNeeds a read and write key

Make a confirmed booking, for example one taken by phone, at the gate or in another system. It takes a place exactly as a website booking does, so two requests can never take the last place.

We work out the price from your products, so you do not send one. Send an Idempotency-Key header with your own unique value: if the same key arrives again, you get the first booking back with status 200 instead of a second booking.

POST /api/v1/bookings
Idempotency-Key: phone-order-10482

{
  "product": "santas-workshop",
  "date": "2026-12-12",
  "time": "10:30",
  "quantities": { "child": 2, "adult": 2 },
  "extras": { "protection": 1 },
  "customer": { "firstName": "Sam", "lastName": "Example",
    "email": "sam@example.com" },
  "marketingConsent": false
}

Returns 201 and the booking. Its source is recorded as api.

PATCH/api/v1/bookings/{reference}

Change a bookingNeeds a read and write key

Move a booking to another date or start time, change the tickets or extras, or correct the customer’s details. Send only what is changing; everything else stays as it is. Moving frees the old place and takes the new one in a single step.

PATCH /api/v1/bookings/TH-7K4Q2M
{ "date": "2026-12-13", "time": "11:00" }

{
  "booking": { "reference": "TH-7K4Q2M", "date": "2026-12-13", "...": "..." },
  "previousTotalPence": 7550,
  "differencePence": -1000
}

The price is worked out again. differencePence is positive when the customer now owes more and negative when they are owed money. A cancelled or checked-in booking cannot be changed.

POST/api/v1/bookings/{reference}/cancel

Cancel a bookingNeeds a read and write key

Cancel a booking and put its place back on sale. Add refundPence to record a refund in the same step, or leave the body empty to cancel without one. Cancelling twice is harmless.

POST /api/v1/bookings/TH-7K4Q2M/cancel
{ "refundPence": 7550, "reason": "Illness" }

POST/api/v1/bookings/{reference}/refunds

Record a refundNeeds a read and write key

Record a full or part refund without cancelling, for example a goodwill gesture. Refunds add up, and the total can never pass what the booking cost. Returns 201 and the booking with its refunds and refundedPence.

POST /api/v1/bookings/TH-7K4Q2M/refunds
{ "amountPence": 1500, "reason": "Late start" }

Webhooks

Add an address in the staff admin under API and we will send it a message for five events: booking.confirmed, booking.changed, booking.cancelled, booking.refunded and booking.checked_in. The body carries the event, your venue and the booking in the shape shown above. Bookings made through the API send the same events as bookings made on your website.

Each request is signed. Check the TicketHero-Signature header against an HMAC of the raw body using your signing secret, and ignore anything that does not match:

import { createHmac, timingSafeEqual } from "node:crypto";

function isFromTicketHero(rawBody, header, secret) {
  const expected =
    "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  return (
    header.length === expected.length &&
    timingSafeEqual(Buffer.from(header), Buffer.from(expected))
  );
}

Answer with any 2xx status within ten seconds. A delivery is tried once; the result of the latest one is shown in the admin, and the bookings endpoint is always there to catch up from.

What to know

Need something that is not here? Tell us and we will build it with you. The roadmap shows what is coming.