diff --git a/.changeset/generated-types-pagination.md b/.changeset/generated-types-pagination.md new file mode 100644 index 0000000..b4c0e5c --- /dev/null +++ b/.changeset/generated-types-pagination.md @@ -0,0 +1,23 @@ +--- +'@passmint/node': minor +--- + +Response types are now derived from the API's OpenAPI spec. Every 0.4.0 type name is still exported. A few types, such as `TemplateDesign` and `PassImages`, are still hand-written. + +New: + +- `passes.createDownloadLink()` and `passes.redemptions()`. +- `startingAfter` on list methods, and `listAll()` auto-pagination on `passes`, `templates`, `webhooks` and `events`, plus `passes.listAllEvents()` and `webhooks.listAllDeliveries()`. +- `templates.list()` takes `includeArchived`, `limit` and `startingAfter`. +- Types for `issuerName`, `locations` and `relevantDate` on template designs, `redemption_policy`, `require_download_link`, the `delivery` and `warnings` fields on pass updates, `warnings` on pass voids and on templates. +- The `pass.update_failed` and `pass.redeemed` webhook events. +- `PassEventType` is a new export. + +Runtime change: `PASSMINT_EVENT_TYPES` now has 9 entries (adds `pass.update_failed` and `pass.redeemed`). Code that subscribes webhooks with `events: [...PASSMINT_EVENT_TYPES]` will start receiving those events. + +Type-level changes: + +- Response types gained fields the API was already returning, for example `Pass.redeemed_count` and `Account.plan`. Reading and casting are unaffected, but object literals you type as `Pass`, `Template` or `Account` by hand need the new fields. +- Event type unions gained `pass.update_failed` and `pass.redeemed`, and `PassEvent['type']` gained `update_failed`, `redeemed`, `download_link_created` and `update_not_delivered`. Exhaustive switches and `Record` maps will flag the new members. +- `Pass.field_values` and `Account.organization_slug` keep their 0.4.0 types for compatibility; see their docs for what the API can actually return. +- Exported response types are now type aliases rather than interfaces. `extends` and `implements` still work; declaration merging into them no longer does. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1854a4d..e01cba5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: node-version: '22' cache: pnpm - run: pnpm install --frozen-lockfile + - run: pnpm generate:check - run: pnpm lint - run: pnpm type-check - run: pnpm build diff --git a/README.md b/README.md index 6f397e0..35aae6f 100644 --- a/README.md +++ b/README.md @@ -129,12 +129,15 @@ API keys starting with `pmk_test_` run against test mode, `pmk_live_` run agains | --- | --- | | `create(params, options?)` | Issue a new pass against a template. | | `retrieve(id)` | Fetch a pass by id. | -| `list(params?)` | List passes, filter by template or holder email. | -| `update(id, params, options?)` | Update field values or metadata on a pass. | -| `void(id)` | Void a pass. | +| `list(params?)` | List passes, filter by template or holder email. Takes `limit` and `startingAfter`. | +| `listAll(params?)` | Async iterator over every matching pass (see Pagination). | +| `update(id, params, options?)` | Update field values or metadata on a pass. Resolves with the pass plus `delivery` (per-wallet push counts) and `warnings`. | +| `void(id)` | Void a pass. Resolves with the pass plus `warnings`. | | `uploadImage(id, slot, data)` | Give this pass its own `strip`, `thumbnail` or `background` image. Returns the updated pass. | | `deleteImage(id, slot)` | Remove the pass's own image for a slot. Returns the updated pass. | -| `events(id)` | List raw lifecycle events for a pass (internal vocabulary, e.g. `installed`). Use `passmint.events` for the canonical `pass.*` stream. | +| `events(id, params?)` | List raw lifecycle events for a pass (internal vocabulary, e.g. `installed`). Use `passmint.events` for the canonical `pass.*` stream. Takes `limit` and `startingAfter`; `listAllEvents(id)` iterates every page. | +| `redemptions(id, params?)` | The pass's scan log: accepted and rejected redemption attempts. | +| `createDownloadLink(id, params?)` | Create a short-lived download link for the pass (`expiresIn` in seconds). | `create` and `update` accept an optional `idempotencyKey` in `options`. If omitted, the SDK generates one for you so retries are safe by default. @@ -142,6 +145,18 @@ API keys starting with `pmk_test_` run against test mode, `pmk_live_` run agains `create` and `update` accept `imageVariant` to render the pass with one of its template's image variants (see below). On `update`, `null` clears it back to the base images. +#### Download links and redemptions + +```ts +const link = await passmint.passes.createDownloadLink(pass.id, { expiresIn: 3600 }) +console.log(link.url, link.expires_at) + +const { data: scans } = await passmint.passes.redemptions(pass.id, { limit: 20 }) +for (const scan of scans) console.log(scan.decision, scan.reason, scan.scanned_at) +``` + +Templates take a `redemptionPolicy` (`{ mode: 'single_use' | 'reusable' | 'count', maxUses? }`) and `requireDownloadLink` on `create` and `update`. Template responses include `warnings` when the saved design breaks a recommended rule, and template designs accept `issuerName`, `locations` and `relevantDate`. + #### Per-pass images A pass can have its own `strip`, `thumbnail` or `background` image, such as a member photo: @@ -163,7 +178,8 @@ Images are PNG, JPEG or WebP, up to 8 MB. Both methods re-render the pass and pu | --- | --- | | `create(params, options?)` | Create a template. | | `retrieve(id)` | Fetch a template by id. | -| `list()` | List all templates. | +| `list(params?)` | List templates. Archived ones are left out unless `includeArchived: true`. Takes `limit` and `startingAfter`. | +| `listAll(params?)` | Async iterator over every matching template. | | `update(id, params)` | Update name, design, platforms, credentials, or archive state. | | `archive(id)` | Archive a template. | | `uploadImage(id, slot, data, options?)` | Upload a PNG/JPEG (`Uint8Array`, `ArrayBuffer` or `Buffer`) to an image slot, optionally as a named `variant`. | @@ -210,7 +226,19 @@ const { data, has_more } = await passmint.events.list({ const next = await passmint.events.list({ startingAfter: data.at(-1)!.id }) ``` -Canonical event types: `pass.issued`, `pass.add_intent`, `pass.added_to_wallet`, `pass.update_pushed`, `pass.update_delivered`, `pass.removed`, `pass.voided` (exported as `PASSMINT_EVENT_TYPES`). +Canonical event types: `pass.issued`, `pass.add_intent`, `pass.added_to_wallet`, `pass.update_pushed`, `pass.update_delivered`, `pass.update_failed`, `pass.removed`, `pass.voided`, `pass.redeemed` (exported as `PASSMINT_EVENT_TYPES`). + +### Pagination + +List endpoints return `{ data, has_more }` and take `startingAfter` (the last item's id). To walk every page, use `listAll`, which fetches pages as you iterate and stops when `has_more` is false: + +```ts +for await (const pass of passmint.passes.listAll({ templateId: 'tmpl_123' })) { + console.log(pass.id) +} +``` + +`passes.redemptions` takes `limit` only (no `startingAfter`). `listAll` exists on `passes`, `templates`, `webhooks` and `events`. For your own list calls, `autoPaginate((startingAfter) => fetchPage(startingAfter))` is exported too. ### Metrics — `passmint.metrics` @@ -233,11 +261,11 @@ Manage webhook endpoints: | Method | Description | | --- | --- | | `create(params)` | Create an endpoint. Returns the signing `secret` **once** — store it. | -| `retrieve(id)` / `list()` | Fetch endpoints. | +| `retrieve(id)` / `list(params?)` | Fetch endpoints. `list` takes `limit` and `startingAfter`; `listAll()` iterates every page. | | `update(id, params)` | Change url, subscribed events, description, or enabled state. | | `delete(id)` | Delete an endpoint. | | `backfill(id, { since, until? })` | Re-emit historical events to one endpoint. | -| `deliveries(id)` | Last 100 delivery attempts, newest first. | +| `deliveries(id, params?)` | Delivery attempts, newest first (last 100 by default). Takes `limit` and `startingAfter`; `listAllDeliveries(id)` iterates every page. | | `replayDelivery(id, deliveryId)` | Re-queue a delivery (including dead ones). | ```ts diff --git a/biome.json b/biome.json index 9a2a11c..1dc8500 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,15 @@ "useIgnoreFile": true }, "files": { - "ignore": ["dist", "node_modules", ".turbo", "coverage", "ecosystem-tests"] + "ignore": [ + "dist", + "node_modules", + ".turbo", + "coverage", + "ecosystem-tests", + "src/generated", + "openapi.json" + ] }, "linter": { "enabled": true, diff --git a/ecosystem-tests/bun/smoke.mjs b/ecosystem-tests/bun/smoke.mjs index ebfdc3b..62d590f 100644 --- a/ecosystem-tests/bun/smoke.mjs +++ b/ecosystem-tests/bun/smoke.mjs @@ -24,7 +24,17 @@ assert.equal(typeof Passmint, 'function') assert.equal(typeof PassmintError, 'function') assert.equal(typeof PassmintAuthError, 'function') assert.equal(typeof PassmintRateLimitError, 'function') -assert.equal(PASSMINT_EVENT_TYPES.length, 7) +assert.deepEqual([...PASSMINT_EVENT_TYPES], [ + 'pass.issued', + 'pass.add_intent', + 'pass.added_to_wallet', + 'pass.update_pushed', + 'pass.update_delivered', + 'pass.update_failed', + 'pass.removed', + 'pass.voided', + 'pass.redeemed', +]) assert.equal(detectMode(apiKey), 'test') const passmint = new Passmint({ apiKey, baseUrl }) diff --git a/ecosystem-tests/deno/smoke.mjs b/ecosystem-tests/deno/smoke.mjs index ebfdc3b..62d590f 100644 --- a/ecosystem-tests/deno/smoke.mjs +++ b/ecosystem-tests/deno/smoke.mjs @@ -24,7 +24,17 @@ assert.equal(typeof Passmint, 'function') assert.equal(typeof PassmintError, 'function') assert.equal(typeof PassmintAuthError, 'function') assert.equal(typeof PassmintRateLimitError, 'function') -assert.equal(PASSMINT_EVENT_TYPES.length, 7) +assert.deepEqual([...PASSMINT_EVENT_TYPES], [ + 'pass.issued', + 'pass.add_intent', + 'pass.added_to_wallet', + 'pass.update_pushed', + 'pass.update_delivered', + 'pass.update_failed', + 'pass.removed', + 'pass.voided', + 'pass.redeemed', +]) assert.equal(detectMode(apiKey), 'test') const passmint = new Passmint({ apiKey, baseUrl }) diff --git a/ecosystem-tests/mock-server.mjs b/ecosystem-tests/mock-server.mjs index a0cf6f7..80fd613 100644 --- a/ecosystem-tests/mock-server.mjs +++ b/ecosystem-tests/mock-server.mjs @@ -24,8 +24,10 @@ const CANONICAL_EVENT_TYPES = [ 'pass.added_to_wallet', 'pass.update_pushed', 'pass.update_delivered', + 'pass.update_failed', 'pass.removed', 'pass.voided', + 'pass.redeemed', ] function errorBody(type, message, extra = {}) { diff --git a/ecosystem-tests/node-js/smoke.mjs b/ecosystem-tests/node-js/smoke.mjs index ebfdc3b..62d590f 100644 --- a/ecosystem-tests/node-js/smoke.mjs +++ b/ecosystem-tests/node-js/smoke.mjs @@ -24,7 +24,17 @@ assert.equal(typeof Passmint, 'function') assert.equal(typeof PassmintError, 'function') assert.equal(typeof PassmintAuthError, 'function') assert.equal(typeof PassmintRateLimitError, 'function') -assert.equal(PASSMINT_EVENT_TYPES.length, 7) +assert.deepEqual([...PASSMINT_EVENT_TYPES], [ + 'pass.issued', + 'pass.add_intent', + 'pass.added_to_wallet', + 'pass.update_pushed', + 'pass.update_delivered', + 'pass.update_failed', + 'pass.removed', + 'pass.voided', + 'pass.redeemed', +]) assert.equal(detectMode(apiKey), 'test') const passmint = new Passmint({ apiKey, baseUrl }) diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..8428b2a --- /dev/null +++ b/openapi.json @@ -0,0 +1,7885 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Passmint API", + "version": "1.0.0", + "description": "Issue and manage Apple Wallet and Google Wallet passes. All endpoints are authenticated with a secret API key." + }, + "servers": [ + { + "url": "https://api.passmint.com" + } + ], + "components": { + "securitySchemes": { + "scannerToken": { + "type": "http", + "scheme": "bearer", + "description": "A scanner device token, obtained from POST /v1/scanners/pair." + }, + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "Your Passmint secret API key, e.g. `pmk_live_…` or `pmk_test_…`. Test keys never touch live data." + } + }, + "schemas": { + "CreateTemplateBody": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "event", + "membership", + "coupon", + "loyalty", + "generic" + ] + }, + "apple_style": { + "type": "string", + "enum": [ + "generic", + "eventTicket", + "boardingPass", + "coupon", + "storeCard" + ] + }, + "design": { + "type": "object", + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "logoText": { + "type": [ + "string", + "null" + ] + }, + "foregroundColor": { + "type": "string", + "pattern": "^(rgb\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*\\)|#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)$" + }, + "backgroundColor": { + "type": "string", + "pattern": "^(rgb\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*\\)|#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)$" + }, + "labelColor": { + "type": "string", + "pattern": "^(rgb\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*\\)|#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)$" + }, + "logoImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "iconImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "stripImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "thumbnailImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "headerFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "primaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "secondaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "auxiliaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "backFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "barcodeFormat": { + "type": [ + "string", + "null" + ], + "enum": [ + "PKBarcodeFormatQR", + "PKBarcodeFormatPDF417", + "PKBarcodeFormatAztec", + "PKBarcodeFormatCode128", + null + ] + }, + "barcodeMessageTemplate": { + "type": [ + "string", + "null" + ], + "description": "What the barcode encodes. Use '{{serialNumber}}' — scanning looks passes up by serial number, and it is unique per pass. '{{fieldValues.}}' inserts a field value. No other placeholders exist." + }, + "issuerName": { + "type": [ + "string", + "null" + ], + "maxLength": 64, + "description": "The issuer shown on the pass and in its notifications (Apple organizationName; Google issuer/program name unless logoText is set). Defaults to your organization's name." + }, + "locations": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "minimum": -90, + "maximum": 90 + }, + "longitude": { + "type": "number", + "minimum": -180, + "maximum": 180 + }, + "altitude": { + "type": "number" + }, + "relevantText": { + "type": "string", + "maxLength": 256, + "description": "Lock-screen text Apple Wallet shows near this location." + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + "maxItems": 10, + "description": "Up to 10 places the pass is relevant near. Apple Wallet suggests the pass on the lock screen there; Google Wallet stores them on the pass." + }, + "relevantDate": { + "type": [ + "string", + "null" + ], + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})$", + "description": "ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only." + } + }, + "required": [ + "foregroundColor", + "backgroundColor", + "labelColor" + ] + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + } + }, + "starter_template_id": { + "type": "string" + }, + "require_download_link": { + "type": "boolean" + }, + "redemption_policy": { + "$ref": "#/components/schemas/RedemptionPolicyInput" + } + }, + "required": [ + "name", + "type", + "apple_style", + "design" + ] + }, + "RedemptionPolicyInput": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "single_use", + "reusable", + "count" + ] + }, + "max_uses": { + "type": "integer", + "minimum": 1, + "description": "Required when mode is count." + } + }, + "required": [ + "mode" + ] + }, + "UpdateTemplateBody": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "design": { + "type": "object", + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "logoText": { + "type": [ + "string", + "null" + ] + }, + "foregroundColor": { + "type": "string", + "pattern": "^(rgb\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*\\)|#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)$" + }, + "backgroundColor": { + "type": "string", + "pattern": "^(rgb\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*\\)|#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)$" + }, + "labelColor": { + "type": "string", + "pattern": "^(rgb\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*\\)|#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)$" + }, + "logoImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "iconImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "stripImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "thumbnailImageKey": { + "type": [ + "string", + "null" + ], + "description": "Storage key from an image upload. Leave null to keep none." + }, + "headerFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "primaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "secondaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "auxiliaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "backFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design." + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "textAlignment": { + "type": [ + "string", + "null" + ], + "enum": [ + "left", + "center", + "right", + "natural", + null + ] + }, + "required": {}, + "changeMessage": { + "type": [ + "string", + "null" + ], + "description": "Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder." + } + }, + "required": [ + "key" + ] + } + }, + "barcodeFormat": { + "type": [ + "string", + "null" + ], + "enum": [ + "PKBarcodeFormatQR", + "PKBarcodeFormatPDF417", + "PKBarcodeFormatAztec", + "PKBarcodeFormatCode128", + null + ] + }, + "barcodeMessageTemplate": { + "type": [ + "string", + "null" + ], + "description": "What the barcode encodes. Use '{{serialNumber}}' — scanning looks passes up by serial number, and it is unique per pass. '{{fieldValues.}}' inserts a field value. No other placeholders exist." + }, + "issuerName": { + "type": [ + "string", + "null" + ], + "maxLength": 64, + "description": "The issuer shown on the pass and in its notifications (Apple organizationName; Google issuer/program name unless logoText is set). Defaults to your organization's name." + }, + "locations": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "minimum": -90, + "maximum": 90 + }, + "longitude": { + "type": "number", + "minimum": -180, + "maximum": 180 + }, + "altitude": { + "type": "number" + }, + "relevantText": { + "type": "string", + "maxLength": 256, + "description": "Lock-screen text Apple Wallet shows near this location." + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + "maxItems": 10, + "description": "Up to 10 places the pass is relevant near. Apple Wallet suggests the pass on the lock screen there; Google Wallet stores them on the pass." + }, + "relevantDate": { + "type": [ + "string", + "null" + ], + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})$", + "description": "ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only." + } + }, + "required": [ + "foregroundColor", + "backgroundColor", + "labelColor" + ] + }, + "archived": { + "type": "boolean" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + } + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "google_issuer_id": { + "type": [ + "string", + "null" + ] + }, + "require_download_link": { + "type": "boolean" + }, + "redemption_policy": { + "$ref": "#/components/schemas/RedemptionPolicyInput" + } + } + }, + "CreateWebhookBody": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The HTTPS URL events are POSTed to.", + "example": "https://example.com/hooks/passmint" + }, + "events": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Required, non-empty. Event types to deliver, or `*` for all.", + "example": [ + "pass.redeemed" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "enabled": { + "type": "boolean", + "description": "Defaults to true." + } + }, + "required": [ + "url", + "events" + ] + }, + "UpdateWebhookBody": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "events": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Event types to deliver (for example `pass.redeemed`), or `*` for all of them.", + "example": [ + "pass.redeemed" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "enabled": { + "type": "boolean" + } + } + }, + "BackfillBody": { + "type": "object", + "properties": { + "since": { + "type": "string", + "description": "ISO 8601 start of the window. Required.", + "example": "2026-10-01T00:00:00Z" + }, + "until": { + "type": "string", + "description": "ISO 8601 end of the window. Defaults to now. The window is capped in length." + } + }, + "required": [ + "since" + ] + }, + "PassIssuedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.issued" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "EventPassSnapshot": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "short_id": { + "type": "string" + }, + "template_id": { + "type": "string" + }, + "organization_id": { + "type": "string" + }, + "serial_number": { + "type": "string" + }, + "holder": { + "type": "object", + "properties": { + "email_hash": { + "type": [ + "string", + "null" + ] + }, + "name_present": { + "type": "boolean" + } + }, + "required": [ + "email_hash", + "name_present" + ] + }, + "voided": { + "type": "boolean" + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "The template image variant the pass renders with, or null for the template's own images." + }, + "url": { + "type": "string" + }, + "download_url": { + "type": [ + "string", + "null" + ] + }, + "google_wallet_url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "short_id", + "template_id", + "organization_id", + "serial_number", + "holder", + "voided", + "image_variant", + "url", + "download_url", + "google_wallet_url" + ] + }, + "PassAddIntentEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.add_intent" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "PassAddedToWalletEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.added_to_wallet" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "PassUpdatePushedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.update_pushed" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "PassUpdateDeliveredEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.update_delivered" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "PassUpdateFailedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.update_failed" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + }, + "delivery_failure": { + "$ref": "#/components/schemas/EventDeliveryFailure" + } + }, + "required": [ + "pass", + "delivery_failure" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "EventDeliveryFailure": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "failed": { + "type": [ + "number", + "null" + ] + }, + "devices": { + "type": [ + "number", + "null" + ] + }, + "context": { + "type": [ + "string", + "null" + ], + "enum": [ + "update", + "void", + null + ] + } + }, + "required": [ + "platform", + "reason", + "failed", + "devices", + "context" + ], + "description": "`data.object.delivery_failure`, present on `pass.update_failed`." + }, + "PassRemovedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.removed" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "PassVoidedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.voided" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "PassRedeemedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.redeemed" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + }, + "redemption": { + "$ref": "#/components/schemas/EventRedemption" + } + }, + "required": [ + "pass", + "redemption" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "EventRedemption": { + "type": "object", + "properties": { + "id": { + "type": [ + "string", + "null" + ] + }, + "scanner_device_id": { + "type": [ + "string", + "null" + ] + }, + "scans_used": { + "type": [ + "number", + "null" + ] + }, + "policy_mode": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "scanner_device_id", + "scans_used", + "policy_mode" + ], + "description": "`data.object.redemption`, present on `pass.redeemed`." + }, + "ScannerPairing": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "scanner_pairing" + ] + }, + "token": { + "type": "string" + }, + "device": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "test", + "live" + ] + }, + "template_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "name", + "mode", + "template_id" + ] + } + }, + "required": [ + "object", + "token", + "device" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "authentication_error", + "authorization_error", + "invalid_request_error", + "not_found_error", + "rate_limit_error", + "idempotency_error", + "api_error", + "billing_error" + ] + }, + "code": { + "type": "string", + "description": "A stable, machine-readable code when the error has a specific cause. Not exhaustive: treat unknown codes as the generic error for the `type`. Codes include `api_key_expired` (401, the key's expiry has passed), `insufficient_scope` (403, the key's scopes do not allow this route), `invalid_limit` (400, `limit` is negative, fractional or non-numeric; 0 is valid and a limit over the maximum is clamped), `invalid_cursor` (400, `starting_after` is not an id on this list) and `plan_limit_exceeded` (402).", + "examples": [ + "api_key_expired", + "insufficient_scope", + "invalid_limit", + "invalid_cursor" + ] + }, + "message": { + "type": "string" + }, + "param": { + "type": "string" + } + }, + "required": [ + "type", + "message" + ] + } + }, + "required": [ + "error" + ] + }, + "ScanCodeRequest": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "code" + ] + }, + "Account": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "account" + ] + }, + "organization_id": { + "type": "string" + }, + "organization_name": { + "type": "string" + }, + "organization_slug": { + "type": [ + "string", + "null" + ] + }, + "mode": { + "type": "string", + "enum": [ + "test", + "live" + ] + }, + "plan": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "usage": { + "type": "object", + "properties": { + "period_start": { + "type": [ + "string", + "null" + ] + }, + "api_calls": { + "type": "object", + "properties": { + "used": { + "type": "integer" + }, + "quota": { + "type": [ + "integer", + "null" + ] + }, + "grace_ceiling": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "used", + "quota", + "grace_ceiling" + ] + }, + "active_passes": { + "type": "object", + "properties": { + "used": { + "type": "integer" + }, + "included": { + "type": "integer" + }, + "ceiling": { + "type": "integer" + } + }, + "required": [ + "used", + "included", + "ceiling" + ] + } + }, + "required": [ + "period_start", + "api_calls", + "active_passes" + ], + "description": "Live-mode usage in the current billing period (a rolling 30 days on plans without one). Pass issuance stops at api_calls.quota; other live writes continue to api_calls.grace_ceiling." + }, + "credentials": { + "type": "object", + "properties": { + "apple_certificates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pass_type_identifier": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string", + "enum": [ + "pending_certificate", + "active", + "revoked" + ] + }, + "expires_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "name", + "pass_type_identifier", + "status", + "expires_at" + ] + } + }, + "google_issuers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "issuer_id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "revoked" + ] + } + }, + "required": [ + "id", + "name", + "issuer_id", + "status" + ] + } + } + }, + "required": [ + "apple_certificates", + "google_issuers" + ], + "description": "The organization's own signing credentials. Live passes are only signed with these, never with Passmint's shared test credentials." + } + }, + "required": [ + "object", + "organization_id", + "organization_name", + "organization_slug", + "mode", + "plan", + "usage", + "credentials" + ] + }, + "EventList": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Event" + } + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "object", + "data", + "has_more" + ] + }, + "Event": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "event" + ] + }, + "type": { + "type": "string", + "enum": [ + "pass.issued", + "pass.add_intent", + "pass.added_to_wallet", + "pass.update_pushed", + "pass.update_delivered", + "pass.update_failed", + "pass.removed", + "pass.voided", + "pass.redeemed" + ] + }, + "api_version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "livemode": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "object": { + "type": "object", + "properties": { + "pass": { + "$ref": "#/components/schemas/EventPassSnapshot" + }, + "redemption": { + "$ref": "#/components/schemas/EventRedemption" + }, + "delivery_failure": { + "$ref": "#/components/schemas/EventDeliveryFailure" + } + }, + "required": [ + "pass" + ] + } + }, + "required": [ + "object" + ] + }, + "source": { + "type": "object", + "properties": { + "platform": { + "type": [ + "string", + "null" + ], + "enum": [ + "apple", + "google", + null + ] + }, + "unit": { + "type": [ + "string", + "null" + ], + "enum": [ + "device", + "object", + null + ] + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "best_effort", + "unconfirmed" + ] + } + }, + "required": [ + "platform", + "unit", + "confidence" + ] + }, + "previous_attributes": { + "type": "null" + } + }, + "required": [ + "id", + "object", + "type", + "api_version", + "created_at", + "idempotency_key", + "livemode", + "data", + "source", + "previous_attributes" + ] + }, + "Funnel": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "funnel" + ] + }, + "group_by": { + "type": "string", + "enum": [ + "total", + "template", + "platform", + "date", + "mode" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FunnelSummary" + } + }, + "confidence": { + "$ref": "#/components/schemas/FunnelConfidence" + } + }, + "required": [ + "object", + "group_by", + "data", + "confidence" + ] + }, + "FunnelSummary": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "issued": { + "type": "number" + }, + "add_intent": { + "type": "number" + }, + "added": { + "type": "number" + }, + "active": { + "type": "number" + }, + "removed": { + "type": "number" + }, + "update_pushed": { + "type": "number" + }, + "update_delivered": { + "type": "number" + }, + "install_rate": { + "type": [ + "number", + "null" + ] + }, + "removal_rate": { + "type": [ + "number", + "null" + ] + }, + "update_delivery_rate": { + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "key", + "issued", + "add_intent", + "added", + "active", + "removed", + "update_pushed", + "update_delivered", + "install_rate", + "removal_rate", + "update_delivery_rate" + ] + }, + "FunnelConfidence": { + "type": "object", + "properties": { + "removed": { + "type": "string" + }, + "update_delivery_rate": { + "type": "string" + }, + "active": { + "type": "string" + } + }, + "required": [ + "removed", + "update_delivery_rate", + "active" + ] + }, + "TemplateList": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Template" + } + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "object", + "data", + "has_more" + ] + }, + "Template": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "template" + ] + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "apple_style": { + "type": "string" + }, + "starter_template_id": { + "type": [ + "string", + "null" + ] + }, + "design": { + "$ref": "#/components/schemas/TemplateDesign" + }, + "archived": { + "type": "boolean" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + } + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "google_issuer_id": { + "type": [ + "string", + "null" + ] + }, + "require_download_link": { + "type": "boolean" + }, + "redemption_policy": { + "$ref": "#/components/schemas/RedemptionPolicy" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Present only when the saved design breaks a recommended rule (an Apple field limit, a duplicate field key, an unknown barcode placeholder, an image the pass style cannot show). The template is saved; fix these before they surprise you at issuance." + }, + "image_variants": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The template's image variant names that hold at least one image, sorted. A pass selects one with `image_variant`.", + "example": [ + "0", + "1", + "2", + "reward" + ] + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "object", + "name", + "type", + "apple_style", + "starter_template_id", + "design", + "archived", + "platforms", + "certificate_set_id", + "google_issuer_id", + "require_download_link", + "redemption_policy", + "image_variants", + "created_at", + "updated_at" + ] + }, + "TemplateDesign": { + "type": "object", + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "logoText": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "translations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "default" + ] + }, + { + "type": "null" + } + ] + }, + "foregroundColor": { + "type": "string" + }, + "backgroundColor": { + "type": "string" + }, + "labelColor": { + "type": "string" + }, + "headerFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "translations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "default" + ] + }, + { + "type": "null" + } + ] + }, + "defaultValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "textAlignment": { + "type": "string" + }, + "changeMessage": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "key" + ], + "additionalProperties": {} + } + }, + "primaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "translations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "default" + ] + }, + { + "type": "null" + } + ] + }, + "defaultValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "textAlignment": { + "type": "string" + }, + "changeMessage": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "key" + ], + "additionalProperties": {} + } + }, + "secondaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "translations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "default" + ] + }, + { + "type": "null" + } + ] + }, + "defaultValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "textAlignment": { + "type": "string" + }, + "changeMessage": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "key" + ], + "additionalProperties": {} + } + }, + "auxiliaryFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "translations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "default" + ] + }, + { + "type": "null" + } + ] + }, + "defaultValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "textAlignment": { + "type": "string" + }, + "changeMessage": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "key" + ], + "additionalProperties": {} + } + }, + "backFields": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "translations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "default" + ] + }, + { + "type": "null" + } + ] + }, + "defaultValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "textAlignment": { + "type": "string" + }, + "changeMessage": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "key" + ], + "additionalProperties": {} + } + }, + "barcodeFormat": { + "type": [ + "string", + "null" + ] + }, + "barcodeMessageTemplate": { + "type": [ + "string", + "null" + ] + }, + "issuerName": { + "type": [ + "string", + "null" + ] + }, + "locations": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "relevantDate": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": {}, + "description": "The template's design as stored. See CreateTemplateBody.design for the write contract." + }, + "RedemptionPolicy": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "single_use", + "reusable", + "count" + ] + }, + "maxUses": { + "type": "integer" + } + }, + "required": [ + "mode" + ] + }, + "DeletedTemplate": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "deleted": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "id", + "deleted" + ] + }, + "TemplateImage": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "template_image" + ] + }, + "template_id": { + "type": "string" + }, + "slot": { + "type": "string" + }, + "variant": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "object", + "template_id", + "slot", + "variant" + ] + }, + "DeletedTemplateImage": { + "allOf": [ + { + "$ref": "#/components/schemas/TemplateImage" + }, + { + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "deleted" + ] + } + ] + }, + "WebhookList": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Webhook" + } + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "object", + "data", + "has_more" + ] + }, + "Webhook": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "webhook" + ] + }, + "url": { + "type": "string" + }, + "events": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "enabled": { + "type": "boolean" + }, + "secret": { + "type": "string" + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "object", + "url", + "events", + "description", + "enabled", + "created_at" + ] + }, + "DeletedWebhook": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "deleted": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "id", + "deleted" + ] + }, + "WebhookDeliveryList": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookDelivery" + } + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "object", + "data", + "has_more" + ] + }, + "WebhookDelivery": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "webhook_delivery" + ] + }, + "webhook_id": { + "type": "string" + }, + "event_type": { + "type": "string" + }, + "status": { + "type": "string" + }, + "attempts": { + "type": "number" + }, + "last_attempt_at": { + "type": [ + "string", + "null" + ] + }, + "next_attempt_at": { + "type": [ + "string", + "null" + ] + }, + "response_status": { + "type": [ + "number", + "null" + ] + }, + "response_body": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "object", + "webhook_id", + "event_type", + "status", + "attempts", + "last_attempt_at", + "next_attempt_at", + "response_status", + "response_body", + "created_at" + ] + }, + "WebhookBackfill": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "backfill" + ] + }, + "enqueued": { + "type": "number" + }, + "capped": { + "type": "boolean" + }, + "window": { + "type": "object", + "properties": { + "since": { + "type": "string" + }, + "until": { + "type": "string" + } + }, + "required": [ + "since", + "until" + ] + } + }, + "required": [ + "object", + "enqueued", + "capped", + "window" + ] + }, + "PassList": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pass" + } + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "object", + "data", + "has_more" + ] + }, + "Pass": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "pass" + ] + }, + "short_id": { + "type": "string" + }, + "template_id": { + "type": "string" + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "serial_number": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "holder_email": { + "type": [ + "string", + "null" + ] + }, + "holder_name": { + "type": [ + "string", + "null" + ] + }, + "field_values": { + "type": "object", + "additionalProperties": {}, + "description": "The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is." + }, + "voided": { + "type": "boolean" + }, + "voided_at": { + "type": [ + "string", + "null" + ] + }, + "redeemed_count": { + "type": "integer" + }, + "first_redeemed_at": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "Free-form key/value data you attached to the pass, or null." + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "The template image variant this pass renders with, or null for the template's own images." + }, + "images": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "pass", + "variant", + "template" + ] + } + }, + "required": [ + "source" + ] + }, + "description": "Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs.", + "example": { + "icon": { + "source": "template" + }, + "logo": { + "source": "template" + }, + "thumbnail": { + "source": "pass" + } + } + }, + "created_via_api": { + "type": "boolean" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + }, + "description": "The wallets this pass was issued for." + }, + "platform_status": { + "type": [ + "object", + "null" + ], + "properties": { + "apple": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + }, + "google": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + } + }, + "description": "Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it." + }, + "url": { + "type": "string" + }, + "download_url": { + "type": [ + "string", + "null" + ] + }, + "google_wallet_url": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "object", + "short_id", + "template_id", + "certificate_set_id", + "serial_number", + "mode", + "holder_email", + "holder_name", + "field_values", + "voided", + "voided_at", + "redeemed_count", + "first_redeemed_at", + "metadata", + "image_variant", + "images", + "created_via_api", + "platforms", + "platform_status", + "url", + "download_url", + "google_wallet_url", + "created_at" + ] + }, + "PassCreateResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "pass" + ] + }, + "short_id": { + "type": "string" + }, + "template_id": { + "type": "string" + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "serial_number": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "holder_email": { + "type": [ + "string", + "null" + ] + }, + "holder_name": { + "type": [ + "string", + "null" + ] + }, + "field_values": { + "type": "object", + "additionalProperties": {}, + "description": "The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is." + }, + "voided": { + "type": "boolean" + }, + "voided_at": { + "type": [ + "string", + "null" + ] + }, + "redeemed_count": { + "type": "integer" + }, + "first_redeemed_at": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "Free-form key/value data you attached to the pass, or null." + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "The template image variant this pass renders with, or null for the template's own images." + }, + "images": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "pass", + "variant", + "template" + ] + } + }, + "required": [ + "source" + ] + }, + "description": "Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs.", + "example": { + "icon": { + "source": "template" + }, + "logo": { + "source": "template" + }, + "thumbnail": { + "source": "pass" + } + } + }, + "created_via_api": { + "type": "boolean" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + }, + "description": "The wallets this pass was issued for." + }, + "platform_status": { + "type": [ + "object", + "null" + ], + "properties": { + "apple": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + }, + "google": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + } + }, + "description": "Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it." + }, + "url": { + "type": "string" + }, + "download_url": { + "type": [ + "string", + "null" + ] + }, + "google_wallet_url": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "object", + "short_id", + "template_id", + "certificate_set_id", + "serial_number", + "mode", + "holder_email", + "holder_name", + "field_values", + "voided", + "voided_at", + "redeemed_count", + "first_redeemed_at", + "metadata", + "image_variant", + "images", + "created_via_api", + "platforms", + "platform_status", + "url", + "download_url", + "google_wallet_url", + "created_at", + "warnings" + ] + }, + "PassUpdateResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "pass" + ] + }, + "short_id": { + "type": "string" + }, + "template_id": { + "type": "string" + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "serial_number": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "holder_email": { + "type": [ + "string", + "null" + ] + }, + "holder_name": { + "type": [ + "string", + "null" + ] + }, + "field_values": { + "type": "object", + "additionalProperties": {}, + "description": "The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is." + }, + "voided": { + "type": "boolean" + }, + "voided_at": { + "type": [ + "string", + "null" + ] + }, + "redeemed_count": { + "type": "integer" + }, + "first_redeemed_at": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "Free-form key/value data you attached to the pass, or null." + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "The template image variant this pass renders with, or null for the template's own images." + }, + "images": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "pass", + "variant", + "template" + ] + } + }, + "required": [ + "source" + ] + }, + "description": "Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs.", + "example": { + "icon": { + "source": "template" + }, + "logo": { + "source": "template" + }, + "thumbnail": { + "source": "pass" + } + } + }, + "created_via_api": { + "type": "boolean" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + }, + "description": "The wallets this pass was issued for." + }, + "platform_status": { + "type": [ + "object", + "null" + ], + "properties": { + "apple": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + }, + "google": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + } + }, + "description": "Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it." + }, + "url": { + "type": "string" + }, + "download_url": { + "type": [ + "string", + "null" + ] + }, + "google_wallet_url": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "delivery": { + "$ref": "#/components/schemas/PassDelivery" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "object", + "short_id", + "template_id", + "certificate_set_id", + "serial_number", + "mode", + "holder_email", + "holder_name", + "field_values", + "voided", + "voided_at", + "redeemed_count", + "first_redeemed_at", + "metadata", + "image_variant", + "images", + "created_via_api", + "platforms", + "platform_status", + "url", + "download_url", + "google_wallet_url", + "created_at", + "delivery", + "warnings" + ] + }, + "PassDelivery": { + "type": "object", + "properties": { + "apple": { + "type": [ + "object", + "null" + ], + "properties": { + "devices": { + "type": "integer" + }, + "pushed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "unregistered": { + "type": "integer" + } + }, + "required": [ + "devices", + "pushed", + "failed", + "unregistered" + ], + "description": "Null when the pass was not delivered to Apple Wallet. `failed` excludes dead tokens (`unregistered`), which mean the holder deleted the pass." + }, + "google": { + "type": [ + "object", + "null" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "updated", + "not_saved", + "failed", + "skipped" + ] + }, + "error": { + "type": "string" + } + }, + "required": [ + "status" + ], + "description": "Null when the pass has no Google Wallet object. `not_saved`: no holder has saved it yet. `skipped`: Google delivery was skipped at issue." + } + }, + "required": [ + "apple", + "google" + ] + }, + "PassVoidResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "pass" + ] + }, + "short_id": { + "type": "string" + }, + "template_id": { + "type": "string" + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "serial_number": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "holder_email": { + "type": [ + "string", + "null" + ] + }, + "holder_name": { + "type": [ + "string", + "null" + ] + }, + "field_values": { + "type": "object", + "additionalProperties": {}, + "description": "The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is." + }, + "voided": { + "type": "boolean" + }, + "voided_at": { + "type": [ + "string", + "null" + ] + }, + "redeemed_count": { + "type": "integer" + }, + "first_redeemed_at": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "Free-form key/value data you attached to the pass, or null." + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "The template image variant this pass renders with, or null for the template's own images." + }, + "images": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "pass", + "variant", + "template" + ] + } + }, + "required": [ + "source" + ] + }, + "description": "Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs.", + "example": { + "icon": { + "source": "template" + }, + "logo": { + "source": "template" + }, + "thumbnail": { + "source": "pass" + } + } + }, + "created_via_api": { + "type": "boolean" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + }, + "description": "The wallets this pass was issued for." + }, + "platform_status": { + "type": [ + "object", + "null" + ], + "properties": { + "apple": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + }, + "google": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + } + }, + "description": "Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it." + }, + "url": { + "type": "string" + }, + "download_url": { + "type": [ + "string", + "null" + ] + }, + "google_wallet_url": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "object", + "short_id", + "template_id", + "certificate_set_id", + "serial_number", + "mode", + "holder_email", + "holder_name", + "field_values", + "voided", + "voided_at", + "redeemed_count", + "first_redeemed_at", + "metadata", + "image_variant", + "images", + "created_via_api", + "platforms", + "platform_status", + "url", + "download_url", + "google_wallet_url", + "created_at", + "warnings" + ] + }, + "PassEventList": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PassEvent" + } + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "object", + "data", + "has_more" + ] + }, + "PassEvent": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "pass_event" + ] + }, + "pass_id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "Event-specific detail, such as the platform or a redemption's scanner. Shape varies by `type`; null when the event carries none." + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "object", + "pass_id", + "type", + "metadata", + "created_at" + ] + }, + "RedemptionList": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Redemption" + } + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "object", + "data", + "has_more" + ] + }, + "Redemption": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "redemption" + ] + }, + "decision": { + "type": "string", + "enum": [ + "accepted", + "rejected" + ] + }, + "reason": { + "type": [ + "string", + "null" + ], + "enum": [ + "already_redeemed", + "count_exhausted", + "voided", + "not_found", + "out_of_scope", + "wrong_mode", + null + ] + }, + "scanner_name": { + "type": [ + "string", + "null" + ] + }, + "scanned_at": { + "type": "string" + } + }, + "required": [ + "id", + "object", + "decision", + "reason", + "scanner_name", + "scanned_at" + ] + }, + "PassDownloadLink": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "pass.download_link" + ] + }, + "pass_id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "expires_in": { + "type": "number" + }, + "expires_at": { + "type": "string" + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "object", + "pass_id", + "url", + "expires_in", + "expires_at", + "created_at" + ] + }, + "PassImageResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "pass" + ] + }, + "short_id": { + "type": "string" + }, + "template_id": { + "type": "string" + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "serial_number": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "holder_email": { + "type": [ + "string", + "null" + ] + }, + "holder_name": { + "type": [ + "string", + "null" + ] + }, + "field_values": { + "type": "object", + "additionalProperties": {}, + "description": "The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is." + }, + "voided": { + "type": "boolean" + }, + "voided_at": { + "type": [ + "string", + "null" + ] + }, + "redeemed_count": { + "type": "integer" + }, + "first_redeemed_at": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "Free-form key/value data you attached to the pass, or null." + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "The template image variant this pass renders with, or null for the template's own images." + }, + "images": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "pass", + "variant", + "template" + ] + } + }, + "required": [ + "source" + ] + }, + "description": "Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs.", + "example": { + "icon": { + "source": "template" + }, + "logo": { + "source": "template" + }, + "thumbnail": { + "source": "pass" + } + } + }, + "created_via_api": { + "type": "boolean" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "apple", + "google" + ] + }, + "description": "The wallets this pass was issued for." + }, + "platform_status": { + "type": [ + "object", + "null" + ], + "properties": { + "apple": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + }, + "google": { + "type": "string", + "enum": [ + "delivered", + "skipped_no_credential", + "failed" + ] + } + }, + "description": "Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it." + }, + "url": { + "type": "string" + }, + "download_url": { + "type": [ + "string", + "null" + ] + }, + "google_wallet_url": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "object", + "short_id", + "template_id", + "certificate_set_id", + "serial_number", + "mode", + "holder_email", + "holder_name", + "field_values", + "voided", + "voided_at", + "redeemed_count", + "first_redeemed_at", + "metadata", + "image_variant", + "images", + "created_via_api", + "platforms", + "platform_status", + "url", + "download_url", + "google_wallet_url", + "created_at", + "warnings" + ] + } + }, + "parameters": {} + }, + "paths": { + "/v1/scanners/pair": { + "post": { + "tags": [ + "Scanning" + ], + "summary": "Exchange a pairing code for a scanner token", + "description": "Unauthenticated: the single-use pairing code is the credential. Rate limited by client IP.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScanCodeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The scanner token, shown once", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScannerPairing" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Rate limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/scan": { + "post": { + "tags": [ + "Scanning" + ], + "summary": "Scan a pass barcode", + "description": "A well-formed scan is always HTTP 200; the accept/reject outcome is in the body.", + "security": [ + { + "scannerToken": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScanCodeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The scan outcome", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "scan" + ] + } + }, + "required": [ + "object" + ], + "additionalProperties": {} + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Rate limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/me": { + "get": { + "tags": [ + "Account" + ], + "summary": "Retrieve the authenticated account", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "The organization the API key belongs to", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Account" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/events": { + "get": { + "tags": [ + "Events" + ], + "summary": "List events", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": false, + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "pass_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "template_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "since", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "until", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "starting_after", + "in": "query" + }, + { + "schema": { + "type": [ + "number", + "null" + ] + }, + "required": false, + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A list of canonical events, most recent first", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventList" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/metrics/funnel": { + "get": { + "tags": [ + "Metrics" + ], + "summary": "Retrieve the pass funnel", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": false, + "name": "template_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "platform", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "since", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "until", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "group_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Funnel metrics for the authenticated organization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Funnel" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/templates": { + "get": { + "tags": [ + "Templates" + ], + "summary": "List templates", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ], + "description": "Include archived templates. Defaults to false." + }, + "required": false, + "description": "Include archived templates. Defaults to false.", + "name": "include_archived", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Return items after this template id (the last item of the previous page)." + }, + "required": false, + "description": "Return items after this template id (the last item of the previous page).", + "name": "starting_after", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A list of the organization's templates — active only, unless include_archived=true", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateList" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Templates" + ], + "summary": "Create a template", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTemplateBody" + } + } + } + }, + "responses": { + "201": { + "description": "The created template", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Template" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "402": { + "description": "Plan limit reached", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Idempotency conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/templates/{id}": { + "get": { + "tags": [ + "Templates" + ], + "summary": "Retrieve a template", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The requested template", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Template" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Templates" + ], + "summary": "Update a template", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTemplateBody" + } + } + } + }, + "responses": { + "200": { + "description": "The updated template", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Template" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Templates" + ], + "summary": "Archive a template", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The template was archived", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedTemplate" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/templates/{id}/images/{slot}": { + "put": { + "tags": [ + "Templates" + ], + "summary": "Upload a template image", + "description": "Writes one image slot of the template — or, with `variant`, of one of its image variants. Passes pick up a change the next time they are issued or updated.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "An Apple image slot the template's style has: icon, logo, strip, thumbnail, background or footer (generic: icon, logo, thumbnail; storeCard and coupon: icon, logo, strip; eventTicket: icon, logo, strip, thumbnail, background; boardingPass: icon, logo, footer). With a variant, only the style's strip, thumbnail or background.", + "example": "strip" + }, + "required": true, + "description": "An Apple image slot the template's style has: icon, logo, strip, thumbnail, background or footer (generic: icon, logo, thumbnail; storeCard and coupon: icon, logo, strip; eventTicket: icon, logo, strip, thumbnail, background; boardingPass: icon, logo, footer). With a variant, only the style's strip, thumbnail or background.", + "name": "slot", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "string", + "description": "The image, base64-encoded: PNG or JPEG (WebP is also accepted). Any resolution — it is resized to the slot's 1x and @2x sizes. 8 MB limit." + }, + "variant": { + "type": [ + "string", + "null" + ], + "description": "Write this image variant's copy of the slot instead of the template's own. 1-32 characters: lowercase letters, digits, - and _, starting with a letter or digit.", + "example": "3" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The image was stored", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateImage" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Templates" + ], + "summary": "Delete a template image", + "description": "Removes one image slot of the template — or, with `?variant=`, of one of its image variants. A variant with no images left no longer appears in the template's `image_variants`. Any slot can be removed, including one the template's style does not use.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "An Apple image slot the template's style has: icon, logo, strip, thumbnail, background or footer (generic: icon, logo, thumbnail; storeCard and coupon: icon, logo, strip; eventTicket: icon, logo, strip, thumbnail, background; boardingPass: icon, logo, footer). With a variant, only the style's strip, thumbnail or background.", + "example": "strip" + }, + "required": true, + "description": "An Apple image slot the template's style has: icon, logo, strip, thumbnail, background or footer (generic: icon, logo, thumbnail; storeCard and coupon: icon, logo, strip; eventTicket: icon, logo, strip, thumbnail, background; boardingPass: icon, logo, footer). With a variant, only the style's strip, thumbnail or background.", + "name": "slot", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "Remove this image variant's copy of the slot. Omit it to remove the template's own." + }, + "required": false, + "description": "Remove this image variant's copy of the slot. Omit it to remove the template's own.", + "name": "variant", + "in": "query" + } + ], + "responses": { + "200": { + "description": "The image was removed (or was already absent)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedTemplateImage" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/webhooks": { + "get": { + "tags": [ + "Webhooks" + ], + "summary": "List webhook endpoints", + "description": "Every endpoint, newest first. Send limit or starting_after to page instead.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "100" + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Return items after this id (the last item of the previous page)." + }, + "required": false, + "description": "Return items after this id (the last item of the previous page).", + "name": "starting_after", + "in": "query" + } + ], + "responses": { + "200": { + "description": "The organization's webhook endpoints", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookList" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Webhooks" + ], + "summary": "Create a webhook endpoint", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWebhookBody" + } + } + } + }, + "responses": { + "201": { + "description": "The created endpoint, including its signing secret (shown once)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Webhook" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "402": { + "description": "Plan limit reached", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/webhooks/{id}": { + "get": { + "tags": [ + "Webhooks" + ], + "summary": "Retrieve a webhook endpoint", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The webhook endpoint", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Webhook" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Webhooks" + ], + "summary": "Update a webhook endpoint", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWebhookBody" + } + } + } + }, + "responses": { + "200": { + "description": "The updated webhook endpoint", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Webhook" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Webhooks" + ], + "summary": "Delete a webhook endpoint", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Confirmation the endpoint was deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedWebhook" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/webhooks/{id}/deliveries": { + "get": { + "tags": [ + "Webhooks" + ], + "summary": "List recent deliveries for a webhook endpoint", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "example": "100" + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Return items after this id (the last item of the previous page)." + }, + "required": false, + "description": "Return items after this id (the last item of the previous page).", + "name": "starting_after", + "in": "query" + } + ], + "responses": { + "200": { + "description": "The endpoint's most recent deliveries", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDeliveryList" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/webhooks/{id}/deliveries/{deliveryId}/replay": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "Replay a webhook delivery", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": true, + "name": "deliveryId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The delivery after being re-queued and flushed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDelivery" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/webhooks/{id}/backfill": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "Backfill historical events to a webhook endpoint", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackfillBody" + } + } + } + }, + "responses": { + "200": { + "description": "How many canonical deliveries were enqueued", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookBackfill" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/passes": { + "get": { + "tags": [ + "Passes" + ], + "summary": "List passes", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "tmpl_123" + }, + "required": false, + "name": "template_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "holder_email", + "in": "query" + }, + { + "schema": { + "type": "string", + "example": "50" + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Return items after this pass id (the last item of the previous page)." + }, + "required": false, + "description": "Return items after this pass id (the last item of the previous page).", + "name": "starting_after", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A list of passes for the authenticated organization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassList" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Passes" + ], + "summary": "Create a pass", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "template_id": { + "type": "string" + }, + "certificate_set_id": { + "type": [ + "string", + "null" + ] + }, + "holder_email": { + "type": [ + "string", + "null" + ] + }, + "holder_name": { + "type": [ + "string", + "null" + ] + }, + "field_values": { + "type": "object", + "additionalProperties": {} + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {} + }, + "platforms": { + "type": "array", + "items": { + "type": "string" + } + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "Render the pass with this template image variant (see the template's `image_variants`). Omit or null for the template's own images." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "The created pass", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassCreateResponse" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "402": { + "description": "Plan limit reached", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Idempotency conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/passes/{id}": { + "get": { + "tags": [ + "Passes" + ], + "summary": "Retrieve a pass", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The requested pass", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pass" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Passes" + ], + "summary": "Update a pass", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "field_values": { + "type": "object", + "additionalProperties": {} + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {} + }, + "image_variant": { + "type": [ + "string", + "null" + ], + "description": "Switch the pass to this template image variant; null switches it back to the template's own images; omit to leave it unchanged. Re-signs and pushes like any other update." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The updated pass", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassUpdateResponse" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Idempotency conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Passes" + ], + "summary": "Void a pass", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The voided pass", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassVoidResponse" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/passes/{id}/events": { + "get": { + "tags": [ + "Passes" + ], + "summary": "List a pass's events", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "example": "100" + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Return events after this event id (the last item of the previous page)." + }, + "required": false, + "description": "Return events after this event id (the last item of the previous page).", + "name": "starting_after", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A list of events for the pass", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassEventList" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/passes/{id}/redemptions": { + "get": { + "tags": [ + "Passes" + ], + "summary": "List a pass's scans", + "description": "Every scan of the pass, accepted or rejected, most recent first. A rejected scan carries its reason and did not change the pass.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "example": "100" + }, + "required": false, + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "The pass's scan log", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RedemptionList" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/passes/{id}/download-links": { + "post": { + "tags": [ + "Passes" + ], + "summary": "Create an expiring download link", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "expires_in": { + "type": "number" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "A tokenized link to the pass page that stops resolving after expires_in seconds", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassDownloadLink" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/passes/{id}/images/{slot}": { + "put": { + "tags": [ + "Passes" + ], + "summary": "Upload a pass image", + "description": "Sets this pass's own image of one slot, over its image variant's and its template's. The pass is re-rendered, re-signed and pushed to installed devices, like any update. On Google Wallet a pass strip is the object's hero image; a pass thumbnail is the object's logo on generic passes and an image module on every other type; background is Apple only. Replacing an image retires the public URL Google fetched the old one from. A voided pass is a 409 (pass_voided). If the Google Wallet object could not be updated, the request still succeeds and `warnings` says why.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "The image slot, by its Apple name: strip, thumbnail or background — whichever of those the template's style has (storeCard and coupon: strip; generic: thumbnail; eventTicket: strip, thumbnail, background; boardingPass: none).", + "example": "thumbnail" + }, + "required": true, + "description": "The image slot, by its Apple name: strip, thumbnail or background — whichever of those the template's style has (storeCard and coupon: strip; generic: thumbnail; eventTicket: strip, thumbnail, background; boardingPass: none).", + "name": "slot", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "string", + "description": "The image, base64-encoded: PNG or JPEG (WebP is also accepted). Any resolution — it is resized to the slot's 1x and @2x sizes. 8 MB limit." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The updated pass, with any non-fatal warnings", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassImageResponse" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Idempotency conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Passes" + ], + "summary": "Delete a pass image", + "description": "Removes this pass's own image of one slot, so the slot falls back to the pass's image variant or its template. The stored image is deleted and its public URL stops resolving: this is also how to erase a pass image (a member photo, say), and it works on a voided pass (which is not re-rendered). Otherwise the pass is re-rendered and pushed. Deleting a slot the pass has no image of changes nothing and returns the pass.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "example": "pass_123" + }, + "required": true, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "The image slot, by its Apple name: strip, thumbnail or background — whichever of those the template's style has (storeCard and coupon: strip; generic: thumbnail; eventTicket: strip, thumbnail, background; boardingPass: none).", + "example": "thumbnail" + }, + "required": true, + "description": "The image slot, by its Apple name: strip, thumbnail or background — whichever of those the template's style has (storeCard and coupon: strip; generic: thumbnail; eventTicket: strip, thumbnail, background; boardingPass: none).", + "name": "slot", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The updated pass, with any non-fatal warnings", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassImageResponse" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden (API key scope, plan or account)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Idempotency conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "webhooks": { + "pass.issued": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.issued event", + "description": "Sent to your webhook endpoint when a `pass.issued` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassIssuedEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.add_intent": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.add_intent event", + "description": "Sent to your webhook endpoint when a `pass.add_intent` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassAddIntentEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.added_to_wallet": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.added_to_wallet event", + "description": "Sent to your webhook endpoint when a `pass.added_to_wallet` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassAddedToWalletEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.update_pushed": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.update_pushed event", + "description": "Sent to your webhook endpoint when a `pass.update_pushed` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassUpdatePushedEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.update_delivered": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.update_delivered event", + "description": "Sent to your webhook endpoint when a `pass.update_delivered` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassUpdateDeliveredEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.update_failed": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.update_failed event", + "description": "Sent to your webhook endpoint when a `pass.update_failed` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassUpdateFailedEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.removed": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.removed event", + "description": "Sent to your webhook endpoint when a `pass.removed` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassRemovedEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.voided": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.voided event", + "description": "Sent to your webhook endpoint when a `pass.voided` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassVoidedEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + }, + "pass.redeemed": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "pass.redeemed event", + "description": "Sent to your webhook endpoint when a `pass.redeemed` event occurs. Verify the signature, then return any 2xx to acknowledge.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PassRedeemedEvent" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx to acknowledge" + } + } + } + } + } +} diff --git a/package.json b/package.json index c8d646e..a031081 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,17 @@ "clean": "rm -rf dist .turbo", "changeset": "changeset", "version-packages": "changeset version && node scripts/sync-version.mjs && pnpm format", - "release": "changeset publish" + "release": "changeset publish", + "sync-spec": "node scripts/sync-spec.mjs", + "generate": "openapi-typescript openapi.json -o src/generated/openapi.ts && node scripts/gen-event-types.mjs", + "generate:check": "pnpm generate && git diff --exit-code -- src/generated" }, "devDependencies": { "@arethetypeswrong/cli": "^0.17.2", "@biomejs/biome": "^1.9.4", "@changesets/cli": "^2.27.11", "@types/node": "^22.10.5", + "openapi-typescript": "7.13.0", "publint": "^0.3.2", "tsdown": "^0.10.1", "typescript": "^5.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71ca30c..448f1f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@types/node': specifier: ^22.10.5 version: 22.19.17 + openapi-typescript: + specifier: 7.13.0 + version: 7.13.0(typescript@5.9.3) publint: specifier: ^0.3.2 version: 0.3.18 @@ -47,6 +50,10 @@ packages: resolution: {integrity: sha512-Izvir8iIoU+X4SKtDAa5kpb+9cpifclzsbA8x/AZY0k0gIfXYQ1fa1B6Epfe6vNA2YfDX8VtrZFgvnXB6aPEoQ==} engines: {node: '>=18'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} @@ -59,6 +66,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} @@ -517,6 +528,16 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.20': + resolution: {integrity: sha512-ypeBZ/6BKXR9+7/TtbKhbl4UgD7raHhPS12oknlKno2A8+lnFkxIwiE/Aklu6L2cd/ioH+fCWuMxi9/p3EyAPw==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rolldown/binding-darwin-arm64@1.0.0-beta.8-commit.852c603': resolution: {integrity: sha512-V0zbNPcyRVZFUUtCiJxXMuOYriszcgzc5wW5DUA5mHDlM0gDc1snsLzM245q5fGlSLmq+Rgerkclv0ZEjgHwDw==} cpu: [arm64] @@ -757,6 +778,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -802,10 +827,16 @@ packages: resolution: {integrity: sha512-MdJqjpodkS5J149zN0Po+HPshkTdUyrvF7CKTafUgv69vBSPtncrj+3IiUgqdd7ElIEkbeXCsEouBUwLrw9Ilg==} engines: {node: '>=16.14.0'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + brace-expansion@2.1.7: + resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -826,6 +857,9 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -863,6 +897,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -956,6 +993,9 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1024,6 +1064,10 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -1036,6 +1080,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1067,6 +1115,13 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -1075,11 +1130,18 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -1188,6 +1250,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1211,6 +1277,12 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -1247,6 +1319,10 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -1293,6 +1369,10 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + postcss@8.5.9: resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} engines: {node: ^10 || ^12 || >=14} @@ -1328,6 +1408,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1429,6 +1513,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1494,6 +1582,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typescript@5.6.1-rc: resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} engines: {node: '>=14.17'} @@ -1529,6 +1621,9 @@ packages: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + valibot@1.0.0: resolution: {integrity: sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==} peerDependencies: @@ -1623,10 +1718,17 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -1656,6 +1758,12 @@ snapshots: typescript: 5.6.1-rc validate-npm-package-name: 5.0.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.2 @@ -1668,6 +1776,8 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 @@ -2093,6 +2203,29 @@ snapshots: dependencies: quansync: 1.0.0 + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.20(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.3.2 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@rolldown/binding-darwin-arm64@1.0.0-beta.8-commit.852c603': optional: true @@ -2267,6 +2400,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + ansi-colors@4.1.3: {} ansi-escapes@7.3.0: @@ -2300,10 +2435,16 @@ snapshots: '@babel/parser': 7.29.2 pathe: 2.0.3 + balanced-match@1.0.2: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 + brace-expansion@2.1.7: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -2325,6 +2466,8 @@ snapshots: chalk@5.6.2: {} + change-case@5.4.4: {} + char-regex@1.0.2: {} chardet@2.1.1: {} @@ -2364,6 +2507,8 @@ snapshots: color-name@1.1.4: {} + colorette@1.4.0: {} + commander@10.0.1: {} consola@3.4.2: {} @@ -2374,9 +2519,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 deep-eql@5.0.2: {} @@ -2450,6 +2597,8 @@ snapshots: extendable-error@0.1.7: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2519,6 +2668,13 @@ snapshots: hookable@5.5.3: {} + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + human-id@4.1.3: {} iconv-lite@0.7.2: @@ -2527,6 +2683,8 @@ snapshots: ignore@5.3.2: {} + index-to-position@1.2.0: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -2547,6 +2705,10 @@ snapshots: jiti@2.6.1: {} + js-levenshtein@1.1.6: {} + + js-tokens@4.0.0: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -2556,8 +2718,14 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} + json-schema-traverse@1.0.0: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -2645,6 +2813,10 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.7 + mri@1.2.0: {} ms@2.1.3: {} @@ -2666,6 +2838,16 @@ snapshots: object-assign@4.1.1: {} + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.20(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + outdent@0.5.0: {} oxc-resolver@9.0.2: @@ -2719,6 +2901,12 @@ snapshots: package-manager-detector@1.6.0: {} + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.7 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse5-htmlparser2-tree-adapter@6.0.1: dependencies: parse5: 6.0.1 @@ -2747,6 +2935,8 @@ snapshots: pify@4.0.1: {} + pluralize@8.0.0: {} + postcss@8.5.9: dependencies: nanoid: 3.3.11 @@ -2779,6 +2969,8 @@ snapshots: require-directory@2.1.1: {} + require-from-string@2.0.2: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -2791,7 +2983,7 @@ snapshots: '@babel/parser': 7.29.2 '@babel/types': 7.29.0 ast-kit: 1.4.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) dts-resolver: 1.2.0 get-tsconfig: 4.14.0 oxc-transform: 0.67.0 @@ -2907,6 +3099,8 @@ snapshots: strip-bom@3.0.0: {} + supports-color@10.2.2: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -2953,7 +3147,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) diff: 7.0.0 empathic: 1.1.0 hookable: 5.5.3 @@ -2974,6 +3168,8 @@ snapshots: tslib@2.8.1: optional: true + type-fest@4.41.0: {} + typescript@5.6.1-rc: {} typescript@5.9.3: {} @@ -3010,6 +3206,8 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 + uri-js-replace@1.0.1: {} + valibot@1.0.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -3019,7 +3217,7 @@ snapshots: vite-node@2.1.9(@types/node@22.19.17)(lightningcss@1.32.0): dependencies: cac: 6.7.14 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) es-module-lexer: 1.7.0 pathe: 1.1.2 vite: 5.4.21(@types/node@22.19.17)(lightningcss@1.32.0) @@ -3054,7 +3252,7 @@ snapshots: '@vitest/spy': 2.1.9 '@vitest/utils': 2.1.9 chai: 5.3.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) expect-type: 1.3.0 magic-string: 0.30.21 pathe: 1.1.2 @@ -3098,8 +3296,12 @@ snapshots: y18n@5.0.8: {} + yaml-ast-parser@0.0.43: {} + yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} + yargs@16.2.0: dependencies: cliui: 7.0.4 diff --git a/scripts/gen-event-types.mjs b/scripts/gen-event-types.mjs new file mode 100644 index 0000000..1d734f5 --- /dev/null +++ b/scripts/gen-event-types.mjs @@ -0,0 +1,27 @@ +// Writes src/generated/event-types.ts from the webhook keys in openapi.json. +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const spec = JSON.parse(readFileSync(resolve(root, 'openapi.json'), 'utf8')) +const types = Object.keys(spec.webhooks ?? {}) + +const body = `// Generated from openapi.json by scripts/gen-event-types.mjs. Do not edit. + +/** + * The Passmint Spec v1 — canonical lifecycle event types delivered to + * webhooks and returned by GET /v1/events. Platform detail lives in \`source\`. + */ +export const PASSMINT_EVENT_TYPES = [ +${types.map((t) => ` ${JSON.stringify(t)},`).join('\n')} +] as const + +/** A canonical lifecycle event type (one of \`PASSMINT_EVENT_TYPES\`). */ +export type PassmintEventType = (typeof PASSMINT_EVENT_TYPES)[number] +` + +const out = resolve(root, 'src/generated/event-types.ts') +mkdirSync(dirname(out), { recursive: true }) +writeFileSync(out, body) +console.log(`Wrote ${out}`) diff --git a/scripts/sync-spec.mjs b/scripts/sync-spec.mjs new file mode 100644 index 0000000..b91d822 --- /dev/null +++ b/scripts/sync-spec.mjs @@ -0,0 +1,22 @@ +// Copies the committed OpenAPI spec into this repo. +// Default: the sibling platform checkout (../platform). Override with PASSMINT_PLATFORM_DIR. +// Pass --from-url to fetch the live spec instead. +import { readFileSync, writeFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const target = resolve(root, 'openapi.json') + +let raw +if (process.argv.includes('--from-url')) { + const res = await fetch('https://api.passmint.com/openapi.json') + if (!res.ok) throw new Error(`Failed to fetch spec: ${res.status}`) + raw = await res.text() +} else { + const platformDir = process.env.PASSMINT_PLATFORM_DIR ?? resolve(root, '../platform') + raw = readFileSync(resolve(platformDir, 'apps/api-worker/openapi.json'), 'utf8') +} + +writeFileSync(target, `${JSON.stringify(JSON.parse(raw), null, 2)}\n`) +console.log(`Wrote ${target}`) diff --git a/src/generated/event-types.ts b/src/generated/event-types.ts new file mode 100644 index 0000000..6c3cf31 --- /dev/null +++ b/src/generated/event-types.ts @@ -0,0 +1,20 @@ +// Generated from openapi.json by scripts/gen-event-types.mjs. Do not edit. + +/** + * The Passmint Spec v1 — canonical lifecycle event types delivered to + * webhooks and returned by GET /v1/events. Platform detail lives in `source`. + */ +export const PASSMINT_EVENT_TYPES = [ + "pass.issued", + "pass.add_intent", + "pass.added_to_wallet", + "pass.update_pushed", + "pass.update_delivered", + "pass.update_failed", + "pass.removed", + "pass.voided", + "pass.redeemed", +] as const + +/** A canonical lifecycle event type (one of `PASSMINT_EVENT_TYPES`). */ +export type PassmintEventType = (typeof PASSMINT_EVENT_TYPES)[number] diff --git a/src/generated/openapi.ts b/src/generated/openapi.ts new file mode 100644 index 0000000..bdf0f3e --- /dev/null +++ b/src/generated/openapi.ts @@ -0,0 +1,3950 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/v1/scanners/pair": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Exchange a pairing code for a scanner token + * @description Unauthenticated: the single-use pairing code is the credential. Rate limited by client IP. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["ScanCodeRequest"]; + }; + }; + responses: { + /** @description The scanner token, shown once */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ScannerPairing"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Rate limited */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/scan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Scan a pass barcode + * @description A well-formed scan is always HTTP 200; the accept/reject outcome is in the body. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["ScanCodeRequest"]; + }; + }; + responses: { + /** @description The scan outcome */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + object: "scan"; + } & { + [key: string]: unknown; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Rate limited */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Retrieve the authenticated account */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The organization the API key belongs to */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Account"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List events */ + get: { + parameters: { + query?: { + type?: string; + pass_id?: string; + template_id?: string; + since?: string; + until?: string; + starting_after?: string; + limit?: number | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of canonical events, most recent first */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EventList"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/metrics/funnel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Retrieve the pass funnel */ + get: { + parameters: { + query?: { + template_id?: string; + platform?: string; + since?: string; + until?: string; + group_by?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Funnel metrics for the authenticated organization */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Funnel"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/templates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List templates */ + get: { + parameters: { + query?: { + limit?: string; + /** @description Include archived templates. Defaults to false. */ + include_archived?: "true" | "false"; + /** @description Return items after this template id (the last item of the previous page). */ + starting_after?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of the organization's templates — active only, unless include_archived=true */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TemplateList"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + /** Create a template */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateTemplateBody"]; + }; + }; + responses: { + /** @description The created template */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Template"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Plan limit reached */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/templates/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Retrieve a template */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested template */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Template"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + /** Archive a template */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The template was archived */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletedTemplate"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + options?: never; + head?: never; + /** Update a template */ + patch: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["UpdateTemplateBody"]; + }; + }; + responses: { + /** @description The updated template */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Template"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + trace?: never; + }; + "/v1/templates/{id}/images/{slot}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Upload a template image + * @description Writes one image slot of the template — or, with `variant`, of one of its image variants. Passes pick up a change the next time they are issued or updated. + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + /** @description An Apple image slot the template's style has: icon, logo, strip, thumbnail, background or footer (generic: icon, logo, thumbnail; storeCard and coupon: icon, logo, strip; eventTicket: icon, logo, strip, thumbnail, background; boardingPass: icon, logo, footer). With a variant, only the style's strip, thumbnail or background. */ + slot: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description The image, base64-encoded: PNG or JPEG (WebP is also accepted). Any resolution — it is resized to the slot's 1x and @2x sizes. 8 MB limit. */ + data?: string; + /** + * @description Write this image variant's copy of the slot instead of the template's own. 1-32 characters: lowercase letters, digits, - and _, starting with a letter or digit. + * @example 3 + */ + variant?: string | null; + }; + }; + }; + responses: { + /** @description The image was stored */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TemplateImage"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + post?: never; + /** + * Delete a template image + * @description Removes one image slot of the template — or, with `?variant=`, of one of its image variants. A variant with no images left no longer appears in the template's `image_variants`. Any slot can be removed, including one the template's style does not use. + */ + delete: { + parameters: { + query?: { + /** @description Remove this image variant's copy of the slot. Omit it to remove the template's own. */ + variant?: string; + }; + header?: never; + path: { + id: string; + /** @description An Apple image slot the template's style has: icon, logo, strip, thumbnail, background or footer (generic: icon, logo, thumbnail; storeCard and coupon: icon, logo, strip; eventTicket: icon, logo, strip, thumbnail, background; boardingPass: icon, logo, footer). With a variant, only the style's strip, thumbnail or background. */ + slot: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The image was removed (or was already absent) */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletedTemplateImage"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/webhooks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List webhook endpoints + * @description Every endpoint, newest first. Send limit or starting_after to page instead. + */ + get: { + parameters: { + query?: { + limit?: string; + /** @description Return items after this id (the last item of the previous page). */ + starting_after?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The organization's webhook endpoints */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WebhookList"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + /** Create a webhook endpoint */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateWebhookBody"]; + }; + }; + responses: { + /** @description The created endpoint, including its signing secret (shown once) */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Webhook"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Plan limit reached */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/webhooks/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Retrieve a webhook endpoint */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The webhook endpoint */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Webhook"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + /** Delete a webhook endpoint */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Confirmation the endpoint was deleted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletedWebhook"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + options?: never; + head?: never; + /** Update a webhook endpoint */ + patch: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["UpdateWebhookBody"]; + }; + }; + responses: { + /** @description The updated webhook endpoint */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Webhook"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + trace?: never; + }; + "/v1/webhooks/{id}/deliveries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List recent deliveries for a webhook endpoint */ + get: { + parameters: { + query?: { + limit?: string; + /** @description Return items after this id (the last item of the previous page). */ + starting_after?: string; + }; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The endpoint's most recent deliveries */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WebhookDeliveryList"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/webhooks/{id}/deliveries/{deliveryId}/replay": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Replay a webhook delivery */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + deliveryId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The delivery after being re-queued and flushed */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WebhookDelivery"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/webhooks/{id}/backfill": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Backfill historical events to a webhook endpoint */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BackfillBody"]; + }; + }; + responses: { + /** @description How many canonical deliveries were enqueued */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WebhookBackfill"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/passes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List passes */ + get: { + parameters: { + query?: { + template_id?: string; + holder_email?: string; + limit?: string; + /** @description Return items after this pass id (the last item of the previous page). */ + starting_after?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of passes for the authenticated organization */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassList"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + /** Create a pass */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + template_id?: string; + certificate_set_id?: string | null; + holder_email?: string | null; + holder_name?: string | null; + field_values?: { + [key: string]: unknown; + }; + metadata?: { + [key: string]: unknown; + } | null; + platforms?: string[]; + /** @description Render the pass with this template image variant (see the template's `image_variants`). Omit or null for the template's own images. */ + image_variant?: string | null; + }; + }; + }; + responses: { + /** @description The created pass */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassCreateResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Plan limit reached */ + 402: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/passes/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Retrieve a pass */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested pass */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Pass"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + /** Void a pass */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The voided pass */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassVoidResponse"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + options?: never; + head?: never; + /** Update a pass */ + patch: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + field_values?: { + [key: string]: unknown; + }; + metadata?: { + [key: string]: unknown; + } | null; + /** @description Switch the pass to this template image variant; null switches it back to the template's own images; omit to leave it unchanged. Re-signs and pushes like any other update. */ + image_variant?: string | null; + }; + }; + }; + responses: { + /** @description The updated pass */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassUpdateResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + trace?: never; + }; + "/v1/passes/{id}/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List a pass's events */ + get: { + parameters: { + query?: { + limit?: string; + /** @description Return events after this event id (the last item of the previous page). */ + starting_after?: string; + }; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of events for the pass */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassEventList"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/passes/{id}/redemptions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List a pass's scans + * @description Every scan of the pass, accepted or rejected, most recent first. A rejected scan carries its reason and did not change the pass. + */ + get: { + parameters: { + query?: { + limit?: string; + }; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The pass's scan log */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RedemptionList"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/passes/{id}/download-links": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Create an expiring download link */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + expires_in?: number; + }; + }; + }; + responses: { + /** @description A tokenized link to the pass page that stops resolving after expires_in seconds */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassDownloadLink"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/passes/{id}/images/{slot}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Upload a pass image + * @description Sets this pass's own image of one slot, over its image variant's and its template's. The pass is re-rendered, re-signed and pushed to installed devices, like any update. On Google Wallet a pass strip is the object's hero image; a pass thumbnail is the object's logo on generic passes and an image module on every other type; background is Apple only. Replacing an image retires the public URL Google fetched the old one from. A voided pass is a 409 (pass_voided). If the Google Wallet object could not be updated, the request still succeeds and `warnings` says why. + */ + put: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + /** @description The image slot, by its Apple name: strip, thumbnail or background — whichever of those the template's style has (storeCard and coupon: strip; generic: thumbnail; eventTicket: strip, thumbnail, background; boardingPass: none). */ + slot: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description The image, base64-encoded: PNG or JPEG (WebP is also accepted). Any resolution — it is resized to the slot's 1x and @2x sizes. 8 MB limit. */ + data?: string; + }; + }; + }; + responses: { + /** @description The updated pass, with any non-fatal warnings */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassImageResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + post?: never; + /** + * Delete a pass image + * @description Removes this pass's own image of one slot, so the slot falls back to the pass's image variant or its template. The stored image is deleted and its public URL stops resolving: this is also how to erase a pass image (a member photo, say), and it works on a voided pass (which is not re-rendered). Otherwise the pass is re-rendered and pushed. Deleting a slot the pass has no image of changes nothing and returns the pass. + */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + /** @description The image slot, by its Apple name: strip, thumbnail or background — whichever of those the template's style has (storeCard and coupon: strip; generic: thumbnail; eventTicket: strip, thumbnail, background; boardingPass: none). */ + slot: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The updated pass, with any non-fatal warnings */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PassImageResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden (API key scope, plan or account) */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Idempotency conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export interface webhooks { + "pass.issued": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.issued event + * @description Sent to your webhook endpoint when a `pass.issued` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassIssuedEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.add_intent": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.add_intent event + * @description Sent to your webhook endpoint when a `pass.add_intent` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassAddIntentEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.added_to_wallet": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.added_to_wallet event + * @description Sent to your webhook endpoint when a `pass.added_to_wallet` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassAddedToWalletEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.update_pushed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.update_pushed event + * @description Sent to your webhook endpoint when a `pass.update_pushed` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassUpdatePushedEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.update_delivered": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.update_delivered event + * @description Sent to your webhook endpoint when a `pass.update_delivered` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassUpdateDeliveredEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.update_failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.update_failed event + * @description Sent to your webhook endpoint when a `pass.update_failed` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassUpdateFailedEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.removed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.removed event + * @description Sent to your webhook endpoint when a `pass.removed` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassRemovedEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.voided": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.voided event + * @description Sent to your webhook endpoint when a `pass.voided` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassVoidedEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "pass.redeemed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * pass.redeemed event + * @description Sent to your webhook endpoint when a `pass.redeemed` event occurs. Verify the signature, then return any 2xx to acknowledge. + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PassRedeemedEvent"]; + }; + }; + responses: { + /** @description Return any 2xx to acknowledge */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export interface components { + schemas: { + CreateTemplateBody: { + name: string; + /** @enum {string} */ + type: "event" | "membership" | "coupon" | "loyalty" | "generic"; + /** @enum {string} */ + apple_style: "generic" | "eventTicket" | "boardingPass" | "coupon" | "storeCard"; + design: { + description?: string | null; + logoText?: string | null; + foregroundColor: string; + backgroundColor: string; + labelColor: string; + /** @description Storage key from an image upload. Leave null to keep none. */ + logoImageKey?: string | null; + /** @description Storage key from an image upload. Leave null to keep none. */ + iconImageKey?: string | null; + /** @description Storage key from an image upload. Leave null to keep none. */ + stripImageKey?: string | null; + /** @description Storage key from an image upload. Leave null to keep none. */ + thumbnailImageKey?: string | null; + headerFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + primaryFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + secondaryFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + auxiliaryFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + backFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + /** @enum {string|null} */ + barcodeFormat?: "PKBarcodeFormatQR" | "PKBarcodeFormatPDF417" | "PKBarcodeFormatAztec" | "PKBarcodeFormatCode128" | null; + /** @description What the barcode encodes. Use '{{serialNumber}}' — scanning looks passes up by serial number, and it is unique per pass. '{{fieldValues.}}' inserts a field value. No other placeholders exist. */ + barcodeMessageTemplate?: string | null; + /** @description The issuer shown on the pass and in its notifications (Apple organizationName; Google issuer/program name unless logoText is set). Defaults to your organization's name. */ + issuerName?: string | null; + /** @description Up to 10 places the pass is relevant near. Apple Wallet suggests the pass on the lock screen there; Google Wallet stores them on the pass. */ + locations?: { + latitude: number; + longitude: number; + altitude?: number; + /** @description Lock-screen text Apple Wallet shows near this location. */ + relevantText?: string; + }[] | null; + /** @description ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only. */ + relevantDate?: string | null; + }; + platforms?: ("apple" | "google")[]; + starter_template_id?: string; + require_download_link?: boolean; + redemption_policy?: components["schemas"]["RedemptionPolicyInput"]; + }; + RedemptionPolicyInput: { + /** @enum {string} */ + mode: "single_use" | "reusable" | "count"; + /** @description Required when mode is count. */ + max_uses?: number; + }; + UpdateTemplateBody: { + name?: string; + design?: { + description?: string | null; + logoText?: string | null; + foregroundColor: string; + backgroundColor: string; + labelColor: string; + /** @description Storage key from an image upload. Leave null to keep none. */ + logoImageKey?: string | null; + /** @description Storage key from an image upload. Leave null to keep none. */ + iconImageKey?: string | null; + /** @description Storage key from an image upload. Leave null to keep none. */ + stripImageKey?: string | null; + /** @description Storage key from an image upload. Leave null to keep none. */ + thumbnailImageKey?: string | null; + headerFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + primaryFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + secondaryFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + auxiliaryFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + backFields?: { + /** @description Stable identifier for this field — the key you pass in field_values when issuing or updating a pass. Letters, numbers and underscores; unique across the design. */ + key: string; + label?: string | null; + defaultValue?: string | null; + /** @enum {string|null} */ + textAlignment?: "left" | "center" | "right" | "natural" | null; + required?: unknown; + /** @description Apple Wallet lock-screen notification for this field, e.g. 'Your seat is now %@'. Must contain %@, which is replaced by the new value. This is the only way an update becomes visible to the holder. */ + changeMessage?: string | null; + }[] | null; + /** @enum {string|null} */ + barcodeFormat?: "PKBarcodeFormatQR" | "PKBarcodeFormatPDF417" | "PKBarcodeFormatAztec" | "PKBarcodeFormatCode128" | null; + /** @description What the barcode encodes. Use '{{serialNumber}}' — scanning looks passes up by serial number, and it is unique per pass. '{{fieldValues.}}' inserts a field value. No other placeholders exist. */ + barcodeMessageTemplate?: string | null; + /** @description The issuer shown on the pass and in its notifications (Apple organizationName; Google issuer/program name unless logoText is set). Defaults to your organization's name. */ + issuerName?: string | null; + /** @description Up to 10 places the pass is relevant near. Apple Wallet suggests the pass on the lock screen there; Google Wallet stores them on the pass. */ + locations?: { + latitude: number; + longitude: number; + altitude?: number; + /** @description Lock-screen text Apple Wallet shows near this location. */ + relevantText?: string; + }[] | null; + /** @description ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only. */ + relevantDate?: string | null; + }; + archived?: boolean; + platforms?: ("apple" | "google")[]; + certificate_set_id?: string | null; + google_issuer_id?: string | null; + require_download_link?: boolean; + redemption_policy?: components["schemas"]["RedemptionPolicyInput"]; + }; + CreateWebhookBody: { + /** + * @description The HTTPS URL events are POSTed to. + * @example https://example.com/hooks/passmint + */ + url: string; + /** + * @description Required, non-empty. Event types to deliver, or `*` for all. + * @example [ + * "pass.redeemed" + * ] + */ + events: string[]; + description?: string | null; + /** @description Defaults to true. */ + enabled?: boolean; + }; + UpdateWebhookBody: { + url?: string; + /** + * @description Event types to deliver (for example `pass.redeemed`), or `*` for all of them. + * @example [ + * "pass.redeemed" + * ] + */ + events?: string[]; + description?: string | null; + enabled?: boolean; + }; + BackfillBody: { + /** + * @description ISO 8601 start of the window. Required. + * @example 2026-10-01T00:00:00Z + */ + since: string; + /** @description ISO 8601 end of the window. Defaults to now. The window is capped in length. */ + until?: string; + }; + PassIssuedEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.issued"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + EventPassSnapshot: { + id: string; + short_id: string; + template_id: string; + organization_id: string; + serial_number: string; + holder: { + email_hash: string | null; + name_present: boolean; + }; + voided: boolean; + /** @description The template image variant the pass renders with, or null for the template's own images. */ + image_variant: string | null; + url: string; + download_url: string | null; + google_wallet_url: string | null; + }; + PassAddIntentEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.add_intent"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + PassAddedToWalletEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.added_to_wallet"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + PassUpdatePushedEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.update_pushed"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + PassUpdateDeliveredEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.update_delivered"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + PassUpdateFailedEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.update_failed"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + delivery_failure: components["schemas"]["EventDeliveryFailure"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + /** @description `data.object.delivery_failure`, present on `pass.update_failed`. */ + EventDeliveryFailure: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + reason: string | null; + failed: number | null; + devices: number | null; + /** @enum {string|null} */ + context: "update" | "void" | null; + }; + PassRemovedEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.removed"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + PassVoidedEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.voided"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + PassRedeemedEvent: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.redeemed"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + redemption: components["schemas"]["EventRedemption"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + /** @description `data.object.redemption`, present on `pass.redeemed`. */ + EventRedemption: { + id: string | null; + scanner_device_id: string | null; + scans_used: number | null; + policy_mode: string | null; + }; + ScannerPairing: { + /** @enum {string} */ + object: "scanner_pairing"; + token: string; + device: { + id: string; + name: string; + /** @enum {string} */ + mode: "test" | "live"; + template_id: string | null; + }; + }; + Error: { + error: { + /** @enum {string} */ + type: "authentication_error" | "authorization_error" | "invalid_request_error" | "not_found_error" | "rate_limit_error" | "idempotency_error" | "api_error" | "billing_error"; + /** + * @description A stable, machine-readable code when the error has a specific cause. Not exhaustive: treat unknown codes as the generic error for the `type`. Codes include `api_key_expired` (401, the key's expiry has passed), `insufficient_scope` (403, the key's scopes do not allow this route), `invalid_limit` (400, `limit` is negative, fractional or non-numeric; 0 is valid and a limit over the maximum is clamped), `invalid_cursor` (400, `starting_after` is not an id on this list) and `plan_limit_exceeded` (402). + * @example api_key_expired + * @example insufficient_scope + * @example invalid_limit + * @example invalid_cursor + */ + code?: string; + message: string; + param?: string; + }; + }; + ScanCodeRequest: { + code: string; + }; + Account: { + /** @enum {string} */ + object: "account"; + organization_id: string; + organization_name: string; + organization_slug: string | null; + /** @enum {string} */ + mode: "test" | "live"; + plan: { + id: string; + name: string; + }; + /** @description Live-mode usage in the current billing period (a rolling 30 days on plans without one). Pass issuance stops at api_calls.quota; other live writes continue to api_calls.grace_ceiling. */ + usage: { + period_start: string | null; + api_calls: { + used: number; + quota: number | null; + grace_ceiling: number | null; + }; + active_passes: { + used: number; + included: number; + ceiling: number; + }; + }; + /** @description The organization's own signing credentials. Live passes are only signed with these, never with Passmint's shared test credentials. */ + credentials: { + apple_certificates: { + id: string; + name: string; + pass_type_identifier: string | null; + /** @enum {string} */ + status: "pending_certificate" | "active" | "revoked"; + expires_at: string | null; + }[]; + google_issuers: { + id: string; + name: string; + issuer_id: string; + /** @enum {string} */ + status: "pending" | "active" | "revoked"; + }[]; + }; + }; + EventList: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["Event"][]; + has_more: boolean; + }; + Event: { + id: string; + /** @enum {string} */ + object: "event"; + /** @enum {string} */ + type: "pass.issued" | "pass.add_intent" | "pass.added_to_wallet" | "pass.update_pushed" | "pass.update_delivered" | "pass.update_failed" | "pass.removed" | "pass.voided" | "pass.redeemed"; + api_version: string; + created_at: string; + idempotency_key: string; + livemode: boolean; + data: { + object: { + pass: components["schemas"]["EventPassSnapshot"]; + redemption?: components["schemas"]["EventRedemption"]; + delivery_failure?: components["schemas"]["EventDeliveryFailure"]; + }; + }; + source: { + /** @enum {string|null} */ + platform: "apple" | "google" | null; + /** @enum {string|null} */ + unit: "device" | "object" | null; + /** @enum {string} */ + confidence: "exact" | "best_effort" | "unconfirmed"; + }; + previous_attributes: null; + }; + Funnel: { + /** @enum {string} */ + object: "funnel"; + /** @enum {string} */ + group_by: "total" | "template" | "platform" | "date" | "mode"; + data: components["schemas"]["FunnelSummary"][]; + confidence: components["schemas"]["FunnelConfidence"]; + }; + FunnelSummary: { + key: string; + issued: number; + add_intent: number; + added: number; + active: number; + removed: number; + update_pushed: number; + update_delivered: number; + install_rate: number | null; + removal_rate: number | null; + update_delivery_rate: number | null; + }; + FunnelConfidence: { + removed: string; + update_delivery_rate: string; + active: string; + }; + TemplateList: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["Template"][]; + has_more: boolean; + }; + Template: { + id: string; + /** @enum {string} */ + object: "template"; + name: string; + type: string; + apple_style: string; + starter_template_id: string | null; + design: components["schemas"]["TemplateDesign"]; + archived: boolean; + platforms: ("apple" | "google")[]; + certificate_set_id: string | null; + google_issuer_id: string | null; + require_download_link: boolean; + redemption_policy: components["schemas"]["RedemptionPolicy"]; + /** @description Present only when the saved design breaks a recommended rule (an Apple field limit, a duplicate field key, an unknown barcode placeholder, an image the pass style cannot show). The template is saved; fix these before they surprise you at issuance. */ + warnings?: string[]; + /** + * @description The template's image variant names that hold at least one image, sorted. A pass selects one with `image_variant`. + * @example [ + * "0", + * "1", + * "2", + * "reward" + * ] + */ + image_variants: string[]; + created_at: string; + updated_at: string; + }; + /** @description The template's design as stored. See CreateTemplateBody.design for the write contract. */ + TemplateDesign: { + description?: string | null; + logoText?: string | { + default: string; + translations?: { + [key: string]: string; + }; + } | null; + foregroundColor?: string; + backgroundColor?: string; + labelColor?: string; + headerFields?: ({ + key: string; + label?: string | { + default: string; + translations?: { + [key: string]: string; + }; + } | null; + defaultValue?: string | number | null; + textAlignment?: string; + changeMessage?: string | null; + } & { + [key: string]: unknown; + })[] | null; + primaryFields?: ({ + key: string; + label?: string | { + default: string; + translations?: { + [key: string]: string; + }; + } | null; + defaultValue?: string | number | null; + textAlignment?: string; + changeMessage?: string | null; + } & { + [key: string]: unknown; + })[] | null; + secondaryFields?: ({ + key: string; + label?: string | { + default: string; + translations?: { + [key: string]: string; + }; + } | null; + defaultValue?: string | number | null; + textAlignment?: string; + changeMessage?: string | null; + } & { + [key: string]: unknown; + })[] | null; + auxiliaryFields?: ({ + key: string; + label?: string | { + default: string; + translations?: { + [key: string]: string; + }; + } | null; + defaultValue?: string | number | null; + textAlignment?: string; + changeMessage?: string | null; + } & { + [key: string]: unknown; + })[] | null; + backFields?: ({ + key: string; + label?: string | { + default: string; + translations?: { + [key: string]: string; + }; + } | null; + defaultValue?: string | number | null; + textAlignment?: string; + changeMessage?: string | null; + } & { + [key: string]: unknown; + })[] | null; + barcodeFormat?: string | null; + barcodeMessageTemplate?: string | null; + issuerName?: string | null; + locations?: { + [key: string]: unknown; + }[] | null; + relevantDate?: string | null; + } & { + [key: string]: unknown; + }; + RedemptionPolicy: { + /** @enum {string} */ + mode: "single_use" | "reusable" | "count"; + maxUses?: number; + }; + DeletedTemplate: { + id: string; + /** @enum {boolean} */ + deleted: true; + }; + TemplateImage: { + /** @enum {string} */ + object: "template_image"; + template_id: string; + slot: string; + variant: string | null; + }; + DeletedTemplateImage: components["schemas"]["TemplateImage"] & { + /** @enum {boolean} */ + deleted: true; + }; + WebhookList: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["Webhook"][]; + has_more: boolean; + }; + Webhook: { + id: string; + /** @enum {string} */ + object: "webhook"; + url: string; + events: string[]; + description: string | null; + enabled: boolean; + secret?: string; + created_at: string; + }; + DeletedWebhook: { + id: string; + /** @enum {boolean} */ + deleted: true; + }; + WebhookDeliveryList: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["WebhookDelivery"][]; + has_more: boolean; + }; + WebhookDelivery: { + id: string; + /** @enum {string} */ + object: "webhook_delivery"; + webhook_id: string; + event_type: string; + status: string; + attempts: number; + last_attempt_at: string | null; + next_attempt_at: string | null; + response_status: number | null; + response_body: string | null; + created_at: string; + }; + WebhookBackfill: { + /** @enum {string} */ + object: "backfill"; + enqueued: number; + capped: boolean; + window: { + since: string; + until: string; + }; + }; + PassList: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["Pass"][]; + has_more: boolean; + }; + Pass: { + id: string; + /** @enum {string} */ + object: "pass"; + short_id: string; + template_id: string; + certificate_set_id: string | null; + serial_number: string; + mode: string; + holder_email: string | null; + holder_name: string | null; + /** @description The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is. */ + field_values: { + [key: string]: unknown; + }; + voided: boolean; + voided_at: string | null; + redeemed_count: number; + first_redeemed_at: string | null; + /** @description Free-form key/value data you attached to the pass, or null. */ + metadata: { + [key: string]: unknown; + } | null; + /** @description The template image variant this pass renders with, or null for the template's own images. */ + image_variant: string | null; + /** + * @description Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs. + * @example { + * "icon": { + * "source": "template" + * }, + * "logo": { + * "source": "template" + * }, + * "thumbnail": { + * "source": "pass" + * } + * } + */ + images: { + [key: string]: { + /** @enum {string} */ + source: "pass" | "variant" | "template"; + }; + }; + created_via_api: boolean; + /** @description The wallets this pass was issued for. */ + platforms: ("apple" | "google")[]; + /** @description Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it. */ + platform_status: { + /** @enum {string} */ + apple?: "delivered" | "skipped_no_credential" | "failed"; + /** @enum {string} */ + google?: "delivered" | "skipped_no_credential" | "failed"; + } | null; + url: string; + download_url: string | null; + google_wallet_url: string | null; + created_at: string; + }; + PassCreateResponse: { + id: string; + /** @enum {string} */ + object: "pass"; + short_id: string; + template_id: string; + certificate_set_id: string | null; + serial_number: string; + mode: string; + holder_email: string | null; + holder_name: string | null; + /** @description The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is. */ + field_values: { + [key: string]: unknown; + }; + voided: boolean; + voided_at: string | null; + redeemed_count: number; + first_redeemed_at: string | null; + /** @description Free-form key/value data you attached to the pass, or null. */ + metadata: { + [key: string]: unknown; + } | null; + /** @description The template image variant this pass renders with, or null for the template's own images. */ + image_variant: string | null; + /** + * @description Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs. + * @example { + * "icon": { + * "source": "template" + * }, + * "logo": { + * "source": "template" + * }, + * "thumbnail": { + * "source": "pass" + * } + * } + */ + images: { + [key: string]: { + /** @enum {string} */ + source: "pass" | "variant" | "template"; + }; + }; + created_via_api: boolean; + /** @description The wallets this pass was issued for. */ + platforms: ("apple" | "google")[]; + /** @description Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it. */ + platform_status: { + /** @enum {string} */ + apple?: "delivered" | "skipped_no_credential" | "failed"; + /** @enum {string} */ + google?: "delivered" | "skipped_no_credential" | "failed"; + } | null; + url: string; + download_url: string | null; + google_wallet_url: string | null; + created_at: string; + warnings: string[]; + }; + PassUpdateResponse: { + id: string; + /** @enum {string} */ + object: "pass"; + short_id: string; + template_id: string; + certificate_set_id: string | null; + serial_number: string; + mode: string; + holder_email: string | null; + holder_name: string | null; + /** @description The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is. */ + field_values: { + [key: string]: unknown; + }; + voided: boolean; + voided_at: string | null; + redeemed_count: number; + first_redeemed_at: string | null; + /** @description Free-form key/value data you attached to the pass, or null. */ + metadata: { + [key: string]: unknown; + } | null; + /** @description The template image variant this pass renders with, or null for the template's own images. */ + image_variant: string | null; + /** + * @description Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs. + * @example { + * "icon": { + * "source": "template" + * }, + * "logo": { + * "source": "template" + * }, + * "thumbnail": { + * "source": "pass" + * } + * } + */ + images: { + [key: string]: { + /** @enum {string} */ + source: "pass" | "variant" | "template"; + }; + }; + created_via_api: boolean; + /** @description The wallets this pass was issued for. */ + platforms: ("apple" | "google")[]; + /** @description Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it. */ + platform_status: { + /** @enum {string} */ + apple?: "delivered" | "skipped_no_credential" | "failed"; + /** @enum {string} */ + google?: "delivered" | "skipped_no_credential" | "failed"; + } | null; + url: string; + download_url: string | null; + google_wallet_url: string | null; + created_at: string; + delivery: components["schemas"]["PassDelivery"]; + warnings: string[]; + }; + PassDelivery: { + /** @description Null when the pass was not delivered to Apple Wallet. `failed` excludes dead tokens (`unregistered`), which mean the holder deleted the pass. */ + apple: { + devices: number; + pushed: number; + failed: number; + unregistered: number; + } | null; + /** @description Null when the pass has no Google Wallet object. `not_saved`: no holder has saved it yet. `skipped`: Google delivery was skipped at issue. */ + google: { + /** @enum {string} */ + status: "updated" | "not_saved" | "failed" | "skipped"; + error?: string; + } | null; + }; + PassVoidResponse: { + id: string; + /** @enum {string} */ + object: "pass"; + short_id: string; + template_id: string; + certificate_set_id: string | null; + serial_number: string; + mode: string; + holder_email: string | null; + holder_name: string | null; + /** @description The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is. */ + field_values: { + [key: string]: unknown; + }; + voided: boolean; + voided_at: string | null; + redeemed_count: number; + first_redeemed_at: string | null; + /** @description Free-form key/value data you attached to the pass, or null. */ + metadata: { + [key: string]: unknown; + } | null; + /** @description The template image variant this pass renders with, or null for the template's own images. */ + image_variant: string | null; + /** + * @description Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs. + * @example { + * "icon": { + * "source": "template" + * }, + * "logo": { + * "source": "template" + * }, + * "thumbnail": { + * "source": "pass" + * } + * } + */ + images: { + [key: string]: { + /** @enum {string} */ + source: "pass" | "variant" | "template"; + }; + }; + created_via_api: boolean; + /** @description The wallets this pass was issued for. */ + platforms: ("apple" | "google")[]; + /** @description Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it. */ + platform_status: { + /** @enum {string} */ + apple?: "delivered" | "skipped_no_credential" | "failed"; + /** @enum {string} */ + google?: "delivered" | "skipped_no_credential" | "failed"; + } | null; + url: string; + download_url: string | null; + google_wallet_url: string | null; + created_at: string; + warnings: string[]; + }; + PassEventList: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["PassEvent"][]; + has_more: boolean; + }; + PassEvent: { + id: string; + /** @enum {string} */ + object: "pass_event"; + pass_id: string; + type: string; + /** @description Event-specific detail, such as the platform or a redemption's scanner. Shape varies by `type`; null when the event carries none. */ + metadata: { + [key: string]: unknown; + } | null; + created_at: string; + }; + RedemptionList: { + /** @enum {string} */ + object: "list"; + data: components["schemas"]["Redemption"][]; + has_more: boolean; + }; + Redemption: { + id: string; + /** @enum {string} */ + object: "redemption"; + /** @enum {string} */ + decision: "accepted" | "rejected"; + /** @enum {string|null} */ + reason: "already_redeemed" | "count_exhausted" | "voided" | "not_found" | "out_of_scope" | "wrong_mode" | null; + scanner_name: string | null; + scanned_at: string; + }; + PassDownloadLink: { + /** @enum {string} */ + object: "pass.download_link"; + pass_id: string; + url: string; + expires_in: number; + expires_at: string; + created_at: string; + }; + PassImageResponse: { + id: string; + /** @enum {string} */ + object: "pass"; + short_id: string; + template_id: string; + certificate_set_id: string | null; + serial_number: string; + mode: string; + holder_email: string | null; + holder_name: string | null; + /** @description The pass's field values by field key. Normally strings; any JSON value sent is stored and returned as-is. */ + field_values: { + [key: string]: unknown; + }; + voided: boolean; + voided_at: string | null; + redeemed_count: number; + first_redeemed_at: string | null; + /** @description Free-form key/value data you attached to the pass, or null. */ + metadata: { + [key: string]: unknown; + } | null; + /** @description The template image variant this pass renders with, or null for the template's own images. */ + image_variant: string | null; + /** + * @description Every image slot the pass renders, keyed by Apple slot name, with the layer it comes from: `pass` (this pass's own image, `PUT /v1/passes/{id}/images/{slot}`), `variant` (its `image_variant`) or `template`. Carries no URLs. + * @example { + * "icon": { + * "source": "template" + * }, + * "logo": { + * "source": "template" + * }, + * "thumbnail": { + * "source": "pass" + * } + * } + */ + images: { + [key: string]: { + /** @enum {string} */ + source: "pass" | "variant" | "template"; + }; + }; + created_via_api: boolean; + /** @description The wallets this pass was issued for. */ + platforms: ("apple" | "google")[]; + /** @description Per-wallet delivery outcome. A wallet is absent when the pass was not issued for it. */ + platform_status: { + /** @enum {string} */ + apple?: "delivered" | "skipped_no_credential" | "failed"; + /** @enum {string} */ + google?: "delivered" | "skipped_no_credential" | "failed"; + } | null; + url: string; + download_url: string | null; + google_wallet_url: string | null; + created_at: string; + warnings: string[]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record; diff --git a/src/index.ts b/src/index.ts index 9c7fe42..3beb2b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export { PassmintRateLimitError, } from './errors' export type { ApiErrorPayload } from './errors' +export { autoPaginate } from './pagination' export { PASSMINT_EVENT_TYPES } from './types' export type { Account, @@ -21,20 +22,29 @@ export type { BackfillResult, BarcodeFormat, CreatePassParams, + CreateDownloadLinkParams, CreateTemplateParams, CreateWebhookParams, + DownloadLink, EventPass, EventSource, FunnelGroupBy, FunnelParams, FunnelResponse, FunnelSummary, + ListDeliveriesParams, ListEventsParams, + ListPassEventsParams, ListPassesParams, + ListRedemptionsParams, ListResponse, + ListTemplatesParams, + ListWebhooksParams, MinimizedHolder, Pass, + PassDelivery, PassEvent, + PassEventType, PassImages, PassImageSlot, PassImageSource, @@ -43,6 +53,8 @@ export type { PassmintMode, PassPlatformStatus, PlatformDeliveryStatus, + Redemption, + RedemptionPolicy, RequestOptions, Template, TemplateDesign, diff --git a/src/pagination.ts b/src/pagination.ts new file mode 100644 index 0000000..c742107 --- /dev/null +++ b/src/pagination.ts @@ -0,0 +1,32 @@ +import type { ListResponse } from './types' + +/** + * Walks a cursor-paginated list: fetches a page, yields its items, and asks for the page + * after the last item's id until `has_more` is false. Stops on an empty page even if the + * server says there is more, so a misbehaving response can't spin forever. + */ +export async function* autoPaginate( + fetchPage: (startingAfter?: string) => Promise>, +): AsyncIterableIterator { + let startingAfter: string | undefined + + for (;;) { + const page = await fetchPage(startingAfter) + + yield* page.data + + const last = page.data.at(-1) + + if (!page.has_more || !last) return + + startingAfter = last.id + } +} + +/** Builds the params for the next page without ever passing `startingAfter: undefined`. */ +export function withCursor

