API documentation
Authenticate, create, list, update, and revoke Apple and Google Wallet passes, and send push notifications to installed passes with the Wallet Issuer API.
Overview
walletissuer exposes a Bearer-authenticated REST API for Apple Wallet and Google Wallet passes — create, update, notify installed devices, and revoke.
Base URL
https://walletissuer.com
All endpoints below are relative to this origin. API access and API keys are available on Pro and Pro Plus; Free and Starter plans, including their trials, cannot use the API. Create a key in the dashboard under API keys, then call from your backend or scripts. On Pro / Pro Plus, use Push notifications (POST /api/v1/passes/{serial}/notify) after changing fields so installed wallets refresh.
Authentication
Keys are shown once at creation. Prefix is wi_live_.
Authorization: Bearer wi_live_…
Dashboard session cookies are for the UI only. Automations and backends must use API keys. Missing or invalid keys return 401 with { "error": "…" }.
/api/v1/passesCreate a pass
Issues Google Wallet objects and signed Apple .pkpass files when platform credentials are configured, using the same pass body for both wallets.
curl -X POST https://walletissuer.com/api/v1/passes \
-H "Authorization: Bearer wi_live_…" \
-H "Content-Type: application/json" \
-d '{
"logoText": "Acme Club",
"title": "Membership",
"header": "Member",
"description": "Show at the door",
"backgroundColor": "#0a0a0a",
"foregroundColor": "#ffffff",
"labelColor": "#c8c8c8",
"fields": [
{ "key": "name", "label": "Name", "value": "Alex Rivera" },
{ "key": "tier", "label": "Tier", "value": "Gold" }
],
"barcode": {
"format": "QR_CODE",
"message": "ACME-1001",
"altText": "ACME-1001"
}
}'201 response:
{
"serial": "ps_…",
"status": "active",
"shareUrl": "https://walletissuer.com/p/ps_…",
"googleSaveUrl": "https://pay.google.com/gp/v/save/…",
"googleObjectId": "issuer.object",
"applePass": null,
"platforms": {
"google": "issued",
"apple": "pending"
},
"body": { /* normalized PassBody */ }
}Creates count toward your monthly quota. If Google Wallet is not configured on the server, the request fails with 503.
/api/v1/passesList passes
Returns up to 100 passes for the workspace, newest updated first. No query parameters.
curl https://walletissuer.com/api/v1/passes \ -H "Authorization: Bearer wi_live_…"
List items are summaries (not full bodies). Use get-by-serial for the complete pass.
{
"passes": [
{
"serial": "ps_…",
"status": "active",
"title": "Acme Club",
"googleSaveUrl": "https://pay.google.com/gp/v/save/…",
"googleObjectId": "issuer.object",
"createdAt": 1728000000000,
"updatedAt": 1728000000000,
"shareUrl": "https://walletissuer.com/p/ps_…"
}
]
}/api/v1/passes/{serial}Get a pass
Returns the full pass record for a serial owned by your workspace.
curl https://walletissuer.com/api/v1/passes/{serial} \
-H "Authorization: Bearer wi_live_…"{
"serial": "ps_…",
"status": "active",
"shareUrl": "https://walletissuer.com/p/ps_…",
"googleSaveUrl": "https://pay.google.com/gp/v/save/…",
"googleObjectId": "issuer.object",
"applePass": null,
"platforms": {
"google": "issued",
"apple": "pending"
},
"body": { /* full PassBody */ },
"createdAt": 1728000000000,
"updatedAt": 1728000000000
}Unknown serials return 404.
/api/v1/passes/{serial}Update a pass
Replaces the pass body and syncs the Google Wallet object when configured. The share URL stays the same.
Full-body replace. The request is run through the same normalizer as create. Omitted fields reset to defaults (for example logoText becomes "Pass"). Always send the complete body you want stored.
curl -X PUT https://walletissuer.com/api/v1/passes/ps_… \
-H "Authorization: Bearer wi_live_…" \
-H "Content-Type: application/json" \
-d '{
"logoText": "Acme Club",
"title": "Membership",
"header": "Member",
"description": "Updated benefits",
"backgroundColor": "#0a0a0a",
"foregroundColor": "#ffffff",
"labelColor": "#c8c8c8",
"fields": [
{ "key": "name", "label": "Name", "value": "Alex Rivera" },
{ "key": "tier", "label": "Tier", "value": "Platinum" }
],
"barcode": {
"format": "QR_CODE",
"message": "ACME-1001",
"altText": "ACME-1001"
}
}'Response shape matches get (full PassRecord). Content-changing updates count toward quota; identical bodies do not. Revoked passes cannot be updated (409).
To push the change to installed wallets with a lock-screen message, call POST …/notify — or enable auto-notify under Dashboard → Push notifications. See Push notifications.
Push notifications
Update passes that are already installed in Apple Wallet and Google Wallet, and show a short message on the customer's lock screen.
How it works. Change the pass with PUT /api/v1/passes/{serial}, then send a push with POST /api/v1/passes/{serial}/notify. One call reaches both wallets:
- Apple Wallet — the pass is re-signed with your message as the
changeMessageon its fields, then an APNs push wakes every registered device. Wallet downloads the new pass and shows the message on the lock screen. - Google Wallet — the pass object is updated in place with your message as
changeMessageon the header and text rows.
Lock-screen text on Apple. Apple Wallet only shows a field's changeMessage when that field's value changes. So every push also stamps a hidden back field (pushUpdate) with the current time. The message then appears even when no visible field changed.
Apple device registration. Apple devices register for pushes only when the installed pass carries the web service URL, which is added to passes created or updated while the workspace has Pro access. appleDevices in the response shows how many devices are registered for the serial.
Plan. Push notifications are included with Pro and Pro Plus. New Free workspaces receive a 30-day Starter trial without a card, then return to Free. Starter is also available as a paid monthly subscription. API endpoints require a Pro or Pro Plus subscription, so Free and Starter workspaces receive 403 with { "error": "…" } and this message:
API key access requires a Pro or Pro Plus subscription. Free and Starter plans, including trials, cannot use API endpoints.
Auto-notify on update. The pass body has no notification field. Instead, turn on auto-notify under Dashboard → Push notifications (dashboard only, not an API setting). With it on, every content-changing PUT /api/v1/passes/{serial} sends "Pass updated" automatically, and DELETE /api/v1/passes/{serial} sends "Pass revoked". Updates with an identical body do not notify. Auto-notify is best-effort: a failed push never fails the update or revoke. For custom text, leave it off and call notify yourself.
/api/v1/passes/{serial}/notifySend a push notification
Pro or Pro Plus subscription. Pushes the current pass to installed Apple Wallet (APNs) and Google Wallet passes with a lock-screen message.
Request body (JSON, all fields optional):
| Field | Type | Notes |
|---|---|---|
| message | string | Lock-screen / change message. Trimmed and cut to 140 characters. If it is missing, empty, or not a string, "Pass updated" is used. An empty or invalid JSON body also falls back to the default. |
curl -X POST https://walletissuer.com/api/v1/passes/{serial}/notify \
-H "Authorization: Bearer wi_live_…" \
-H "Content-Type: application/json" \
-d '{ "message": "You earned 50 points" }'const res = await fetch("https://walletissuer.com/api/v1/passes/" + serial + "/notify", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.WALLETISSUER_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ message: "You earned 50 points" }),
});
const result = await res.json();
if (!res.ok) throw new Error(result.error);
// result.google: "updated" | "skipped" | "failed"
// result.apple: "pushed" | "skipped" | "failed"200 response:
{
"serial": "ps_…",
"message": "You earned 50 points",
"google": "updated",
"apple": "pushed",
"appleDevices": 1,
"appleSent": 1,
"appleMode": "token"
}| Field | Notes |
|---|---|
| serial | The pass serial. |
| message | The message actually sent (after trimming / default). |
updated, skipped (Google Wallet not configured on the server), or failed. | |
| apple | pushed (at least one device reached), skipped (Apple Wallet or push not configured, or no registered devices), or failed. |
| appleDevices | Apple devices registered for this serial. |
| appleSent | Successful APNs deliveries. |
| appleMode | Optional. APNs auth used: token, mtls, or none. Present when a push was attempted. |
| appleErrors | Optional array of error strings when Apple delivery had problems. |
A 200 means the request was processed. Check google and apple for per-wallet results. Every send is recorded in the dashboard history under Push notifications. Notify calls do not count toward your monthly pass quota.
Errors
| Status | When |
|---|---|
| 400 | Revoked pass ("Cannot notify a revoked pass.") or inactive workspace ("Organization is inactive"). |
| 401 | "Missing Bearer API key" or "Invalid API key". |
| 403 | Workspace is not on a Pro or Pro Plus subscription (message above). |
| 404 | Serial not in your workspace ("Pass not found: {serial}"). |
/api/v1/passes/{serial}Revoke a pass
Marks the pass revoked and syncs Google Wallet when configured. Idempotent if already revoked.
curl -X DELETE https://walletissuer.com/api/v1/passes/{serial} \
-H "Authorization: Bearer wi_live_…"Returns the pass with status: "revoked".
/api/v1/assetsUpload an image
Pro and Pro Plus only. Stores the file in org-owned R2 and returns a public URL for pass image fields.
curl -X POST https://walletissuer.com/api/v1/assets \ -H "Authorization: Bearer wi_live_…" \ -F "file=@logo.png" \ -F "kind=logo"
Multipart form: required file; optional kind — logo, icon, hero, strip, thumbnail, or other. PNG, JPEG, or WebP up to 1 MB.
201 response:
{
"id": "ast_…",
"orgId": "org_…",
"kind": "logo",
"r2Key": "orgs/…/ast_….png",
"contentType": "image/png",
"byteSize": 18432,
"width": null,
"height": null,
"createdAt": 1728000000000,
"url": "https://walletissuer.com/a/ast_…"
}Put url into logoUrl, heroUrl, etc. on create or update. External HTTPS URLs also work if you host images yourself. Files are served publicly at /a/{assetId}. Free plan returns 403.
Pass body
JSON object accepted by create and update. Values are trimmed and length-capped; invalid colors fall back to defaults.
| Field | Notes |
|---|---|
| logoText | Primary brand line. Default Pass. Max 60. |
| title | Optional. Max 80. Leave it blank to hide the title — it is not filled in as Untitled. logoText stays the name line. On Apple, a header with no title shows only the header. |
| header | Optional. Max 60. |
| description | Optional. Max 280. |
| passStyle | Optional. storeCard (default, loyalty) or generic (business card / QR-front). Apple emits the matching pass type; Google stays on the generic class with contact from backFields when set. |
| backgroundColor | Hex #RRGGBB only. Default #0a0a0a. |
| foregroundColor | Hex #RRGGBB. Default #ffffff. |
| labelColor | Hex #RRGGBB. Default #c8c8c8. |
| fields | Array of { key, label, value }. Max 8 rows. Keys/labels max 40; values max 200. Front-side rows. Empty allowed when passStyle is generic. |
| backFields | Optional. Same shape as fields, max 8. Shown on the Apple Wallet reverse; for generic passes also used as Google textModulesData. |
| barcode | Optional. format: QR_CODE, PDF_417, AZTEC, or CODE_128. message required when set (max 500). Optional altText (max 80) is the only text under the code. When altText is omitted, Apple shows nothing under the QR and Google gets a blank caption so the raw barcode value is not printed. |
| logoUrl / iconUrl / thumbnailUrl | Optional HTTP(S) image URLs. Stripped on Free. Prefer upload via /api/v1/assets on Pro / Pro Plus. |
| stripUrl | Apple Wallet strip (under the header). Prefer a pre-cropped PNG at 1125×369 (storeCard artwork). Wallet then fits that image into a taller 375×144 slot and would otherwise cut the left and right edges. The .pkpass pads the strip into 1125×432 (@3x), 750×288, and 375×144 with the pass background so the full width stays visible. Used for Apple; ignored by Google when heroUrl is set. |
| heroUrl | Google Wallet wide banner. Also used as the Apple strip only if stripUrl is empty or fails to load. Keep stripUrl and heroUrl as separate assets when Apple and Google need different crops. |
| expiration | Optional ISO-ish datetime string. Wired to Google Wallet validTimeInterval when set. |
Errors
Failures return JSON { "error": "message" } with an HTTP status.
| Status | When |
|---|---|
| 400 | Invalid body, missing asset file, or other validation errors. |
| 401 | Missing or invalid Bearer API key. |
| 403 | Workspace is not on a Pro or Pro Plus subscription (message above). |
| 404 | Pass serial not found in your workspace. |
| 409 | Attempting to update a revoked pass. |
| 429 | Monthly pass quota exceeded (creates / content-changing updates). |
| 503 | Google Wallet credentials not configured on the server. |
Plans & quota
Creates and content-changing updates count toward your monthly quota.
- Free — 1,000 / month; no API access, custom images (image URL fields stripped) and no push notifications. New workspaces start with a 30-day Starter trial (Starter features and quota), then return to Free.
- Starter — 10,000 / month; custom images and live loyalty updates. Available as a paid monthly subscription; new Free accounts can try Starter for 30 days without a card. API tools require Pro or Pro Plus.
- Pro — 100,000 / month; custom images via
/api/v1/assetsor external HTTPS URLs; Push notifications API. - Pro Plus — 1,000,000 / month soft limit; custom images, push notifications, and team features.
Generate keys on Pro or Pro Plus under Dashboard → API keys after signing up.