BUILD WITH ADDPASS
Your app.
A pass in Wallet.
The complete integration reference, open to you and your coding agent. Explore the API, build your flow, and get a sandbox key when you’re ready to test.
https://app.addpass.io/api/v1/generateWhat you can build
Generate signed generic Apple Wallet passes from JSON: membership cards, event details, contact information, and barcodes connected to your own application. AddPass handles signing; you do not supply an Apple signing certificate for this integration.
You can read the entire reference and write your integration before creating an account. A sandbox API key is needed when you are ready to make real requests. No template ID is required.
Your application handles membership eligibility, verification, check-in and redemption. This API does not provide native Google Wallet issuance, a push-update workflow or revocation of installed passes. Compatible Google Wallet clients may import .pkpass files; test on your target devices.
Give your agent the reference
Use this prompt in your coding agent, then refine the integration together. The Markdown reference and OpenAPI description are public and require no login.
Agent prompt
Read https://www.addpass.io/assets/docs/addpass-api-reference.md and integrate AddPass into this project. Inspect the existing server framework, authentication and member data first. Use the existing architecture. Build a server-side POST to https://app.addpass.io/api/v1/generate with Authorization: Bearer from ADDPASS_API_KEY. Start with primaryText and Accept: application/json. If I do not have an API key, explain the sandbox signup at https://app.addpass.io/sign-up/SANDBOX?source=webflow-api&landing=api-reference and implement the code using the environment variable while I configure it. Never ask me to paste a secret into chat, and never place it in browser code. Protect the app route with authentication, authorization, input validation and per-user limits. Return the download link only to the authorized user. Handle errors and do not add automatic generation retries. Then help me iterate on branding, fields, barcodes and delivery. Explain sandbox field replacements so we can distinguish them from integration bugs. Let me evaluate and refine the integration before choosing a production plan. Treat passUrl as temporary and expiresAt as file retention, not URL expiry. For persistent delivery, use the binary response and our own storage. Do not invent endpoints, template IDs, installed-pass update/revocation support, native Google Wallet issuance or built-in membership verification. Report separately what was tested locally, against the API and on a Wallet device.
Get a key when you are ready
- Create a developer sandbox account, or sign in to the developer portal if you already have access.
- Create an API key and save it when shown. Configure ADDPASS_API_KEY in your server environment or secret store.
- Send Authorization: Bearer <ADDPASS_API_KEY>. Keep the key out of browser code, public environment variables, URLs, Git, chat and logs.
Your application needs its own protected server route. Authenticate the caller, authorize access to the member or record, validate inputs, and limit issuance per user before making a generation request.
Make a request
POST https://app.addpass.io/api/v1/generate
Authorization: Bearer <ADDPASS_API_KEY>
Content-Type: application/json
Accept: application/jsonprimaryText is the only required payload field. Send a JSON object smaller than 1 MB. Strings are trimmed; required strings cannot be blank.
{
"primaryText": "My First Pass"
}curl --fail-with-body --request POST 'https://app.addpass.io/api/v1/generate' \
--header "Authorization: Bearer $ADDPASS_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{"primaryText":"My First Pass"}'For a file instead, set Accept: application/vnd.apple.pkpass and save the successful response as a .pkpass file. The JSON and binary examples below describe alternative delivery choices; you do not need to make both requests.
Choose how to deliver the pass
| Request header | Success | Result |
|---|---|---|
Accept: application/json | 201 Created | JSON with passId, passUrl, expiresAt and message. |
Accept: application/vnd.apple.pkpass | 200 OK | Signed binary .pkpass; Content-Type: application/vnd.apple.pkpass. |
No Accept header | 200 OK | Binary .pkpass by default. |
Optional X-AddPass-Delivery: link or stream overrides Accept. Other values return 400. If Accept includes both JSON and the pass media type, JSON takes precedence. Use an explicit single media type.
The JSON passUrl is a signed download URL valid for approximately 15 minutes. expiresAt is the stored file’s retention deadline, approximately seven days after generation. It is not the URL expiry or the expiration of the installed pass. Treat the URL as sensitive and temporary.
For a durable member download, request the binary file and store it in your own authorized storage/delivery flow. Do not save passUrl as a permanent membership URL. passId identifies the generated file; this reference does not define a link-refresh API.
JSON response shape (placeholders shown; these are not working download credentials):
{
"passId": "<generated-pass-id>",
"passUrl": "<temporary-signed-download-url>",
"expiresAt": "<ISO-8601-file-retention-deadline>",
"message": "<delivery-guidance>"
}Server-side JavaScript example
Run in a server runtime with fetch, such as Node.js 20+. Call this helper from your authenticated, authorized server route; it does not implement your app’s access controls. Validate the payload before calling it.
export async function createWalletPass(payload) {
const apiKey = process.env.ADDPASS_API_KEY;
if (!apiKey) throw new Error('Configure ADDPASS_API_KEY on the server.');
const response = await fetch('https://app.addpass.io/api/v1/generate', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const details = await response.json().catch(() => ({}));
throw new Error(`AddPass ${response.status}: ${details.error || 'Request failed'}`);
}
const result = await response.json();
if (typeof result.passUrl !== 'string') {
throw new Error('AddPass did not return a download link.');
}
return result; // Deliver to the authorized caller; do not log the URL.
}For binary delivery, change Accept to application/vnd.apple.pkpass and consume response.arrayBuffer() after checking response.ok. Persist those bytes with the .pkpass extension and serve them with application/vnd.apple.pkpass. Do not parse a successful binary response as JSON.
Customize and evaluate
Use the sandbox to refine your integration over multiple requests. Begin with a minimal pass, then add branding, member fields and a barcode. Keep the example data fictional while testing.
{
"primaryText": "Riley Morgan",
"logoText": "Northside Club",
"description": "Northside Club membership",
"backgroundColor": "#211C38",
"foregroundColor": "#FFFFFF",
"labelColor": "#D5C6F1",
"headerLabelRight": "MEMBER",
"headerTextRight": "2026",
"secondaryLabelLeft": "Member number",
"secondaryTextLeft": "NS-1042",
"secondaryLabelRight": "Level",
"secondaryTextRight": "Community",
"backfields": [
{
"label": "Support",
"value": "Contact your club for membership questions."
}
],
"barcode": {
"format": "qr",
"message": "https://example.com/members/NS-1042"
}
}- Change logoText and the three color fields to match your brand.
- Add publicly accessible logo and thumbnail image URLs after a text-only pass works. Image hosts must return image bytes without authentication; unavailable images can make the request fail.
- Point the barcode at your application’s verification flow, or encode an identifier your scanner understands. The example.com URL is illustrative.
- Open generated passes on your target devices. Check long names, contrast, image quality, barcode scanning and the delivery flow.
- Review your current allowance in the portal as you iterate. Each successful generation consumes allowance, including repeated requests for the same content.
New sandbox accounts receive visible sandbox markings. These replace secondary and auxiliary labels and values; a marking can also be added to backfields. This can hide customized field text even when the request is correct. Sandbox evaluation does not show the complete production layout in those positions. Check the portal for your account’s current behavior and allowance.
Choose a paid developer plan when you are ready for production issuance. You can keep evaluating the integration in the sandbox within its allowance; generating one pass does not mean your integration is finished.
Request field reference
These are the recommended JSON field names. All fields except primaryText are optional. Lengths are measured after trimming; use short text and check the rendered result. Avoid undocumented fields: an accepted request does not mean every supplied property affects the pass.
| Field | Type | Description and limits |
|---|---|---|
primaryText | string | Required main text. Must be nonempty after trimming. Required · Max 36 characters |
logoText | string | Brand text in the pass header. Omit to keep the default. Max 23 characters |
description | string | Pass description. Defaults to Generated via Add Pass API. Max 80 characters |
headerLabelRight | string | Upper-right field label. Max 13 characters |
headerTextRight | string | Upper-right field value. Max 12 characters |
secondaryLabelLeft | string | Left secondary label. Sandbox markings may replace this field. Max 30 characters |
secondaryTextLeft | string | Left secondary value. Sandbox markings may replace this field. Max 40 characters |
secondaryLabelRight | string | Right secondary label. Sandbox markings may replace this field. Max 30 characters |
secondaryTextRight | string | Right secondary value. Sandbox markings may replace this field. Max 40 characters |
auxiliaryLabelLeft | string | Left auxiliary label. Sandbox markings may replace this field. Max 30 characters |
auxiliaryTextLeft | string | Left auxiliary value. Sandbox markings may replace this field. Max 40 characters |
auxiliaryLabelRight | string | Right auxiliary label. Sandbox markings may replace this field. Max 30 characters |
auxiliaryTextRight | string | Right auxiliary value. Sandbox markings may replace this field. Max 40 characters |
backgroundColor | string | Background color; six-digit hex, optionally prefixed with #. Invalid colors fall back to defaults. |
foregroundColor | string | Text color; six-digit hex, optionally prefixed with #. |
labelColor | string | Label color; six-digit hex, optionally prefixed with #. |
customLogoURL | string | Public HTTP(S) image URL for the logo. A transparent PNG is recommended. No authentication headers are sent when fetching it. |
thumbnailURL | string | Public HTTP(S) image URL for the thumbnail. Use a PNG or JPEG image, not an HTML preview page. |
backfields | array | Back-of-pass details. Send at most 30 entries; later entries are discarded. Sandbox marking can occupy one entry. Max 30 entries |
backfields[].label | string | Optional label. Max 60 characters |
backfields[].value | string | Required nonempty value. Required · Max 200 characters |
barcode | object | Barcode configuration. Your app supplies the verification or redemption workflow. |
barcode.format | string | Barcode format. qr, aztec, pdf417 · Default: qr |
barcode.message | string | Value encoded in the barcode, such as your verification URL or opaque member identifier. Required · Max 256 characters |
barcode.messageEncoding | string | Encoding used for the message. Use characters compatible with the selected encoding. Max 40 characters · Default: iso-8859-1 |
barcode.label | string | Optional text displayed with the barcode. Max 60 characters |
qrCodeText | string | Shortcut for a QR barcode. Takes precedence over barcode when supplied; use only one form. Max 120 characters |
location | object | Optional location relevance hint. Does not implement access control or guarantee a notification. |
location.lat | number | Latitude. Required |
location.long | number | Longitude. Required |
relevantDate | string | Optional relevance date. Send an ISO 8601 timestamp with a timezone. This is not a revocation mechanism. Max 64 characters |
serialNumber | string | Pass serial number. Omit for a generated unique value. Reusing one does not provide an idempotency guarantee or a push-update API. Max 80 characters |
Compatibility inputs also exist: backFields, colors.background/foreground/label, primaryLabel, secondary and auxiliary arrays, qrCode, and location aliases. Prefer the canonical fields above for new integrations. expirationDate and date are legacy date inputs; do not treat them as a supported installed-pass expiration or revocation workflow.
Errors, limits and retries
Application errors normally return JSON with an error string. Malformed JSON, body-size limits or upstream failures can return a different response; make JSON parsing fall back safely.
{"error":"primaryText: is required."}| HTTP status | Meaning | Next step |
|---|---|---|
400 | Invalid payload or delivery header | Check the error text, required primaryText, field lengths, date values, barcode format, and X-AddPass-Delivery. |
401 | Missing, malformed, revoked or unknown key | Send Authorization: Bearer followed by your active API key. A Firebase sign-in token is not an API key. |
402 | Generation allowance exhausted | Check your remaining allowance and current period in the developer portal. |
403 | Developer plan unavailable | Check developer access in the portal. A recognized sandbox plan can generate test passes. |
413 | Request body too large | Keep the JSON body below 1 MB. Reference images by URL instead of embedding image data. |
422 | Remote image could not be loaded | Check that image URLs return image bytes publicly, without login. Try the request with image fields omitted. |
429 | Too many requests | Respect Retry-After (seconds). Read RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (Unix seconds) when present. |
500 | Pass generation failed | Keep the error and request time for support. Do not blindly repeat requests after an uncertain outcome. |
Generation is not documented as idempotent. A serialNumber is not an idempotency key. After a network timeout, a pass may already have been generated and allowance consumed. Avoid automatic retry loops; surface the uncertain outcome and let the caller decide whether to issue another request.
Keep quota exhaustion (402) separate from request rate limiting (429). Use the response headers for rate-limit guidance and the developer portal for your current generation allowance. A client should handle lower limits or temporary failures without assuming a fixed throughput guarantee.
Verify your integration
- Before a key is configured, test your own validation, authorization and response handling with fixtures. Label these as local tests.
- With a configured sandbox key, make an explicit request and verify the API response. Do not generate passes automatically during page rendering.
- Download and open the actual pass on a Wallet device. API success and on-device installation are separate checks.
- Test customization, failures and delivery as part of the application flow. Record which checks passed and which still need a real device or production account.
Need help with an integration? Contact support@addpass.io with the HTTP status, request time and a redacted description. Do not send API keys or signed download URLs.