validation_failed | 400 | Zod body validation failed. issues array has details. |
voucher_not_found | 404 | No voucher with that ID. |
voucher_inactive | 422 | Voucher exists but is not currently active. |
voucher_not_wallet_flow | 422 | Tried to claim an instant-flow voucher. |
voucher_not_yet_valid | 422 | Voucher’s validFrom is in the future. |
voucher_expired | 422 | Past validUntil. |
voucher_out_of_scope | 403 | Voucher’s merchant or category is outside your partner allowlist. |
voucher_not_valid_at_outlet | 422 | Voucher not linked to this outlet. |
merchant_not_found | 404 | |
merchant_inactive | 422 | |
merchant_out_of_scope | 403 | |
outlet_not_found | 404 | |
outlet_inactive | 422 | |
outlet_pin_not_set | 422 | Outlet hasn’t configured a PIN yet — staff needs to set one. |
invalid_pin | 401 | Wrong 4-digit PIN. |
no_claim | 422 | Wallet voucher redeemed without first claiming. |
voucher_not_yet_applicable | 422 | Wallet voucher redeemed before its applicableFrom date. details: { applicableFrom }. |
voucher_no_longer_applicable | 422 | Wallet voucher redeemed after its applicableUntil date. details: { applicableUntil }. |
not_a_favorite | 403 | Voucher is favoritesOnly and user hasn’t favorited the merchant. |
limit_reached | 422 | Per-user cycle redemption count exceeded. details: { limit, used, cycleKey }. |
global_cap_reached | 422 | Shared cycle cap exhausted across all users for this voucher. details: { cap, used, cycleKey }. |
too_far | 422 | GPS distance > 150m. details: { distanceMeters, maxMeters }. |
codes_exhausted | 422 | Manual code pool empty — the merchant needs to upload more. |
code_generation_failed | 500 | Internal: auto code generator gave up. Treat as retryable. |
rate_limited | 429 | Too many redemption attempts. details: { retryAfterSeconds }. |
user_not_found | 404 | (DELETE) No user for (partner, externalUserId). |
upsert_failed | 500 | (POST users) Race-recovery failed; safe to retry. |