Developers / WalletPassBuilder
API documentation
Create, read, update, and archive Apple Wallet and Google Wallet passes, check your pass-credit balance, and send notifications - all over a simple REST API.
Authentication
Every request is authenticated with a bearer token in the Authorization header. Generate a key from Dashboard → API - it’s shown once, in full, at creation. There’s one key per account with full access to that account’s passes, credits, and notifications; regenerating it immediately invalidates the previous one.
curl https://walletpassbuilder.com/api/v1/passes \
-H "Authorization: Bearer wpb_live_••••••••••••••••••••••••••••••••••••"Keys never expire on their own but can be regenerated any time. Never expose a key in client-side code - call the API from your server.
Workspaces
An account can have several workspaces, each with its own passes, stats, automations and credits. Your API key works across all of them. To say which workspace a request is for, send its id in the X-Workspace-Id header, right next to your API key:
curl -X POST https://walletpassbuilder.com/api/v1/passes \
-H "Authorization: Bearer wpb_live_••••••••••••••••••••••••••••••••••••" \
-H "X-Workspace-Id: 66f0c1a2b3c4d5e6f7a8b9c0" \
-H "Content-Type: application/json" \
-d '{ "passTemplate": { ... } }'- Where to find an id. Under Dashboard → API (every workspace, with a copy button) or Settings → Workspace (the one you’re in).
- If you leave it out, the request uses your default workspace. Choose it under Dashboard → API or Settings → Workspace (“Make default”). New accounts start with their first workspace as the default.
- Where it applies. Create pass (omitted: the default workspace), and List passes, List notifications and Send event (omitted: every workspace). Endpoints addressed by a pass id, like
/v1/passes/:id, ignore it - a pass already belongs to one workspace. - Errors. An id that isn’t one of your workspaces returns
invalid_request. The header is the only way to name a workspace: aworkspaceIdfield in the body or query string is rejected withinvalid_request.
Passes
A pass’s design lives in passTemplate - the same JSON shape the builder itself saves, so anything you can design in the dashboard, you can send here. For a dynamic pass (one row per recipient - loyalty cards, event tickets, etc.), also pass passData: an array of { values: { <field>: <string> } } objects, one per recipient. Omit passData (or send an empty array) for a single static pass.
passType, cardTitle, logoText, backgroundColor, and assets.logo are required - everything else defaults sensibly if you leave it out.
passTemplate fields (most commonly used)
| Name | Type | Required | Description |
|---|---|---|---|
| passType | "generic" | "loyalty" | Yes | generic covers gift cards, tickets, and membership cards; loyalty adds a stamp counter. Every pass renders with the same visual layout regardless of type. |
| backgroundColor | string (hex) | Yes | Pass background color, e.g. "#130c09". Shared by both wallets. |
| logoText | string | Yes | Brand or business name shown next to the logo. |
| header | string | No | Small header text at the top of the pass. |
| cardTitle | string | Yes | Main title, e.g. "$50 gift card". |
| assets.logo | string | Yes | Small square logo image URL. |
| assets.appleHero | string | null | No | Apple Wallet hero/strip image URL. |
| assets.googleHero | string | null | No | Google Wallet hero image URL. |
| rows | { left, middle, right: { label: string, value: string } }[] | No | One entry per row of fields, each with three columns. Only the first entry is rendered today - more may be supported later, so send extra rows and they'll just be ignored rather than rejected. |
| scan.hidden | boolean | No | Hides the barcode/QR block entirely. |
| scan.type | "barcode" | "qr" | No | Which scan code to render. Ignored if scan.hidden is true. |
| scan.value | string | No | The encoded value. Blank by default - set this unless scan.hidden is true. |
| scan.label | string | No | Text shown under the scan code. |
| backSections | BackSection[] | No | Extra rows on the back of the pass: { id, type: "Text" | "Link" | "Phone", label, value }. Link opens value as a URL, Phone dials it, Text just displays it. id is reserved on the value "__notification" - a Send Notification call upserts that one section, so using it yourself means a future notification will overwrite it. |
| apple.textColor | string (hex) | No | Primary text color. Apple Wallet only - Google Wallet has no equivalent field. |
| apple.labelColor | string (hex) | No | Field label color. Apple Wallet only. |
| apple.topRightLabel / apple.topRightValue | string | No | A small label/value pair in the pass's top-right corner. Apple Wallet only. |
passTemplate example
{
"passType": "generic",
"backgroundColor": "#130c09",
"logoText": "Danny's Cafe",
"header": "",
"cardTitle": "$50 gift card",
"assets": {
"logo": "https://storage.googleapis.com/.../logo.png",
"rectLogo": null,
"googleHero": "https://storage.googleapis.com/.../hero.png",
"appleHero": "https://storage.googleapis.com/.../hero.png",
"appleThumbnail": null
},
"rows": [
{
"left": { "label": "VALID TILL", "value": "SEP 2030" },
"middle": { "label": "", "value": "" },
"right": { "label": "VALID AT", "value": "New York" }
}
],
"scan": {
"hidden": false,
"type": "barcode",
"value": "3495709095830",
"label": "3495709095830"
},
"backSections": [
{ "id": "terms", "type": "Text", "label": "Terms", "value": "Valid at participating locations only. Not redeemable for cash." },
{ "id": "support", "type": "Phone", "label": "Support", "value": "+15551234567" },
{ "id": "website", "type": "Link", "label": "Our website", "value": "https://dannyscafe.example.com" }
],
"notification": {
"triggers": [],
"eventStartDate": "",
"scheduledExpiry": "",
"locations": [],
"beacons": []
},
"apple": {
"textColor": "#ffffff",
"labelColor": "#fafafa",
"topRightLabel": "",
"topRightValue": ""
},
"restrictSharing": false,
"accessCode": ""
}/v1/passesCreate a pass
Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| passTemplate | PassTemplate | Yes | The pass design - see the field reference above. |
| passData | PassDataRow[] | No | One entry per recipient for a dynamic pass: { values: { <field>: string } }. Omit, or send an empty array, for a single static pass. |
| X-Workspace-Id | string (header) | No | Which workspace the pass belongs to - find a workspace’s id on the API Keys page. Omit to use the account’s default workspace. See Workspaces. |
Response fields
| Name | Type | Description |
|---|---|---|
| passId | string | Unique id for this pass - also its Apple serial number. Use it in the get/update/delete/notification endpoints. |
| passUrl | string | Public share link - auto-detects the visitor's wallet and shows the right "Add to Wallet" button. This is what you share with your customer. |
| workspaceId | string | The workspace the pass was created in - the one you named, or your default if you didn't. |
Static pass
Request
curl -X POST https://walletpassbuilder.com/api/v1/passes \
-H "Authorization: Bearer wpb_live_..." \
-H "Content-Type: application/json" \
-d '{
"passTemplate": {
"passType": "generic",
"backgroundColor": "#130c09",
"logoText": "Danny's Cafe",
"header": "",
"cardTitle": "$50 gift card",
"assets": {
"logo": "https://storage.googleapis.com/.../logo.png",
"rectLogo": null,
"googleHero": "https://storage.googleapis.com/.../hero.png",
"appleHero": "https://storage.googleapis.com/.../hero.png",
"appleThumbnail": null
},
"rows": [
{
"left": { "label": "VALID TILL", "value": "SEP 2030" },
"middle": { "label": "", "value": "" },
"right": { "label": "VALID AT", "value": "New York" }
}
],
"scan": {
"hidden": false,
"type": "barcode",
"value": "3495709095830",
"label": "3495709095830"
},
"backSections": [
{ "id": "terms", "type": "Text", "label": "Terms", "value": "Valid at participating locations only. Not redeemable for cash." },
{ "id": "support", "type": "Phone", "label": "Support", "value": "+15551234567" },
{ "id": "website", "type": "Link", "label": "Our website", "value": "https://dannyscafe.example.com" }
],
"notification": {
"triggers": [],
"eventStartDate": "",
"scheduledExpiry": "",
"locations": [],
"beacons": []
},
"apple": {
"textColor": "#ffffff",
"labelColor": "#fafafa",
"topRightLabel": "",
"topRightValue": ""
},
"restrictSharing": false,
"accessCode": ""
}
}'Response
201 Created
{
"passId": "10a1b2c3d4e5f6",
"passUrl": "https://gowalletpass.com/p/10a1b2c3d4e5f6",
"workspaceId": "66f0c1a2b3c4d5e6f7a8b9c0"
}Dynamic pass (one per recipient)
Request
curl -X POST https://walletpassbuilder.com/api/v1/passes \
-H "Authorization: Bearer wpb_live_..." \
-H "Content-Type: application/json" \
-d '{
"passTemplate": {
"passType": "generic",
"backgroundColor": "#130c09",
"logoText": "Danny's Cafe",
"header": "",
"cardTitle": "Member Card",
"assets": {
"logo": "https://storage.googleapis.com/.../logo.png",
"rectLogo": null,
"googleHero": "https://storage.googleapis.com/.../hero.png",
"appleHero": "https://storage.googleapis.com/.../hero.png",
"appleThumbnail": null
},
"rows": [
{
"left": { "label": "NAME", "value": "@name" },
"middle": { "label": "", "value": "" },
"right": { "label": "MEMBER ID", "value": "@memberId" }
}
],
"scan": {
"hidden": false,
"type": "qr",
"value": "@memberId",
"label": "@memberId"
},
"backSections": [
{ "id": "terms", "type": "Text", "label": "Terms", "value": "Membership is non-transferable." },
{ "id": "website", "type": "Link", "label": "Manage membership", "value": "https://dannyscafe.example.com/account" }
],
"notification": {
"triggers": [],
"eventStartDate": "",
"scheduledExpiry": "",
"locations": [],
"beacons": []
},
"apple": {
"textColor": "#ffffff",
"labelColor": "#fafafa",
"topRightLabel": "",
"topRightValue": ""
},
"restrictSharing": false,
"accessCode": ""
},
"passData": [
{ "values": { "name": "Alice", "memberId": "1001" } },
{ "values": { "name": "Bob", "memberId": "1002" } }
]
}'Response
201 Created
{
"passId": "10f7e2a9c31b",
"passUrl": "https://gowalletpass.com/p/10f7e2a9c31b",
"workspaceId": "66f0c1a2b3c4d5e6f7a8b9c0"
}/v1/passes/:idGet a pass
Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (path) | Yes | The pass id, returned as passId when it was created. |
Response fields
| Name | Type | Description |
|---|---|---|
| passId | string | Unique id for this pass. |
| passTemplate | PassTemplate | The pass's full design. |
| passData | PassDataRow[] | Recipient rows, for a dynamic pass. Empty for a static pass. |
| status | "live" | "archived" | "blocked" | Whether the pass is currently active. "blocked" means we disabled it for a policy violation - it can't be set via this API. |
| createdAt | string (ISO 8601) | When the pass was created. |
| updatedAt | string (ISO 8601) | When the pass was last updated. |
| passUrl | string | Public share link for this pass. |
Request
curl https://walletpassbuilder.com/api/v1/passes/10a1b2c3d4e5f6 \
-H "Authorization: Bearer wpb_live_..."Response
200 OK
{
"passId": "10a1b2c3d4e5f6",
"passTemplate": { "...": "..." },
"passData": [],
"status": "live",
"createdAt": "2026-01-15T18:04:11.000Z",
"updatedAt": "2026-01-15T18:04:11.000Z",
"passUrl": "https://gowalletpass.com/p/10a1b2c3d4e5f6"
}/v1/passesList passes
?status=live or ?status=archived, or scope to one workspace with the X-Workspace-Id header.Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | "live" | "archived" | "blocked" (query) | No | Filter results to just one status. Omit to get every status. |
| X-Workspace-Id | string (header) | No | Scope results to one workspace - find its id on the API Keys page. Omit to merge every workspace on the account. |
Response fields
| Name | Type | Description |
|---|---|---|
| passes | PassListItem[] | Every matching pass - see the fields listed under Get Pass above. |
Request
curl https://walletpassbuilder.com/api/v1/passes?status=live \
-H "Authorization: Bearer wpb_live_..."Response
200 OK
{
"passes": [
{
"passId": "10a1b2c3d4e5f6",
"passTemplate": { "...": "..." },
"passData": [],
"status": "live",
"createdAt": "2026-01-15T18:04:11.000Z",
"updatedAt": "2026-01-15T18:04:11.000Z",
"passUrl": "https://gowalletpass.com/p/10a1b2c3d4e5f6"
}
]
}/v1/passes/:idUpdate a pass
Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (path) | Yes | The pass id to update. |
| passTemplate | PassTemplate | Yes | The full new pass design - not a partial patch of individual fields. |
| passData | PassDataRow[] | No | New recipient rows. Omit to leave the existing data unchanged. |
Response fields
| Name | Type | Description |
|---|---|---|
| passId | string | Unique id for this pass - also its Apple serial number. Use it in the get/update/delete/notification endpoints. |
| passUrl | string | Public share link - auto-detects the visitor's wallet and shows the right "Add to Wallet" button. This is what you share with your customer. |
| workspaceId | string | The workspace the pass was created in - the one you named, or your default if you didn't. |
Request
curl -X PATCH https://walletpassbuilder.com/api/v1/passes/10a1b2c3d4e5f6 \
-H "Authorization: Bearer wpb_live_..." \
-H "Content-Type: application/json" \
-d '{ "passTemplate": { "cardTitle": "$75 gift card", "...": "..." } }'Response
200 OK
{
"passId": "10a1b2c3d4e5f6",
"passUrl": "https://gowalletpass.com/p/10a1b2c3d4e5f6"
}/v1/passes/:idDelete (archive) a pass
Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (path) | Yes | The pass id to archive. |
Response fields
| Name | Type | Description |
|---|---|---|
| passId | string | The archived pass's id. |
| status | "archived" | Always "archived" on success. |
Request
curl -X DELETE https://walletpassbuilder.com/api/v1/passes/10a1b2c3d4e5f6 \
-H "Authorization: Bearer wpb_live_..."Response
200 OK
{ "passId": "10a1b2c3d4e5f6", "status": "archived" }/v1/creditsGet credit balance
Response fields
| Name | Type | Description |
|---|---|---|
| balance | integer | Total pass credits on the account's plan. |
| consumed | integer | Credits used so far. |
| remaining | integer | balance minus consumed, floored at 0. |
| remainingPct | number | remaining as a percentage of balance, 0-100. |
| low | boolean | true when remainingPct is 10% or less. |
Request
curl https://walletpassbuilder.com/api/v1/credits \
-H "Authorization: Bearer wpb_live_..."Response
200 OK
{
"balance": 1000,
"consumed": 214,
"remaining": 786,
"remainingPct": 78.6,
"low": false
}/v1/passes/:id/notificationsSend or schedule a notification
scheduledAt (an ISO 8601 timestamp in the future) - queues it to go out later. subject is optional (35 characters max); message is required (500 characters max).Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (path) | Yes | The pass id to notify holders of. |
| message | string | Yes | The notification body. 500 characters max. |
| subject | string | No | A short subject/title. 35 characters max. |
| scheduledAt | string (ISO 8601) | No | Send later instead of immediately. Must be in the future. |
Response fields
| Name | Type | Description |
|---|---|---|
| notification.id | string | Unique id for this notification - the only handle to cancel it later. |
| notification.status | "scheduled" | "sending" | "sent" | "failed" | "canceled" | Current delivery status. |
| notification.scheduledAt | string (ISO 8601) | null | When it's due to send, if scheduled. |
| notification.sentAt | string (ISO 8601) | null | When it actually sent, once it has. |
| notification.error | string | What went wrong, if status is "failed". |
| notification.results | { applePushed, googleMessaged: boolean } | Which wallets were actually reached. |
Request
curl -X POST https://walletpassbuilder.com/api/v1/passes/10a1b2c3d4e5f6/notifications \
-H "Authorization: Bearer wpb_live_..." \
-H "Content-Type: application/json" \
-d '{
"subject": "New reward",
"message": "You just unlocked a free coffee - come redeem it today!"
}'Response
201 Created
{
"notification": {
"id": "3f9c1e2b-...",
"status": "sent",
"scheduledAt": null,
"sentAt": "2026-01-15T18:10:03.000Z",
"results": { "applePushed": true, "googleMessaged": true }
}
}Automatic triggers
Separate from sending a notification yourself (above), a pass can also be configured to surface itself on the lock screen automatically - at an event’s start time, before it expires, or when the holder is physically near a location or iBeacon. These are set on passTemplate at create/update time; the OS decides exactly when to show it, not you.
Apple Wallet only, currently - none of these triggers have a Google Wallet equivalent yet in this API. If you need both wallets notified, use Send Notification instead, which reaches both.
| Name | Type | Required | Description |
|---|---|---|---|
| notification.triggers | ("eventStart" | "beforeExpiry" | "nearLocation" | "nearBeacon")[] | No | Which triggers are on for this pass. Each one also needs its own field(s) below to actually fire. |
| notification.eventStartDate | string (ISO 8601) | Conditional | Required if notification.triggers includes "eventStart". Surfaces the pass on the lock screen at this time. |
| notification.scheduledExpiry | string (ISO 8601) | Conditional | Required if notification.triggers includes "beforeExpiry". Surfaces the pass 48 hours before this time. Whether or not the trigger is on, the pass is also marked expired at this time on both wallets: Apple Wallet’s expiration date, and Google Wallet’s valid-until time. |
| notification.locations | NotifyLocation[] | Conditional | Required if notification.triggers includes "nearLocation". Up to 10: { id, country, address, latitude, longitude }. |
| notification.beacons | NotifyBeacon[] | Conditional | Required if notification.triggers includes "nearBeacon". Up to 10: { id, uuid, major, minor, message }. |
passTemplate fields for an event ticket that notifies at start time and near the venue
{
"notification": {
"triggers": ["eventStart", "nearLocation"],
"eventStartDate": "2026-09-14T19:00:00.000Z",
"locations": [
{ "id": "loc1", "country": "US", "address": "41 Street, NYC", "latitude": "40.758", "longitude": "-73.985" }
]
}
}/v1/notificationsList notifications
X-Workspace-Id header to see only one workspace’s.Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| X-Workspace-Id | string (header) | No | Only list notifications for passes in this workspace. Omit to include every workspace. See Workspaces. |
Response fields
| Name | Type | Description |
|---|---|---|
| notifications | Notification[] | Every notification - see the fields listed under Send Notification above. |
Request
curl https://walletpassbuilder.com/api/v1/notifications \
-H "Authorization: Bearer wpb_live_..."Response
200 OK
{ "notifications": [ { "id": "3f9c1e2b-...", "...": "..." } ] }/v1/notifications/:idCancel a scheduled notification
?passId=....Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (path) | Yes | The notification id, returned as notification.id when it was scheduled. |
| passId | string (query) | Yes | The pass the notification was scheduled for. |
Response fields
| Name | Type | Description |
|---|---|---|
| id | string | The canceled notification's id. |
| status | "canceled" | Always "canceled" on success. |
Request
curl -X DELETE "https://walletpassbuilder.com/api/v1/notifications/3f9c1e2b-...?passId=10a1b2c3d4e5f6" \
-H "Authorization: Bearer wpb_live_..."Response
200 OK
{ "id": "3f9c1e2b-...", "status": "canceled" }/v1/eventsSend a custom event
data can be any JSON payload; automation conditions read it by dot-path, e.g. event.data.tier.Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
| X-Workspace-Id | string (header) | No | Only match automations in this workspace. Omit to match automations in every workspace. See Workspaces. |
| name | string | Yes | A caller-chosen event identifier, e.g. order.shipped. Matched exactly against each automation’s trigger. 100 characters max. |
| data | object | No | Any JSON payload, up to 32KB. Stored on the run and readable by condition/action config in the automation builder. |
Response fields
| Name | Type | Description |
|---|---|---|
| triggered | integer | How many automation runs were queued. |
| runIds | string[] | The id of each queued run. |
Request
curl -X POST https://walletpassbuilder.com/api/v1/events \
-H "Authorization: Bearer wpb_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "order.shipped",
"data": { "passId": "10a1b2c3d4e5f6", "tier": "vip" }
}'Response
201 Created
{ "triggered": 1, "runIds": ["3f9c1e2b-..."] }Rate limits
100 requests per minute per API key, across all endpoints. Exceeding it returns 429 rate_limited with a Retry-After header (seconds until you can retry). If you need a higher limit for a bulk import or migration, reach out and we’ll raise it for your account.
Error codes
Every error response has the same shape:
{
"error": {
"code": "not_found",
"message": "No pass found with that id."
}
}| Code | Status | Meaning |
|---|---|---|
| unauthorized | 401 | The API key is missing, invalid, or has been revoked (regenerated since it was issued). |
| invalid_request | 422 | The request body or a parameter is missing or malformed - see the message for specifics. |
| not_found | 404 | No matching pass or notification exists for this account - also returned for an id that belongs to a different account, so a key can never confirm another account's data exists. |
| credit_limit_exceeded | 402 | Creating or growing this pass would exceed the account's pass-credit balance. |
| rate_limited | 429 | Too many requests. Check the Retry-After header (seconds) before retrying. |
| internal_error | 500 | Something went wrong on our end. Safe to retry. |
Machine-readable reference
The full API contract is published as an OpenAPI 3.1 spec - useful for generating a client, importing into Postman, or wiring up an MCP/agent tool.