Scan Manager API Docs ← Back to app

Integration API Reference

Connect your ticketing platform to Scan Manager to import events, sync and manage tickets in real time.

Endpoints
Overview

The Integration API lets authorised third-party platforms push event and ticket data directly into Scan Manager. Once imported, tickets are immediately available to door staff scanning via the mobile app or Zebra scanner.

Authentication

Every request must include these HTTP headers:

HeaderValue
X-Api-KeyYour API key (starts with es_)required
X-Api-EmailThe email address associated with your API keyrequired
Content-Typeapplication/jsonrequired
Acceptapplication/jsonrecommended

Generate API keys from your promoter dashboard under Settings → API Keys. The plain-text key is shown only once — store it securely in a secrets manager or environment variable, never in source code.

curl -X GET "https://scan-manager.app/api/integration/handshake" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Accept: application/json"
GET
/handshake

Verify that your credentials are valid and the connection is working. Call this first when setting up your integration. Marks the key as connected in the dashboard.

{ "success": true, "message": "Connection established successfully.", "data": { "promoter_name": "Acme Events Ltd", "promoter_id": 42, "connected_at": "2025-10-01T12:00:00+00:00" } }
Door User Provisioning

Use these endpoints when your ticketing platform needs to provision scanner users automatically. Door users authenticate in the scanner app with email + password and can be restricted to selected events.

ⓘ Write endpoints are rate-limited to 60 requests/min per API key.
GET
/door-users

List all door users for the authenticated promoter.

{ "success": true, "data": [ { "id": 77, "name": "Main Entrance", "email": "door.main@example.com", "role": "door", "allowed_event_ids": [101, 102], "event_restricted": true, "created_at": "2026-06-26T09:00:00+00:00", "updated_at": "2026-06-26T09:00:00+00:00" } ] }
POST
/door-users

Create a new door user for scanner login.

FieldTypeNotes
namestringDisplay name for the scanner operatorrequired
emailstringMust be globally uniquerequired
passwordstringMinimum 8 charactersrequired
allowed_event_idsinteger[]If omitted or empty, user can scan all promoter eventsoptional
{ "name": "North Gate", "email": "north.gate@example.com", "password": "ChangeMe123!", "allowed_event_ids": [101, 102] }
{ "success": true, "message": "Door user created successfully.", "data": { "id": 78, "name": "North Gate", "email": "north.gate@example.com", "role": "door", "allowed_event_ids": [101, 102], "event_restricted": true, "created_at": "2026-06-26T09:05:00+00:00", "updated_at": "2026-06-26T09:05:00+00:00" } }
POST
PUT /door-users/{id}

Update name, email, password, and/or event restrictions for an existing door user.

{ "name": "North Gate Team", "allowed_event_ids": [101] }
{ "success": true, "message": "Door user updated successfully.", "data": { "id": 78, "name": "North Gate Team", "email": "north.gate@example.com", "role": "door", "allowed_event_ids": [101], "event_restricted": true, "created_at": "2026-06-26T09:05:00+00:00", "updated_at": "2026-06-26T09:20:00+00:00" } }
POST
DELETE /door-users/{id}

Delete a door user. Any active scanner session tokens for that user should be considered invalid for future logins.

{ "success": true, "message": "Door user deleted successfully." }
GET
/events

Returns all events belonging to your promoter account, ordered by date descending. Use this to look up event_id values for subsequent calls.

{ "success": true, "data": [ { "id": 101, "name": "Summer Festival 2025", "event_date": "2025-08-15 19:00:00", "venue": "Brixton Academy, London", "tickets_count": 1200 } ] }
GET
/events/{id}

Returns a single event with live scan statistics. Only returns events belonging to your promoter account.

{ "success": true, "data": { "id": 101, "name": "Summer Festival 2025", "event_date": "2025-08-15T19:00:00+00:00", "venue": "Brixton Academy, London", "status": "active", "capacity": null, "tickets_total": 1200, "tickets_scanned": 843, "tickets_cancelled": 12, "scan_rate": 70.3 } }
{ "success": false, "message": "Event not found." }
GET
/events/{id}/tickets

Returns a paginated list of all tickets for a given event. Supports filtering by scan status or ticket status.

ParameterTypeNotes
per_pageintegerResults per page (default 100, max 500)optional
pageintegerPage number (default 1)optional
scannedbooleanFilter by scan state: true or falseoptional
statusstringFilter by ticket status: valid or cancelledoptional
{ "success": true, "event": { "id": 101, "name": "Summer Festival 2025", "event_date": "2025-08-15T19:00:00+00:00", "venue": "Brixton Academy, London" }, "data": [ { "ticket_uid": "TKT-ABC123", "order_ref": "ORD-9001", "name": "Jane Smith", "email": "jane@example.com", "ticket_type": "General Admission", "provider": "MyTicketingApp", "status": "valid", "scanned": true, "scanned_at": "2025-08-15T21:34:12+00:00" } ], "meta": { "total": 1200, "per_page": 100, "current_page": 1, "last_page": 12 } }
{ "success": false, "message": "Event not found." }
POST
/import

