Guides
Error handling
The error envelope, status codes, and how to react to each.
Every error is JSON with a stable envelope:
{ "error": "insufficient_funds", "message": "Wallet balance is too low for this reward" }Validation errors (400) also include an issues array with per-field
details.
Status codes and what to do
| Status | error | What to do |
|---|---|---|
400 | bad_request | Fix the request — the message names each invalid field |
401 | unauthorized | Check the Authorization: Bearer header and that the key isn't revoked |
402 | insufficient_funds | Top up, then retry |
403 | forbidden | The key lacks the needed scope (read vs write) |
404 | not_found | Wrong id, or the resource belongs to another merchant |
422 | validation | The request is well-formed but can't be fulfilled (e.g. canceling a redeemed reward, amount outside an item's range) |
429 | — | Rate limited (300 requests/minute). Back off and retry |
5xx | internal | Retry with backoff; use idempotency keys for sends |
Retry rules of thumb
- Never blind-retry
400/422— the request will fail the same way. - Don't retry
402until a top-up settles. - Do retry timeouts,
429, and5xxwith exponential backoff — and always send anIdempotency-KeyonPOST /v1/rewardsso retries can't double-send.
Card issuance failures
Issuance happens asynchronously, so a 201 from POST /v1/rewards doesn't
guarantee every card materializes. If the upstream provider rejects a card,
you get a card.failed webhook and the value is re-credited to the reward
(choice) or wallet. The reward detail endpoint shows per-card status.