Errors & fail-closed
HTTP status codes and the fail-closed contract that prevents a billing outage from granting free usage.
Fail-closed contract
The single most important rule of the Universal Wallet: a failure denies usage, it never grants it.
When the ledger is unreachable, a request times out, or a response is malformed, callers must treat the operation as denied:
| Operation | On failure |
|---|---|
| Deduct credits | allowed: false — do not run the billable feature |
| Read balance | 0 — degrade the UI gracefully |
| List transactions | empty page (items: [], nextCursor: null) |
| Create top-up session | url: null — surface a friendly retry message |
The @misar/wallet SDK implements exactly this contract, so a transient outage can never silently grant free AI usage or phantom credits.
Never invert the default
Do not "allow on error" for convenience. If you cannot confirm the user has credits, you must not run the billable feature. This is the core security property of the wallet.
HTTP status codes
| Code | Meaning | Typical cause |
|---|---|---|
200 | Success | Request processed. For deduct, still check allowed. |
400 | Bad request | Invalid amountDollars (non-integer or outside 10–100000), missing required field, or invalid Stripe signature on the webhook. |
401 | Unauthorized | Missing/invalid SSO bearer token. |
403 | Forbidden | Missing/invalid x-wallet-service-key, or a bearer token attempting a service-key-only endpoint, or accessing another user's wallet without the service key. |
429 | Rate limited | Too many requests. Back off and retry. |
5xx | Server error | Ledger or upstream unavailable. Treat as fail-closed. |
Validation errors
POST /io/wallet/topup-session rejects bad amounts before creating any Stripe session:
{ "error": "amount_dollars must be an integer" }{ "error": "Minimum top-up is $10" }{ "error": "Maximum top-up per transaction is $100,000" }Retrying
429and5xxare safe to retry with backoff.- When retrying a
deduct, always send the sameidempotency_keyso the retry cannot double-charge. See Idempotency & concurrency. - A
deductthat returns200withallowed: falseis not an error to retry — the user is simply out of credits. Prompt a top-up instead.