The Tickets API lets you retrieve tickets for your events and scan them programmatically. This is useful for building custom check-in apps, integrating with access control systems, or syncing attendee data.
Prerequisites
You need an API key with the following scopes:
| Scope | Required for |
|---|
tickets.read | Listing tickets |
tickets.write | Scanning tickets |
Create an API key from the Tickable dashboard.
Listing Tickets
Tickets are scoped to an event. Pass the event ID to retrieve all tickets:
curl https://api.tickable.io/events/550e8400-e29b-41d4-a716-446655440000/tickets \
-H "Authorization: Bearer tk_live_YOUR_API_KEY"
The response includes pagination and a status field for each ticket:
{
"data": [
{
"id": "c3d4e5f6-7890-4a1b-2c3d-4e5f6a7b8c9d",
"scannable_code": "c3d4e5f6-7890-4a1b-2c3d-4e5f6a7b8c9d",
"ticket_type_id": "b2c4e6a8-1234-4f5a-9c8d-0e1f2a3b4c5d",
"order_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"owner_name": "Jane Doe",
"owner_email": "jane@example.com",
"seat": "A12",
"status": "valid",
"scanned_at": null,
"cancelled_at": null
}
],
"pagination": {
"total": 48,
"limit": 25,
"offset": 0,
"has_more": true
}
}
Ticket Status
| Status | Meaning |
|---|
valid | Ticket has not been scanned or cancelled |
scanned | Ticket has already been scanned |
cancelled | Ticket has been cancelled |
Filtering by Order
To get all tickets belonging to a specific order, use the order_id query parameter:
curl "https://api.tickable.io/events/550e8400.../tickets?order_id=f47ac10b..." \
-H "Authorization: Bearer tk_live_YOUR_API_KEY"
Scanning Tickets
Use the scan endpoint to mark a ticket as scanned. The API validates scanner profiles, timeslot restrictions, and scan windows automatically.
curl -X POST https://api.tickable.io/tickets/c3d4e5f6-7890-4a1b-2c3d-4e5f6a7b8c9d/scan \
-H "Authorization: Bearer tk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scanner_id": "e5f6a7b8-9012-4c3d-4e5f-6a7b8c9d0e1f"}'
The scanner_id is optional. If omitted, the first active scanner in your organization is used:
curl -X POST https://api.tickable.io/tickets/c3d4e5f6-7890-4a1b-2c3d-4e5f6a7b8c9d/scan \
-H "Authorization: Bearer tk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Scan Response
The response includes the scan result along with event and ticket details:
{
"ticket_id": "c3d4e5f6-7890-4a1b-2c3d-4e5f6a7b8c9d",
"status": "valid",
"scanned_before": false,
"event": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Summer Festival 2026",
"location": "Vondelpark"
},
"timeslot": {
"title": "Morning Session",
"starts_at": "2026-06-15T10:00:00Z",
"ends_at": "2026-06-15T12:00:00Z"
},
"ticket_type": {
"id": "b2c4e6a8-1234-4f5a-9c8d-0e1f2a3b4c5d",
"name": "Early Bird",
"price": 2500
},
"owner_name": "Jane Doe",
"owner_email": "jane@example.com",
"seat": "A12",
"scanned_at": "2026-06-15T10:05:00Z",
"cancelled_at": null,
"related_tickets": null
}
Scan Status
The status field tells you the outcome of the scan:
| Status | Meaning |
|---|
valid | Ticket was successfully scanned |
scanned | Ticket was already scanned before |
cancelled | Ticket has been cancelled |
not_allowed | Scanner profile rules or scan window prevented the scan |
A scanned status means the ticket was already used. The scanned_before field is true in this case. The original scanned_at timestamp is preserved.
When the event has order scanning enabled, the response includes related_tickets — all other tickets from the same order. This is useful for group check-in scenarios:
{
"related_tickets": [
{
"id": "a1b2c3d4-5678-4e9f-0a1b-2c3d4e5f6a7b",
"seat": "A13",
"owner_name": "John Doe",
"owner_email": "john@example.com",
"scanned_at": null,
"cancelled_at": null,
"ticket_type_name": "Early Bird"
}
]
}
Example: Custom Check-in App
async function scanTicket(ticketId, scannerId) {
const response = await fetch(
`https://api.tickable.io/tickets/${ticketId}/scan`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer tk_live_YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({ scanner_id: scannerId }),
}
);
const result = await response.json();
switch (result.status) {
case 'valid':
console.log(`Welcome, ${result.owner_name}!`);
break;
case 'scanned':
console.log(`Already scanned at ${result.scanned_at}`);
break;
case 'cancelled':
console.log('This ticket has been cancelled');
break;
case 'not_allowed':
console.log('Scan not allowed — check scanner profile or scan window');
break;
}
return result;
}