Документация API
Интегрируйте создание пассов в ваши приложения
Contents
Аутентификация
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
Базовый 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.
Лимиты запросов
- 30 requests per minute per API key for
/passes,/clients,/scan - 10 requests per minute per API key for
/webhooksand/triggers - Rate limit headers in responses:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset - When rate limited, a
429response includes aRetry-Afterheader
Коды ошибок
| Code | Description |
|---|---|
400 | Bad Request — invalid input or JSON body |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — plan limit reached, not on Pro plan, or webhook quota exceeded |
404 | Not Found — resource does not exist or belongs to another tenant |
429 | Too Many Requests — rate limit exceeded |
500 | Internal Server Error |
All errors return JSON: { "error": "message" }
Эндпоинты
GET/passesСписок пассов
Параметры запроса
| Field | Type | Description |
|---|---|---|
search | string | Match by pass name or code |
status | string | active, used, expired, revoked |
limit | number | Page size (default 25, max 100) |
page | number | Page number (default 1) |
Ответ (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": "john@example.com" },
"publicUrl": "https://your-domain.com/p/AbCd1234EfGh?token=...",
"createdAt": "2025-06-01T12:00:00.000Z"
}
]POST/passesСоздание пасса
Тело запроса
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "single" or "multi" |
name | string | No | Human-readable label |
note | string | No | Internal note (not shown on public page) |
clientName | string | No | Auto-create or attach client by name |
maxUsages | number | No | Max scan count for multi passes (null = unlimited) |
validFrom | string | No | ISO 8601 date (e.g. "2025-06-01T00:00:00Z") |
validUntil | string | No | ISO 8601 date |
customFields | array | No | Array of { "key": "...", "value": "...", "isPublic": true } |
templateId | string | No | Pass template ID for styling |
locationId | string | No | Location ID to attach |
Ответ (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/:passIdПолучить пасс
Returns the full pass including custom fields and attached client.
Ответ (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": "john@example.com", "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/:passIdОбновить пасс
Тело запроса
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Update label |
note | string | No | Internal note |
status | string | No | "active" or "revoked" |
maxUsages | number | null | No | Update scan limit |
validFrom | string | null | No | ISO 8601 date or null |
validUntil | string | null | No | ISO 8601 date or null |
customFields | array | No | Replaces all existing fields |
When customFields is provided, all existing fields are replaced. Status change emits a pass.status_changed webhook.
POST/passes/:passId/activateАктивировать пасс
Sets status to active. Returns 400 if already active. Emits pass.status_changed and notifies wallet apps.
Ответ (200)
{ "id": "clx1abc...", "code": "AbCd1234EfGh", "name": "VIP Guest", "status": "active" }POST/passes/:passId/revokeОтозвать пасс
Sets status to revoked. Returns 400 if already revoked. Emits pass.status_changed and notifies wallet apps.
Ответ (200)
{ "id": "clx1abc...", "code": "AbCd1234EfGh", "name": "VIP Guest", "status": "revoked" }POST/scanСканировать пасс
Тело запроса
| Field | Type | Required | Description |
|---|---|---|---|
code | string | Yes | The pass code (from QR or public URL) |
locationId | string | No | Location where the scan is performed |
timezone | string | No | IANA timezone used for calendar-day limits (default UTC) |
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.locationId is optional; when supplied, it must belong to this organization. The recorded location name is snapshotted so historical scans are unaffected by later renames or deletion.timezone is optional and should be an IANA timezone such as Europe/London. Missing, invalid, or offset-only values fall back to UTC.
Ответ (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",
"location": { "id": "loc_123", "name": "Downtown Shop" }
}
}Usage limit reached (200)
{
"success": false,
"result": "redemption_limit_reached",
"code": "redemption_limit_reached",
"message": "This pass has reached its usage limit.",
"lastRedemptionAt": "2026-08-16T08:42:13.000Z",
"nextRedemptionAt": "2026-08-16T22:00:00.000Z",
"redemptionLimit": { "type": "daily", "limit": 2 },
"timezone": "Europe/Podgorica"
}On failure, response is 200 with success: false and result:revoked, already_used, not_yet_valid, expired, max_usages_reached, redemption_limit_reached.
GET/clientsСписок клиентов
Параметры запроса
| Field | Type | Description |
|---|---|---|
search | string | Match by name, email or phone |
limit | number | Page size (default 25, max 100) |
page | number | Page number (default 1) |
Ответ (200)
[
{
"id": "clx2...",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"notes": null,
"createdAt": "2025-06-01T12:00:00.000Z"
}
]POST/clientsСоздать клиента
Тело запроса
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Client display name |
email | string | No | Email address |
phone | string | No | Phone number |
notes | string | No | Internal notes |
Ответ (201)
{
"id": "clx2...",
"name": "John Doe",
"email": "john@example.com",
"phone": null,
"notes": null,
"createdAt": "2025-06-01T12:00:00.000Z"
}Emits client.created webhook.
GET/clients/:clientIdПолучить клиента
Ответ (200)
{
"id": "clx2...",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"notes": "VIP customer",
"createdAt": "2025-06-01T12:00:00.000Z"
}GET/webhooksСписок вебхуков
Ответ (200)
[
{
"id": "wh_...",
"event": "pass.scanned",
"targetUrl": "https://example.com/hook",
"active": true,
"createdAt": "2025-06-01T12:00:00.000Z"
}
]POST/webhooksПодписаться на вебхук
Тело запроса
| Field | Type | Required | Description |
|---|---|---|---|
event | string | Yes | One of: pass.created, pass.scanned, pass.status_changed, client.created |
targetUrl | string | Yes | HTTPS URL receiving POST payloads |
Tenant has a fixed webhook quota — exceeding it returns 403. Subscribed events POST a JSON payload to targetUrl.
Ответ (201)
{
"id": "wh_...",
"event": "pass.scanned",
"targetUrl": "https://example.com/hook",
"active": true,
"createdAt": "2025-06-01T12:00:00.000Z"
}pass.scanned payload
{
"event": "pass.scanned",
"timestamp": "2026-08-14T12:30:00.000Z",
"data": {
"id": "scan_123",
"passId": "pass_123",
"code": "AbCd1234EfGh",
"result": "success",
"scannedAt": "2026-08-14T12:30:00.000Z",
"location": {
"id": "loc_123",
"name": "Downtown Shop"
},
"scannedById": "user_123",
"scannedByName": "Shop Operator",
"scannedByEmail": "operator@example.com"
}
}location is null when no location was supplied. Only successful scans emit pass.scanned; unsuccessful attempts remain available in scan history.
DELETE/webhooks/:webhookIdОтписаться от вебхука
Ответ (200)
{ "success": true }GET/triggersОпрос триггеров
Polling fallback used by Zapier. Returns recent items for the requested event since a given timestamp.
Параметры запроса
| Field | Type | Required | Description |
|---|---|---|---|
event | string | Yes | One of: pass.created, pass.scanned, pass.status_changed, client.created |
since | string | No | ISO 8601 date — return items after this time (default last 24h) |
limit | number | No | Max 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 scan record with location: { id, name } | null; for pass.status_changed a pass with changedAt; for client.created the client record.
Примеры
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", "locationId": "loc_123", "timezone": "Europe/London" }'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);
