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.

https://walletpassbuilder.com/api/v1OpenAPI specGet your API key

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: a workspaceId field in the body or query string is rejected with invalid_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)

NameTypeRequiredDescription
passType"generic" | "loyalty"Yesgeneric covers gift cards, tickets, and membership cards; loyalty adds a stamp counter. Every pass renders with the same visual layout regardless of type.
backgroundColorstring (hex)YesPass background color, e.g. "#130c09". Shared by both wallets.
logoTextstringYesBrand or business name shown next to the logo.
headerstringNoSmall header text at the top of the pass.
cardTitlestringYesMain title, e.g. "$50 gift card".
assets.logostringYesSmall square logo image URL.
assets.appleHerostring | nullNoApple Wallet hero/strip image URL.
assets.googleHerostring | nullNoGoogle Wallet hero image URL.
rows{ left, middle, right: { label: string, value: string } }[]NoOne 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.hiddenbooleanNoHides the barcode/QR block entirely.
scan.type"barcode" | "qr"NoWhich scan code to render. Ignored if scan.hidden is true.
scan.valuestringNoThe encoded value. Blank by default - set this unless scan.hidden is true.
scan.labelstringNoText shown under the scan code.
backSectionsBackSection[]NoExtra 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.textColorstring (hex)NoPrimary text color. Apple Wallet only - Google Wallet has no equivalent field.
apple.labelColorstring (hex)NoField label color. Apple Wallet only.
apple.topRightLabel / apple.topRightValuestringNoA 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": ""
}
POST/v1/passes

Create a pass

Creates and signs a new pass. Costs one pass credit for a static pass, or one credit per row for a dynamic one.

Request parameters

NameTypeRequiredDescription
passTemplatePassTemplateYesThe pass design - see the field reference above.
passDataPassDataRow[]NoOne entry per recipient for a dynamic pass: { values: { <field>: string } }. Omit, or send an empty array, for a single static pass.
X-Workspace-Idstring (header)NoWhich 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

NameTypeDescription
passIdstringUnique id for this pass - also its Apple serial number. Use it in the get/update/delete/notification endpoints.
passUrlstringPublic share link - auto-detects the visitor's wallet and shows the right "Add to Wallet" button. This is what you share with your customer.
workspaceIdstringThe 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"
}
GET/v1/passes/:id

Get a pass

Fetches one pass by id, including its full design and (for a dynamic pass) every row.

Request parameters

NameTypeRequiredDescription
idstring (path)YesThe pass id, returned as passId when it was created.

Response fields

NameTypeDescription
passIdstringUnique id for this pass.
passTemplatePassTemplateThe pass's full design.
passDataPassDataRow[]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.
createdAtstring (ISO 8601)When the pass was created.
updatedAtstring (ISO 8601)When the pass was last updated.
passUrlstringPublic 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"
}
GET/v1/passes

List passes

Every pass the account owns across every workspace, newest first. Filter with ?status=live or ?status=archived, or scope to one workspace with the X-Workspace-Id header.

Request parameters

NameTypeRequiredDescription
status"live" | "archived" | "blocked" (query)NoFilter results to just one status. Omit to get every status.
X-Workspace-Idstring (header)NoScope results to one workspace - find its id on the API Keys page. Omit to merge every workspace on the account.

Response fields

NameTypeDescription
passesPassListItem[]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"
    }
  ]
}
PATCH/v1/passes/:id

Update a pass

Re-signs an existing pass with a new design. The pass keeps the same id and share link, and every device that already saved it is pushed the update automatically. Omit passData to leave it unchanged.

Request parameters

NameTypeRequiredDescription
idstring (path)YesThe pass id to update.
passTemplatePassTemplateYesThe full new pass design - not a partial patch of individual fields.
passDataPassDataRow[]NoNew recipient rows. Omit to leave the existing data unchanged.

Response fields

NameTypeDescription
passIdstringUnique id for this pass - also its Apple serial number. Use it in the get/update/delete/notification endpoints.
passUrlstringPublic share link - auto-detects the visitor's wallet and shows the right "Add to Wallet" button. This is what you share with your customer.
workspaceIdstringThe 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"
}
DELETE/v1/passes/:id

Delete (archive) a pass

Archives the pass. It drops off the live list, its share link stops working, and every copy people already saved is marked expired in their wallet - voided on Apple Wallet, moved to Expired passes on Google Wallet. Neither wallet lets an issuer remove a pass from someone’s phone, so the pass stays there until the person deletes it themselves. Archiving is reversible: un-archiving in the dashboard brings the saved copies back to normal. This mirrors the dashboard’s Archive action; pass history and credit usage are preserved rather than hard-deleted. Apple doesn’t guarantee when (or whether) a phone receives the update, so check a pass’s status on your side when it’s redeemed.

