Every error is an RFC 9457 problem document with content type application/problem+json. Branch on status and the last part of type — never on detail, which is written for people and may change.
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://stickytier.com/docs/api/errors#balance-insufficient",
"title": "Insufficient balance",
"status": 422,
"detail": "Insufficient available balance."
}What is safe to retry
429, 500, 409 in_progress and any network failure: retry with the same Idempotency-Key, backing off. Everything else is a fact about your request — retrying unchanged returns the same answer. The idempotency guide has the full table.
Every error
401 unauthorized
Unauthorized. The Authorization header is missing or malformed, the key does not exist, it was revoked, its rotation grace has ended, or the programme is suspended.
What to do: Send `Authorization: Bearer sticky_live_…` (or `sticky_test_…`). We never say which of these it was — check the key in Developers → API keys.
403 forbidden
Forbidden. The key is valid but lacks the scope this endpoint needs, or the member's account is paused, suspended or deleted.
What to do: The detail names the missing scope. Create a key with that scope — scopes cannot be added to an existing key.
404 not-found
Not found. No such resource in THIS programme. Another programme's ids always look like this too.
What to do: Check you are using the member's public id (M-…), not your own customer id, and the right mode (a test key cannot see live data).
409 conflict
Conflict. An Idempotency-Key was reused with a different body; a request with the same key is still running (`reason: in_progress`); a phone number belongs to another member; a reservation was confirmed with a different order_ref; or the plan refuses the action (`reason: plan_restricted`).
What to do: For `in_progress`, retry after the Retry-After seconds. For a body mismatch, you have a key collision or a retry bug — never reuse a key for a different request.
422 validation
Validation failed. The body is not JSON or a field is missing, mistyped or out of range.
What to do: `errors` lists every problem as `{ path, message }`. In a batch, validation is reported per event and never blocks the others.
422 balance-insufficient
Insufficient balance. The member does not have enough spendable points for this redemption or reservation. Points on hold (return window) and points already reserved are not spendable.
What to do: Read `balances.spendable_cents` on the member, or ask /v1/redemption-quotes first. Until 2026-09 the voucher endpoint answered `402 insufficient-balance` for this; it now matches reservations.
422 rule-ineligible
Programme rules reject this action. The programme's own redemption rules say no: below the minimum, above the maximum share of the order, blocked payment method, or too many open reservations. `reason` says which.
What to do: Call /v1/redemption-quotes with the cart total and payment method — it returns the allowed range before you try.
429 rate-limited
Too many requests. This key exceeded its rate limit (default 10 requests per second, bursts of 50).
What to do: Wait `Retry-After` seconds (also in the body as `retry_after`). Send events in batches of up to 100 rather than one call each.
500 internal
Internal error. Something failed on our side. The request may or may not have been applied.
What to do: Retry with the same Idempotency-Key — a write that did land will replay instead of applying twice.