PassLite

PassLite API Reference

Everything you need to create, manage, and distribute digital wallet passes programmatically.

Base URL

text
https://api.passlite.io/api/v1

Authentication

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

  1. Sign in to your Dashboard
  2. Navigate to Settings → API Keys
  3. Click Create API Key, give it a name, and copy the key
  4. Store the key securely -- it is only shown once

Authentication

API Key (recommended)

Pass your API key in the X-API-Key header.

bash
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.

bash
curl https://api.passlite.io/api/v1/businesses/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Error Responses

401Missing or invalid API key / token.
403Valid credentials but insufficient permissions for the requested resource.

Issue a Pass

POST/businesses/{business_id}/passes

Create and issue a new wallet pass to a customer.

Request Body

FieldTypeDescription
template_idstringID of the pass template to use. required
customer_namestringFull name of the pass holder.
customer_emailstringEmail address for delivery and lookup.
external_idstringYour system's unique identifier for this pass.
initial_balancenumberStarting balance for gift card / stored-value passes.
pointsintegerStarting loyalty points.
barcode_datastringCustom barcode content for this pass. Overrides template barcode. For external ticketing systems.
nfc_datastringCustom NFC payload for this pass. Overrides template NFC message.
metadataobjectArbitrary key-value pairs attached to the pass.

Response

json
{
  "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

bash
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

POST/businesses/{business_id}/passes/bulk

Issue up to 500 passes in a single request. The response includes both successful and failed items, supporting partial success.

Request Body

json
{
  "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)

json
{
  "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

bash
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

GET/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

json
{
  "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

PATCH/businesses/{business_id}/passes/{identifier}

Update fields on an existing pass. Only provided fields are changed.

Updatable fields

  • customer_name
  • customer_email
  • external_id
  • points
  • balance
  • stamps
  • metadata (merged with existing)

Example

bash
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

PATCH/businesses/{business_id}/passes/bulk

Update multiple passes at once. Each item in the array must include an identifier (pass ID, serial number, or external ID).

Request

json
{
  "passes": [
    { "identifier": "pass_abc123", "points": 200 },
    { "identifier": "cust_9822", "metadata": { "tier": "silver" } }
  ]
}

Response

json
{
  "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

POST.../passes/{id}/stamp?count=1
json
{ "id": "pass_abc123", "stamps": 4, "status": "active" }

Add Points

POST.../passes/{id}/points?points=50
json
{ "id": "pass_abc123", "points": 200, "status": "active" }

Charge Balance

POST.../passes/{id}/charge?amount=15.0

Deducts from the pass balance. Returns 400 if insufficient funds.

json
{ "id": "pass_abc123", "balance": 85.0, "status": "active" }

Top Up Balance

POST.../passes/{id}/topup?amount=20.0
json
{ "id": "pass_abc123", "balance": 105.0, "status": "active" }

Check In

POST.../passes/{id}/checkin
json
{ "id": "pass_abc123", "status": "checked_in", "checked_in_at": "2026-04-07T14:00:00Z" }

Redeem

POST.../passes/{id}/redeem
json
{ "id": "pass_abc123", "status": "redeemed", "redeemed_at": "2026-04-07T15:00:00Z" }

Void and Reactivate

Void a Pass

DELETE.../passes/{id}

Marks the pass as voided. The pass is not permanently deleted and can be reactivated.

json
{ "id": "pass_abc123", "status": "voided", "voided_at": "2026-04-07T16:00:00Z" }

Reactivate a Pass

POST.../passes/{id}/reactivate
json
{ "id": "pass_abc123", "status": "active", "reactivated_at": "2026-04-07T17:00:00Z" }

Pass Lookup

By Email

GET.../passes/lookup/email/{email}

Returns an array of all passes associated with the given email address.

json
[
  { "id": "pass_abc123", "serial_number": "PL-00042", "status": "active" },
  { "id": "pass_def456", "serial_number": "PL-00099", "status": "redeemed" }
]

By External ID

GET.../passes/lookup/external/{external_id}

Returns the pass matching your system's external identifier.

json
{ "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

EventDescription
pass.createdA new pass was issued.
pass.updatedA pass was updated.
pass.redeemedA pass was redeemed.
pass.voidedA pass was voided.
pass.scannedA pass QR code was scanned.
pass.installedA pass was added to a wallet.
pass.uninstalledA pass was removed from a wallet.

Payload Format

json
{
  "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.

javascript
// 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
# 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

POST/businesses/{business_id}/webhooks-- Create webhook
GET/businesses/{business_id}/webhooks-- List webhooks
DELETE/businesses/{business_id}/webhooks/{id}-- Delete webhook

Scan 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.

text
Normal:  https://app.passlite.io/validate/PB-XXXXX
Signed:  https://app.passlite.io/validate/PB-XXXXX?sig=a1b2c3d4e5f6

Scan 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.

SettingBehaviourUse Case
No cooldownAlways allowLoyalty cards (default)
One-time use (0)Block after first check-in permanentlyEvent tickets, cinema tickets
1 min – 24 hoursBlock within window, allow afterPOS 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.

bash
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.

FeatureFreePro (£19/mo)
Templates1Unlimited
Issued passes100Unlimited
Campaigns1Unlimited
Reward milestones1Unlimited
API access✓
Automations✓
Bulk operations✓
Webhooks120
Analytics export✓
Online payments✓ (1% + 20p)
json
// 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

bash
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:

bash
curl "https://api.passlite.io/api/v1/public/customer/passes?token=eyJ..."

# Returns all passes for that email with stamps, points, balance

This 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 POST and PATCH endpoints.

Example

bash
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

HeaderDescription
X-RateLimit-LimitMaximum requests per window (60).
X-RateLimit-RemainingRequests remaining in the current window.
X-RateLimit-ResetUnix 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

json
{
  "error": {
    "code": "not_found",
    "message": "Pass with identifier pass_xyz does not exist.",
    "status": 404
  }
}

Common Error Codes

StatusCodeDescription
400bad_requestInvalid or missing request parameters.
401unauthorizedMissing or invalid authentication.
403forbiddenInsufficient permissions.
404not_foundResource does not exist.
409conflictDuplicate resource (e.g. external_id).
422validation_errorRequest body failed validation.
429rate_limitedToo many requests. Retry later.
500internal_errorUnexpected server error.

Need help? Contact us at support@passlite.io