API & webhooks
For developers, Cleartix offers a REST API (Studio and Pro) and webhooks (Pro) so you can connect ticketing to your own systems. Prefer talking to your events instead of coding? Connect an AI assistant over MCP — that works on every plan.
API keys
Create API keys under Settings to use the Cleartix REST API. With a key you can read your data from your own code or tools — your orders, attendees and per-event sales stats (tickets sold, check-ins and revenue, broken down by ticket type, without any attendee data) — to power dashboards, sync a CRM or build a custom report. The API can also create and manage your events, event templates, ticket types, dates, access codes, add-ons, capacity pools, gates, event emails and locations, so a booking form on your own website, an internal planning tool or an AI assistant can set up events without anyone clicking through the dashboard.
Each key gets its own scopes, so you can hand an integration exactly the access it needs — for example only the aggregated sales stats:
You can change a key's scopes at any time from the same page (Edit scopes); the change applies immediately to every integration using it, without issuing a new key.
| Scope | What it allows |
|---|---|
events:read | List and read events, ticket types, dates, the readiness check and starter kits |
events:write | Create, change, publish, cancel and delete events, and manage their ticket types, dates, access codes, add-ons, capacity pools and gates |
templates:read | List and read event templates |
templates:write | Create, change and delete templates, and save an event as a template |
locations:read | List and read locations |
locations:write | Create, change and delete locations |
emails:read | Read and preview an event's emails (confirmation, reminder, …) |
emails:write | Override an event's emails and send a one-off mailing to its attendees |
orders:read | List and read orders |
orders:write | Refund orders (whole or per ticket), cancel orders that are still awaiting payment, and book bank transfers or invoices as paid |
attendees:read | List and read attendees, search tickets and read a ticket's check-in status |
tickets:write | Resend the tickets and confirmation of an order to the buyer |
checkin:write | Check tickets in and undo check-ins |
waitlist:write | List an event's waitlist and remove entries from it |
team:write | List, invite, change and remove team members, manage invites and assign scanners to gates |
privacy:write | Export or anonymise everything you hold about a customer (GDPR access and erasure requests) |
audit:read | Read the organisation's audit log |
billing:read | Read your plan, monthly event usage and platform invoices |
stats:read | Aggregated sales stats per event |
widgets:read | List and read embeddable widgets |
widgets:write | Create, change and delete widgets |
Warning: a key with a write scope can change or delete what your team sees in the dashboard, and emails:write can send mail to your attendees. Only grant write scopes to integrations that really need them, and keep a separate read-only key for reporting.
Webhooks
Rather than polling the API, register webhooks so Cleartix notifies your endpoint when something happens, for example:
- A ticket sold
- An attendee checked in
- An order refunded
- A product low on stock
When the event occurs, Cleartix sends a request to your URL. Deliveries are signed, so you can verify each one really came from Cleartix, and they're retried if your endpoint is temporarily down — so a brief outage won't lose an event.
Webhook payloads
Every webhook is an HTTP POST whose JSON body uses the same envelope — only event and data change:
{
"id": "evt_3f9a2b7c1d4e5f60",
"event": "ticket.sold",
"created_at": "2026-06-12T14:05:00.000Z",
"organization_id": "org_abc123",
"data": {}
}
Two headers travel with every request:
X-ClearTix-Signature—sha256=followed by the HMAC-SHA256 of the raw request body, keyed with your endpoint's signing secret. Verify it before trusting a payload.X-ClearTix-Event— the event type, e.g.ticket.sold.
The data object depends on the event. Each event below shows an example data and its JSON Schema.
ticket.sold — ticket purchased and paid
Sent when tickets are issued against a paid order: an online payment, a free or POS order, guest tickets, a pass redemption, or a bank transfer, POS payment or invoice you booked as paid. totalAmount is "0" for €0 orders.
{ "orderId": "ord_9f8e7d6c", "ticketCount": 2, "totalAmount": "50.00" }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ticket.sold · data",
"type": "object",
"additionalProperties": false,
"required": ["orderId", "ticketCount", "totalAmount"],
"properties": {
"orderId": { "type": "string" },
"ticketCount": { "type": "integer", "minimum": 0 },
"totalAmount": { "type": "string", "description": "Decimal total as a string, e.g. \"50.00\" (\"0\" for free orders)" }
}
}
ticket.checked_in — attendee checked in at the event
Sent for every check-in: scanner app (also scans synced later after a connection drop), a manual check-in in the dashboard, and check-ins through the API or an AI assistant.
{ "ticketId": "tkt_1a2b3c4d", "eventId": "ev_5e6f7a8b", "checkedInAt": "2026-06-12T14:05:00.000Z" }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ticket.checked_in · data",
"type": "object",
"additionalProperties": false,
"required": ["ticketId", "eventId", "checkedInAt"],
"properties": {
"ticketId": { "type": "string" },
"eventId": { "type": "string" },
"checkedInAt": { "type": "string", "format": "date-time" }
}
}
ticket.refunded — ticket refunded
Sent for every refund made in Cleartix — a single refund from the dashboard, the API or an AI assistant, and each order refunded automatically when you cancel an event.
{ "orderId": "ord_9f8e7d6c", "refundRef": "re_4d3c2b1a", "refundAmount": 2500 }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ticket.refunded · data",
"type": "object",
"additionalProperties": false,
"required": ["orderId", "refundRef", "refundAmount"],
"properties": {
"orderId": { "type": "string" },
"refundRef": { "type": "string", "description": "Provider refund reference" },
"refundAmount": { "type": "integer", "description": "Refunded amount in cents" }
}
}
order.created — order confirmed (including free orders)
Sent when an order becomes paid, together with ticket.sold (pass purchases send only order.created). A settled invoice is also reported with status: "paid".
{ "orderId": "ord_9f8e7d6c", "kind": "tickets", "status": "paid" }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "order.created · data",
"type": "object",
"additionalProperties": false,
"required": ["orderId", "status"],
"properties": {
"orderId": { "type": "string" },
"kind": { "type": "string", "enum": ["tickets", "pass", "shop"] },
"status": { "type": "string", "enum": ["paid"] }
}
}
order.shipped — shop order shipped
Sent when you mark an order as shipped (dashboard, API or AI assistant), with the carrier and the tracking code when one was entered.
{ "orderId": "ord_9f8e7d6c", "carrier": "bpost", "trackingCode": "323212345678901234", "shippedAt": "2026-06-12T14:05:00.000Z" }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "order.shipped · data",
"type": "object",
"additionalProperties": false,
"required": ["orderId", "carrier", "trackingCode", "shippedAt"],
"properties": {
"orderId": { "type": "string" },
"carrier": { "type": "string", "enum": ["bpost", "postnl", "dhl", "dpd", "gls", "ups", "other"] },
"trackingCode": { "type": ["string", "null"] },
"shippedAt": { "type": "string", "format": "date-time" }
}
}
order.refunded — products or shipping refunded
Sent for a refund of product lines or the shipping line (shop orders and shipped extras), with every refunded line. Ticket refunds keep sending ticket.refunded.
{ "orderId": "ord_9f8e7d6c", "refundRef": "re_1a2b3c", "amountCents": 3095, "lines": [{ "orderAddOnId": "oao_1", "productId": "prod_1", "shipping": false, "quantity": 1, "amountCents": 2500, "restocked": true }, { "orderAddOnId": null, "productId": null, "shipping": true, "quantity": 1, "amountCents": 595, "restocked": false }] }
product.stock_low — a product is almost sold out
Sent once when an order takes the available stock of a physical product to 5 or fewer units (it fires again only after the stock went back above the threshold and dropped below it once more). Use it to reorder merchandise or to warn your team.
{ "productId": "prod_1", "stock": 200, "stockAvailable": 4, "threshold": 5 }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "product.stock_low · data",
"type": "object",
"additionalProperties": false,
"required": ["productId", "stock", "stockAvailable", "threshold"],
"properties": {
"productId": { "type": "string" },
"stock": { "type": "integer", "description": "Total stock configured on the product" },
"stockAvailable": { "type": "integer", "description": "Units still available after this order" },
"threshold": { "type": "integer", "const": 5 }
}
}
event.published — event published and live
{ "eventId": "ev_5e6f7a8b", "eventName": "Summer Yoga Retreat", "startAt": "2026-07-01T09:00:00.000Z" }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "event.published · data",
"type": "object",
"additionalProperties": false,
"required": ["eventId", "eventName", "startAt"],
"properties": {
"eventId": { "type": "string" },
"eventName": { "type": "string" },
"startAt": { "type": "string", "format": "date-time" }
}
}
consent.opted_in — buyer opted in to marketing
Sent when a buyer ticks the marketing opt-in at checkout (events and passes).
{ "buyerEmail": "buyer@example.com", "consentPurpose": "Event checkout for event ev_5e6f7a8b", "consentedAt": "2026-06-15T09:00:00.000Z", "eventId": "ev_5e6f7a8b" }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "consent.opted_in · data",
"type": "object",
"additionalProperties": false,
"required": ["buyerEmail", "consentPurpose", "consentedAt"],
"properties": {
"buyerEmail": { "type": "string", "format": "email" },
"consentPurpose": { "type": "string" },
"consentedAt": { "type": "string", "format": "date-time" },
"eventId": { "type": "string", "description": "Present for event checkout; omitted for pass purchases" }
}
}
consent.withdrawn — buyer withdrew marketing consent
Sent when someone unsubscribes through the link in an e-mail, and when an active opt-in is erased by a GDPR deletion request — so your mailing tool can stop mailing them too.
{ "buyerEmail": "buyer@example.com", "withdrawnAt": "2026-06-15T09:00:00.000Z" }
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "consent.withdrawn · data",
"type": "object",
"additionalProperties": false,
"required": ["buyerEmail", "withdrawnAt"],
"properties": {
"buyerEmail": { "type": "string", "format": "email" },
"withdrawnAt": { "type": "string", "format": "date-time" }
}
}
Widgets via the API
Everything you can do with events widgets in the dashboard is also available over the API, so a website integration — for example a WordPress plugin — can create and place widgets without anyone copying snippets by hand. Keys need the widgets:read scope to list widgets and widgets:write to create, change or delete them.
| Method | Endpoint | What it does |
|---|---|---|
GET | /api/v1/widgets | List your widgets (paginated) |
POST | /api/v1/widgets | Create a widget |
GET | /api/v1/widgets/:id | One widget, including its embed snippet |
PATCH | /api/v1/widgets/:id | Change name, events, view, colors, CSS or language |
DELETE | /api/v1/widgets/:id | Delete a widget |
GET | /api/v1/widgets/:id/events | The events the widget currently shows (?locale=nl|fr|en) |
POST | /api/v1/widgets/preview | Which events a draft selection would show, without saving |
GET | /api/v1/widgets/options | Your templates, upcoming events, locations and branding — everything a configuration screen needs |
GET | /api/v1/events/:id/embed | Embed snippet for a single event's checkout (events:read) |
A widget body uses the same fields as the dashboard form:
{
"name": "Homepage",
"view": "list",
"selectionMode": "templates",
"eventTemplateIds": ["tpl_abc123"],
"dateFrom": "2026-10-01T00:00:00.000Z",
"theme": { "primaryColor": "#4f46e5", "borderRadius": "md" },
"defaultLocale": "nl"
}
selectionMode is one of all_upcoming, templates (with eventTemplateIds), manual (with eventIds) or location (with locationIds); view is list or calendar. Every widget the API returns carries an embed block with the loader script URL, the iframe URL, a ready-to-paste snippet and the data-* attributes the script reads — render either the snippet or your own <script>/<iframe> from those values. Because the snippet only references the widget by ID, later changes through the API or the dashboard show up on the website immediately.
A shop widget sets "type": "shop" and lists the categories it shows in productCategoryIds (empty = the whole shop, with the category filter); event fields such as selectionMode do not apply to it. Widgets without a type are events widgets ("type": "events"). See Shop widgets.
Managing events over the API
Every request carries your key as a bearer token, JSON bodies use the same fields as the dashboard forms, and responses are wrapped in { "data": … } (errors in { "error", "message" }). The examples below take an event from draft to published in three calls.
1. Create a draft event with one ticket type (events:write):
curl -X POST https://cleartix.io/api/v1/events \
-H "Authorization: Bearer ctk_live_…" \
-H "Content-Type: application/json" \
-d '{
"title": { "en": "Quiz night" },
"description": {},
"locales": ["en"],
"startAt": "2026-11-14T18:30:00.000Z",
"endAt": "2026-11-14T22:00:00.000Z",
"timezone": "Europe/Brussels",
"ticketTypes": [
{ "name": { "en": "Team (max. 4 people)" }, "basePrice": 20, "capacity": 40 }
]
}'
The response contains the new event's id and status: "draft". Ticket types can also be added or changed later via POST /api/v1/events/{id}/ticket-types and PATCH …/ticket-types/{ttId}; GET /api/v1/events/{id}/readiness tells you what is still missing before the event can go live.
2. Publish it (events:write):
curl -X POST https://cleartix.io/api/v1/events/ev_5e6f7a8b/publish \
-H "Authorization: Bearer ctk_live_…"
3. Set the reminder email for this event (emails:write):
curl -X PUT https://cleartix.io/api/v1/events/ev_5e6f7a8b/emails/event_reminder \
-H "Authorization: Bearer ctk_live_…" \
-H "Content-Type: application/json" \
-d '{
"subject": { "en": "See you tomorrow at the quiz night!" },
"body": { "en": "Doors open at 19:00. Bring your team name and your thinking caps." },
"isActive": true
}'
GET /api/v1/events/{id}/emails shows the effective email per type (your override or the organisation default), POST …/emails/{type}/preview renders it with sample variables, and DELETE …/emails/{type} removes the override again.
Ticket layout. GET/PUT /api/v1/ticket-layout reads and replaces your organisation's default content for the bottom third of PDF tickets (sponsors, ads); GET/PUT /api/v1/events/{id}/ticket-layout reads and sets an event's mode (inherit, custom or hidden) and its own content. Content is per language as Tiptap JSON, like email bodies. Reading needs events:read, changing needs events:write. See Sponsors and ads on your tickets.
Templates work the same way under /api/v1/templates: create one with POST, plan up to 50 dates at once with POST /api/v1/templates/{id}/schedule (send dryRun: true to preview the dates and the monthly cap check without creating anything), or apply it to an existing event with POST /api/v1/templates/{id}/apply. A PATCH on a template (or an event) is a partial update: only the fields you send change — everything else, including its ticket types and emails, stays as it was. Event responses return the configured seat cap as capacity and the seats still free as availableCapacity; the older eventCapacity field is deprecated (still accepted on input, still returned as an alias for now).
Orders, tickets and check-in. GET /api/v1/orders lists the orders of your whole organisation (filter with eventId, status, paymentMethod, email, free text q or a from/to window), POST /api/v1/orders/{id}/refund refunds a whole order or specific tickets, and POST /api/v1/orders/{id}/cancel cancels an order that is still awaiting payment (both orders:write). To reconcile your bank statements, send the incoming payments (amount, payment message, payer name) to POST /api/v1/orders/reconcile (orders:read): you get, per line, the order waiting for that bank transfer or invoice payment, and nothing is booked. POST /api/v1/orders/mark-paid then books up to 50 orders as paid and POST /api/v1/orders/{id}/mark-paid a single one (orders:write); pass expectedAmount and an order whose total differs is refused with 409 AMOUNT_MISMATCH. GET /api/v1/orders/{id} also includes the bank message (wireTransferReference) and the invoice number (invoiceNumber). GET /api/v1/tickets searches tickets across all events by e-mail, name, ticket or order ID, and POST /api/v1/tickets/{id}/resend regenerates the tickets and e-mails the confirmation to the buyer again (tickets:write). POST /api/v1/events/{id}/guest-tickets issues free guest tickets — a €0 order for press, sponsors or volunteers that consumes capacity like a sale and e-mails the tickets (orders:write); PATCH /api/v1/tickets/{id} replaces a ticket's attendee data, and POST /api/v1/tickets/{id}/transfer hands a ticket to someone else with a new QR code and an e-mail to accept it (both tickets:write). For the door, GET /api/v1/tickets/{id} returns a ticket's check-in status and scan history, POST /api/v1/tickets/{id}/check-in checks it in and POST /api/v1/tickets/{id}/undo-check-in reverts that (checkin:write). POST /api/v1/events/{id}/unpublish takes a published event without orders off sale again. Refunds are recorded in the name of the person who requested them, so /refund only works for an AI assistant that signed in via OAuth — a plain API key gets 409 CONFLICT and you refund from the dashboard instead.
Waitlist, team, privacy, audit and billing. GET /api/v1/events/{id}/waitlist lists an event's waitlist and DELETE /api/v1/events/{id}/waitlist/{entryId} removes one entry (waitlist:write). GET/PATCH/DELETE /api/v1/team/members[/{id}] list, re-role and remove team members, GET/POST /api/v1/team/invites and DELETE /api/v1/team/invites/{id} (plus POST …/{id}/resend) manage pending invitations, and POST/DELETE /api/v1/events/{id}/gates/{gateId}/scanners[/{membershipId}] assign scanners to a gate or take them off it (all team:write). POST /api/v1/privacy/export returns everything you hold about an e-mail address and POST /api/v1/privacy/anonymize irreversibly erases it (privacy:write). GET /api/v1/audit-log reads the audit log (audit:read), and GET /api/v1/billing and GET /api/v1/billing/invoices return your plan, monthly event usage and platform invoices (billing:read).
Locations live under /api/v1/locations, starter kits (quiz, fundraising dinner, workshop series, …) under GET /api/v1/starter-kits, and POST /api/v1/uploads/presign gives you an upload URL for an event or template image (the response also carries a permanent publicUrl). If you would rather not PUT bytes yourself, POST /api/v1/uploads/import with { "url": "https://…/photo.jpg" } (or base64) lets Cleartix download and store the image for you. To place an image inside the event description, add an image node to the Tiptap document with src set to that publicUrl; the optional width (sm, md, full) and align (left, center, right) attributes control size and position exactly like the editor. Every write call is recorded in your audit log with the key's prefix, so you can always see which integration changed what.
The shop over the API
The Shop tag of the spec covers the catalogue and fulfilment: GET /api/v1/products (filter with ?kind=physical|digital and ?active=true|false), POST/PATCH for products, POST /api/v1/products/{id}/file/presign + PUT /api/v1/products/{id}/file to upload a digital product's file, /api/v1/shipping-methods, GET /api/v1/shipments for the worklist, POST /api/v1/orders/{id}/ship and POST /api/v1/orders/{id}/lines/{lineId}/fulfil to mark an order shipped or a collected line handed over. GET /api/v1/orders/{id} returns the order's kind (tickets, pass or shop), its lines[] with fulfilment state, download count and refunded units, and a shipping block (method, address, carrier, tracking). GET /api/v1/stats/products (scope stats:read, Studio/Pro) gives units, orders and revenue per product split by channel (shop / event) with refunds; AI assistants get the same through the get_product_sales tool.
The rest of the shop can be managed over the API too: categories under /api/v1/product-categories (list, create, change, delete and reorder; set a product's category with categoryId on the product), the order of the catalogue with POST /api/v1/products/reorder ({ "ids": [...] }), and the shop settings — shop open or closed, intro text, shipping & returns policy, digital consent text, ship-from address and category layout — with GET/PATCH /api/v1/shop/settings. Shop widgets are regular widgets under /api/v1/widgets (see above).
OpenAPI specification
The full API is described in a machine-readable OpenAPI 3.1 document at https://cleartix.io/api/v1/openapi.json. Import it into Postman or Swagger UI to explore the endpoints, or feed it to a client generator for your language. Every operation lists the scope it needs and the exact request and response shapes — it is the complete reference for the endpoints summarised above.
Keep keys safe
An API key acts on behalf of your organisation, so treat it like a password:
- Never commit it to source control or share it publicly.
- Use a separate key per integration where you can.
- Rotate a key immediately if it leaks, and delete keys you no longer use.
Note: The REST API is available on Studio and Pro; webhooks are a Pro feature. Always verify the webhook signature before trusting a payload — it's how you tell genuine Cleartix calls from anything else hitting your endpoint.
Last updated: October 20, 2026