Esc to close · ⌘K / Ctrl-K opens search anywhere
Every error, on every endpoint, uses one JSON envelope:
{
"error": {
"message": "Human-readable explanation",
"type": "rate_limit_error",
"code": "daily_limit_reached"
}
} Branch on code (stable, machine-oriented); show message to
humans. 429 responses also carry a retry-after header (seconds).
| HTTP | Code | When | What to do |
|---|---|---|---|
| 401 | invalid_api_key | Missing, malformed, revoked, or expired key. | Check the Authorization: Bearer br-… header; mint a new key on the dashboard. |
| 404 | model_not_found | Model id isn't in the catalog and isn't a BYOK-discovered model. | List valid ids at GET /v1/models; for BYOK models use the provider/model-id form. |
| 400 | no_route | Model exists but no route satisfies your constraints (commonly data_policy: india_only, or a pinned provider that's down/unconfigured). | Relax the constraint, pick an India-resident model from the catalog, or wait out the outage (GET /health). |
| 402 | insufficient_credits | Standard key, balance is zero or negative. | Top up. Not retryable until you do. |
| 429 | rate_limit_exceeded | Per-minute limit hit (key's rpm_limit or the 120/min gateway cap). | Back off per retry-after; raise the key's limit if self-imposed. |
| 429 | daily_limit_reached | Key's daily request cap spent. | Resets at midnight IST. Raise the cap, or upgrade off a trial key. |
| 429 | budget_exceeded | The key's or its workspace's monthly ₹ budget is spent. | Raise the budget on the dashboard, or wait for the new month (IST). |
| 502 | all_routes_failed | Every eligible route errored; the message includes the last upstream error. | Retry with backoff — circuits recover in ~30 s. Check status for incidents. |
| HTTP | Code | When |
|---|---|---|
| 401 | no_session | Calling a /me/* endpoint without being signed in. |
| 400 | duplicate_key_name | An active key with that name already exists in your org. |
| 400 | trial_ceiling | Trying to raise a trial key past 60 req/min or 200 req/day. |
| 503 | provider_not_configured | That OAuth sign-in method isn't configured on this deployment. |
| HTTP | Code | When |
|---|---|---|
| 400 | address_required | Creating an order before adding a billing address. |
| 400 | bad_address | Billing address failed validation (the message names the field). |
| 400 | promo_invalid | Promo code doesn't exist, is expired, or is exhausted. |
| 400 | promo_redeemed | Your org already redeemed this code. |
| 503 | billing_disabled | Billing isn't configured on this deployment. |
| HTTP | Code | When |
|---|---|---|
| 400 | bad_key | Submitted key doesn't look valid for that provider. |
| 400 | unknown_provider | No such BYOK provider id. |
| 503 | byok_disabled | BYOK isn't configured on this deployment. |
The management endpoints share a small set of validation codes:
| HTTP | Code | When |
|---|---|---|
| 403 | forbidden | Action needs a role you don't have (most writes are owner/admin-only). |
| 404 | not_found | No such collection, endpoint, chain, member, or invitation in your org. |
| 400 | bad_steps / bad_model | A chain or collection has invalid steps, or targets a model that isn't a catalog id. |
| 400 | bad_name / bad_readme | Name or README fails validation (length or characters). |
| 400 | cap / team_cap / member_cap | A per-org limit is reached (collections, team orgs, or members). |
| 400 | bad_request / unavailable | BYOE config is invalid (e.g. SSRF-blocked URL) or the feature isn't configured. |
| 400 | bad_email / bad_role / already_member / already_invited / last_owner / personal_org | Membership errors — see Teams (you can't demote the last owner, or add members to a personal org). |
| 400 | duplicate_workspace | A workspace with that name already exists in the org. |
With the OpenAI SDKs, BharatRouter errors surface as the SDK's standard exceptions — the envelope rides inside. A retry policy that covers everything above:
# Python
import time, openai
def chat(client, **kw):
for attempt in range(4):
try:
return client.chat.completions.create(**kw)
except openai.RateLimitError as e: # 429: rate/daily/budget
code = (getattr(e, "body", None) or {}).get("error", {}).get("code")
if code in ("daily_limit_reached", "budget_exceeded"):
raise # waiting seconds won't help
time.sleep(2 ** attempt)
except openai.APIStatusError as e:
if e.status_code == 502: # all_routes_failed
time.sleep(2 ** attempt) # circuits recover in ~30s
else:
raise # 400/401/402: fix, don't retry
raise RuntimeError("retries exhausted") // Node
try {
await client.chat.completions.create({ model: "krutrim-2", messages });
} catch (err) {
const code = err?.error?.code ?? err?.code;
if (code === "insufficient_credits") notifyOwnerToTopUp();
else if (err.status === 429) scheduleRetry(err.headers?.["retry-after"]);
else if (err.status === 502) scheduleRetry(30); // all_routes_failed
else throw err;
} Rule of thumb: retry 429 (except daily_limit_reached /
budget_exceeded) and 502 with backoff; never blind-retry 400/401/402 —
they need a fix, not patience.