Pass API
Reference

/api/scan

The staff scanner app — 9 routes, any role.

Session-authenticated from a paired device. Unlike the dashboard, every role is admitted: in a one-person shop the owner is the barista. /api/scan/pair and /api/scan/device-login are the two ways in and are themselves unguarded.

If a device is unpaired while a session is still warm, the next call is a 401 (not 403) with this device has been unpaired; scan a QR from the dashboard — so the app wipes its stored token and re-pairs rather than showing a permissions error.

Pairing

MethodPath
POST/api/scan/pairExchange the dashboard's QR for a durable device token
POST/api/scan/device-loginOpen a session with a stored device token
POST/api/scan/logout204
GET/api/scan/meThe signed-in staff member, role and current branch
GET/api/scan/branchesThe merchant's open branches
PUT/api/scan/branchSwitch the device's branch

At the till

MethodPath
GET/api/scan/passes/{serialOrCode}Look up a pass. Read-only — writes no ledger row
POST/api/scan/stampEarn. Takes idempotencyKey, deviceId, count, optional amount
POST/api/scan/redeemClaim, spend, or admit — the program type decides which

{serialOrCode} accepts either the UUID from the QR or the short typeable code printed under it. The short code is a 40-bit prefix of the serial, so two passes can in principle share one: that answers 409 two cards start with this code rather than guessing.

Both writes require an idempotencyKey you generate — a double tap awards one stamp.

What redeem means

Program type
STAMPSThe card restarts: cycle advances, balance returns to zero
POINTSThe balance draws down by the amount applied. Partial is normal
COUPONOne use is spent; the last one leaves the pass terminally REDEEMED
TICKETThe holder is admitted. The first scan is terminal — no cooldown, and a second scan is a loud 409

POST /api/scan/stamp against a ticket is a 400: a ticket is admitted at the door, not stamped. A ticket scanned past its derived end is a 409 naming when the event ended.

On this page