Get started
Errors
The error envelope, status codes, the retryable flag, and how to handle failures well.
Every error, from any endpoint, has the same shape. Parse the code — the message is written for a human reading a log and may change without notice.
The envelope
json
{
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "player balance is below the requested stake",
"request_id": "fa1bc6b7-bd54-480e-92c1-6ffa700d6039",
"retryable": false
}
}codestringrequired | Stable machine-readable identifier. Branch on this. |
messagestringrequired | Human-readable detail. Not stable; do not parse. |
request_idstringrequired | Quote this to support and we can find the exact request. Also returned as the X-Request-Id header on every response. |
retryablebooleanrequired | Whether retrying the identical request with the same transaction_id can succeed. |
iretryable is the field that matters
It removes the guesswork from your retry loop. If it is
true, back off and send the same request again. If it is false, retrying will fail the same way forever — fix the request or surface it.Status codes
400Bad Request | Malformed body — not JSON, or a field of the wrong shape. |
401Unauthorized | Missing, expired or invalid token. |
403Forbidden | Authenticated, but not permitted — wrong audience, suspended account, or an IP outside the allowlist. |
404Not Found | No such resource, or it belongs to another operator. We do not distinguish the two. |
409Conflict | Idempotency conflict — same transaction_id, different body. |
422Unprocessable | Well-formed but invalid: a stake outside limits, an unsupported currency, an illegal move. |
429Rate limited | Too many requests. Honour Retry-After. |
500Server error | Our fault. Retryable. Quote the request id if it persists. |
504Wallet timeout | Your wallet did not answer in time. Retryable with the same id — see Idempotency. |
Why 404 and not 403
Asking for another operator's session returns 404, not 403. Answering "forbidden" would confirm the resource exists, which leaks the shape of other tenants' data to anyone willing to enumerate ids. Absence and denial look identical from outside.
Handling errors well
js
const res = await fetch(url, options);
const body = await res.json();
if (!res.ok) {
const { code, retryable, request_id } = body.error;
// Log the request id with everything. It is the only thing that lets
// support find your exact call in our logs.
logger.warn({ code, request_id, status: res.status }, "casinoapipro error");
if (retryable) return scheduleRetry();
switch (code) {
case "INSUFFICIENT_FUNDS": return tellPlayer("Not enough balance");
case "BET_LIMIT_EXCEEDED": return tellPlayer("Stake above the table limit");
case "IDEMPOTENCY_CONFLICT": return alertEngineering(request_id);
default: return alertEngineering(request_id);
}
}A full list of codes lives in Error codes.