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": "…" }.

POST
/api/v1/passes

Create 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.

GET
/api/v1/passes

List 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_…"
    }
  ]
}
GET
/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.

PUT
/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 changeMessage on 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 changeMessage on 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.

POST
/api/v1/passes/{serial}/notify

Send 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):

FieldTypeNotes
messagestringLock-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"
}
FieldNotes
serialThe pass serial.
messageThe message actually sent (after trimming / default).
googleupdated, skipped (Google Wallet not configured on the server), or failed.
applepushed (at least one device reached), skipped (Apple Wallet or push not configured, or no registered devices), or failed.
appleDevicesApple devices registered for this serial.
appleSentSuccessful APNs deliveries.
appleModeOptional. APNs auth used: token, mtls, or none. Present when a push was attempted.
appleErrorsOptional 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

StatusWhen
400Revoked pass ("Cannot notify a revoked pass.") or inactive workspace ("Organization is inactive").
401"Missing Bearer API key" or "Invalid API key".
403Workspace is not on a Pro or Pro Plus subscription (message above).
404Serial not in your workspace ("Pass not found: {serial}").
DELETE
/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".

POST
/api/v1/assets

Upload 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.

FieldNotes
logoTextPrimary brand line. Default Pass. Max 60.
titleOptional. 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.
headerOptional. Max 60.
descriptionOptional. Max 280.
passStyleOptional. 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.
backgroundColorHex #RRGGBB only. Default #0a0a0a.
foregroundColorHex #RRGGBB. Default #ffffff.
labelColorHex #RRGGBB. Default #c8c8c8.
fieldsArray of { key, label, value }. Max 8 rows. Keys/labels max 40; values max 200. Front-side rows. Empty allowed when passStyle is generic.
backFieldsOptional. Same shape as fields, max 8. Shown on the Apple Wallet reverse; for generic passes also used as Google textModulesData.
barcodeOptional. 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 / thumbnailUrlOptional HTTP(S) image URLs. Stripped on Free. Prefer upload via /api/v1/assets on Pro / Pro Plus.
stripUrlApple 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.
heroUrlGoogle 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.
expirationOptional ISO-ish datetime string. Wired to Google Wallet validTimeInterval when set.

Errors

Failures return JSON { "error": "message" } with an HTTP status.

StatusWhen
400Invalid body, missing asset file, or other validation errors.
401Missing or invalid Bearer API key.
403Workspace is not on a Pro or Pro Plus subscription (message above).
404Pass serial not found in your workspace.
409Attempting to update a revoked pass.
429Monthly pass quota exceeded (creates / content-changing updates).
503Google 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/assets or 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.