SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
17.7 KB

# OpenAI error catalogue & robust handling — atlas

Status: catalogue DOCUMENTED; 20+ shapes LIVE_VERIFIED by deliberate cheap probes on 2026-09-18 (all rejected before generation, cost $0) plus errors logged by the other atlas agents (reports/live-requests.jsonl, tmp-live/). Rate-limit/overload/quota families are DOCUMENTED only (not triggerable safely). Sources: Error codes guide · Rate limits guide · Spend limits · IP allowlist · Misalignment monitoring · Cybersecurity safeguards · Image generation → errors · Production best practices · OpenAPI Error, ErrorResponse, ErrorEvent, Responses incomplete_details · WIF token exchange errors. Last verified: 2026-09-18. Machine-readable: generated/fragments/errors/openai-errors.json (54 records).

# 1. Error body shapes (all observed)

jsonc
// A. canonical (every model/resource endpoint)
{"error": {"message": "Missing required parameter: 'model'.", "type": "invalid_request_error", "param": "model", "code": "missing_required_parameter"}}
// B. bare string (Administration endpoints without the scope — HTTP 403)
{"error": "You have insufficient permissions for this operation. Missing scopes: api.management.read. Check that you have the correct role in your organization, and if you're using a restricted API key, that it has the necessary scopes."}
// C. empty body (Cloudflare edge, unknown URL) — HTTP 404, only CF-RAY header, no x-request-id
// D. streaming (after HTTP 200): SSE `event: error` / `response.failed` carrying {code, message, param, sequence_number}
// E. optional extensions: error.misalignment {classification, message, continuation_instruction}; error.moderation_details {moderation_stage, categories[]}

type, message, param, code are all required by the spec but param/code are frequently null. type is almost always invalid_request_error, even for 401/403/404 — discriminate on HTTP status + code, not on type.

# 2. Catalogue

