{"openapi":"3.0.3","info":{"title":"Passyn API","version":"1.0.0","description":"The Infrastructure Layer for Controlled Short-Term Access. All endpoints return the `{ success, data, error, meta }` envelope and carry X-RateLimit-* headers.","contact":{"name":"Passyn by Blansyn","url":"https://sub.blansyn.com"}},"servers":[{"url":"/api","description":"This deployment"}],"tags":[{"name":"Passes"},{"name":"Access"},{"name":"Merchants"},{"name":"Users"},{"name":"Analytics"},{"name":"Webhooks"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"pk_live_… / pk_test_…"}},"schemas":{"Envelope":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"nullable":true},"error":{"nullable":true,"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"nullable":true}}},"meta":{"type":"object"}},"required":["success","data","error","meta"]}}},"paths":{"/v1/passes":{"post":{"operationId":"passes-create","tags":["Passes"],"summary":"Create a pass","description":"Creates a pass in the merchant context of the API key. Merchant-scoped keys only. Requires scope `passes:write`.","security":[{"apiKey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","durationHours","price"],"properties":{"name":{"type":"string","description":"Display name, 2–120 chars."},"type":{"type":"string","description":"DAY | WEEKEND | WEEKLY | EXAM | PROJECT | CUSTOM (default DAY)."},"durationHours":{"type":"integer","description":"Access window after activation, 1–8760."},"price":{"type":"number","description":"Price in major units, e.g. 12.50."},"currency":{"type":"string","description":"ISO 4217 code (default USD)."},"maxUses":{"type":"integer","nullable":true,"description":"Usage cap within the window. null = unlimited."},"isActive":{"type":"boolean","description":"Publish immediately (default false = draft)."},"description":{"type":"string","description":"Customer-facing description."},"color":{"type":"string","description":"Hex accent color, e.g. #F4B740."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}},"get":{"operationId":"passes-list","tags":["Passes"],"summary":"List passes","description":"Lists passes. Merchant-scoped keys see their own passes (drafts included); developer keys see published passes across the platform. Requires scope `passes:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"type","in":"query","required":false,"description":"Filter by pass type.","schema":{"type":"string"}},{"name":"active","in":"query","required":false,"description":"Filter by published state.","schema":{"type":"boolean"}},{"name":"merchantId","in":"query","required":false,"description":"Filter by merchant (developer keys).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1–100 (default 20).","schema":{"type":"integer"}},{"name":"offset","in":"query","required":false,"description":"Pagination offset (default 0).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":[{"id":"pass_cm4xyz…","name":"Gym Day Pass","type":"DAY","price":12}],"error":null,"meta":{"total":42,"limit":20,"offset":0}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/passes/{id}":{"get":{"operationId":"passes-get","tags":["Passes"],"summary":"Get a pass","description":"Retrieves a single pass by id. Requires scope `passes:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Pass id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"pass_cm4xyz…","name":"Gym Day Pass","type":"DAY","durationHours":24,"price":12},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}},"patch":{"operationId":"passes-update","tags":["Passes"],"summary":"Update a pass","description":"Partially updates a pass you own. All body fields are optional. Requires scope `passes:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Pass id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"name":{"type":"string","description":"New display name."},"price":{"type":"number","description":"New price."},"isActive":{"type":"boolean","description":"Publish / unpublish."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"pass_cm4xyz…","name":"Gym Day Pass","isActive":false},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}},"delete":{"operationId":"passes-delete","tags":["Passes"],"summary":"Archive a pass","description":"Archives a pass (soft delete): removed from sale immediately; existing accesses keep working until they expire. Requires scope `passes:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Pass id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"pass_cm4xyz…","archived":true},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/access/activate":{"post":{"operationId":"access-activate","tags":["Access"],"summary":"Activate a pass","description":"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. Requires scope `access:write`.","security":[{"apiKey":[]}],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"accessId":{"type":"string","description":"Existing pending access to activate."},"userId":{"type":"string","description":"User to grant access to (with passId)."},"passId":{"type":"string","description":"Pass to grant (with userId)."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/access/verify":{"get":{"operationId":"access-verify","tags":["Access"],"summary":"Verify access","description":"The hot path: does this user have valid access right now? Checks expiry and usage limits, settles lazy expiration, and (optionally) consumes one use. Requires scope `access:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"accessId","in":"query","required":false,"description":"Verify a specific access.","schema":{"type":"string"}},{"name":"userId","in":"query","required":false,"description":"Verify by user (with passId).","schema":{"type":"string"}},{"name":"passId","in":"query","required":false,"description":"Verify by pass (with userId).","schema":{"type":"string"}},{"name":"consume","in":"query","required":false,"description":"Count this verification as a use.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"valid":true,"expiresAt":"2026-06-12T09:00:00.000Z","remainingUses":4,"accessId":"acc_cm4abc…"},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/access/revoke":{"post":{"operationId":"access-revoke","tags":["Access"],"summary":"Revoke access","description":"Immediately revokes an access. Emits pass.revoked and writes an audit entry. Requires scope `access:write`.","security":[{"apiKey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accessId"],"properties":{"accessId":{"type":"string","description":"Access to revoke."},"reason":{"type":"string","description":"Optional reason, stored on the access."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"accessId":"acc_cm4abc…","status":"REVOKED"},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/access/history":{"get":{"operationId":"access-history","tags":["Access"],"summary":"Access history","description":"Lists a user's accesses, newest first, including expired and revoked ones. Requires scope `access:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"userId","in":"query","required":true,"description":"User id.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1–100 (default 20).","schema":{"type":"integer"}},{"name":"offset","in":"query","required":false,"description":"Pagination offset.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":[{"id":"acc_cm4abc…","passId":"pass_cm4xyz…","status":"EXPIRED","usageCount":3}],"error":null,"meta":{"total":7,"limit":20,"offset":0}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/merchants":{"post":{"operationId":"merchants-create","tags":["Merchants"],"summary":"Register a merchant","description":"Registers a merchant account (platform partners). The merchant starts in PENDING until approved. Requires scope `merchants:write`.","security":[{"apiKey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string","description":"Business name."},"email":{"type":"string","description":"Owner email — a user account is provisioned."},"website":{"type":"string","description":"Business website."},"country":{"type":"string","description":"ISO 3166-1 alpha-2 (default US)."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"mer_cm4def…","name":"FitNation Gyms","slug":"fitnation-gyms","status":"PENDING"},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/merchants/{id}":{"get":{"operationId":"merchants-get","tags":["Merchants"],"summary":"Get a merchant","description":"Retrieves a merchant's public profile. Requires scope `merchants:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Merchant id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"mer_cm4def…","name":"FitNation Gyms","slug":"fitnation-gyms","status":"APPROVED"},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}},"patch":{"operationId":"merchants-update","tags":["Merchants"],"summary":"Update a merchant","description":"Updates merchant profile fields. Merchant-scoped keys can only update themselves. Requires scope `merchants:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Merchant id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"name":{"type":"string","description":"Business name."},"description":{"type":"string","description":"Profile description."},"webhookUrl":{"type":"string","nullable":true,"description":"Default webhook URL."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"mer_cm4def…","name":"FitNation Gyms"},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/users":{"post":{"operationId":"users-create","tags":["Users"],"summary":"Register an end user","description":"Creates (or returns, if the email already exists) a subscriber account to attach accesses to — the typical first call when issuing passes via API. Requires scope `users:write`.","security":[{"apiKey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string","description":"Full name."},"email":{"type":"string","description":"Unique email."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"usr_cm4ghi…","email":"ada@example.com","created":true},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/users/me":{"get":{"operationId":"users-me","tags":["Users"],"summary":"Get key context","description":"Returns the identity behind the API key — merchant or developer — plus its permissions and rate limit. Useful as a health/auth check. Requires scope `users:read`.","security":[{"apiKey":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"keyId":"key_cm4jkl…","mode":"TEST","context":"developer","permissions":["passes:read","access:read"],"rateLimit":100},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}},"patch":{"operationId":"users-me-update","tags":["Users"],"summary":"Update key owner profile","description":"Updates the profile of the account that owns this API key. Requires scope `users:write`.","security":[{"apiKey":[]}],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"name":{"type":"string","description":"New display name."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"usr_cm4ghi…","name":"Ada Lovelace"},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/analytics/merchant":{"get":{"operationId":"analytics-merchant","tags":["Analytics"],"summary":"Merchant analytics","description":"Revenue, activation and conversion aggregates for the key's merchant. Requires scope `analytics:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"days","in":"query","required":false,"description":"Window in days, 1–365 (default 30).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/analytics/platform":{"get":{"operationId":"analytics-platform","tags":["Analytics"],"summary":"Platform analytics","description":"Platform-wide aggregates. Requires the admin-issued platform:read scope. Requires scope `platform:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"days","in":"query","required":false,"description":"Window in days (default 30).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"revenue30":84210.4,"activePasses":3120,"merchantCount":1800},"error":null,"meta":{"days":30}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/webhooks":{"post":{"operationId":"webhooks-create","tags":["Webhooks"],"summary":"Register an endpoint","description":"Registers a webhook endpoint for the key's merchant. The signing secret is returned once, in this response only. Requires scope `webhooks:write`.","security":[{"apiKey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","description":"HTTPS endpoint URL."},"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to, e.g. [\"pass.purchased\"]."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"whe_cm4mno…","url":"https://api.yourapp.com/webhooks/passyn","events":["pass.purchased"],"secret":"whsec_…"},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}},"get":{"operationId":"webhooks-list","tags":["Webhooks"],"summary":"List endpoints","description":"Lists the merchant's endpoints with recent delivery stats. Requires scope `webhooks:read`.","security":[{"apiKey":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":[{"id":"whe_cm4mno…","url":"https://api.yourapp.com/webhooks/passyn","isActive":true}],"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/webhooks/{id}":{"delete":{"operationId":"webhooks-delete","tags":["Webhooks"],"summary":"Delete an endpoint","description":"Removes a webhook endpoint. In-flight deliveries are not retried. Requires scope `webhooks:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Endpoint id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"success":true,"data":{"id":"whe_cm4mno…","deleted":true},"error":null,"meta":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"The owning account is suspended or banned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"The requested resource does not exist (or isn't yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"422":{"description":"The payload failed validation — details lists each field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limit exceeded — retry after X-RateLimit-Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}}}}