REST API·v1.4 OpenAPI 3.0

API reference

The Passyn REST API is organized around predictable resource-oriented URLs. All requests accept JSON-encoded bodies and return standard HTTP response codes and JSON error objects.

OpenAPI 3.0 SpecLaunch API Playground

Authentication

Every request to /api/v1/* authenticates with an API key in the Authorization header. Test keys (pk_test_…) operate on sandbox semantics; live keys (pk_live_…) move real money. Keys carry scoped permissions — a request without the required scope returns 403 forbidden.

curl https://api.passyn.dev/v1/users/me \
-H "Authorization: Bearer pk_test_c187…"

Rate limiting

Limits are enforced per key, per minute — 100 req/min on free keys, 1,000 on paid, custom on Enterprise. Every response includes standard rate-limit headers; on HTTP 429, back off until X-RateLimit-Reset.

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1765459200

Webhook events

Passyn signs every delivery with Passyn-Signature: t=<unix>,v1=<hmac> where v1 is HMAC-SHA256 of `${t}.${body}` with your endpoint secret. Reject anything older than 5 minutes.

pass.purchased

A pass was bought; the transaction succeeded.

pass.activated

Access flipped to ACTIVE — the clock started.

pass.expired

Access ended by time or usage limit.

pass.revoked

Access was manually revoked.

settlement.paid

A payout to your account was completed.

Error codes

Failures use a uniform JSON envelope with success: false and a typed error object.

CodeHTTPMeaning
unauthorized401Missing or invalid API key.
revoked_key401The API key has been revoked.
expired_key401The API key has passed its expiry date.
suspended_account403The owning account is suspended or banned.
forbidden403The key lacks the required permission scope.
not_found404The requested resource does not exist (or isn't yours).
email_taken409An account with this email already exists.
validation_error422The payload failed validation — details lists each field.
rate_limited429Rate limit exceeded — retry after X-RateLimit-Reset.
internal_error500Unexpected server error — safe to retry with backoff.
POST/v1/passes
passes:write

Create a pass

Creates a pass in the merchant context of the API key. Merchant-scoped keys only.

Parameters

NameInTypeDescription
namerequiredbodystringDisplay name, 2–120 chars.
typebodyenumDAY | WEEKEND | WEEKLY | EXAM | PROJECT | CUSTOM (default DAY).
durationHoursrequiredbodyintegerAccess window after activation, 1–8760.
pricerequiredbodynumberPrice in major units, e.g. 12.50.
currencybodystringISO 4217 code (default USD).
maxUsesbodyinteger|nullUsage cap within the window. null = unlimited.
isActivebodybooleanPublish immediately (default false = draft).
descriptionbodystringCustomer-facing description.
colorbodystringHex accent color, e.g. #F4B740.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("POST", "/v1/passes", {
"name": "example",
"type": "example",
"durationHours": 24,
"price": 12.5
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "pass_cm4xyz…",
"name": "Gym Day Pass",
"type": "DAY",
"durationHours": 24,
"price": 12,
"currency": "USD",
"maxUses": null,
"isActive": true,
"createdAt": "2026-06-11T09:00:00.000Z"
},
"error": null,
"meta": {}
}
GET/v1/passes
passes:read

List passes

Lists passes. Merchant-scoped keys see their own passes (drafts included); developer keys see published passes across the platform.

Parameters

NameInTypeDescription
typequeryenumFilter by pass type.
activequerybooleanFilter by published state.
merchantIdquerystringFilter by merchant (developer keys).
limitqueryintegerPage size, 1–100 (default 20).
offsetqueryintegerPagination offset (default 0).

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/passes?type=example&active=true");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": [
{
"id": "pass_cm4xyz…",
"name": "Gym Day Pass",
"type": "DAY",
"price": 12
}
],
"error": null,
"meta": {
"total": 42,
"limit": 20,
"offset": 0
}
}
GET/v1/passes/:id
passes:read

Get a pass

Retrieves a single pass by id.

Parameters

NameInTypeDescription
idrequiredpathstringPass id.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/passes/pass_cm4xyz");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "pass_cm4xyz…",
"name": "Gym Day Pass",
"type": "DAY",
"durationHours": 24,
"price": 12
},
"error": null,
"meta": {}
}
PATCH/v1/passes/:id
passes:write

Update a pass

Partially updates a pass you own. All body fields are optional.

Parameters

NameInTypeDescription
idrequiredpathstringPass id.
namebodystringNew display name.
pricebodynumberNew price.
isActivebodybooleanPublish / unpublish.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("PATCH", "/v1/passes/pass_cm4xyz", {
"name": "example",
"price": 12.5,
"isActive": true
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "pass_cm4xyz…",
"name": "Gym Day Pass",
"isActive": false
},
"error": null,
"meta": {}
}
DELETE/v1/passes/:id
passes:write

Archive a pass

Archives a pass (soft delete): removed from sale immediately; existing accesses keep working until they expire.

Parameters

NameInTypeDescription
idrequiredpathstringPass id.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("DELETE", "/v1/passes/pass_cm4xyz");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "pass_cm4xyz…",
"archived": true
},
"error": null,
"meta": {}
}
POST/v1/access/activate
access:write

Activate a pass

Activates access for a user. Provide an existing accessId (from a purchase), or userId + passId to create-and-activate directly (API-driven issuance). Idempotent on already-active accesses.

Parameters

NameInTypeDescription
accessIdbodystringExisting pending access to activate.
userIdbodystringUser to grant access to (with passId).
passIdbodystringPass to grant (with userId).

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("POST", "/v1/access/activate", {
"accessId": "example",
"userId": "example",
"passId": "example"
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"accessId": "acc_cm4abc…",
"status": "ACTIVE",
"activatedAt": "2026-06-11T09:00:00.000Z",
"expiresAt": "2026-06-12T09:00:00.000Z"
},
"error": null,
"meta": {}
}
GET/v1/access/verify
access:read

Verify access

The hot path: does this user have valid access right now? Checks expiry and usage limits, settles lazy expiration, and (optionally) consumes one use.

Parameters

NameInTypeDescription
accessIdquerystringVerify a specific access.
userIdquerystringVerify by user (with passId).
passIdquerystringVerify by pass (with userId).
consumequerybooleanCount this verification as a use.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/access/verify?accessId=example&userId=example");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"valid": true,
"expiresAt": "2026-06-12T09:00:00.000Z",
"remainingUses": 4,
"accessId": "acc_cm4abc…"
},
"error": null,
"meta": {}
}
POST/v1/access/revoke
access:write

Revoke access

Immediately revokes an access. Emits pass.revoked and writes an audit entry.

Parameters

NameInTypeDescription
accessIdrequiredbodystringAccess to revoke.
reasonbodystringOptional reason, stored on the access.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("POST", "/v1/access/revoke", {
"accessId": "example",
"reason": "example"
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"accessId": "acc_cm4abc…",
"status": "REVOKED"
},
"error": null,
"meta": {}
}
GET/v1/access/history
access:read

Access history

Lists a user's accesses, newest first, including expired and revoked ones.

Parameters

NameInTypeDescription
userIdrequiredquerystringUser id.
limitqueryintegerPage size, 1–100 (default 20).
offsetqueryintegerPagination offset.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/access/history?userId=example&limit=example");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": [
{
"id": "acc_cm4abc…",
"passId": "pass_cm4xyz…",
"status": "EXPIRED",
"usageCount": 3
}
],
"error": null,
"meta": {
"total": 7,
"limit": 20,
"offset": 0
}
}
POST/v1/merchants
merchants:write

Register a merchant

Registers a merchant account (platform partners). The merchant starts in PENDING until approved.

Parameters

NameInTypeDescription
namerequiredbodystringBusiness name.
emailrequiredbodystringOwner email — a user account is provisioned.
websitebodystringBusiness website.
countrybodystringISO 3166-1 alpha-2 (default US).

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("POST", "/v1/merchants", {
"name": "example",
"email": "example",
"website": "example",
"country": "example"
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "mer_cm4def…",
"name": "FitNation Gyms",
"slug": "fitnation-gyms",
"status": "PENDING"
},
"error": null,
"meta": {}
}
GET/v1/merchants/:id
merchants:read

Get a merchant

Retrieves a merchant's public profile.

Parameters

NameInTypeDescription
idrequiredpathstringMerchant id.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/merchants/pass_cm4xyz");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "mer_cm4def…",
"name": "FitNation Gyms",
"slug": "fitnation-gyms",
"status": "APPROVED"
},
"error": null,
"meta": {}
}
PATCH/v1/merchants/:id
merchants:write

Update a merchant

Updates merchant profile fields. Merchant-scoped keys can only update themselves.

Parameters

NameInTypeDescription
idrequiredpathstringMerchant id.
namebodystringBusiness name.
descriptionbodystringProfile description.
webhookUrlbodystring|nullDefault webhook URL.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("PATCH", "/v1/merchants/pass_cm4xyz", {
"name": "example",
"description": "example",
"webhookUrl": "example"
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "mer_cm4def…",
"name": "FitNation Gyms"
},
"error": null,
"meta": {}
}
POST/v1/users
users:write

Register an end user

Creates (or returns, if the email already exists) a subscriber account to attach accesses to — the typical first call when issuing passes via API.

Parameters

NameInTypeDescription
namerequiredbodystringFull name.
emailrequiredbodystringUnique email.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("POST", "/v1/users", {
"name": "example",
"email": "example"
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "usr_cm4ghi…",
"email": "[email protected]",
"created": true
},
"error": null,
"meta": {}
}
GET/v1/users/me
users:read

Get key context

Returns the identity behind the API key — merchant or developer — plus its permissions and rate limit. Useful as a health/auth check.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/users/me");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"keyId": "key_cm4jkl…",
"mode": "TEST",
"context": "developer",
"permissions": [
"passes:read",
"access:read"
],
"rateLimit": 100
},
"error": null,
"meta": {}
}
PATCH/v1/users/me
users:write

Update key owner profile

Updates the profile of the account that owns this API key.

Parameters

NameInTypeDescription
namebodystringNew display name.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("PATCH", "/v1/users/me", {
"name": "example"
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "usr_cm4ghi…",
"name": "Ada Lovelace"
},
"error": null,
"meta": {}
}
GET/v1/analytics/merchant
analytics:read

Merchant analytics

Revenue, activation and conversion aggregates for the key's merchant.

Parameters

NameInTypeDescription
daysqueryintegerWindow in days, 1–365 (default 30).

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/analytics/merchant?days=example");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"revenue": [
{
"label": "Jun 1",
"value": 240.5
}
],
"activations": [
{
"label": "Jun 1",
"value": 18
}
],
"byType": [
{
"name": "DAY",
"value": 1204
}
]
},
"error": null,
"meta": {
"days": 30
}
}
GET/v1/analytics/platform
platform:read

Platform analytics

Platform-wide aggregates. Requires the admin-issued platform:read scope.

Parameters

NameInTypeDescription
daysqueryintegerWindow in days (default 30).

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/analytics/platform?days=example");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"revenue30": 84210.4,
"activePasses": 3120,
"merchantCount": 1800
},
"error": null,
"meta": {
"days": 30
}
}
POST/v1/webhooks
webhooks:write

Register an endpoint

Registers a webhook endpoint for the key's merchant. The signing secret is returned once, in this response only.

Parameters

NameInTypeDescription
urlrequiredbodystringHTTPS endpoint URL.
eventsrequiredbodystring[]Events to subscribe to, e.g. ["pass.purchased"].

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("POST", "/v1/webhooks", {
"url": "example",
"events": ["pass.purchased"]
});
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "whe_cm4mno…",
"url": "https://api.yourapp.com/webhooks/passyn",
"events": [
"pass.purchased"
],
"secret": "whsec_…"
},
"error": null,
"meta": {}
}
GET/v1/webhooks
webhooks:read

List endpoints

Lists the merchant's endpoints with recent delivery stats.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("GET", "/v1/webhooks");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": [
{
"id": "whe_cm4mno…",
"url": "https://api.yourapp.com/webhooks/passyn",
"isActive": true
}
],
"error": null,
"meta": {}
}
DELETE/v1/webhooks/:id
webhooks:write

Delete an endpoint

Removes a webhook endpoint. In-flight deliveries are not retried.

Parameters

NameInTypeDescription
idrequiredpathstringEndpoint id.

Request

import Passyn from "@passyn/node";
const passyn = new Passyn("pk_test_…");
const result = await passyn.request("DELETE", "/v1/webhooks/pass_cm4xyz");
console.log(result.data);

Response · 200 OK

{
"success": true,
"data": {
"id": "whe_cm4mno…",
"deleted": true
},
"error": null,
"meta": {}
}