Introduction
The API speaks JSON over HTTPS. Send JSON, receive JSON — every request that carries a body sets Content-Type: application/json, and every response, including errors, is a JSON document.
There is one resource that matters: the card. You create it, and the API answers with ready-made links — the public card page, the Apple Wallet pass, the Google Wallet save link and a downloadable vCard. Hand those links to the person the card belongs to; there is nothing else to build.
Card edits propagate on their own. When you PATCH a card, every installed pass updates automatically — nobody reinstalls anything.
Base URL
https://wallet.azzamkh.sa/api/v1Authentication
Every request carries an API key in the Authorization header:
Authorization: Bearer wasm_live_h2n8xq...Create a key from the API keys page in your dashboard. The full key — wasm_live_ followed by a random secret — is shown once, at creation. Store it in an environment variable or a secret manager; the server keeps only a hash and cannot show it to you again.
Keep keys out of client-side code, repositories and logs. If a key leaks, revoke it on the same page and create a new one — a revoked key answers 401 immediately.
Check your key
GET/api/v1/me
Returns the account behind the key: the email, the account role, the id of the key you called with, and how many cards the account has. Useful as a smoke test before wiring anything else.
Request
curl https://wallet.azzamkh.sa/api/v1/me \ -H "Authorization: Bearer $WASM_API_KEY"Response — 200 OK
{ "email": "sarah@nawa.example", "role": "USER", "keyId": "key_h2n8xqf4", "cards": 3}Quickstart
Create a card and open it in Wallet, in three requests. You need an API key from the dashboard — export it as WASM_API_KEY and the samples below run as they are.
1Check your key
A 200 with your email means the key works. Anything else — see Errors.
Request
curl https://wallet.azzamkh.sa/api/v1/me \ -H "Authorization: Bearer $WASM_API_KEY"Response
{ "email": "sarah@nawa.example", "role": "USER", "keyId": "key_h2n8xqf4", "cards": 3}2Create the card
One request, one card. The response arrives with links that are ready to hand over: open links.page on any phone and it shows the card with the right add-to-wallet button for that platform — or send links.applePass straight to an iPhone.
Request
curl -X POST https://wallet.azzamkh.sa/api/v1/cards \ -H "Authorization: Bearer $WASM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sarah Al-Otaibi", "role": "Product Designer", "company": "Nawa Studio", "phone": "+966 50 123 4567", "color": "#1d3557" }'Response
{ "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#1d3557", "fgColor": "#f0e9de", "labelColor": "#bfc8d8", "fields": [ { "id": "fld_1", "slot": "primary", "label": "", "value": "Sarah Al-Otaibi", "labelAr": "", "valueAr": "" }, { "id": "fld_2", "slot": "secondary", "label": "Role", "value": "Product Designer", "labelAr": "", "valueAr": "" }, { "id": "fld_3", "slot": "secondary", "label": "Company", "value": "Nawa Studio", "labelAr": "", "valueAr": "" }, { "id": "fld_4", "slot": "back", "label": "Phone", "value": "+966 50 123 4567", "labelAr": "", "valueAr": "" } ], "ownerEmail": "sarah@nawa.example", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T10:24:07.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" }}3Watch it land
installs counts phones that actually added the pass to a Wallet — not downloads, not page views. Once someone adds the card, this is where you see it.
Request
curl https://wallet.azzamkh.sa/api/v1/cards/sarah-alotaibi/stats \ -H "Authorization: Bearer $WASM_API_KEY"Response
{ "views": 12, "scans": 3, "installs": 2, "installsByPlatform": { "apple": 2, "google": 0 }}That is the whole loop: create, hand over, measure. The rest of this page is the detail — every field, every endpoint, every error.
The card object
Every endpoint that returns a card returns this shape. The links are the part you hand to end users; everything else describes the card itself.
Attributes
| Name | Type | Description |
|---|---|---|
id | string | Unique identifier, card_…. Never changes. |
slug | string | URL-friendly handle. Interchangeable with id in every /cards/{id} endpoint. |
title | string | The card's display name — the name it was created with. |
language | string | ar, en or auto. With auto the pass follows the phone's language. |
bgColor, fgColor, labelColor | string | The pass colours as hex. You set the background with color; text and label colours are derived automatically so they always stay readable. |
fields | array | The pass fields in Apple's slot layout (header, primary, secondary, auxiliary, back), each with English text and optional Arabic counterparts. |
ownerEmail | string | Who the card belongs to — you, or the recipientEmail it was created for. |
createdAt, updatedAt | string | ISO 8601 timestamps, UTC. |
links.page | string | The public card page. Shows the card with the right add-to-wallet button for whatever phone opens it. |
links.applePass | string | Opened on an iPhone, adds the card to Apple Wallet. |
links.googleSave | string or null | Adds the card to Google Wallet. `null` until Google Wallet is enabled for the service — always check before showing a Google button. |
links.vcard | string | Downloads the card as a .vcf contact file. |
Example
{ "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#1d3557", "fgColor": "#f0e9de", "labelColor": "#bfc8d8", "fields": [ { "id": "fld_1", "slot": "primary", "label": "", "value": "Sarah Al-Otaibi", "labelAr": "", "valueAr": "" }, { "id": "fld_2", "slot": "secondary", "label": "Role", "value": "Product Designer", "labelAr": "", "valueAr": "" }, { "id": "fld_3", "slot": "secondary", "label": "Company", "value": "Nawa Studio", "labelAr": "", "valueAr": "" }, { "id": "fld_4", "slot": "back", "label": "Phone", "value": "+966 50 123 4567", "labelAr": "", "valueAr": "" } ], "ownerEmail": "sarah@nawa.example", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T10:24:07.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" }}Create a card
POST/api/v1/cards
Only `name` is required. The request below is complete: one field in, a working pass out — links.applePass adds it to Apple Wallet, links.googleSave to Google Wallet. Colour defaults to the Wasm ink, text colours are derived, and the name takes the pass's primary slot.
Every other parameter refines the same card: role, company, phone and email fill the rows under the name, color restyles it, link sets where the QR points. When you're ready for full layout control or a logo, see Going deeper.
By default the card belongs to your account. With recipientEmail it is created for that person: it appears in their account and they own it. Add invite: true to email them the add-to-wallet buttons in the same motion — or send the invite later with POST /cards/{id}/invite.
Body parameters
| Name | Type | Required | Description |
|---|---|---|---|
name | string | required | The person's name. Becomes the headline of the pass and the card page. |
role | string | optional | Job title, shown under the name. |
company | string | optional | Company or organisation. |
phone | string | optional | Shown on the card and included in the vCard download. |
email | string | optional | Contact email shown on the card. |
link | string | optional | A URL shown on the card page — a website, a LinkedIn profile. |
color | string | optional | The pass background as hex, like #1d3557. Text colours are derived automatically to stay readable. |
language | string | optional | ar, en or auto (the default). |
recipientEmail | string | optional | Create the card for this person — it belongs to them, not to you. |
invite | boolean | optional | With recipientEmail, also email them the add-to-wallet buttons. Default false. |
Request
curl -X POST https://wallet.azzamkh.sa/api/v1/cards \ -H "Authorization: Bearer $WASM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sarah Al-Otaibi" }'Response — 201 Created
{ "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#16120f", "fgColor": "#f0e9de", "labelColor": "#8a8177", "fields": [ { "id": "name", "slot": "primary", "label": "Name", "value": "Sarah Al-Otaibi", "labelAr": "الاسم", "valueAr": "" } ], "ownerEmail": "you@example.com", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T10:24:07.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" }}Accounts have a card limit. Creating past it answers 403 with code card_limit.
Going deeper: full layout and logos
POST/api/v1/cards
The same endpoint, using everything at once. The simple parameters place themselves: name takes the pass's primary slot, and role, company, phone, email fill the secondary row in that order.
For exact control, send fields instead — an array of { slot, label, value, labelAr, valueAr } where slot is one of header, primary, secondary, auxiliary or back. That is the card's real layout model, the same one the dashboard editor uses; Apple caps the slots (one primary, four secondary, four auxiliary), and anything over a cap is dropped. fields and the simple text parameters are mutually exclusive in one request.
Logos travel as their own request, because an image doesn't belong in a JSON body: POST /cards/{id}/logo with multipart form data and a file part (PNG, JPEG, WebP or GIF, under 5 MB — it is re-encoded server-side). DELETE /cards/{id}/logo removes it and the pass falls back to the Wasm mark. Both answer with the updated card.
Request
curl -X POST https://wallet.azzamkh.sa/api/v1/cards \ -H "Authorization: Bearer $WASM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sarah Al-Otaibi", "role": "Product Designer", "company": "Nawa Studio", "phone": "+966 50 123 4567", "email": "sarah@nawa.example", "link": "https://nawa.example/sarah", "color": "#1d3557", "language": "auto", "recipientEmail": "sarah@nawa.example", "invite": true }'Response — 201 Created
{ "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#1d3557", "fgColor": "#f0e9de", "labelColor": "#bfc8d8", "fields": [ { "id": "fld_1", "slot": "primary", "label": "", "value": "Sarah Al-Otaibi", "labelAr": "", "valueAr": "" }, { "id": "fld_2", "slot": "secondary", "label": "Role", "value": "Product Designer", "labelAr": "", "valueAr": "" }, { "id": "fld_3", "slot": "secondary", "label": "Company", "value": "Nawa Studio", "labelAr": "", "valueAr": "" }, { "id": "fld_4", "slot": "back", "label": "Phone", "value": "+966 50 123 4567", "labelAr": "", "valueAr": "" } ], "ownerEmail": "sarah@nawa.example", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T10:24:07.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" }}Everything here can also be sent to PATCH /cards/{id} after creation — nothing is decided forever at create time.
Set or remove the logo
POST/api/v1/cards/{id}/logo
Multipart, not JSON. The image is re-encoded through the same pipeline as the dashboard uploader, sized for the pass's logo strip, and replaces any previous logo. Sending DELETE to the same path removes it.
Request
curl -X POST https://wallet.azzamkh.sa/api/v1/cards/card_9f2c81kzt/logo \ -H "Authorization: Bearer $WASM_API_KEY" \ -F "file=@logo.png"Response — 200 OK
{ "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#1d3557", "fgColor": "#f0e9de", "labelColor": "#bfc8d8", "fields": [ { "id": "fld_1", "slot": "primary", "label": "", "value": "Sarah Al-Otaibi", "labelAr": "", "valueAr": "" }, { "id": "fld_2", "slot": "secondary", "label": "Role", "value": "Product Designer", "labelAr": "", "valueAr": "" }, { "id": "fld_3", "slot": "secondary", "label": "Company", "value": "Nawa Studio", "labelAr": "", "valueAr": "" }, { "id": "fld_4", "slot": "back", "label": "Phone", "value": "+966 50 123 4567", "labelAr": "", "valueAr": "" } ], "ownerEmail": "sarah@nawa.example", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T10:24:07.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" }}Wide marks read better than tall ones — Apple's logo strip is roughly 160×50 points.
List cards
GET/api/v1/cards
Returns the cards your account can see, as a cursor-paged list. Each entry is a full card object.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | optional | Page size, up to 100. |
cursor | string | optional | The nextCursor from the previous page. |
Request
curl https://wallet.azzamkh.sa/api/v1/cards?limit=25 \ -H "Authorization: Bearer $WASM_API_KEY"Response — 200 OK
{ "cards": [ { "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#1d3557", "fgColor": "#f0e9de", "labelColor": "#bfc8d8", "fields": [ { "id": "fld_1", "slot": "primary", "label": "", "value": "Sarah Al-Otaibi", "labelAr": "", "valueAr": "" }, { "id": "fld_2", "slot": "secondary", "label": "Role", "value": "Product Designer", "labelAr": "", "valueAr": "" }, { "id": "fld_3", "slot": "secondary", "label": "Company", "value": "Nawa Studio", "labelAr": "", "valueAr": "" }, { "id": "fld_4", "slot": "back", "label": "Phone", "value": "+966 50 123 4567", "labelAr": "", "valueAr": "" } ], "ownerEmail": "sarah@nawa.example", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T10:24:07.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" } } ], "nextCursor": "card_7d0a44mrw"}nextCursor is null on the last page. Pass it back as ?cursor= to fetch the next one.
Get a card
GET/api/v1/cards/{id}
Fetches one card, by id or by slug — both work everywhere {id} appears. An unknown id answers 404 with code not_found.
Request
curl https://wallet.azzamkh.sa/api/v1/cards/sarah-alotaibi \ -H "Authorization: Bearer $WASM_API_KEY"Response — 200 OK
{ "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#1d3557", "fgColor": "#f0e9de", "labelColor": "#bfc8d8", "fields": [ { "id": "fld_1", "slot": "primary", "label": "", "value": "Sarah Al-Otaibi", "labelAr": "", "valueAr": "" }, { "id": "fld_2", "slot": "secondary", "label": "Role", "value": "Product Designer", "labelAr": "", "valueAr": "" }, { "id": "fld_3", "slot": "secondary", "label": "Company", "value": "Nawa Studio", "labelAr": "", "valueAr": "" }, { "id": "fld_4", "slot": "back", "label": "Phone", "value": "+966 50 123 4567", "labelAr": "", "valueAr": "" } ], "ownerEmail": "sarah@nawa.example", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T10:24:07.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" }}Update a card
PATCH/api/v1/cards/{id}
A partial update: it accepts the same fields as Create a card, every one optional, and only what you send changes.
Edits reach installed passes automatically. Change a role or a colour and every phone that added the card gets the new version — nobody downloads anything twice.
Request
curl -X PATCH https://wallet.azzamkh.sa/api/v1/cards/card_9f2c81kzt \ -H "Authorization: Bearer $WASM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "role": "Head of Design", "color": "#0b3d2e" }'Response — 200 OK
{ "id": "card_9f2c81kzt", "slug": "sarah-alotaibi", "title": "Sarah Al-Otaibi", "language": "auto", "bgColor": "#0b3d2e", "fgColor": "#f0e9de", "labelColor": "#b5c6bd", "fields": [ { "id": "fld_1", "slot": "primary", "label": "", "value": "Sarah Al-Otaibi", "labelAr": "", "valueAr": "" }, { "id": "fld_2", "slot": "secondary", "label": "Role", "value": "Head of Design", "labelAr": "", "valueAr": "" }, { "id": "fld_3", "slot": "secondary", "label": "Company", "value": "Nawa Studio", "labelAr": "", "valueAr": "" }, { "id": "fld_4", "slot": "back", "label": "Phone", "value": "+966 50 123 4567", "labelAr": "", "valueAr": "" } ], "ownerEmail": "sarah@nawa.example", "createdAt": "2026-08-09T10:24:07.000Z", "updatedAt": "2026-08-09T14:02:31.000Z", "links": { "page": "https://wallet.azzamkh.sa/c/sarah-alotaibi", "applePass": "https://wallet.azzamkh.sa/api/pass/sarah-alotaibi", "googleSave": null, "vcard": "https://wallet.azzamkh.sa/api/vcard/sarah-alotaibi" }}Delete a card
DELETE/api/v1/cards/{id}
Deletes the card. Its links stop working and installed passes stop receiving updates. There is no undo.
Request
curl -X DELETE https://wallet.azzamkh.sa/api/v1/cards/card_9f2c81kzt \ -H "Authorization: Bearer $WASM_API_KEY"Response — 200 OK
{ "ok": true}Invites
POST/api/v1/cards/{id}/invite
Emails the card's owner the add-to-wallet buttons — the same email invite: true sends at creation. Use it when the first one went missing, or when you created the card without an invite and the person is ready for it now.
Request
curl -X POST https://wallet.azzamkh.sa/api/v1/cards/card_9f2c81kzt/invite \ -H "Authorization: Bearer $WASM_API_KEY"Response — 200 OK
{ "ok": true, "sentTo": "sarah@nawa.example"}Stats
GET/api/v1/cards/{id}/stats
How the card is doing. views counts opens of the public card page, scans counts QR-code scans, and installs counts phones that added the pass to a Wallet — split by platform under installsByPlatform.
Request
curl https://wallet.azzamkh.sa/api/v1/cards/sarah-alotaibi/stats \ -H "Authorization: Bearer $WASM_API_KEY"Response — 200 OK
{ "views": 218, "scans": 34, "installs": 25, "installsByPlatform": { "apple": 21, "google": 4 }}installs counts Wallet registrations, not pass downloads — a pass fetched on a desktop and never added to a phone does not count.
Errors & rate limits
Every failure, from a missing key to a missing field, answers with the same envelope: an HTTP status code, a machine-readable code, and a human-readable message that says what to fix.
{ "error": { "code": "validation_failed", "message": "name is required." }}| Status | Code | When |
|---|---|---|
| 401 | missing_key | No Authorization header, or one that is not Bearer wasm_live_…. |
| 401 | invalid_key | The key does not exist. |
| 401 | revoked_key | The key was revoked from the dashboard. |
| 403 | account_inactive | The account behind the key is deactivated. |
| 403 | card_limit | Creating a card past the account's card limit. |
| 404 | not_found | No card with that id or slug — or not one your key can see. |
| 422 | validation_failed | The body did not validate; the message names the field. |
| 429 | rate_limited | Too many requests — see the rate limit below. |
Rate limit
120 requests per minute, per key. Beyond that, requests answer 429 with code rate_limited until the window passes. If you bulk-create cards, space the requests out and retry 429s with backoff rather than hammering.