This article is intended for the person consuming the PassPass REST API. For an overview and configuration from the dashboard, see Public API and Webhooks.
Interactive documentation (Swagger): api.passpass.be/api/public/docs
Authentication
Each request carries your API key in the Authorization header:
GET /api/v1/events HTTP/1.1
Host: api.passpass.be
Authorization: Bearer pk_live_9f2c4a1b...
The key is linked to your organization. All returned data is automatically filtered on your organization.
The key's secret is displayed only once at creation. If you lose it, perform a rotation from the dashboard (the old secret stops working immediately).
Validate your configuration at startup with GET /v1/me : it is the only endpoint that requires no scope.
Available Endpoints
The API is read-only. 7 GET endpoints:
GET /v1/me : introspection of your API key (scopes, organization, last use). No scope required
GET /v1/events : list of your events (all statuses, drafts, and private included). Scope:
events:readGET /v1/events/:id : detail of an event. Scope:
events:readGET /v1/orders : list of orders. Scope:
orders:readGET /v1/orders/:id : detail of an order. Scope:
orders:readGET /v1/events/:eventId/attendees : attendees of an event. Scope:
attendees:readGET /v1/orders/:orderId/attendees : attendees of an order. Scope:
attendees:read
Permissions (scopes)
Scopes control the fields returned, not just access to endpoints. An unauthorized field is absent, not null.
events:read : access to event endpoints
orders:read : access to order endpoints (amount, buyer, billing, line items, status)
orders:read:financial : service_fee, payout_amount, refundable_amount, historical_service_fee
orders:read:form_answers : order form answers
attendees:read : access to attendee endpoints (minimal data, no personal data)
attendees:read:contact : first_name, last_name, email
attendees:read:financial : ticket_price
attendees:read:form_answers : attendee form answers
attendees:read:full : access_token (automatically includes contact and financial)
Scopes are fixed at key creation. To change permissions, create a new key and remove the old one. access_token is the secret identifier behind the ticket's QR code. Only request attendees:read:full if your system truly needs it.
Pagination
All lists are paginated:
{
"data": [],
"total": 137,
"page": 1,
"limit": 25,
"total_pages": 6
}
Parameters: ?page= (default 1) and ?limit= (default 25, maximum 100). Always paginate up to total_pages and reread the returned limit.
Conventions
Fields in snake_case (exception: buyer and billing in camelCase)
Amounts in euros (not in cents).
25= 25.00€Dates in ISO 8601 UTC
Multilingual fields:
{"fr": "...", "en": "...", "nl": "..."}Field not authorized by scopes: absent, not
nullNew fields possible without notice, do not reject an unknown field
Events
GET /v1/events returns all your events (drafts and private included, only deleted are excluded). No status or date filter on the API side: filter on the client side on status / start_at.
Use case: gauge dashboard, ticket stock tracking, event list in a back-office.
{
"id": 1535,
"title": {"fr": "Mon événement"},
"short_description": {"fr": "Description courte"},
"address_label": {"fr": "Bruxelles"},
"address_full": {"fr": "Rue de la Loi 1, 1000 Bruxelles"},
"status": "published",
"visibility": "public",
"sold_count": 128,
"max_capacity": 500,
"start_at": "2026-06-01T18:00:00.000Z",
"end_at": "2026-06-01T23:00:00.000Z",
"timezone": "Europe/Brussels",
"currency": "EUR",
"tickets": [
{
"id": 4525,
"type": "ticket",
"name": {"fr": "Entrée Standard"},
"description": null,
"price": 25,
"vat_rate": 21,
"max_quantity": 300,
"sold_count": 128,
"visibility": "visible",
"sell_start_at": null,
"sell_end_at": null
}
],
"created_at": "2026-01-12T14:22:00.000Z",
"updated_at": "2026-04-17T09:00:00.000Z"
}
Orders
GET /v1/orders returns the orders of your organization.
Available Filters
status :
paid(default),pending,cancelled, orallcreated_after : ISO-8601 date, orders created from this moment (inclusive)
created_before : ISO-8601 date, orders created up to this moment (inclusive)
By default, only paid orders are returned. Use ?status=all if you want the full picture (cancellations, ongoing carts). This is the most common trap for accounting reconciliations.
Statuses
paid : paid and not canceled
cancelled : canceled (whether paid or not). Expired orders (abandoned cart) are also found here
pending : neither paid nor canceled (ongoing cart, pending transfer)
Use case: accounting export, financial reconciliation, CRM synchronization.
{
"id": 65817,
"event_id": 1535,
"status": "paid",
"origin": "online",
"currency": "EUR",
"amount_total": 200,
"buyer": {
"firstName": "Marie",
"lastName": "Dupont",
"email": "marie@example.com"
},
"billing": {
"identifierType": "vat",
"companyName": "Demo Corp SRL",
"vat": "BE0123456789",
"vatCountry": "BE",
"addressStreet": "Rue de la Loi 1",
"addressCity": "Bruxelles",
"addressZip": "1000",
"addressCountry": "BE"
},
"line_items": [
{
"type": "ticket",
"ticket_id": 4525,
"name": {"fr": "Entrée Standard"},
"quantity": 2,
"unit_amount": 100,
"subtotal": 200
}
],
"form_answers": null,
"paid_at": "2026-04-17T10:00:00.000Z",
"cancelled_at": null,
"created_at": "2026-04-17T10:00:00.000Z",
"updated_at": "2026-04-17T10:00:00.000Z",
"service_fee": 4.6,
"payout_amount": 195.4,
"refundable_amount": 4.6,
"historical_service_fee": 4.6
}
Understanding the Fields
buyer : identity entered at checkout (first name, last name, email). In camelCase.
billing : company billing data. Present only if the buyer checked "I order on behalf of a company" at checkout. Otherwise null. In camelCase. Contains: identifierType (vat or enterprise), companyName, vat, vatCountry, addressStreet, addressCity, addressZip, addressCountry.
line_items : order details. Each line has a type:
ticket: purchased ticket, unit_amount = unit pricefee: service fee (a single aggregated line)discount: promo code / discount, negative unit_amount
Financial block (scope orders:read:financial) :
service_fee: current service feehistorical_service_fee: fees fixed at the time of salepayout_amount: net amount to be paid to the organizerrefundable_amount: part of the fees still refundable
form_answers (scope orders:read:form_answers) : answers to the custom order form.
Attendees
GET /v1/events/:eventId/attendees or GET /v1/orders/:orderId/attendees returns the attendees. One attendee = one ticket.
Only attendees with status active are returned. Canceled or draft tickets are absent.
Use case: attendance list, offline access control, nominative export.
{
"id": "019fdb0d-...",
"event_id": 1535,
"order_id": 65817,
"status": "active",
"ticket": {
"id": 4525,
"type": "ticket",
"name": {"fr": "Entrée Standard"}
},
"first_name": "Marie",
"last_name": "Dupont",
"email": "marie@example.com",
"ticket_price": 100,
"access_token": "019fdb0d-...",
"created_at": "2026-04-17T10:00:00.000Z",
"updated_at": "2026-04-17T10:00:00.000Z",
"form_answers": [
{
"type": "selection",
"field_id": "abc123...",
"question_title": {"fr": "Quel est votre statut actuel ?"},
"answer": {"fr": "Indépendant"}
},
{
"type": "unique-choice",
"field_id": "def456...",
"question_title": {"fr": "Régime alimentaire"},
"answer": {"fr": "Végétarien"}
},
{
"type": "short-text",
"field_id": "ghi789...",
"question_title": {"fr": "Nom entreprise"},
"answer": "Demo Corp"
},
{
"type": "formatted-field",
"field_id": "jkl012...",
"question_title": {"fr": "Téléphone"},
"answer": "+32400000000"
},
{
"type": "formatted-field",
"field_id": "mno345...",
"question_title": {"fr": "Code postal"},
"answer": "1000"
}
]
}
Understanding the Fields
Fields are conditioned by scopes. Absent if the scope is not granted:
first_name, last_name, email (scope attendees:read:contact)
ticket_price (scope attendees:read:financial) : amount in euros
form_answers (scope attendees:read:form_answers) : attendee form answers. Possible types:
selection,unique-choice,short-text,formatted-field. The selection and unique-choice types have multilingual answers, the others have string answersaccess_token (scope attendees:read:full) : the secret identifier behind the ticket's QR code, read by the PassPass Organizer mobile app during entry control. This token can, for example, be reassigned on a badge to replicate a QR code readable by PassPass ticket control. Transmitting this token is equivalent to giving access to the ticket
Introspection
GET /v1/me returns your key information. No scope required.
{
"api_key_id": "019d-...",
"api_key_prefix": "pk_live_9f2c4a1b",
"api_key_name": "Production server",
"organization_id": "019d-...",
"scopes": ["events:read", "orders:read", "attendees:read"],
"last_used_at": "2026-04-17T09:58:00.000Z",
"created_at": "2026-01-12T14:22:00.000Z"
}
Limits
100 requests per minute per API key. Beyond that: HTTP 429. If your need is real-time, use webhooks instead of polling.
Error Codes
401 : key absent, invalid or disabled
403 : valid key but insufficient scope
404 : resource nonexistent or outside your organization
400 : invalid parameter (status, date format...)
429 : quota exceeded (100 req/min)
API or Webhooks?
React to a sale in real-time → Webhooks
order.paidSynchronize a CRM continuously → Webhooks + API for catch-up
Accounting export, reconciliation → API
/v1/orders?status=all&created_after=...Attendance list, access control → API
/v1/events/:id/attendeesGauge dashboard / stocks → API
/v1/events(sold_count, max_quantity)Replay a past history → API (the webhooks do not replay the past)
The robust scheme: webhooks for real-time + periodic API call to catch up on what a network incident might have missed.
For any technical questions or need for support, feel free to contact our support.