( + params: P, + cursor: string | undefined, +): P & { startingAfter?: string } { + return { ...params, ...(cursor ? { startingAfter: cursor } : {}) } +} diff --git a/src/resources/events.ts b/src/resources/events.ts index 442ec53..32587b3 100644 --- a/src/resources/events.ts +++ b/src/resources/events.ts @@ -1,4 +1,5 @@ import type { PassmintHttpClient } from '../client' +import { autoPaginate, withCursor } from '../pagination' import type { ListEventsParams, ListResponse, PassmintEvent } from '../types' export class EventsResource { @@ -19,4 +20,8 @@ export class EventsResource { }, }) } + /** Iterate every matching event, fetching further pages as needed. */ + listAll(params: ListEventsParams = {}): AsyncIterableIterator { + return autoPaginate((cursor) => this.list(withCursor(params, cursor))) + } } diff --git a/src/resources/passes.ts b/src/resources/passes.ts index 41c0b49..2ba937e 100644 --- a/src/resources/passes.ts +++ b/src/resources/passes.ts @@ -1,12 +1,19 @@ import { type PassmintHttpClient, generateIdempotencyKey } from '../client' import { toBase64 } from '../encoding' +import { autoPaginate, withCursor } from '../pagination' import type { + CreateDownloadLinkParams, CreatePassParams, + DownloadLink, + ListPassEventsParams, ListPassesParams, + ListRedemptionsParams, ListResponse, Pass, + PassDelivery, PassEvent, PassImageSlot, + Redemption, RequestOptions, UpdatePassParams, } from '../types' @@ -53,18 +60,28 @@ export class PassesResource { template_id: params.templateId, holder_email: params.holderEmail, limit: params.limit, + starting_after: params.startingAfter, }, }) } - update(id: string, params: UpdatePassParams, options: RequestOptions = {}): Promise { + /** Iterate every matching pass, fetching further pages as needed. */ + listAll(params: ListPassesParams = {}): AsyncIterableIterator { + return autoPaginate((cursor) => this.list(withCursor(params, cursor))) + } + + update( + id: string, + params: UpdatePassParams, + options: RequestOptions = {}, + ): Promise { const body: Record = {} if (params.fieldValues !== undefined) body.field_values = params.fieldValues if (params.metadata !== undefined) body.metadata = params.metadata // null clears the variant back to the base images, so only omit the key // when truly unset. if (params.imageVariant !== undefined) body.image_variant = params.imageVariant - return this.http.request({ + return this.http.request({ method: 'PATCH', path: `/v1/passes/${encodeURIComponent(id)}`, body, @@ -72,8 +89,8 @@ export class PassesResource { }) } - void(id: string): Promise { - return this.http.request({ + void(id: string): Promise { + return this.http.request({ method: 'DELETE', path: `/v1/passes/${encodeURIComponent(id)}`, }) @@ -106,10 +123,39 @@ export class PassesResource { }) } - events(id: string): Promise> { + events(id: string, params: ListPassEventsParams = {}): Promise> { return this.http.request>({ method: 'GET', path: `/v1/passes/${encodeURIComponent(id)}/events`, + query: { limit: params.limit, starting_after: params.startingAfter }, + }) + } + + /** Iterate every event for this pass, fetching further pages as needed. */ + listAllEvents(id: string, params: ListPassEventsParams = {}): AsyncIterableIterator { + return autoPaginate((cursor) => this.events(id, withCursor(params, cursor))) + } + + /** The pass's scan log: every accepted or rejected redemption attempt, newest first. */ + redemptions(id: string, params: ListRedemptionsParams = {}): Promise> { + return this.http.request>({ + method: 'GET', + path: `/v1/passes/${encodeURIComponent(id)}/redemptions`, + query: { limit: params.limit }, + }) + } + + /** + * Create a short-lived link that downloads this pass. Not an idempotent + * route on the server, so no Idempotency-Key is sent. + */ + createDownloadLink(id: string, params: CreateDownloadLinkParams = {}): Promise { + const body: Record = {} + if (params.expiresIn !== undefined) body.expires_in = params.expiresIn + return this.http.request({ + method: 'POST', + path: `/v1/passes/${encodeURIComponent(id)}/download-links`, + body, }) } } diff --git a/src/resources/templates.ts b/src/resources/templates.ts index 0f4fee9..b4b4c81 100644 --- a/src/resources/templates.ts +++ b/src/resources/templates.ts @@ -1,8 +1,11 @@ import { type PassmintHttpClient, generateIdempotencyKey } from '../client' import { toBase64 } from '../encoding' +import { autoPaginate, withCursor } from '../pagination' import type { CreateTemplateParams, ListResponse, + ListTemplatesParams, + RedemptionPolicy, RequestOptions, Template, TemplateImage, @@ -12,6 +15,14 @@ import type { VariantImageSlot, } from '../types' +/** The request schema spells the cap `max_uses`; the camelCase param type matches what GET returns. */ +function redemptionPolicyBody(policy: RedemptionPolicy) { + return { + mode: policy.mode, + ...(policy.maxUses !== undefined ? { max_uses: policy.maxUses } : {}), + } +} + export class TemplatesResource { constructor(private readonly http: PassmintHttpClient) {} @@ -24,6 +35,10 @@ export class TemplatesResource { starter_template_id: params.starterTemplateId, } if (params.platforms !== undefined) body.platforms = params.platforms + if (params.redemptionPolicy !== undefined) + body.redemption_policy = redemptionPolicyBody(params.redemptionPolicy) + if (params.requireDownloadLink !== undefined) + body.require_download_link = params.requireDownloadLink return this.http.request