Access badges over the API
Invite people to an access badge, read who holds one and the card numbers your doors need, and suspend or revoke badges from your own systems.
An access badge is a door credential in Apple Wallet or Google Wallet. You design and publish a badge in the dashboard; the API then hands it out and takes it away, so an HR system can give staff their badge on their first day and remove it on their last.
EnterpriseBefore you start#
- Your account has access badges, and at least one badge is published. See Access badges to design and publish one.
- An API key with badge scopes. The Issue access badges preset can invite
people and read holders. Suspending, revoking and removing need
badges.revoke, which only Full access or a custom key holds — deliberately, since handing out a door credential and taking one away are different powers.
The words#
- A badge (in the API, a badge template) is the design and configuration, such as Harbour HQ Staff.
- A holder is a person who has been given one.
- A badge pass is the credential itself, on one holder's phone. A holder who changes phones ends up with a new badge pass.
- An invite is a single-use link that gives one person one badge pass.
Scopes#
| Scope | Allows |
|---|---|
badges.viewAll | Listing badges, invites, holders and their badge passes |
badges.issue | Inviting someone, and cancelling an invite |
badges.edit | Renaming a holder, refreshing a badge pass's design |
badges.revoke | Suspending, resuming and revoking badge passes, removing a holder |
Find the badge's ID#
curl https://api.walletap.com/badge/templates \
-H "X-API-Key: $WALLETAP_API_KEY"{
"templates": [
{ "id": "badge_123", "name": "Harbour HQ Staff", "active": true, "appleStatus": "active", … }
]
}active is true for a published badge. GET /badge/templates/{id} returns
one badge.
Invite someone#
curl -X POST https://api.walletap.com/badge/templates/badge_123/invites \
-H "X-API-Key: $WALLETAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "sofia.rossi@example.com",
"firstName": "Sofia",
"lastName": "Rossi",
"days": 14,
"sendToEmail": true
}'| Field | Notes |
|---|---|
email, phone | Who the badge is for. At least one is required. A phone number alone identifies the person too |
firstName, lastName | The name on the badge |
days | How long the link works, 1 to 365. 30 by default |
sendToEmail | true emails the link. Off unless you set it |
sendToPhone | true texts the link over the account's messaging channels. Off unless you set it |
{
"code": "Kq3xR2a",
"url": "https://walletap.com/b/Kq3xR2a",
"email": "sofia.rossi@example.com",
"phone": null,
"expiresAt": 1761390000000,
"days": 14,
"reused": false,
"delivery": { … }
}Unlike issuing a pass, inviting someone sends nothing unless you ask: with both
flags left out, you get the link back and deliver it yourself. delivery
reports how each requested channel went; a failed send still leaves a working
link.
The url is the credential. Whoever opens it first can add the badge to their
phone, so send it only to the person it names.
How invites behave:
- One live link per person. Inviting the same person again, before they have
added the badge, returns the same link — and sends it again if you ask —
with
reusedset totrue. To replace a link instead, cancel the invite first. - One badge per link. Once the badge is in a wallet, the link cannot add another. Inviting the person again then makes a new link, for a new phone or after they removed the badge.
- Nothing is used up until the link is opened. An invite nobody opens costs nothing.
List and cancel invites#
GET /badge/templates/{id}/invites lists the badge's invite links, newest first,
each with its url, email and expiresAt. A link stays in the list after it is
used or expires; cancelling the invite removes it.
To withdraw one, send the person's email:
curl -X DELETE https://api.walletap.com/badge/templates/badge_123/invites \
-H "X-API-Key: $WALLETAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "sofia.rossi@example.com" }'The link stops working. A badge already in someone's wallet is left alone — remove the holder to take that away.
List holders#
curl "https://api.walletap.com/badge/users?badgeTemplateId=badge_123" \
-H "X-API-Key: $WALLETAP_API_KEY"{
"users": [
{
"id": "holder_123",
"email": "daniel.okafor@example.com",
"phone": null,
"name": "Daniel Okafor",
"active": true,
"credentials": [
{
"passId": "bp_123",
"platform": "APPLE_WALLET",
"format": "H10301",
"bits": 26,
"facilityCode": 1,
"cardNumber": 38,
"wiegandHex": "202004C",
"keyset": "…"
}
]
},
{
"id": null,
"email": "sofia.rossi@example.com",
"name": "",
"invited": true,
"invitedAt": 1760180400000,
"expiresAt": 1761390000000,
"inviteUrl": "https://walletap.com/b/Kq3xR2a",
"credentials": []
}
]
}Leave out badgeTemplateId to list holders of every badge on the account.
- Someone invited who has not added their badge yet has
id: nullandinvited: true. credentialslists the badges in the holder's wallet right now, as a door controller or locker enrols them:format,facilityCodeandcardNumber, or the whole frame inwiegandHex. One call gives you every number your access system needs.credentials: nullmeans the numbers could not be read just now. Retry; never treat it as "no badges", or you would lock someone out.
A card number belongs to one badge pass. When someone gets a new badge, they get a new number, so update anything you configured with the old one.
One holder's badge passes#
Returns each badge pass the holder has from your badges, with its id, status,
platform, issuedAt and credentials. badgeTemplateId narrows it to one
badge.
status | Means |
|---|---|
ACTIVE | In the wallet and opening doors |
CREATED, ISSUE_INITIATED, ISSUING | Being added to the wallet |
SUSPENDED, USER_SUSPENDED | Paused, not opening doors |
REVOKED, USER_REVOKED, CANCELLED, EXPIRED | Ended for good |
ISSUE_FAILED | Adding it to the wallet failed, so it never opened a door |
Suspend, resume or revoke a badge pass#
curl -X POST https://api.walletap.com/badge/passes/bp_123/lifecycle \
-H "X-API-Key: $WALLETAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "action": "suspend", "reason": "Lost phone reported" }'action | Does | Scope |
|---|---|---|
suspend | Stops the badge opening doors, until it is resumed | badges.revoke |
resume | Turns a suspended badge back on | badges.revoke |
revoke | Ends the badge for good and removes it from the wallet. The person needs a new invite to get another | badges.revoke |
refresh | Pushes the badge's current design into the wallet. Access is unchanged | badges.edit |
For suspend and resume, reason (up to 200 characters) is kept in the
credential's audit trail. The call answers 202: the change takes effect a
moment later, so read the badge pass back with GET /badge/users/{userId}/passes
to see its new status.
Rename a holder#
curl -X PATCH https://api.walletap.com/badge/users/holder_123 \
-H "X-API-Key: $WALLETAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "lastName": "Okafor-Novak" }'Send firstName, lastName or both. The new name is pushed to every badge the
person holds from you; refreshed in the response lists them.
Remove a holder#
curl -X DELETE https://api.walletap.com/badge/users/holder_123 \
-H "X-API-Key: $WALLETAP_API_KEY"This is offboarding in one call: every badge you issued the person is revoked,
and every invite you sent them is withdrawn. If they also hold a badge from
another Walletap account, that badge is untouched and holderKept is true.
If something goes wrong#
403 "access badges not enabled" — the account does not have access
badges. See plans.
403 "Missing permission: badges.issue" (or another badge scope) — the key
lacks that scope. See Scopes.
409 "not_published" — the badge has not been published yet. Publish it in
the dashboard; invites work from then on.
400 "a valid email address or phone number is required" — send a
well-formed email or phone.
404 "not found" — the badge, holder or badge pass does not exist or is not
one of yours. A badge ID from another account can also answer 403
"forbidden".
502 with an error such as users_list_failed or lifecycle_failed — the
wallet credential service did not answer. Retry after a short wait.