Errors

Every error is JSON with the same shape and the right HTTP status. The code is stable and machine-readable — branch on it, never on the human message. details is optional and carries structured context (balances, amounts, validation issues).

json
{
  "code": "ledger/insufficient_balance",
  "message": "insufficient session balance for bet",
  "details": { "balance": "100", "amount": "150" }
}
Retryable means what, exactly
No = a deterministic client error; retrying the identical request will fail the same way — fix the input. Yes = a transient condition (rate limit, upstream/on-chain rail unavailable); retry with backoff. For money writes, always retry with the same idempotency key so a retry can never double-apply.

auth/*

CodeHTTPMeaningRetryable
auth/missing_token401No bearer token suppliedNo
auth/invalid_token401Key or session invalid, or out of scopeNo
auth/expired_token401Session access token expired — refresh itNo
auth/method_not_allowed401That login method is not enabled for this appNo
auth/invalid_signature401SIWS signature did not verifyNo
auth/challenge_expired401SIWS challenge too old — request a new oneNo
auth/challenge_already_redeemed401SIWS challenge was already usedNo
auth/invalid_init_data401Telegram initData did not verifyNo
auth/init_data_stale401Telegram initData too oldNo
auth/init_data_replayed401Telegram initData reusedNo
auth/invalid_otp401Email code is wrongNo
auth/otp_not_found401No pending code for that email — initiate againNo
auth/otp_locked401Too many wrong attempts — locked outNo

user/*

CodeHTTPMeaningRetryable
user/not_found404No such user in this app/scopeNo
user/suspended403The user is suspendedNo
compliance/blocked403Blocked by a compliance ruleNo

wallet/* & sign/*

CodeHTTPMeaningRetryable
wallet/not_found404No such wallet for this user/scopeNo
wallet/insufficient_balance409Not enough balance for the operationNo
wallet/export_blocked403Export not allowed (policy or balance floor)No
wallet/already_exported409Wallet was already exportedNo
sign/invalid_transaction400The transaction could not be parsed/validatedNo
sign/wallet_archived409Wallet is archived and cannot signNo
sign/internal_failure500Signer failed unexpectedlyYes

withdrawal/*

CodeHTTPMeaningRetryable
withdrawal/insufficient_balance409Not enough balance to withdrawNo
withdrawal/limit_exceeded403Crypto withdrawal disabled/limited for this appNo

ledger/*

CodeHTTPMeaningRetryable
ledger/session_not_found404Unknown or wrong-app session tokenNo
ledger/session_closed409Session is settling or already settledNo
ledger/insufficient_balance409Bet exceeds the session balanceNo
ledger/bet_not_found404win references a bet that does not existNo
ledger/bet_rolled_back409The bet arrived after its rollbackNo
ledger/currency_mismatch409Operation currency differs from the session'sNo

treasury & custody/*

Custodial treasury and pool-escrow custody share the custody/* namespace.

CodeHTTPMeaningRetryable
custody/wallet_not_found404No wallet for the user in this environmentNo
custody/rail_unavailable501The on-chain money rail is not configured/availableYes
custody/not_confirmed503Move is in flight — its on-chain fate is not yet settledYes
custody/insufficient_balance409User's real balance is too low for the moveNo
custody/app_insufficient409The app treasury is too low to cover the payoutNo
custody/pool_not_found404No such poolNo
custody/pool_currency_mismatch409Stake/pool currency does not matchNo
custody/pool_settled409Pool is being or was already settledNo
custody/pool_refunded409Pool was already refundedNo
custody/lock_in_flight409A lock for this bet is already in progressNo
custody/conservation_violation409rake + payouts ≠ locked stakes — settle rejectedNo
custody/ref_conflict409This ref was already used for a different moveNo
custody/ref_voided409The referenced move was voidedNo

referral/*

CodeHTTPMeaningRetryable
referral/user_not_found404No such user for the referral opNo
referral/code_not_found404No referral code matchesNo
referral/code_collision409Generated code collided — retry generationYes
referral/cycle409Attribution would create a referral cycleNo
referral/insufficient_earnings409Withdrawal exceeds accrued earningsNo

validation, idempotency, ratelimit & server

CodeHTTPMeaningRetryable
validation/bad_request400Body/params failed validation (see details.issues)No
idempotency/key_in_use409That idempotency key is mid-flight for a different requestYes
ratelimit/exceeded429Too many requests — back offYes
server/internal500Unexpected server errorYes

Idempotent operations (e.g. a duplicate ledger transactionUuid or a repeated idempotencyKey) are not errors — they return the stored original result, so a safe retry always converges.

These codes are returned by the endpoints in the API reference inside the standard error envelope.