Error code reference.
Every HTTP status and error.code the gateway returns, what causes it, and how your client should handle it.
Error response shape
Standard error body
Every error response from Anoman is a single JSON object with an error key. The shape matches OpenAI’s error envelope so existing SDK error handling works unchanged.
{
"error": {
"type": "invalid_request_error" | "guardrail_error" | "rate_limit_error" | "provider_error" | "billing_error",
"code": "machine_readable_code",
"message": "Human-readable explanation",
"param": "messages.0.content" // (optional) which field caused the error
}
}The type field groups errors into five families; code is the specific machine-readable string you should switch on in client code.
Reference
All error codes
Codes are grouped by family. Retryable column tells you whether a retry-with-backoff is the appropriate response.
| HTTP | Code | Type | Cause | Retry? |
|---|---|---|---|---|
| 401 | auth_missing | invalid_request | No Authorization header | No |
| 401 | auth_invalid | invalid_request | Malformed or unknown key | No |
| 403 | auth_revoked | invalid_request | Key was revoked | No |
| 402 | budget_exceeded | billing | Per-key monthly USD cap hit | No |
| 402 | insufficient_credits | billing | Prepaid balance exhausted | No |
| 402 | token_quota_exceeded | billing | Weekly or monthly token cap hit for a model tier class | No |
| 403 | prompt_injection | guardrail | DeBERTa classifier flagged the user content | No |
| 403 | pii_blocked | guardrail | PII detected and policy mode is `block` | No |
| 403 | content_violation | guardrail | Content moderation blocked the request | No |
| 403 | tool_denied | guardrail | Tool name appears in policy denylist | No |
| 403 | tool_not_allowed | guardrail | Tool not in policy allowlist | No |
| 400 | model_not_found | invalid_request | Model slug not in our catalog | No |
| 400 | context_length_exceeded | invalid_request | Prompt + completion exceeds model context window | No |
| 400 | vision_not_supported | invalid_request | Image input passed to a text-only model | No |
| 429 | rate_limit_exceeded | rate_limit | RPM or TPM burst guard tripped | Yes |
| 429 | concurrent_limit_exceeded | rate_limit | Inflight cap reached for this key | Yes |
| 503 | provider_unavailable | provider | All upstream routes failed after retries | Yes |
| 504 | provider_timeout | provider | Upstream did not respond within 120s | Yes |
| 503 | cost_cap_open | provider | Circuit breaker open on the upstream | Yes |
Auth + budget + guardrail errors return immediately and won’t succeed on retry — fix the request or the configuration. Rate limit + provider errors are transient — retry with exponential backoff and respect the Retry-After header.
Guardrail blocks
Reading 403 guardrail errors
When a guardrail blocks a request the response is 403 and the type is guardrail_error. The full guardrail pass/fail breakdown is also exposed on the successful response in the _anoman.guardrails object, see Guardrails.
{
"error": {
"type": "guardrail_error",
"code": "prompt_injection",
"message": "Request blocked by prompt injection detector (score 0.94 > threshold 0.85)."
}
}Guardrail block is the right behavior — don’t silently retry. Either surface to the end user (with a generic refusal message) or escalate to a human review queue if the surface is high-stakes.
Spend & rate boundaries
Reading 402 and 429 responses
Don’t confuse the spend boundary with the flood guard. 402 token_quota_exceeded means you hit a model tier class’s weekly or monthly token cap — not retryable until the window rolls over (buy a token pack, upgrade, or switch model class). 429 rate_limit_exceeded means an RPM/TPM burst guard filled up, and concurrent_limit_exceeded means too many simultaneous inflight requests — both transient, safe to retry with backoff.
// HTTP 402 Payment Required — the spend boundary on flat tiers.
{
"error": {
"type": "billing_error",
"code": "token_quota_exceeded",
"message": "Monthly token cap reached for the 'premium' model tier class.",
"model_tier_class": "premium",
"window": "monthly"
}
}Full per-tier limits + headers reference at Rate limits.
Retry recipe
Exponential backoff with jitter
The OpenAI / Anthropic SDKs already retry by default, but the defaults are conservative. Here’s an explicit pattern that respects Retry-After:
import time
import random
from openai import OpenAI
from openai import APIStatusError
client = OpenAI(base_url="https://api.anoman.io/v1", api_key="anm-sk-...")
def chat_with_retry(messages, model="gpt-4o-mini", max_attempts=5):
for attempt in range(max_attempts):
try:
return client.chat.completions.create(model=model, messages=messages)
except APIStatusError as e:
# Retry on 429 (rate limit), 503 (provider unavailable),
# 504 (gateway timeout). Bail on 4xx auth/budget errors.
if e.status_code not in {429, 503, 504}:
raise
if attempt == max_attempts - 1:
raise
# Respect Retry-After header when present.
retry_after = float(e.response.headers.get("retry-after", 0))
sleep = retry_after or min(2 ** attempt + random.random(), 30)
time.sleep(sleep)See guardrails fire on real requests.
The dashboard surfaces every block with the prompt that caused it.