Skip to main content
All routes under /me/* are JWT-authenticated (Authorization: Bearer <jwt>). Full schemas with try-it-out are in the API Reference tab.

Endpoints at a glance

MethodPathPurpose
GET/meBootstrap call. User + your brand block.
GET/me/claimsList the user’s wallet claims.
POST/me/claimsAdd a wallet-flow voucher to the user’s wallet.
GET/me/redemptionsList the user’s redemption history.
POST/me/redemptionsAtomically redeem a voucher at an outlet.
GET/me/favoritesList merchants the user has favorited.
POST/me/favoritesFavorite 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 a retry-after header (seconds):
{ "error": "rate_limited", "details": { "retryAfterSeconds": 42 } }
This guards against PIN brute-forcing.

GPS rules

GPS stateResult
Coordinates supplied AND within 150m of outletstatus: "SUCCESS"
Coordinates supplied but > 150m awayRejected with too_far — no row written. Includes details: { distanceMeters, maxMeters }.
Coordinates omittedRedemption succeeds but is recorded as status: "FLAGGED", flagReason: "gps_unavailable".
Outlet has no coordinates configuredGeofence 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 (redemptionCount on the voucher): how many times this user can redeem this voucher per cycle (ONE_TIME, MONTHLY, or BIWEEKLY). Exceeded → limit_reached (422) with details: { 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) with details: { cap, used, cycleKey }.
Both checks happen inside the same transaction with advisory locks, so two concurrent redemptions can’t both pass the count check.

Voucher codes

If the voucher’s codeSource 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 marked favoritesOnly, 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’s claimFlow 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 applicableFromvoucher_not_yet_applicable (422) with details: { applicableFrom }.
  • After applicableUntilvoucher_no_longer_applicable (422) with details: { applicableUntil }.
Both bounds are independent and optional. Surface the window in your UI so claimants know when the voucher becomes usable and when it expires.

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.