Request parameters

NameTypeRequiredDescription
idstring (path)YesThe pass id to archive.

Response fields

NameTypeDescription
passIdstringThe 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" }
GET/v1/credits

Get credit balance

Returns the account's total balance, usage, and how much is left. Usage counts whichever is higher: passes created, or the number of times they've been saved to a device.

Response fields

NameTypeDescription
balanceintegerTotal pass credits on the account's plan.
consumedintegerCredits used so far.
remainingintegerbalance minus consumed, floored at 0.
remainingPctnumberremaining as a percentage of balance, 0-100.
lowbooleantrue 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
}
POST/v1/passes/:id/notifications

Send or schedule a notification

Pushes a message to everyone who saved the pass - to both Apple Wallet (lock-screen banner) and Google Wallet (in-app message). Sends immediately, or - with 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

NameTypeRequiredDescription
idstring (path)YesThe pass id to notify holders of.
messagestringYesThe notification body. 500 characters max.
subjectstringNoA short subject/title. 35 characters max.
scheduledAtstring (ISO 8601)NoSend later instead of immediately. Must be in the future.

Response fields

NameTypeDescription
notification.idstringUnique id for this notification - the only handle to cancel it later.
notification.status"scheduled" | "sending" | "sent" | "failed" | "canceled"Current delivery status.
notification.scheduledAtstring (ISO 8601) | nullWhen it's due to send, if scheduled.
notification.sentAtstring (ISO 8601) | nullWhen it actually sent, once it has.
notification.errorstringWhat 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.

NameTypeRequiredDescription
notification.triggers("eventStart" | "beforeExpiry" | "nearLocation" | "nearBeacon")[]NoWhich triggers are on for this pass. Each one also needs its own field(s) below to actually fire.
notification.eventStartDatestring (ISO 8601)ConditionalRequired if notification.triggers includes "eventStart". Surfaces the pass on the lock screen at this time.
notification.scheduledExpirystring (ISO 8601)ConditionalRequired 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.locationsNotifyLocation[]ConditionalRequired if notification.triggers includes "nearLocation". Up to 10: { id, country, address, latitude, longitude }.
notification.beaconsNotifyBeacon[]ConditionalRequired 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" }
    ]
  }
}
GET/v1/notifications

List notifications

Every notification - sent, scheduled, or failed - across every pass the account owns, newest first. Send the X-Workspace-Id header to see only one workspace’s.

Request parameters

NameTypeRequiredDescription
X-Workspace-Idstring (header)NoOnly list notifications for passes in this workspace. Omit to include every workspace. See Workspaces.

Response fields

NameTypeDescription
notificationsNotification[]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-...", "...": "..." } ] }
DELETE/v1/notifications/:id

Cancel a scheduled notification

Cancels a notification that hasn’t gone out yet. Requires the pass id as a query parameter, since that’s how the notification’s data cluster is resolved: ?passId=....

Request parameters

NameTypeRequiredDescription
idstring (path)YesThe notification id, returned as notification.id when it was scheduled.
passIdstring (query)YesThe pass the notification was scheduled for.

Response fields

NameTypeDescription
idstringThe 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" }
POST/v1/events

Send a custom event

Sends a named custom event, matching it against every active automation listening for that event name and queuing a run for each. Purely a trigger - this returns as soon as matching automations are queued, not once they’ve finished running. data can be any JSON payload; automation conditions read it by dot-path, e.g. event.data.tier.

Request parameters

NameTypeRequiredDescription
X-Workspace-Idstring (header)NoOnly match automations in this workspace. Omit to match automations in every workspace. See Workspaces.
namestringYesA caller-chosen event identifier, e.g. order.shipped. Matched exactly against each automation’s trigger. 100 characters max.
dataobjectNoAny JSON payload, up to 32KB. Stored on the run and readable by condition/action config in the automation builder.

Response fields

NameTypeDescription
triggeredintegerHow many automation runs were queued.
runIdsstring[]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."
  }
}
CodeStatusMeaning
unauthorized401The API key is missing, invalid, or has been revoked (regenerated since it was issued).
invalid_request422The request body or a parameter is missing or malformed - see the message for specifics.
not_found404No 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_exceeded402Creating or growing this pass would exceed the account's pass-credit balance.
rate_limited429Too many requests. Check the Retry-After header (seconds) before retrying.
internal_error500Something 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.