Skip to main content
All consumer routes are JWT-authenticated (Authorization: Bearer <jwt>) with the same partner-issued token you use for the webview. The /me/* action routes have full schemas with try-it-out in the API Reference tab; the read-only merchant browse routes are documented under Browsing merchants below.

Endpoints at a glance

Browsing merchants

Use these to render a brand catalogue in your own UI — e.g. a merchant listing screen — before or alongside opening the webview. Both use the same partner-issued consumer JWT as the webview, and both honour your partner scope: if your account is restricted to an allowlist of brands or categories, only in-scope merchants are returned (out-of-scope ids 404).

GET /merchants

Returns an array of active merchants, ordered alphabetically by name (or by curated order when featured=true, or nearest-first when live lat/lng are supplied). Optional query parameters: Each merchant object:

GET /merchants/{id}

Returns one merchant (same fields as above) plus its active outlets. Returns 404 not_found if the merchant doesn’t exist, is inactive, or is outside your partner scope. Additional field:

Image paths

logoPath, bannerPath, and voucher imagePath are storage paths, not always absolute URLs. If a value begins with /uploads/…, prefix it with the API origin (https://vouchersx.com/uploads/…) to load the image. If it is already an https://… URL, use it as-is.

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):
This guards against PIN brute-forcing.

GPS rules

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.