NormaHub knowledge base

Errors and safe retries

Find the cause by HTTP status and error.code: permanent errors get fixed, temporary ones retried with backoff. Diagnose by X-Request-Id.

1. Error format

Every NormaHub error is an HTTP status plus a body with a machine-readable code. The status names the class (auth, billing, limits, availability); error.code names the exact cause. Branch on the status+code pair, not on the message text: messages may change, codes are stable. Russian version: Ошибки AI API.

{
  "error": {
    "message": "Human-readable message",
    "type": "invalid_request_error",
    "code": "invalid_request"
  }
}

Save the X-Request-Id header of every request — it links your client call to the server-side record and is found without your key or prompt.

2. Code table

Permanent errors are never fixed by retrying: fix the request, key, balance, permissions, or size. A fast key check is one curl to /v1/models: it separates a dead key from live funds in seconds.

400
invalid_request
Check the JSON and required route fields.
401
invalid_api_key
Key missing, invalid, or revoked.
402
insufficient_balance
Top up the API key owner's balance.
403
model_not_allowed
Model or endpoint unavailable to this key.
413
request_too_large
Shrink the payload or the passed context.
429
budget_exceeded
API key limit reached.
503
provider_unavailable
Model or upstream provider temporarily unavailable.
# 401 or 402? Check with curl in 10 seconds:
curl -s -o /dev/null -w "%{http_code}
" \
  https://api.normahub.cc/v1/models \
  -H "Authorization: Bearer $NORMAHUB_API_KEY"
# 401 — key invalid or revoked, issue a new one
# 200 — key alive, check balance and key limits

3. Retry policy

Retry only temporary 429 and 5xx — with exponential delay, jitter against retry storms, and a hard operation deadline. Honor Retry-After when the server sends it. Interactive UIs are often better off showing a clear failure with a retry button than holding the user in an invisible queue.

import os
import random
import time
from openai import OpenAI, APIError

client = OpenAI(
    api_key=os.environ["NORMAHUB_API_KEY"],
    base_url="https://api.normahub.cc/v1",
)

RETRIABLE = {429, 500, 502, 503}

def chat(messages, tries=4, deadline=60):
    started = time.monotonic()
    for attempt in range(tries):
        try:
            return client.chat.completions.create(
                model="MODEL_ID",
                messages=messages,
                timeout=20,
            )
        except APIError as e:
            retryable = (e.status_code in RETRIABLE
                         and time.monotonic() - started < deadline)
            if not retryable or attempt == tries - 1:
                raise
            delay = min(2 ** attempt + random.uniform(0, 1), 15)
            time.sleep(delay)
    raise RuntimeError("unreachable")

Never auto-retry 400, 401, 402, 403 or 413: a queue of such retries creates load without approaching success. A separate risk is recovery after an incident: retries returning without an upper bound start a second load wave. SDK examples live in the Python guide.

4. Diagnostics and limits

Minimum investigation set: time, endpoint, model ID, HTTP status, error.code, X-Request-Id. Never write the full key, prompts or answers into plain application logs — also check CI artifacts, docker history and HTTP client exception texts, where secrets leak most often.

Limits come on two levels: the owner balance and the key spend limit. After recovery check both, otherwise «funds exist but requests rejected». See balance, limits and history in the dashboard, and price every retry with the token cost logic.

Частые вопросы

401 or 403 — what is the difference?

401 is a key problem: missing, invalid, or revoked. 403 is an access problem: the key is alive, but the model or endpoint is unavailable to it. Fix accordingly: a new key versus a different model or route.

Request hit 402. What do I top up?

The key owner's balance and the key's own spend limit. These are different entities: funds may exist while the key limit is exhausted. Check both in the dashboard.

Should I retry after a connection drop?

Only if the operation is idempotent or you can verify its state. Upstream may have already processed the request before the drop — a blind retry executes the action twice.

What do I send to support on error?

Request time, endpoint, model ID, HTTP status, error.code and X-Request-Id. That is enough to find the operation. Never attach the full API key or confidential content.