HTTP type code Meaning / message Retry? Verified
400 invalid_request_error invalid_type wrong JSON type for param no ✔ probe
400 invalid_request_error missing_required_parameter required field absent no ✔ probe
400 invalid_request_error unknown_parameter unrecognised field (strict schema) no ✔ probe
400 invalid_request_error unsupported_parameter field not allowed for this model/endpoint (temperature on reasoning models, dimensions on ada-002…) no ✔ other agents
400 invalid_request_error invalid_value out-of-range / bad enum (e.g. max_tokens is too large: … supports at most 32768) no ✔ probe
400 invalid_request_error integer_below_min_value, integer_above_max_value, string_above_max_length, array_above_max_length bound violations no ✔ / docs
400 invalid_request_error mutually_exclusive_parameters e.g. previous_response_id + conversation no ✔ other agents
400 invalid_request_error invalid_json body not parseable no ✔ probe
400 invalid_request_error unsupported_content_type non-JSON Content-Type on JSON endpoint no ✔ probe
400 invalid_request_error invalid_beta missing/incorrect OpenAI-Beta no ✔ other agents
400 invalid_request_error previous_response_not_found chained response unavailable (deleted, store=false, WS state lost) → resend full context no ✔ other agents
400 invalid_request_error websocket_connection_limit_reached Responses WS 60-min cap → reconnect yes (new conn) docs
400 invalid_request_error context_length_exceeded prompt + output > context window no docs
400 invalid_request_error invalid_prompt prompt rejected before generation no docs
400 invalid_request_error (null, param: service_tier) "Invalid service_tier argument… not allowed for this project" no docs
400 invalid_request_error (null) X-Client-Request-Id non-ASCII / > 512 chars no ✔ probe
400 image_generation_user_error moderation_blocked (+moderation_details) image prompt/input blocked no docs
400 invalid_request_error content_policy_violation legacy policy rejection no docs (LEGACY)
400 invalid_request_error invalid_image, invalid_image_format, image_parse_error, invalid_base64_image, image_file_too_large… image input problems no docs
400 invalid_request_error response_already_completed multi-agent response.inject on a finished response (stream event) no docs (BETA)
400 invalid_request_error fine_tune_not_found, upload_not_pending, invalid_function_parameters, invalid_offer, call_id_not_found resource-specific validation codes no ✔ other agents
401 invalid_request_error invalid_api_key "Incorrect API key provided: sk-inv***" no ✔ probe
401 invalid_request_error (null) "Missing bearer authentication in header" no ✔ probe
401 invalid_request_error mismatched_organization OpenAI-Organization ≠ key's org no ✔ probe
401 invalid_request_error invalid_project "No such project" (OpenAI-Project) no ✔ probe
401 invalid_request_error (null) audit logs without api.audit_logs.read no ✔ probe
401 invalid_request_error ip_not_authorized outside IP allowlist no docs
401 — — "You must be a member of an organization to use the API" no docs
403 (string body) — admin endpoint without scope (api.management.read, api.usage.read, api.roles.read, api.groups.read, api.mtls.read) no ✔ probe ×12
403 invalid_request_error insufficient_permissions structured scope error (api.external_storage.read) no ✔ probe
403 invalid_request_error misalignment_policy_violation misalignment monitoring block (may carry error.misalignment) never docs
403 invalid_request_error cyber_policy cybersecurity safeguard (ZDR orgs; may arrive mid-stream) no docs
403 invalid_request_error unsupported_country_region_territory unsupported location no docs
403 — — mtls.auth.openai.com wrong method/path no docs
403 — — GET /v1/safety/cases/{id} unknown id no ✔ other agents
404 invalid_request_error model_not_found model absent or not accessible (project model permissions, org not enabled) no ✔ probe
404 invalid_request_error (null) "Response with id … not found", conversations, files, evals after delete no ✔ probe
404 not_found_error not_found_error typed 404 on newer resources (vaults, skills, chatkit, agents, videos) no ✔ other agents
404 invalid_request_error fine_tune_not_found, safety_alert_not_found resource-specific 404 codes no ✔ other agents
404 (empty) — unknown URL / retired routes (/v1/assistants, /v1/threads/*, /v1/realtime/sessions) no ✔ probe
405 invalid_request_error (null) "Invalid method for URL (PATCH /v1/models)" no ✔ probe
409 invalid_request_error — concurrent modification (SDK ConflictError) once docs
422 invalid_request_error — well-formed but unprocessable (SDK UnprocessableEntityError, docs: "try again") maybe docs
429 rate_limit_error rate_limit_exceeded RPM/TPM/RPD/IPM exhausted ("Rate limit reached for … Please try again in Xs") yes (Retry-After) docs
429 rate_limit_error slow_down ramp too fast (> +50 %/15 min above 1M TPM) yes, slower docs
429 insufficient_quota insufficient_quota no quota/billing no docs
429 insufficient_quota credit_balance_exhausted prepaid credits gone no docs
429 insufficient_quota organization_spend_limit_exceeded / project_spend_limit_exceeded configured hard spend limits no (until raised/reset) docs
429 insufficient_quota organization_usage_limit_exceeded OpenAI-assigned tier limit no docs
429 insufficient_quota billing_hard_limit_reached legacy name no LEGACY
500 server_error (null) "The server had an error while processing your request" yes ✔ 1× other agents
503 service_unavailable_error server_is_overloaded model capacity exhausted (Retry-After) yes docs
200 + stream error event server_error, rate_limit_exceeded, invalid_prompt, vector_store_timeout, image codes… error after the stream started (response.failed.response.error) depends; never replay consumed output blindly docs
200 status: incomplete incomplete_details.reason ∈ {max_output_tokens, max_messages, content_filter, steered} not an error object; Chat `finish_reason: length content_filter` no
4xx OAuth invalid_subject_token, invalid_grant, invalid_request WIF token exchange no docs
— SDK APIConnectionError, APITimeoutError network / timeout (600 s default) yes docs

# 3. Retryability matrix

Signal Retry Wait Notes
network error / timeout yes (≤ 3–5) exp. backoff 0.5→8 s + jitter idempotency: Responses/Chat are not idempotent — a timed-out request may have completed and been billed (background: true + polling avoids double work)
408, 409, 425 yes (once/twice) short
429 rate_limit_exceeded, slow_down yes ≥ Retry-After (else backoff); after slow_down also cut rate failed requests still count toward RPM
429 insufficient_quota family (credit_balance_exhausted, *_spend_limit_exceeded, *_usage_limit_exceeded) no — needs billing/config action
500 server_error, 502/504 yes backoff check status.openai.com
503 server_is_overloaded yes ≥ Retry-After switch model/tier as fallback
x-should-retry: false no — SDK honours it
400/401/403/404/405/422(schema) no — fix request / credentials / permissions
403 misalignment_policy_violation, cyber_policy, 400 moderation_blocked never automatically — escalate to a human
stream error after partial output no blind replay — reconcile with GET /v1/responses/{id}

SDK defaults: Python/Node/Ruby/Java/Go retry 2× on 408/409/429/5xx and connection errors, honouring retry-after-ms/Retry-After ≤ 60 s (bigger → give up and surface the error). Python maps 503 to InternalServerError, not RateLimitError — catch both.

# 4. Robust handling — Python

python
import random, time
import openai
from openai import OpenAI

client = OpenAI(max_retries=0, timeout=60)   # do our own retry so we can honour long Retry-After and log everything
NON_RETRYABLE_429 = {"insufficient_quota", "credit_balance_exhausted", "organization_spend_limit_exceeded",
                     "project_spend_limit_exceeded", "organization_usage_limit_exceeded"}

def call_with_retry(fn, attempts=5, cap=30.0):
    for attempt in range(1, attempts + 1):
        try:
            return fn()
        except (openai.APIConnectionError, openai.APITimeoutError) as e:
            delay = min(cap, 0.5 * 2 ** attempt) * (1 + random.random() / 4)
        except openai.RateLimitError as e:                       # 429
            if e.code in NON_RETRYABLE_429 or (e.body or {}).get("type") == "insufficient_quota":
                raise                                              # billing problem: retrying cannot help
            ra = e.response.headers.get("retry-after") or e.response.headers.get("retry-after-ms")
            delay = (float(ra) / (1000 if "ms" in str(e.response.headers.get("retry-after-ms", "")) else 1)) if ra and ra.replace(".", "").isdigit() else min(cap, 0.5 * 2 ** attempt)
            delay += random.random() / 4
        except openai.InternalServerError as e:                  # 500 / 502 / 503 server_is_overloaded / 504
            if e.response.headers.get("x-should-retry") == "false":
                raise
            ra = e.response.headers.get("retry-after")
            delay = float(ra) if ra and ra.isdigit() else min(cap, 0.5 * 2 ** attempt) * (1 + random.random() / 4)
        except openai.PermissionDeniedError as e:                # 403 — never retry; may be a safety block
            if e.code in {"misalignment_policy_violation", "cyber_policy"}:
                notify_operator(e); raise
            raise
        except openai.APIStatusError as e:                       # 400/401/404/405/409/422 …
            log.error("openai %s %s code=%s param=%s req=%s", e.status_code, e.message, e.code, e.param, e.request_id)
            raise
        if attempt == attempts:
            raise
        time.sleep(delay)

resp = call_with_retry(lambda: client.responses.create(model="gpt-5.4-nano", input="Reply with OK.", max_output_tokens=16))
if resp.status == "incomplete":                                   # not an exception!
    print("incomplete:", resp.incomplete_details.reason)         # max_output_tokens | max_messages | content_filter | steered
print(resp.output_text, resp._request_id)

Streaming: wrap for event in stream:; on event.type == "error" or "response.failed" stop, record event.code, and reconcile with client.responses.retrieve(id) instead of re-sending.

# 5. Robust handling — TypeScript

ts
import OpenAI from "openai";
const client = new OpenAI({ maxRetries: 0, timeout: 60_000 });
const NON_RETRYABLE_429 = new Set(["insufficient_quota", "credit_balance_exhausted", "organization_spend_limit_exceeded", "project_spend_limit_exceeded", "organization_usage_limit_exceeded"]);
const sleep = (s: number) => new Promise((r) => setTimeout(r, s * 1000));

export async function withRetry<T>(fn: () => Promise<T>, attempts = 5, cap = 30): Promise<T> {
  for (let attempt = 1; ; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const backoff = Math.min(cap, 0.5 * 2 ** attempt) * (1 + Math.random() / 4);
      let delay: number | null = null;
      if (err instanceof OpenAI.APIConnectionError) delay = backoff;                       // network / timeout
      else if (err instanceof OpenAI.RateLimitError) {                                     // 429
        const type = (err.error as any)?.type;
        if ((err.code && NON_RETRYABLE_429.has(err.code)) || type === "insufficient_quota") throw err;
        const ra = err.headers?.get("retry-after-ms") ?? err.headers?.get("retry-after");
        delay = ra && !Number.isNaN(Number(ra)) ? Number(ra) / (err.headers?.get("retry-after-ms") ? 1000 : 1) + Math.random() / 4 : backoff;
      } else if (err instanceof OpenAI.InternalServerError) {                              // 500/502/503/504
        if (err.headers?.get("x-should-retry") === "false") throw err;
        const ra = err.headers?.get("retry-after");
        delay = ra && !Number.isNaN(Number(ra)) ? Number(ra) : backoff;
      } else if (err instanceof OpenAI.PermissionDeniedError) {                            // 403 — never retry
        if (err.code === "misalignment_policy_violation" || err.code === "cyber_policy") notifyOperator(err);
        throw err;
      } else if (err instanceof OpenAI.APIError) {                                          // 400/401/404/405/409/422
        console.error("openai", err.status, err.code, err.param, err.requestID, err.message);
        throw err;
      } else throw err;
      if (attempt >= attempts) throw err;
      await sleep(delay);
    }
  }
}

const resp = await withRetry(() => client.responses.create({ model: "gpt-5.4-nano", input: "Reply with OK.", max_output_tokens: 16 }));
if (resp.status === "incomplete") console.warn("incomplete:", resp.incomplete_details?.reason);
console.log(resp.output_text, resp._request_id);

A framework-free classifier for raw HTTP responses (handles the string-body 403 and the bodyless 404) lives in examples/shared/errors/classify_error.{py,ts} and is exercised by tests/openai/test_errors.py.

# 6. Live probes behind this page (2026-09-18, all $0)

Probe Result
POST /v1/responses model does-not-exist-model 404 model_not_found
max_output_tokens: "sixteen" 400 invalid_type param max_output_tokens
body without model 400 missing_required_parameter
extra field 400 unknown_parameter
truncated JSON 400 invalid_json
Content-Type: text/plain 400 unsupported_content_type
Authorization: Bearer sk-invalid 401 invalid_api_key
no Authorization 401 "Missing bearer authentication in header"
bad OpenAI-Organization / OpenAI-Project 401 mismatched_organization / invalid_project
GET /v1/responses/resp_000… 404 (code null)
GET /v1/this_endpoint_does_not_exist 404 empty body
PATCH /v1/models 405
max_tokens: 100000000 (chat, gpt-4.1-nano) 400 invalid_value "supports at most 32768 completion tokens" (not context_length_exceeded)
non-ASCII X-Client-Request-Id 400
14 Administration GETs 403 string-body (api.management.read, api.usage.read, api.roles.read, api.groups.read, api.mtls.read), 403 structured insufficient_permissions (external storage), 401 (audit logs)