anoman
Sign InGet API Key
Documentation home

Error code reference.

Every HTTP status and error.code the gateway returns, what causes it, and how your client should handle it.

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.

All error codes

Codes are grouped by family. Retryable column tells you whether a retry-with-backoff is the appropriate response.

HTTPCodeTypeCauseRetry?
401auth_missinginvalid_requestNo Authorization headerNo
401auth_invalidinvalid_requestMalformed or unknown keyNo
403auth_revokedinvalid_requestKey was revokedNo
402budget_exceededbillingPer-key monthly USD cap hitNo
402insufficient_creditsbillingPrepaid balance exhaustedNo
402token_quota_exceededbillingWeekly or monthly token cap hit for a model tier classNo
403prompt_injectionguardrailThe injection classifier flagged the user contentNo
403pii_blockedguardrailPII detected and policy mode is blockNo
403content_violationguardrailContent moderation blocked the requestNo
403tool_deniedguardrailTool name appears in policy denylist and the group's tool policy mode is Block (Monitor, the default, records the hit and lets the request through)No
403tool_not_allowedguardrailTool not in policy allowlist and the group's tool policy mode is Block (Monitor records it instead)No
400model_not_foundinvalid_requestModel slug not in our catalogNo
400context_length_exceededinvalid_requestPrompt + completion exceeds model context windowNo
400vision_not_supportedinvalid_requestImage input passed to a text-only modelNo
429rate_limit_exceededrate_limitRPM or TPM burst guard trippedYes
429concurrent_limit_exceededrate_limitInflight cap reached for this keyYes
503provider_unavailableproviderAll upstream routes failed after retriesYes
504provider_timeoutproviderUpstream did not respond within 120sYes
503cost_cap_openproviderCircuit breaker open on the upstreamYes

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.

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.

403 — prompt injection blocked

{
  "error": {
    "type": "guardrail_error",
    "code": "prompt_injection",
    "message": "Request blocked by prompt injection detector (score 0.94 > threshold 0.85)."
  }
}

403 — tool denied by policy (only when the group's tool policy mode is Block)

{
  "error": {
    "type": "guardrail_error",
    "code": "tool_denied",
    "message": "Tool 'delete_file' is denied by policy group 'production'.",
    "param": "tools.0"
  }
}

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.

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.

402 — token quota exceeded (model tier class)

// 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"
  }
}

429 — RPM burst guard tripped

// Response headers also include:
//   x-anoman-rate-limit-rpm:     120
//   x-anoman-rate-remaining-rpm: 0
//   retry-after: 24
 
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "120 RPM limit exceeded. Retry after 24s."
  }
}

429 — inflight cap (concurrent requests)

// Response headers:
//   x-anoman-inflight-cap: 150
//   x-anoman-inflight-current: 150
//   x-anoman-tier: pro
 
{
  "error": {
    "type": "rate_limit_error",
    "code": "concurrent_limit_exceeded",
    "message": "Per-key inflight cap (150) reached. Drop concurrency or upgrade tier."
  }
}

Full per-tier limits + headers reference at Rate limits.

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:

Python — robust retry with exponential backoff

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)

TypeScript — robust retry

import OpenAI, { APIError } from "openai";
 
const client = new OpenAI({
  baseURL: "https://api.anoman.io/v1",
  apiKey: process.env.ANOMAN_API_KEY,
});
 
async function chatWithRetry<T>(
  fn: () => Promise<T>,
  maxAttempts = 5,
): Promise<T> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (!(err instanceof APIError)) throw err;
      // Only retry rate limit + transient upstream errors.
      if (![429, 503, 504].includes(err.status ?? 0)) throw err;
      if (attempt === maxAttempts - 1) throw err;
      const retryAfter = Number(err.headers?.["retry-after"] ?? 0);
      const sleep = retryAfter || Math.min(2 ** attempt + Math.random(), 30);
      await new Promise(r => setTimeout(r, sleep * 1000));
    }
  }
  throw new Error("unreachable");
}

See guardrails fire on real requests.

The dashboard surfaces every block with the prompt that caused it.

On this page