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.
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: 100X-RateLimit-Remaining: 97X-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.purchasedA pass was bought; the transaction succeeded.
pass.activatedAccess flipped to ACTIVE — the clock started.
pass.expiredAccess ended by time or usage limit.
pass.revokedAccess was manually revoked.
settlement.paidA payout to your account was completed.
Error codes
Failures use a uniform JSON envelope with success: false and a typed error object.
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid API key. |
revoked_key | 401 | The API key has been revoked. |
expired_key | 401 | The API key has passed its expiry date. |
suspended_account | 403 | The owning account is suspended or banned. |
forbidden | 403 | The key lacks the required permission scope. |
not_found | 404 | The requested resource does not exist (or isn't yours). |
email_taken | 409 | An account with this email already exists. |
validation_error | 422 | The payload failed validation — details lists each field. |
rate_limited | 429 | Rate limit exceeded — retry after X-RateLimit-Reset. |
internal_error | 500 | Unexpected server error — safe to retry with backoff. |
/v1/passesCreate a pass
Creates a pass in the merchant context of the API key. Merchant-scoped keys only.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
namerequired | body | string | Display name, 2–120 chars. |
type | body | enum | DAY | WEEKEND | WEEKLY | EXAM | PROJECT | CUSTOM (default DAY). |
durationHoursrequired | body | integer | Access window after activation, 1–8760. |
pricerequired | body | number | Price in major units, e.g. 12.50. |
currency | body | string | ISO 4217 code (default USD). |
maxUses | body | integer|null | Usage cap within the window. null = unlimited. |
isActive | body | boolean | Publish immediately (default false = draft). |
description | body | string | Customer-facing description. |
color | body | string | Hex 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": {}}
/v1/passesList passes
Lists passes. Merchant-scoped keys see their own passes (drafts included); developer keys see published passes across the platform.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
type | query | enum | Filter by pass type. |
active | query | boolean | Filter by published state. |
merchantId | query | string | Filter by merchant (developer keys). |
limit | query | integer | Page size, 1–100 (default 20). |
offset | query | integer | Pagination 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}}
/v1/passes/:idGet a pass
Retrieves a single pass by id.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | Pass 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": {}}
/v1/passes/:idUpdate a pass
Partially updates a pass you own. All body fields are optional.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | Pass id. |
name | body | string | New display name. |
price | body | number | New price. |
isActive | body | boolean | Publish / 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": {}}
/v1/passes/:idArchive a pass
Archives a pass (soft delete): removed from sale immediately; existing accesses keep working until they expire.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | Pass 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": {}}
/v1/access/activateActivate 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
| Name | In | Type | Description |
|---|---|---|---|
accessId | body | string | Existing pending access to activate. |
userId | body | string | User to grant access to (with passId). |
passId | body | string | Pass 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": {}}
/v1/access/verifyVerify 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
| Name | In | Type | Description |
|---|---|---|---|
accessId | query | string | Verify a specific access. |
userId | query | string | Verify by user (with passId). |
passId | query | string | Verify by pass (with userId). |
consume | query | boolean | Count 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": {}}
/v1/access/revokeRevoke access
Immediately revokes an access. Emits pass.revoked and writes an audit entry.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
accessIdrequired | body | string | Access to revoke. |
reason | body | string | Optional 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": {}}
/v1/access/historyAccess history
Lists a user's accesses, newest first, including expired and revoked ones.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
userIdrequired | query | string | User id. |
limit | query | integer | Page size, 1–100 (default 20). |
offset | query | integer | Pagination 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}}
/v1/merchantsRegister a merchant
Registers a merchant account (platform partners). The merchant starts in PENDING until approved.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
namerequired | body | string | Business name. |
emailrequired | body | string | Owner email — a user account is provisioned. |
website | body | string | Business website. |
country | body | string | ISO 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": {}}
/v1/merchants/:idGet a merchant
Retrieves a merchant's public profile.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | Merchant 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": {}}
/v1/merchants/:idUpdate a merchant
Updates merchant profile fields. Merchant-scoped keys can only update themselves.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | Merchant id. |
name | body | string | Business name. |
description | body | string | Profile description. |
webhookUrl | body | string|null | Default 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": {}}
/v1/usersRegister 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
| Name | In | Type | Description |
|---|---|---|---|
namerequired | body | string | Full name. |
emailrequired | body | string | Unique 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…","created": true},"error": null,"meta": {}}
/v1/users/meGet 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": {}}
/v1/users/meUpdate key owner profile
Updates the profile of the account that owns this API key.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
name | body | string | New 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": {}}
/v1/analytics/merchantMerchant analytics
Revenue, activation and conversion aggregates for the key's merchant.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
days | query | integer | Window 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}}
/v1/analytics/platformPlatform analytics
Platform-wide aggregates. Requires the admin-issued platform:read scope.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
days | query | integer | Window 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}}
/v1/webhooksRegister an endpoint
Registers a webhook endpoint for the key's merchant. The signing secret is returned once, in this response only.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
urlrequired | body | string | HTTPS endpoint URL. |
eventsrequired | body | string[] | 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": {}}
/v1/webhooksList 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": {}}
/v1/webhooks/:idDelete an endpoint
Removes a webhook endpoint. In-flight deliveries are not retried.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | Endpoint 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": {}}