API Documentation

Integrate pass creation into your applications

Pro plan only. The REST API is available exclusively on the Pro plan. Free and Basic plans cannot create API keys. See pricing →

Contents

Authentication

All API requests require a valid API key sent in the Authorization header. API keys can be created in the Dashboard → API section (Pro plan, admin only).

Authorization: Bearer 2pk_your_api_key_here

Base URL

https://your-domain.com/api/v1

All endpoints below are relative to this base URL. CORS is enabled — preflight OPTIONS is supported on every endpoint.

Rate Limits

  • 30 requests per minute per API key for /passes, /clients, /scan
  • 10 requests per minute per API key for /webhooks and /triggers
  • Rate limit headers in responses: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • When rate limited, a 429 response includes a Retry-After header

Error Codes

CodeDescription
400Bad Request — invalid input or JSON body
401Unauthorized — missing or invalid API key
403Forbidden — plan limit reached, not on Pro plan, or webhook quota exceeded
404Not Found — resource does not exist or belongs to another tenant
429Too Many Requests — rate limit exceeded
500Internal Server Error

All errors return JSON: { "error": "message" }

Endpoints

GET/passesList Passes

Query Parameters

FieldTypeDescription
searchstringMatch by pass name or code
statusstringactive, used, expired, revoked
limitnumberPage size (default 25, max 100)
pagenumberPage number (default 1)

Response (200)

[
  {
    "id": "clx1abc...",
    "code": "AbCd1234EfGh",
    "name": "VIP Guest",
    "type": "single",
    "status": "active",
    "usagesCount": 0,
    "maxUsages": null,
    "validFrom": null,
    "validUntil": "2025-12-31T23:59:59.000Z",
    "client": { "name": "John Doe", "email": "[email protected]" },
    "publicUrl": "https://your-domain.com/p/AbCd1234EfGh?token=...",
    "createdAt": "2025-06-01T12:00:00.000Z"
  }
]

POST/passesCreate a Pass

Request Body

FieldTypeRequiredDescription
typestringYes"single" or "multi"
namestringNoHuman-readable label
notestringNoInternal note (not shown on public page)
clientNamestringNoAuto-create or attach client by name
maxUsagesnumberNoMax scan count for multi passes (null = unlimited)
validFromstringNoISO 8601 date (e.g. "2025-06-01T00:00:00Z")
validUntilstringNoISO 8601 date
customFieldsarrayNoArray of { "key": "...", "value": "...", "isPublic": true }
templateIdstringNoPass template ID for styling
locationIdstringNoLocation ID to attach

Response (201)

{
  "id": "clx1abc...",
  "code": "AbCd1234EfGh",
  "name": "VIP Guest",
  "type": "single",
  "status": "active",
  "maxUsages": null,
  "usagesCount": 0,
  "validFrom": null,
  "validUntil": "2025-12-31T23:59:59.000Z",
  "publicUrl": "https://your-domain.com/p/AbCd1234EfGh?token=...",
  "createdAt": "2025-06-01T12:00:00.000Z"
}

GET/passes/:passIdGet Pass

Returns the full pass including custom fields and attached client.

Response (200)

{
  "id": "clx1abc...",
  "code": "AbCd1234EfGh",
  "name": "VIP Guest",
  "type": "single",
  "status": "active",
  "usagesCount": 0,
  "maxUsages": null,
  "validFrom": null,
  "validUntil": "2025-12-31T23:59:59.000Z",
  "fields": [
    { "key": "Full Name", "value": "John Doe", "isPublic": true }
  ],
  "client": {
    "id": "clx2...", "name": "John Doe", "email": "[email protected]", "phone": null
  },
  "publicUrl": "https://your-domain.com/p/AbCd1234EfGh?token=...",
  "createdAt": "2025-06-01T12:00:00.000Z",
  "updatedAt": "2025-06-01T12:00:00.000Z"
}

PATCH/passes/:passIdUpdate Pass

Request Body

FieldTypeRequiredDescription
namestringNoUpdate label
notestringNoInternal note
statusstringNo"active" or "revoked"
maxUsagesnumber | nullNoUpdate scan limit
validFromstring | nullNoISO 8601 date or null
validUntilstring | nullNoISO 8601 date or null
customFieldsarrayNoReplaces all existing fields

When customFields is provided, all existing fields are replaced. Status change emits a pass.status_changed webhook.

POST/passes/:passId/activateActivate Pass

Sets status to active. Returns 400 if already active. Emits pass.status_changed and notifies wallet apps.

Response (200)

{ "id": "clx1abc...", "code": "AbCd1234EfGh", "name": "VIP Guest", "status": "active" }

POST/passes/:passId/revokeRevoke Pass

Sets status to revoked. Returns 400 if already revoked. Emits pass.status_changed and notifies wallet apps.

