/me/* are JWT-authenticated
(Authorization: Bearer <jwt>). Full schemas with try-it-out are in the
API Reference tab.
Endpoints at a glance
| Method | Path | Purpose |
|---|---|---|
GET | /me | Bootstrap call. User + your brand block. |
GET | /me/claims | List the user’s wallet claims. |
POST | /me/claims | Add a wallet-flow voucher to the user’s wallet. |
GET | /me/redemptions | List the user’s redemption history. |
POST | /me/redemptions | Atomically redeem a voucher at an outlet. |
GET | /me/favorites | List merchants the user has favorited. |
POST | /me/favorites | Favorite a merchant. |
DELETE | /me/favorites/{merchantId} | Unfavorite a merchant. |
Redemption — the important one
POST /me/redemptions is the only complex endpoint. A redemption is
atomic — the PIN check, GPS distance check, redemption limit, and code
dispensing all happen inside a single transaction. Either everything
succeeds or no row is written.
Rate limiting
5 attempts per user per 60-second sliding window. The 6th attempt returns HTTP 429 with aretry-after header (seconds):
GPS rules
| GPS state | Result |
|---|---|
| Coordinates supplied AND within 150m of outlet | status: "SUCCESS" |
| Coordinates supplied but > 150m away | Rejected with too_far — no row written. Includes details: { distanceMeters, maxMeters }. |
| Coordinates omitted | Redemption succeeds but is recorded as status: "FLAGGED", flagReason: "gps_unavailable". |
| Outlet has no coordinates configured | Geofence can’t run — status: "SUCCESS" with flagReason: "outlet_not_geolocated" set as an audit tag for admin follow-up (not a FLAGGED redemption). |
FLAGGED redemptions (the gps_unavailable case) are stored for support
review. They count toward the user’s cycle redemption limit and (if the
voucher dispenses codes) a code is still dispensed.
Per-redemption limits
Two independent caps can apply:- Per-user cycle quota (
redemptionCounton the voucher): how many times this user can redeem this voucher per cycle (ONE_TIME,MONTHLY, orBIWEEKLY). Exceeded →limit_reached(422) withdetails: { limit, used, cycleKey }. - Global cycle cap (
globalRedemptionCap): how many times all users combined can redeem the voucher in the current cycle. Useful for capped-inventory promos. Exceeded →global_cap_reached(422) withdetails: { cap, used, cycleKey }.
Voucher codes
If the voucher’scodeSource is MANUAL or AUTO, the response includes a
code field — display this to the user. If the voucher has no codes
configured, code is null.
When the manual code pool is empty, the redemption fails with codes_exhausted
(422) — the redemption row is not written. The merchant needs to upload
more codes.
Favorites-only vouchers
If the voucher is markedfavoritesOnly, the user must have favorited the
merchant first. Otherwise the redemption fails with not_a_favorite
(403). Favorite via POST /me/favorites.
Wallet flow
If the voucher’sclaimFlow is WALLET, the user must claim it first via
POST /me/claims. Otherwise redemption fails with no_claim (422).
Instant-flow vouchers skip this step.
Redemption window (wallet vouchers)
Wallet vouchers can carry an optional redemption window —applicableFrom
and/or applicableUntil timestamps on the voucher. Claiming is never
blocked (the user can add the voucher to their wallet any time), but
redemption is gated to the window:
- Before
applicableFrom→voucher_not_yet_applicable(422) withdetails: { applicableFrom }. - After
applicableUntil→voucher_no_longer_applicable(422) withdetails: { applicableUntil }.
Scope enforcement
If your partner has a restricted scope (allowlist of merchants or categories), every/me/* mutation enforces it. Out-of-scope vouchers or
merchants return 403 with voucher_out_of_scope / merchant_out_of_scope
even if the user knows the UUID. The webview catalog already filters by
scope; this server-side check is the second layer of defence.