Skip to content

Reference

Errors

Every failure returns a typed JSON envelope and a standard HTTP status. Branch on the stable type string (or the status code) — never on the human-readable message, which may change.

The error envelope

On /v1/chat/completions a failed request returns these fields:

error envelope
{
  "error": {
    "message": "Insufficient balance for this request: about ₹{needed} is needed for the prompt, ₹{available} is available. Top up to continue.",
    "type": "insufficient_balance",
    "code": 402,
    "error_code": "UB-GW-NNN",
    "request_id": "7b679c45-424f-4153-b72e-e7217758065f"
  }
}
FieldTypeNotes
messagestringHuman-readable description. For logs, not for control flow.
typestringStable machine code from the taxonomy below. Branch on this.
codenumberThe numeric HTTP status (e.g. 402), mirrored in the response status line.
error_codestringA stable UB-<AREA>-<NNN> code identifying this exact failure — see below.
request_idstringIdentifies the request; also on the x-generation-id header. Quote it in support tickets.

error_code is always shaped UB-<AREA>-<NNN> — a short uppercase area token (e.g. GW for the gateway itself) and a three-digit number. The same code always means the same thing everywhere it appears, independent of which endpoint returned it. It exists so a specific failure can be identified precisely without exposing how the platform is built internally — message is deliberately generic for a server-class (5xx) failure, so error_code is often the more precise signal in that case.

When you contact support about a failed request, quote both error_code and request_id — together they let us find the exact failure without you needing to know (or us needing to explain) what produced it internally.

Error types

The full taxonomy. HTTP status codes are standard, so status-based SDK exceptions (a 429 raising RateLimitError, a 401 raising AuthenticationError) work unchanged.

typeHTTPWhen it happensRetry?
authentication401Missing, malformed, expired or revoked key.No — fix the key
permission_denied403Key or role lacks access; IP outside the CIDR allowlist; model blocked by workspace policy.No
content_policy_violation403Blocked by a content guardrail.No
insufficient_balance402Workspace ₹ balance can't cover the request's prompt, or an earlier request left an outstanding amount (UB-GW-295) that a top-up must clear.No — top up
budget_exceeded402A workspace or org budget cap was hit.No
payment_required402This key's own spend cap was hit.No
invalid_request400Malformed body, bad parameters, or invalid JSON.No — fix the request
context_length_exceeded400Prompt exceeds the model's context window.No — shorten / middle-out
max_tokens_exceeded400Reserved in the error taxonomy; unoblox does not emit it today and never limits or changes your max_tokens itself.No
not_found404Unknown model, or no endpoints serve it.No
rate_limit_exceeded429Per-key or workspace RPM exceeded.Yes — after Retry-After
provider_unavailable503Upstream unreachable, or a fail-closed BYOK credential.Yes — backoff
provider_overloaded503Upstream at capacity.Yes — backoff
no_capacity503No eligible or healthy endpoints right now.Yes — backoff
timeout504Upstream did not respond in time.Yes — backoff
server500Internal gateway error.Yes — backoff
unmapped500Unclassified internal error.Yes — backoff

Handling errors

python
import os
from openai import OpenAI, APIStatusError

client = OpenAI(
    base_url="https://api.unoblox.ai/v1",
    api_key=os.environ["UNOBLOX_API_KEY"],
)

try:
    resp = client.chat.completions.create(
        model="<model-id>",  # browse /models for a real one
        messages=[{"role": "user", "content": "Hello"}],
    )
except APIStatusError as e:
    err = e.response.json().get("error", {})
    # Standard HTTP status codes — branch on these for portable handling.
    if e.status_code == 402:
        raise SystemExit(f"payment required: {err.get('type')}")   # top up / raise a cap
    elif e.status_code == 429:
        retry_after = float(e.response.headers.get("retry-after", "1"))
        ...  # back off retry_after seconds, then retry
    elif e.status_code >= 500:
        ...  # transient upstream/server — retry with exponential backoff
    else:
        raise  # 400/401/403/404 — a client error; fix the request

Three ways a request can be refused for money

All three are 402 but carry distinct type strings so you can respond correctly:

typeCauseFix
insufficient_balanceThe workspace prepaid ₹ balance is too low.Top up credits.
budget_exceededA workspace/org budget ceiling was reached.Raise or reset the budget.
payment_requiredThe API key's own spend cap was reached.Raise the key's limit_minor, or wait for its window to reset.

A key spend-cap refusal also sets x-unoblox-refusal: spend_limit_exceeded and, unlike the other two, its message never states a figure. An insufficient_balance message DOES state your available balance (see the envelope example above) — it is not a generic message.

Errors on the compatibility skins

The native endpoint returns the unoblox type vocabulary above. The compatibility skins reshape the same underlying error into the envelope their SDK expects:

/v1/responses — OpenAI shape (a param field; a string code):

responses skin error
{ "error": { "message": "…", "type": "insufficient_quota", "param": null, "code": "insufficient_quota" } }

/v1/messages — Anthropic shape:

messages skin error
{ "type": "error", "error": { "type": "billing_error", "message": "…" } }

The HTTP status is the same across all three surfaces; only the JSON shape and the type strings differ.

Errors mid-stream

Once a streamed response has started, its HTTP status is already 200. A failure after that point arrives as a terminal error event in the stream rather than an HTTP status — see Streaming.

Analytics API errors

The management-key-authenticated Analytics API (analytics query/meta, credits balance, usage export) adds four codes of its own:

error_codeStatusCause
UB-API-381401The management key does not admit at all — malformed/unknown/wrong-class, dead (disabled/revoked/retired/deleted/expired), missing the required usage operation grant, or outside its own CIDR allowlist. One undifferentiated code, deliberately, so a probe can't learn which reason applied.
UB-API-382403The key is otherwise fully valid, but the URL names a workspace other than the key's own.
UB-API-383403An inference (ub-gw-…) key was presented to credits/balance or usage/activity/export — those two accept a session or a management key only.
UB-API-414403A session or management key's workspace role doesn't permit the dimension it asked for — member needs MembersView, api_key needs ApiKeysView.

Retry only what is retryable.

Retry 429, 503 and 504 with exponential backoff, using a fresh Idempotency-Key for each attempt — reusing the same one gets the retry itself refused with a 400, not a replayed response. Do not retry a 4xx — fix the request or the account first. See Rate limits & idempotency.