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%
9.8 KB · 100 lines markdown
Rendered Raw Blame History
1# Anthropic API errors — catalogue, retry matrix, robust handlers23**Status:** `DOCUMENTED`; 400/401/404 shapes and messages `LIVE_VERIFIED` 2026-09-18 (15 deliberate errors on `claude-haiku-4-5-20251001`, nothing billed; raws `tmp-live/anthropic-core/i*_*.json`). `x-should-retry` header `LIVE_DISCOVERED`.4**Sources:** https://platform.claude.com/docs/en/api/errors · https://platform.claude.com/docs/en/build-with-claude/streaming#error-events · https://platform.claude.com/docs/en/api/rate-limits · https://platform.claude.com/docs/en/api/beta-headers#error-handling · SDK pages (…/sdks/python#handling-errors etc.)5**Machine-readable:** `generated/fragments/errors/anthropic-errors.json`6**Last verified:** 2026-09-1878## Error shape910```json11{"type": "error", "error": {"type": "not_found_error", "message": "model: claude-does-not-exist"}, "request_id": "req_011CfBxeBpDojVzndwYnobEw"}12```13Always JSON; `error.type`/`error.message` always present; `request_id` mirrors the `request-id` header (quote it to support). Enum of `error.type` may grow (versioning policy). Mid-stream errors (`event: error`) have the same `error` object but **no** `request_id`.1415## Catalogue + retry matrix1617| HTTP | `error.type` | Meaning | Retry? | Action | Live message(s) |18|---|---|---|---|---|---|19| 400 | `invalid_request_error` | malformed/invalid request, unsupported param for the model, invalid version/beta header, spend limit reached (org/workspace) | **no** | fix the request; the message names the field | `max_tokens: Field required` · `max_tokens: 1000000 > 64000, which is the maximum allowed number of output tokens for claude-haiku-4-5-20251001` · `temperature: range: 0..1` · `top_k: Input should be a valid integer` · `` `temperature` and `top_p` cannot both be specified for this model. Please use only one.`` · `messages: at least one message is required` · `The request body is not valid JSON: …` · `anthropic-version: "2020-01-01" is not a valid version` · `anthropic-version: "2023-01-01" not allowed for this endpoint` · ``Unexpected value(s) `does-not-exist-2099-01-01` for the `anthropic-beta` header…`` · `This model does not support assistant message prefill. The conversation must end with a user message.` (claude-sonnet-5) · `adaptive thinking is not supported on this model` · ``messages.2: `tool_use` ids were found without `tool_result` blocks immediately after: …`` · `Server tools are not supported in the count_tokens endpoint: web_search_20250305…` · `'claude-haiku-4-5-20251001' does not support inference_geo.` · ``'…' does not support the `speed` parameter…`` · `betas: Extra inputs are not permitted` · `output_format: Extra inputs are not permitted` · `The /v1/complete endpoint has been deprecated…` |20| 401 | `authentication_error` | key malformed/revoked/expired (AWS: bad SigV4) | no | fix credentials | `invalid x-api-key` |21| 402 | `billing_error` | billing problem | no | fix payment in Console / AWS Marketplace | — |22| 403 | `permission_error` | key lacks permission (workspace/org settings) | no | check access; **not** "does not exist" | — |23| 404 | `not_found_error` | unknown path, id or model | no | check path/ids; `GET /v1/models` | `model: claude-does-not-exist` · `Not found` (unknown path) |24| 409 | `conflict_error` | state conflict / uniqueness | yes (SDK default) | resolve then retry | — |25| 413 | `request_too_large` | > 32 MB Messages/count_tokens, 256 MB batches, 500 MB files (from Cloudflare) | no | shrink / Files API / batches | — |26| 422 | (SDK `UnprocessableEntityError`) | not in the HTTP list; SDK class exists | no | treat as 400 | — |27| 429 | `rate_limit_error` | RPM/ITPM/OTPM bucket, acceleration limit, monthly tier spend cap (no `retry-after`, keeps failing), Claude Code workspace spend limit | yes, unless spend cap | honor `retry-after`; exponential backoff + jitter; ramp gradually | — |28| 500 | `api_error` | internal error | yes | backoff; support with request_id | — |29| 504 | `timeout_error` | processing timeout | yes | stream / batches for long requests | — |30| 529 | `overloaded_error` | capacity (all users); also as SSE `error` event after 200 | yes | backoff; Priority Tier reduces it | — |31| 200 | `stop_reason: refusal` | **not an error** — classifier refusal with `stop_details.category` | n/a | fallback model / `fallbacks` beta | — |3233Documented validation families (400): prefill unsupported (Claude 4.6+, Mythos Preview); thinking blocks modified (`messages.i.content.j: … cannot be modified`); `thinking.type.enabled` unsupported on Claude 4.7+ ("Use thinking.type.adaptive and output_config.effort"); adaptive unsupported on ≤4.5; disabled unsupported on Fable 5.x / Mythos 5.x / Mythos Preview; forced `tool_choice` `any`/`tool` unsupported on Fable 5.1 / Mythos 5.1; thinking block bound to another conversation (`Invalid signature in thinking block…`, `thinking-binding-controls-2026-08-01`); `block_binding: Extra inputs are not permitted` without that header; AWS "Outbound web identity federation is disabled for your account".3435Server-tool failures are **not HTTP errors**: result blocks carry `error_code` (web_search: `invalid_tool_input|unavailable|max_uses_exceeded|too_many_requests|query_too_long|request_too_large`; web_fetch adds `url_too_long|url_not_allowed|url_not_in_prior_context|url_not_accessible|unsupported_content_type|content_too_large`; code execution: `execution_time_exceeded`, `output_file_too_large`, `file_not_found`). Treat `too_many_requests`/`unavailable` as transient.3637## Response headers on errors3839`request-id` (always), `x-should-retry: false` (every 4xx observed; SDKs honor `true`/`false`), `anthropic-organization-id` (absent on 401 and unknown-path 404), `retry-after` (429/529 when applicable), `CF-RAY`. Rate-limit buckets are not sent on error responses.4041## SDK defaults4243Retries: 2, exponential backoff (Python 0.5 s → 8 s, jitter), on connection errors, 408, 409, 429, ≥500, timeouts; `retry-after` honored. Timeout 10 min; non-streaming requests expected to exceed it are refused (`ValueError` / `AnthropicError("Streaming is required…")`). Typed classes: Python `anthropic.NotFoundError` etc.; TS `Anthropic.NotFoundError`; Ruby `Anthropic::Errors::NotFoundError`; Java `com.anthropic.errors.NotFoundException`; C# `AnthropicNotFoundException`; Go `*anthropic.Error` (branch on `StatusCode`). Catch the most specific class first; never string-match messages.4445## Robust handler — Python (`examples/shared/errors/anthropic_error_handling.py`, LIVE_VERIFIED)4647```python48import random, time, anthropic49client = anthropic.Anthropic(max_retries=0)  # we do our own retry here; default 2 is fine in production50FATAL = (anthropic.BadRequestError, anthropic.AuthenticationError, anthropic.PermissionDeniedError,51         anthropic.NotFoundError, anthropic.UnprocessableEntityError)5253def call_with_backoff(fn, attempts=4, base=0.5, cap=8.0):54    for i in range(attempts):55        try:56            return fn()57        except anthropic.APIStatusError as e:                       # 4xx/5xx with parsed body58            if isinstance(e, FATAL) or e.response.headers.get("x-should-retry") == "false" or i == attempts - 1:59                raise                                               # log e.status_code, e.request_id, e.body["error"]60            ra = e.response.headers.get("retry-after")61            time.sleep(float(ra) if ra else min(cap, base * 2 ** i) * (1 + random.random() / 4))62        except (anthropic.APIConnectionError, anthropic.APITimeoutError):63            if i == attempts - 1: raise64            time.sleep(min(cap, base * 2 ** i))6566msg = call_with_backoff(lambda: client.messages.create(model="claude-haiku-4-5-20251001", max_tokens=16,67                                                       messages=[{"role": "user", "content": "Reply with OK."}]))68if msg.stop_reason == "refusal":  # HTTP 200 but no usable answer69    ...  # retry on a fallback model, read msg.stop_details.category70```71Streaming: wrap the `with client.messages.stream(...)` block in the same handler — a mid-stream `error` event raises `anthropic.APIError`; retry the **whole** request. Note: anthropic 1.7.0 dropped `temperature`/`top_p`/`top_k` kwargs — use `extra_body`.7273## Robust handler — TypeScript (`examples/shared/errors/anthropic_error_handling.ts`, LIVE_VERIFIED)7475```ts76import Anthropic from "@anthropic-ai/sdk";77const client = new Anthropic({ maxRetries: 0 });78const fatal = (e: Anthropic.APIError) => e instanceof Anthropic.BadRequestError || e instanceof Anthropic.AuthenticationError ||79  e instanceof Anthropic.PermissionDeniedError || e instanceof Anthropic.NotFoundError || e instanceof Anthropic.UnprocessableEntityError;8081async function withBackoff<T>(fn: () => Promise<T>, attempts = 4, base = 500, cap = 8000): Promise<T> {82  for (let i = 0; ; i++) {83    try { return await fn(); }84    catch (e) {85      if (!(e instanceof Anthropic.APIError)) throw e;                       // APIConnectionError etc. also extend APIError86      if (fatal(e) || e.headers?.get?.("x-should-retry") === "false" || i === attempts - 1) throw e; // e.status, e.requestID, e.error87      const ra = e.headers?.get?.("retry-after");88      await new Promise(r => setTimeout(r, ra ? Number(ra) * 1000 : Math.min(cap, base * 2 ** i) * (1 + Math.random() / 4)));89    }90  }91}92const msg = await withBackoff(() => client.messages.create({ model: "claude-haiku-4-5-20251001", max_tokens: 16, messages: [{ role: "user", content: "Reply with OK." }] }));93if (msg.stop_reason === "refusal") { /* fallback model; msg.stop_details?.category */ }94```95For `client.messages.stream()`, attach `.on("error", …)` and/or await `finalMessage()` inside the same wrapper.9697## Errors vs stop reasons9899Errors = HTTP 4xx/5xx (or SSE `error` event), no usable content. Stop reasons = HTTP 200 with content and a `stop_reason` (`refusal`, `max_tokens`, `pause_turn` need application handling) — see `docs/anthropic/stop-reasons.md`.100