Errors
Every error carries the same envelope:
{
"detail": "Debate not found",
"error": {
"code": "debate_not_found",
"message": "Debate not found",
"details": {},
"request_id": "req_b39e2875c54c4b5585f0608e6fe183db"
}
}Branch on error.code. The HTTP status is too coarse - a 403 is either "not your debate" or "your plan does not include this", and they need different handling. message is prose that will get reworded; detail exists for older clients.
Codes are append-only: an existing code never changes meaning, and new ones may appear. Treat an unrecognised code as its HTTP status.
Codes
| Code | Status | Meaning |
|---|---|---|
unauthenticated | 401 | No token |
invalid_token | 401 | Expired, revoked, or malformed |
insufficient_scope | 403 | Valid token, missing scope. details names it. |
forbidden | 403 | Not yours |
plan_feature_unavailable | 403 | Your plan does not include this feature |
quota_exceeded | 402 | Allowance exhausted. details has usage and reset time. |
concurrency_limit | 402 | Too many debates running at once |
debate_not_found | 404 | |
claim_not_found | 404 | |
query_not_found | 404 | Panel does not exist |
session_not_active | 409 | Retryable - the session is still starting |
idempotency_conflict | 409 | Same key, still in flight |
idempotency_key_reused | 422 | Same key, different body |
invalid_request | 400 | Malformed |
validation_failed | 422 | Body failed validation; detail lists fields |
rate_limited | 429 | Slow down; honour Retry-After |
upstream_unavailable | 503 | A dependency is down. Retry with backoff. |
internal_error | 500 | Our fault. Quote the request_id. |
What to retry
| Retry with backoff | 429, 500, 502, 503, and session_not_active |
| Retry only with a fixed request | 400, 422 |
| Do not retry | 401, 403, 404 - the answer will not change |
For anything that creates or spends, retry with the same Idempotency-Key so a retry after an ambiguous timeout cannot act twice.
Request ids
Every response carries X-Request-Id, and it appears inside the error envelope. Log it. Quoting it in a support request is the difference between us finding the exact call in seconds and not finding it.
You can send your own instead - pass X-Request-Id and it is echoed back and used in our logs, so one id spans both systems.