Create a new event and import its tickets in one request. Duplicate ticket_uid values within the same event are automatically skipped.

FieldTypeNotes
event.namestringEvent name (max 255)required
event.event_datedatetimeYYYY-MM-DD HH:MM:SS or ISO 8601required
event.venuestringVenue name or address (max 255)required
tickets[].ticket_uidstringUnique identifier — the value encoded in the barcode or QR coderequired
tickets[].order_refstringOrder or booking referenceoptional
tickets[].namestringAttendee full nameoptional
tickets[].emailstringAttendee email addressoptional
tickets[].ticket_typestringe.g. "General Admission", "VIP"optional
tickets[].providerstringName of the originating platformoptional
{ "event": { "name": "Summer Festival 2025", "event_date": "2025-08-15 19:00:00", "venue": "Brixton Academy, London" }, "tickets": [ { "ticket_uid": "TKT-ABC123", "order_ref": "ORD-9001", "name": "Jane Smith", "email": "jane@example.com", "ticket_type": "General Admission", "provider": "MyTicketingApp" } ] }
{ "success": true, "message": "Event created with 1 tickets imported.", "event": { "id": 101, "name": "Summer Festival 2025", "ticket_count": 1 }, "imported": 1, "skipped": 0, "data": { "event_id": 101, "event_name": "Summer Festival 2025", "tickets_imported": 1, "tickets_skipped": 0, "errors": [] } }
POST
/sync-tickets

Add more tickets to an existing event. Identify the event by event_id (preferred) or by matching on event_name + event_date. Tickets whose ticket_uid already exists on that event are silently skipped.

FieldTypeNotes
event_idintegerPreferred — use id from GET /eventsoptional*
event_namestringExact event name (fallback)optional*
event_datedateNarrows the event_name lookupoptional
tickets[]arraySame fields as POST /importrequired

* Provide at least one of event_id or event_name.

{ "event_id": 101, "tickets": [ { "ticket_uid": "TKT-XYZ789", "order_ref": "ORD-9003", "name": "Alice Jones", "email": "alice@example.com", "ticket_type": "VIP", "provider": "MyTicketingApp" }, { "ticket_uid": "TKT-XYZ790", "name": "Bob Brown", "ticket_type": "General Admission" } ] }
{ "success": true, "message": "2 tickets imported, 0 skipped.", "event_id": 101, "imported": 2, "skipped": 0 }
POST
/tickets/status

Mark tickets as cancelled. Cancelled tickets stay in the system but cannot be scanned — the scanner app rejects them and shows a Cancelled result to door staff. Use this for refunded or transferred tickets where you want an audit trail.

ⓘ Rate limit: 60 requests per minute per API key.
FieldTypeNotes
ticket_uidsstring[]Array of ticket UIDs to cancelrequired
statusstringMust be "cancelled"required
{ "ticket_uids": [ "TKT-ABC123", "TKT-DEF456" ], "status": "cancelled" }
{ "success": true, "updated": 2, "skipped": 0 }

Tickets not found or belonging to another promoter are silently skipped.

POST
/tickets/remove

Permanently remove tickets from the event. Removed tickets are soft-deleted and will no longer appear in the scanner app or ticket lists. Use this when a ticket must be fully invalidated (e.g. duplicate orders, data corrections).

⚠ Destructive action. Removed tickets cannot be recovered via the API. If you may need the ticket again, use POST /tickets/status to cancel it instead.
ⓘ Rate limit: 60 requests per minute per API key. Maximum 200 UIDs per request.
FieldTypeNotes
ticket_uidsstring[]Array of ticket UIDs to remove (max 200 per request)required
reasonstringShort reason for audit log (e.g. "refunded", "duplicate")optional
{ "ticket_uids": [ "TKT-ABC123", "TKT-DEF456" ], "reason": "refunded" }
{ "success": true, "message": "2 ticket(s) removed, 0 not found or already removed.", "removed": 2, "skipped": 0 }

Tickets not found, already removed, or belonging to another promoter are counted as skipped.

POST /tickets/statusPOST /tickets/remove
EffectSets status = cancelledSoft-deletes the ticket row
Scanner behaviourShows "Cancelled" to door staffTicket not found
Reversible?YesNo (via API)
Best forRefunds with audit trailDuplicates, data corrections
POST
/tickets/lookup

Query the scan status of one or more tickets by ticket_uid. Returns whether each ticket has been scanned, when it was scanned, and its current status. Useful for syncing your ticketing platform after an event.

ⓘ Rate limit: 60 requests per minute per API key. Maximum 200 UIDs per request.
FieldTypeNotes
ticket_uidsstring[]Array of ticket UIDs to look up (max 200 per request)required
{ "ticket_uids": [ "TKT-ABC123", "TKT-DEF456" ] }
{ "success": true, "data": { "TKT-ABC123": { "ticket_uid": "TKT-ABC123", "order_ref": "ORD-9001", "name": "Jane Smith", "email": "jane@example.com", "ticket_type": "General Admission", "status": "valid", "scanned": true, "scanned_at": "2025-08-15T21:34:12+00:00", "event": { "id": 101, "name": "Summer Festival 2025", "event_date": "2025-08-15 19:00:00", "venue": "Brixton Academy, London" } }, "TKT-DEF456": { "ticket_uid": "TKT-DEF456", "order_ref": "ORD-9002", "name": "John Doe", "email": "john@example.com", "ticket_type": "VIP", "status": "valid", "scanned": false, "scanned_at": null, "event": { "id": 101, "name": "Summer Festival 2025", "event_date": "2025-08-15 19:00:00", "venue": "Brixton Academy, London" } } }, "missing": [] }

