A Claude integration fails in a small number of well-defined ways, and the same failure looks different in Python than in Go. The productive way to think about error handling is in two layers: first learn what each HTTP status actually indicates (that knowledge transfers across all seven SDKs and every platform), then map it onto your language's idiom. The official SDKs expose typed errors that carry the status code and the API's error body — the exact class or type names vary per language, so check the repository README for your SDK's error hierarchy rather than guessing from another language's.
Status code semantics that matter in practice
| Status | What it usually means | Sensible reaction |
|---|---|---|
| 400 | Invalid request — malformed body, or a parameter this model rejects | Fix the request; never retry as-is |
| 401 / 403 | Credential missing, wrong, or lacking permission/scope | Check credential source and scoping, not your prompt |
| 404 | Wrong route — or a retired model ID | Verify endpoint and model against the models list |
| 429 | Rate limit exceeded for your usage tier | Back off with jitter, bound concurrency |
| 5xx / overloaded | Transient server-side trouble | Retry with exponential backoff |
Two of these deserve enterprise-specific elaboration. 400s are frequently model-migration artifacts, not bugs: newer models reject parameters older ones accepted — manual extended thinking, non-default temperature/top_p/top_k, and assistant prefill all return 400 on Sonnet 5 and recent Opus models, and input exceeding the context window returns a 400 invalid_request_error ("prompt is too long"). 403s are platform-flavored: on the first-party API a 403 can mean your Workload Identity Federation token lacks the OAuth scope for that endpoint; on Claude Platform on AWS a 403 means the request reached the server but the workspace ID is wrong or the IAM principal is missing an action. And on that platform, a SigV4 region mismatch produces a generic signature-rejection error rather than a specific diagnostic — worth knowing before you burn an afternoon on it.
Language idioms
Python raises exceptions: wrap client.messages.create(...) in try/except, catch the SDK's error types (narrow ones for statuses you handle specially, the base type as a backstop), and let truly unexpected exceptions propagate. TypeScript rejects promises: try/await/catch, or .catch() per promise when fanning out with Promise.allSettled so one failure doesn't sink a batch. Go returns error values from client.Messages.New(ctx, ...) — check them immediately, use errors.As-style unwrapping to reach the typed API error, and remember that a cancelled context surfaces as an error too. Java and C# throw; catch the SDK's exception types around client.messages().create(params) / await client.Messages.Create(parameters) and translate them at your service boundary into your application's error model.
Errors that arrive as successful responses
Two failure modes return HTTP 200 and bite teams that only check status codes. First, safety refusals: on recent models a refused request completes with stop_reason: "refusal" and a stop_details.category field (categories include "cyber" and "bio") — your code should branch on stop_reason, not just parse the text. Second, context overflow mid-generation: on Claude 4.5+ models, a request whose input plus max_tokens exceeds the window is accepted and stops with stop_reason: "model_context_window_exceeded", giving you a truncated-by-design response. Treat stop_reason as part of your error handling surface, in every language.
Where to go next
Pair this with configuring retries in the SDK and handling 429 rate-limit errors; if you run concurrent workloads, async and concurrent SDK calls shows why bounded fan-out prevents most 429s in the first place.