Response (200)

{ "id": "clx1abc...", "code": "AbCd1234EfGh", "name": "VIP Guest", "status": "revoked" }

POST/scanScan Pass

Request Body

FieldTypeRequiredDescription
codestringYesThe pass code (from QR or public URL)

Validates the pass against status, validity dates, and usage limits. Records a scan and emits pass.scanned.single passes flip to used after the first successful scan.

Response (200)

{
  "success": true,
  "result": "success",
  "pass": {
    "id": "clx1abc...",
    "code": "AbCd1234EfGh",
    "name": "VIP Guest",
    "type": "single",
    "status": "used",
    "usagesCount": 1,
    "maxUsages": null
  },
  "scan": { "id": "clx3...", "scannedAt": "2025-06-01T12:30:00.000Z" }
}

On failure, response is 200 with success: false and result:revoked, already_used, not_yet_valid, expired, max_usages_reached.

GET/clientsList Clients

Query Parameters

FieldTypeDescription
searchstringMatch by name, email or phone
limitnumberPage size (default 25, max 100)
pagenumberPage number (default 1)

Response (200)

[
  {
    "id": "clx2...",
    "name": "John Doe",
    "email": "[email protected]",
    "phone": "+1234567890",
    "notes": null,
    "createdAt": "2025-06-01T12:00:00.000Z"
  }
]

POST/clientsCreate Client

Request Body

FieldTypeRequiredDescription
namestringYesClient display name
emailstringNoEmail address
phonestringNoPhone number
notesstringNoInternal notes

Response (201)

{
  "id": "clx2...",
  "name": "John Doe",
  "email": "[email protected]",
  "phone": null,
  "notes": null,
  "createdAt": "2025-06-01T12:00:00.000Z"
}

Emits client.created webhook.

GET/clients/:clientIdGet Client

Response (200)

{
  "id": "clx2...",
  "name": "John Doe",
  "email": "[email protected]",
  "phone": "+1234567890",
  "notes": "VIP customer",
  "createdAt": "2025-06-01T12:00:00.000Z"
}

GET/webhooksList Webhooks

Response (200)

[
  {
    "id": "wh_...",
    "event": "pass.scanned",
    "targetUrl": "https://example.com/hook",
    "active": true,
    "createdAt": "2025-06-01T12:00:00.000Z"
  }
]

POST/webhooksSubscribe Webhook

Request Body

FieldTypeRequiredDescription
eventstringYesOne of: pass.created, pass.scanned, pass.status_changed, client.created
targetUrlstringYesHTTPS URL receiving POST payloads

Tenant has a fixed webhook quota — exceeding it returns 403. Subscribed events POST a JSON payload to targetUrl.

Response (201)

{
  "id": "wh_...",
  "event": "pass.scanned",
  "targetUrl": "https://example.com/hook",
  "active": true,
  "createdAt": "2025-06-01T12:00:00.000Z"
}

DELETE/webhooks/:webhookIdUnsubscribe Webhook

Response (200)

{ "success": true }

GET/triggersPoll Triggers

Polling fallback used by Zapier. Returns recent items for the requested event since a given timestamp.

Query Parameters

FieldTypeRequiredDescription
eventstringYesOne of: pass.created, pass.scanned, pass.status_changed, client.created
sincestringNoISO 8601 date — return items after this time (default last 24h)
limitnumberNoMax items (default 50, max 100)

Each event returns a different shape — for pass.created the items match the list-passes response with extra fields, clientName, clientEmail, clientPhone; for pass.scanned each item is a flat scan record; for pass.status_changed a pass with changedAt; for client.created the client record.

Examples

curl — create pass

curl -X POST https://your-domain.com/api/v1/passes \
  -H "Authorization: Bearer 2pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "single",
    "name": "VIP Guest",
    "validUntil": "2025-12-31T23:59:59Z",
    "customFields": [
      { "key": "Full Name", "value": "John Doe", "isPublic": true }
    ]
  }'

curl — scan pass

curl -X POST https://your-domain.com/api/v1/scan \
  -H "Authorization: Bearer 2pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "code": "AbCd1234EfGh" }'

curl — subscribe webhook

curl -X POST https://your-domain.com/api/v1/webhooks \
  -H "Authorization: Bearer 2pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "pass.scanned",
    "targetUrl": "https://example.com/hook"
  }'

JavaScript (fetch)

const response = await fetch("https://your-domain.com/api/v1/passes", {
  method: "POST",
  headers: {
    "Authorization": "Bearer 2pk_your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "single",
    name: "VIP Guest",
    customFields: [
      { key: "Full Name", value: "John Doe", isPublic: true }
    ],
  }),
});

const data = await response.json();
console.log(data.publicUrl);