The data object is keyed by ticket_uid for easy lookup. UIDs not found or belonging to another promoter are listed in missing.

statusscannedMeaning
validfalseTicket is valid and has not been scanned yet
validtrueTicket was successfully scanned at the door
cancelledfalseTicket has been cancelled and will be rejected at the door
Webhook Guide

When a ticket is scanned successfully, Scan Manager can push a webhook to your endpoint. Configure endpoint URLs from the promoter dashboard under Webhooks.

EventWhen it fires
ticket.scannedA valid ticket is checked in by a door user
event.createdAn event is created in Scan Manager
event.updatedAn event is updated in Scan Manager
HeaderDescription
X-Webhook-EventEvent type, e.g. ticket.scanned
X-Webhook-DeliveryUnique delivery ID
X-Webhook-SignatureHMAC signature in format sha256=<hash>

Compute an HMAC SHA-256 hash of the raw request body using your webhook secret, then compare to X-Webhook-Signature.

# Pseudocode signature_header = request.headers["X-Webhook-Signature"] expected = "sha256=" + HMAC_SHA256(raw_body, webhook_secret) if !constant_time_equals(signature_header, expected): reject 401
{ "ticket_uid": "TKT-ABC123", "ticket_name": "Jane Smith", "ticket_type": "General Admission", "order_ref": "ORD-9001", "event_id": 101, "event_name": "Summer Festival 2025", "scanned_at": "2026-06-26T09:30:01+00:00", "door_user": "North Gate", "event": "ticket.scanned", "timestamp": "2026-06-26T09:30:02+00:00", "promoter": 42 }
ⓘ Webhook deliveries are retried up to 3 attempts on non-2xx responses or network failures.
Error Codes
HTTP StatusMeaningCommon cause
401UnauthorizedMissing or invalid X-Api-Key / X-Api-Email, or key is inactive.
402Payment RequiredSubscription expired or inactive.
403ForbiddenPlan does not include API access. Upgrade to Enterprise.
404Not FoundEvent not found or belongs to a different promoter.
422Unprocessable EntityValidation failed. The errors field lists specific issues.
429Too Many RequestsRate limit exceeded. Retry after one minute.
5xxServer ErrorUnexpected error. Contact support if this persists.
{ "success": false, "message": "Validation failed.", "errors": { "ticket_uids": ["The ticket uids field is required."] } }
Quick-start cURL Examples
curl -X GET "https://scan-manager.app/api/integration/handshake" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com"
curl -X POST "https://scan-manager.app/api/integration/import" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Content-Type: application/json" \ -d '{ "event": { "name": "Summer Festival 2025", "event_date": "2025-08-15 19:00:00", "venue": "Brixton Academy" }, "tickets": [{ "ticket_uid": "TKT-001", "name": "Jane Smith" }] }'
curl -X POST "https://scan-manager.app/api/integration/sync-tickets" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Content-Type: application/json" \ -d '{ "event_id": 101, "tickets": [ { "ticket_uid": "TKT-002", "order_ref": "ORD-9002", "name": "Bob Jones", "email": "bob@example.com", "ticket_type": "General Admission", "provider": "MyTicketingApp" } ] }'
curl -X POST "https://scan-manager.app/api/integration/tickets/status" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Content-Type: application/json" \ -d '{ "ticket_uids": ["TKT-001"], "status": "cancelled" }'
curl -X POST "https://scan-manager.app/api/integration/tickets/remove" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Content-Type: application/json" \ -d '{ "ticket_uids": ["TKT-001"], "reason": "refunded" }'
curl -X POST "https://scan-manager.app/api/integration/tickets/lookup" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Content-Type: application/json" \ -d '{ "ticket_uids": [ "TKT-ABC123", "TKT-DEF456" ] }'
curl -X GET "https://scan-manager.app/api/integration/events/101" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com"
curl -X GET "https://scan-manager.app/api/integration/events/101/tickets?per_page=100&page=1" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com"
curl -X GET "https://scan-manager.app/api/integration/events/101/tickets?scanned=false" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com"
curl -X POST "https://scan-manager.app/api/integration/door-users" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Content-Type: application/json" \ -d '{ "name": "North Gate", "email": "north.gate@example.com", "password": "ChangeMe123!", "allowed_event_ids": [101, 102] }'
curl -X PUT "https://scan-manager.app/api/integration/door-users/78" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com" \ -H "Content-Type: application/json" \ -d '{ "allowed_event_ids": [101] }'
curl -X DELETE "https://scan-manager.app/api/integration/door-users/78" \ -H "X-Api-Key: es_yourkeyhere" \ -H "X-Api-Email: you@example.com"