وسم

Developer documentation

The Wasm API

Create business cards that live in Apple Wallet and Google Wallet, hand them to their owners, update them after they are installed, and read how they perform. Everything the dashboard does, this API does with a few lines of code.

On this page

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

URL
https://wallet.azzamkh.sa/api/v1

Authentication

Every request carries an API key in the Authorization header:

HTTP
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

JSON
{  "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

JSON
{  "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

JSON
{  "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

JSON
{  "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

NameTypeDescription
idstringUnique identifier, card_…. Never changes.
slugstringURL-friendly handle. Interchangeable with id in every /cards/{id} endpoint.
titlestringThe card's display name — the name it was created with.
languagestringar, en or auto. With auto the pass follows the phone's language.
bgColor, fgColor, labelColorstringThe pass colours as hex. You set the background with color; text and label colours are derived automatically so they always stay readable.
fieldsarrayThe pass fields in Apple's slot layout (header, primary, secondary, auxiliary, back), each with English text and optional Arabic counterparts.
ownerEmailstringWho the card belongs to — you, or the recipientEmail it was created for.
createdAt, updatedAtstringISO 8601 timestamps, UTC.
links.pagestringThe public card page. Shows the card with the right add-to-wallet button for whatever phone opens it.
links.applePassstringOpened on an iPhone, adds the card to Apple Wallet.
links.googleSavestring or nullAdds the card to Google Wallet. `null` until Google Wallet is enabled for the service — always check before showing a Google button.
links.vcardstringDownloads the card as a .vcf contact file.

Example

JSON
{  "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

NameTypeRequiredDescription
namestringrequiredThe person's name. Becomes the headline of the pass and the card page.
rolestringoptionalJob title, shown under the name.
companystringoptionalCompany or organisation.
phonestringoptionalShown on the card and included in the vCard download.
emailstringoptionalContact email shown on the card.
linkstringoptionalA URL shown on the card page — a website, a LinkedIn profile.
colorstringoptionalThe pass background as hex, like #1d3557. Text colours are derived automatically to stay readable.
languagestringoptionalar, en or auto (the default).
recipientEmailstringoptionalCreate the card for this person — it belongs to them, not to you.
invitebooleanoptionalWith 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

JSON
{  "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

JSON
{  "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.

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

NameTypeRequiredDescription
limitintegeroptionalPage size, up to 100.
cursorstringoptionalThe 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

JSON
{  "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

JSON
{  "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

JSON
{  "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

JSON
{  "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

JSON
{  "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

JSON
{  "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.

JSON
{  "error": {    "code": "validation_failed",    "message": "name is required."  }}
StatusCodeWhen
401missing_keyNo Authorization header, or one that is not Bearer wasm_live_….
401invalid_keyThe key does not exist.
401revoked_keyThe key was revoked from the dashboard.
403account_inactiveThe account behind the key is deactivated.
403card_limitCreating a card past the account's card limit.
404not_foundNo card with that id or slug — or not one your key can see.
422validation_failedThe body did not validate; the message names the field.
429rate_limitedToo 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.