🎬 New — watch the 2-minute guide videos →

Errors

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).

All error codes

Inference (chat & embeddings)

HTTPCodeWhenWhat to do
401invalid_api_keyMissing, malformed, revoked, or expired key.Check the Authorization: Bearer br-… header; mint a new key on the dashboard.
404model_not_foundModel 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.
400no_routeModel 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).
402insufficient_creditsStandard key, balance is zero or negative.Top up. Not retryable until you do.
429rate_limit_exceededPer-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.
429daily_limit_reachedKey's daily request cap spent.Resets at midnight IST. Raise the cap, or upgrade off a trial key.
429budget_exceededThe key's or its workspace's monthly ₹ budget is spent.Raise the budget on the dashboard, or wait for the new month (IST).
502all_routes_failedEvery eligible route errored; the message includes the last upstream error.Retry with backoff — circuits recover in ~30 s. Check status for incidents.

Dashboard & account

HTTPCodeWhen
401no_sessionCalling a /me/* endpoint without being signed in.
400duplicate_key_nameAn active key with that name already exists in your org.
400trial_ceilingTrying to raise a trial key past 60 req/min or 200 req/day.
503provider_not_configuredThat OAuth sign-in method isn't configured on this deployment.

Billing

HTTPCodeWhen
400address_requiredCreating an order before adding a billing address.
400bad_addressBilling address failed validation (the message names the field).
400promo_invalidPromo code doesn't exist, is expired, or is exhausted.
400promo_redeemedYour org already redeemed this code.
503billing_disabledBilling isn't configured on this deployment.

BYOK

HTTPCodeWhen
400bad_keySubmitted key doesn't look valid for that provider.
400unknown_providerNo such BYOK provider id.
503byok_disabledBYOK isn't configured on this deployment.

Routing, collections, endpoints & teams

The management endpoints share a small set of validation codes:

HTTPCodeWhen
403forbiddenAction needs a role you don't have (most writes are owner/admin-only).
404not_foundNo such collection, endpoint, chain, member, or invitation in your org.
400bad_steps / bad_modelA chain or collection has invalid steps, or targets a model that isn't a catalog id.
400bad_name / bad_readmeName or README fails validation (length or characters).
400cap / team_cap / member_capA per-org limit is reached (collections, team orgs, or members).
400bad_request / unavailableBYOE config is invalid (e.g. SSRF-blocked URL) or the feature isn't configured.
400bad_email / bad_role / already_member / already_invited / last_owner / personal_orgMembership errors — see Teams (you can't demote the last owner, or add members to a personal org).
400duplicate_workspaceA workspace with that name already exists in the org.

Handling errors in code

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.