PassLite API Reference
Everything you need to create, manage, and distribute digital wallet passes programmatically.
Base URL
https://api.passlite.io/api/v1Authentication
All API requests require authentication via an API key sent in the X-API-Key header. API keys are prefixed with pb_live_ for production and pb_test_ for sandbox.
How to get an API key
- Sign in to your Dashboard
- Navigate to Settings → API Keys
- Click Create API Key, give it a name, and copy the key
- Store the key securely -- it is only shown once
Authentication
API Key (recommended)
Pass your API key in the X-API-Key header.
curl https://api.passlite.io/api/v1/businesses/me \
-H "X-API-Key: pb_live_your_api_key_here"Bearer Token
Alternatively, authenticate with a JWT access token obtained from /auth/login.
curl https://api.passlite.io/api/v1/businesses/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Error Responses
Issue a Pass
/businesses/{business_id}/passesCreate and issue a new wallet pass to a customer.
Request Body
| Field | Type | Description |
|---|---|---|
| template_id | string | ID of the pass template to use. required |
| customer_name | string | Full name of the pass holder. |
| customer_email | string | Email address for delivery and lookup. |
| external_id | string | Your system's unique identifier for this pass. |
| initial_balance | number | Starting balance for gift card / stored-value passes. |
| points | integer | Starting loyalty points. |
| barcode_data | string | Custom barcode content for this pass. Overrides template barcode. For external ticketing systems. |
| nfc_data | string | Custom NFC payload for this pass. Overrides template NFC message. |
| metadata | object | Arbitrary key-value pairs attached to the pass. |
Response
{
"id": "pass_abc123",
"serial_number": "PL-00042",
"template_id": "tpl_loyalty_gold",
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"external_id": "cust_9821",
"status": "active",
"points": 0,
"balance": null,
"stamps": 0,
"download_url": "https://api.passlite.io/pass/PL-00042",
"created_at": "2026-04-07T10:30:00Z"
}Example
curl -X POST https://api.passlite.io/api/v1/businesses/biz_123/passes \
-H "X-API-Key: pb_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tpl_loyalty_gold",
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"external_id": "cust_9821"
}'Bulk Issue Passes
/businesses/{business_id}/passes/bulkIssue up to 500 passes in a single request. The response includes both successful and failed items, supporting partial success.
Request Body
{
"passes": [
{
"template_id": "tpl_loyalty_gold",
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"external_id": "cust_9821"
},
{
"template_id": "tpl_loyalty_gold",
"customer_name": "John Smith",
"customer_email": "john@example.com",
"external_id": "cust_9822"
}
]
}Response (partial success)
{
"total": 2,
"succeeded": 1,
"failed": 1,
"results": [
{
"index": 0,
"status": "created",
"pass": { "id": "pass_abc123", "serial_number": "PL-00042" }
},
{
"index": 1,
"status": "error",
"error": { "code": "duplicate_external_id", "message": "external_id cust_9822 already exists" }
}
]
}Example
curl -X POST https://api.passlite.io/api/v1/businesses/biz_123/passes/bulk \
-H "X-API-Key: pb_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"passes": [
{ "template_id": "tpl_loyalty_gold", "customer_email": "a@example.com" },
{ "template_id": "tpl_loyalty_gold", "customer_email": "b@example.com" }
]
}'Get a Pass
/businesses/{business_id}/passes/{identifier}Retrieve a single pass by its id, serial_number, or external_id. The API automatically detects the identifier type.
Response
{
"id": "pass_abc123",
"serial_number": "PL-00042",
"template_id": "tpl_loyalty_gold",
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"external_id": "cust_9821",
"status": "active",
"points": 150,
"balance": null,
"stamps": 3,
"metadata": { "tier": "gold" },
"download_url": "https://api.passlite.io/pass/PL-00042",
"created_at": "2026-04-07T10:30:00Z",
"updated_at": "2026-04-07T14:22:00Z"
}Update a Pass
/businesses/{business_id}/passes/{identifier}Update fields on an existing pass. Only provided fields are changed.
Updatable fields
customer_namecustomer_emailexternal_idpointsbalancestampsmetadata(merged with existing)
Example
curl -X PATCH https://api.passlite.io/api/v1/businesses/biz_123/passes/pass_abc123 \
-H "X-API-Key: pb_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "customer_name": "Jane Smith", "metadata": { "tier": "platinum" } }'Bulk Update Passes
/businesses/{business_id}/passes/bulkUpdate multiple passes at once. Each item in the array must include an identifier (pass ID, serial number, or external ID).
Request
{
"passes": [
{ "identifier": "pass_abc123", "points": 200 },
{ "identifier": "cust_9822", "metadata": { "tier": "silver" } }
]
}Response
{
"total": 2,
"succeeded": 2,
"failed": 0,
"results": [
{ "index": 0, "status": "updated", "pass": { "id": "pass_abc123" } },
{ "index": 1, "status": "updated", "pass": { "id": "pass_def456" } }
]
}Pass Actions
Perform common operations on a pass. All actions return the updated pass object.
Add Stamp
.../passes/{id}/stamp?count=1{ "id": "pass_abc123", "stamps": 4, "status": "active" }Add Points
.../passes/{id}/points?points=50{ "id": "pass_abc123", "points": 200, "status": "active" }Charge Balance
.../passes/{id}/charge?amount=15.0Deducts from the pass balance. Returns 400 if insufficient funds.
{ "id": "pass_abc123", "balance": 85.0, "status": "active" }Top Up Balance
.../passes/{id}/topup?amount=20.0{ "id": "pass_abc123", "balance": 105.0, "status": "active" }Check In
.../passes/{id}/checkin{ "id": "pass_abc123", "status": "checked_in", "checked_in_at": "2026-04-07T14:00:00Z" }Redeem
.../passes/{id}/redeem{ "id": "pass_abc123", "status": "redeemed", "redeemed_at": "2026-04-07T15:00:00Z" }Void and Reactivate
Void a Pass
.../passes/{id}Marks the pass as voided. The pass is not permanently deleted and can be reactivated.
{ "id": "pass_abc123", "status": "voided", "voided_at": "2026-04-07T16:00:00Z" }Reactivate a Pass
.../passes/{id}/reactivate{ "id": "pass_abc123", "status": "active", "reactivated_at": "2026-04-07T17:00:00Z" }Pass Lookup
By Email
.../passes/lookup/email/{email}Returns an array of all passes associated with the given email address.
[
{ "id": "pass_abc123", "serial_number": "PL-00042", "status": "active" },
{ "id": "pass_def456", "serial_number": "PL-00099", "status": "redeemed" }
]By External ID
.../passes/lookup/external/{external_id}Returns the pass matching your system's external identifier.
{ "id": "pass_abc123", "external_id": "cust_9821", "status": "active" }Webhooks
PassLite can send real-time event notifications to your server via HTTPS webhooks.
Available Events
| Event | Description |
|---|---|
| pass.created | A new pass was issued. |
| pass.updated | A pass was updated. |
| pass.redeemed | A pass was redeemed. |
| pass.voided | A pass was voided. |
| pass.scanned | A pass QR code was scanned. |
| pass.installed | A pass was added to a wallet. |
| pass.uninstalled | A pass was removed from a wallet. |
Payload Format
{
"event": "pass.created",
"timestamp": "2026-04-07T10:30:00Z",
"data": {
"id": "pass_abc123",
"serial_number": "PL-00042",
"business_id": "biz_123",
"status": "active"
}
}HMAC-SHA256 Verification
Every webhook request includes an X-PassLite-Signature header. Verify it using your webhook secret.
// Node.js
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}# Python
import hmac, hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Management Endpoints
/businesses/{business_id}/webhooks-- Create webhook/businesses/{business_id}/webhooks-- List webhooks/businesses/{business_id}/webhooks/{id}-- Delete webhookScan Security
Two features to prevent QR screenshot fraud and duplicate scans. Configure per template in the editor under Settings → Scan Security.
Signed Barcodes
When enabled, the QR barcode encodes a validation URL with an HMAC-SHA256 signature. The server verifies the signature on every scan. Even if a QR is screenshotted, the server controls validity.
Normal: https://app.passlite.io/validate/PB-XXXXX
Signed: https://app.passlite.io/validate/PB-XXXXX?sig=a1b2c3d4e5f6Scan Cooldown
Prevents the same pass from being checked in twice within a configurable window. Only applies to check-in actions — verify (read-only) is always allowed.
| Setting | Behaviour | Use Case |
|---|---|---|
| No cooldown | Always allow | Loyalty cards (default) |
| One-time use (0) | Block after first check-in permanently | Event tickets, cinema tickets |
| 1 min – 24 hours | Block within window, allow after | POS anti-double-scan |
When blocked, the API returns 429 with a message like “Already checked in at 14:30” or “Try again in 3m 20s”.
Auto-Stamp on Scan
When enabled on a template, scanning a pass in Verify mode automatically triggers a check-in. Staff scans the QR and the stamp is added instantly — no need to tap a button. Respects the scan cooldown to prevent double-stamps.
Per-Pass Barcode Override
External systems can control the barcode content of individual passes by setting barcode_data when issuing or updating a pass. This overrides the template barcode. Set to null to revert to the template default.
curl -X POST https://api.passlite.io/api/v1/businesses/biz_123/passes \
-H "X-API-Key: pb_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tpl_event",
"customer_email": "jane@example.com",
"barcode_data": "TICKET-XYZ-12345",
"nfc_data": "custom-nfc-payload"
}'Plan Limits
Features are gated by plan. PRO-only endpoints return 402 Payment Required with an upgrade prompt on the Free plan.
| Feature | Free | Pro (£19/mo) |
|---|---|---|
| Templates | 1 | Unlimited |
| Issued passes | 100 | Unlimited |
| Campaigns | 1 | Unlimited |
| Reward milestones | 1 | Unlimited |
| API access | — | ✓ |
| Automations | — | ✓ |
| Bulk operations | — | ✓ |
| Webhooks | 1 | 20 |
| Analytics export | — | ✓ |
| Online payments | — | ✓ (1% + 20p) |
// 402 response when limit reached
{
"error": "pro_feature",
"feature": "automations",
"plan": "free",
"message": "Automations is a PRO feature. Upgrade to unlock."
}Customer Authentication
End users (customers) can access their passes via passwordless magic link login. No account creation needed — they enter their email and receive a 15-minute login link.
Send Magic Link
curl -X POST https://api.passlite.io/api/v1/public/customer/magic-link \
-H "Content-Type: application/json" \
-d '{"email": "customer@example.com"}'
# Always returns {"sent": true} (no email enumeration)Get Customer Passes
The magic link email contains a URL with a JWT token. Use it to fetch the customer's passes:
curl "https://api.passlite.io/api/v1/public/customer/passes?token=eyJ..."
# Returns all passes for that email with stamps, points, balanceThis enables pass recovery — if a customer loses their phone, they can log in at /my-passes, see all their passes, and re-download them to a new device.
Idempotency
To safely retry requests without creating duplicate resources, include an Idempotency-Key header with a unique string (e.g. a UUID).
- Keys are scoped to your API key and have a 24-hour TTL.
- If the same key is sent again within 24 hours, the original response is returned.
- Supported on all
POSTandPATCHendpoints.
Example
curl -X POST https://api.passlite.io/api/v1/businesses/biz_123/passes \
-H "X-API-Key: pb_live_your_api_key_here" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{ "template_id": "tpl_loyalty_gold", "customer_email": "jane@example.com" }'Rate Limits
The API enforces a limit of 60 requests per minute per API key. Rate limit information is included in every response.
Response Headers
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests per window (60). |
| X-RateLimit-Remaining | Requests remaining in the current window. |
| X-RateLimit-Reset | Unix timestamp when the window resets. |
When rate limited, the API returns a 429 Too Many Requests response. Retry after the time indicated in X-RateLimit-Reset.
Errors
All errors follow a consistent JSON format.
Error Format
{
"error": {
"code": "not_found",
"message": "Pass with identifier pass_xyz does not exist.",
"status": 404
}
}Common Error Codes
| Status | Code | Description |
|---|---|---|
| 400 | bad_request | Invalid or missing request parameters. |
| 401 | unauthorized | Missing or invalid authentication. |
| 403 | forbidden | Insufficient permissions. |
| 404 | not_found | Resource does not exist. |
| 409 | conflict | Duplicate resource (e.g. external_id). |
| 422 | validation_error | Request body failed validation. |
| 429 | rate_limited | Too many requests. Retry later. |
| 500 | internal_error | Unexpected server error. |
Need help? Contact us at support@passlite.io
