Streaming, Errors & Resilience

401 vs 402 vs 403: Why Authentication, Billing, and Permission Errors Need Different Fixes

Three status codes, three different owners: 401 goes to whoever manages credentials, 402 goes to whoever pays the bill, and 403 goes to whoever configures scopes and roles. Routing them to the wrong desk wastes an incident.

Claude 3P 101 · Updated July 2026 · Unofficial guide

When a request to the Claude API is refused for access reasons, the response tells you precisely which kind of refusal it is. Every error body has the same shape — a top-level "type": "error", an error object with type and message, and a request_id you can quote to support — and the three access-failure variants map cleanly to who has to act.

The taxonomy

HTTP codeError typeWhat it meansWho fixes it
401authentication_errorThe API key is missing, malformed, revoked, or simply wrong — the request could not be attributed to a valid identityCredential owner: rotate or correct the key, check the header it's sent in
402billing_errorThe identity is valid, but the account has a billing problem blocking usageBilling admin: resolve payment or plan status in the Console
403permission_errorThe identity is valid and billing is fine, but this key lacks permission for this resource or operationOrg/workspace admin: adjust key scope, workspace, or role

The distinction matters operationally because all three look identical to a naive retry loop: the request "failed." But per Anthropic's retry guidance, 4xx access errors are not retryable — the official SDKs auto-retry 429s, 5xx errors, and connection failures with exponential backoff, and deliberately do not retry these. A 401 retried a thousand times is still a 401; worse, an alerting system that lumps them in with transient errors hides a hard outage ("every request is failing auth") inside a metric that usually means "the API is busy."

Catching them distinctly in code

The Python SDK raises typed exceptions, so you can branch without inspecting status codes: AuthenticationError for 401 and PermissionDeniedError for 403, with BadRequestError, NotFoundError, RateLimitError, and friends alongside. Billing errors don't get their own class, but every APIStatusError subclass exposes a .type property carrying the API's error-type string — check it for "billing_error":

from anthropic import Anthropic, AuthenticationError, \
    PermissionDeniedError, APIStatusError

client = Anthropic()
try:
    msg = client.messages.create(model="claude-sonnet-5",
        max_tokens=256, messages=[{"role": "user", "content": "Hi"}])
except AuthenticationError:
    alert("401: rotate/verify the API key")       # credentials desk
except PermissionDeniedError:
    alert("403: key lacks scope for this route")  # workspace admin
except APIStatusError as e:
    if e.type == "billing_error":
        alert("402: billing blocked — finance")   # billing desk
    else:
        raise

Catch the most specific exception first; the base classes exist as fallbacks, not first resorts.

What 403 means changes by platform

On third-party platforms the same status code points at platform-specific configuration, so the "who fixes it" column shifts:

Claude Platform on AWS: a 403 means the request reached the server and was rejected — the usual suspects are a wrong workspace_id or a missing IAM action on the calling principal. Note also that first-party sk-ant-api... keys do not work there at all; keys are issued in the AWS Console. See the Platform-on-AWS setup errors guide.

Microsoft Foundry: with Entra ID authentication, 403s usually mean a missing Azure RBAC role such as Cognitive Services User on the Foundry resource. Foundry also returns two correlation IDs — request-id (Anthropic) and apim-request-id (Azure API Management) — and Anthropic advises quoting both to support. Foundry error codes has the full picture.

Rule of thumb: log the error type string, not just the HTTP code, and route alerts by it. authentication_error pages the on-call for secrets management; billing_error notifies finance and engineering leadership; permission_error opens a ticket with whoever owns workspace or IAM configuration. None of them should feed a retry queue.

Where to go next

The full error-code reference covers the rest of the taxonomy (429, 500, 529, and the streaming-specific cases), and SDK error handling goes deeper on exception hierarchies across languages.

Sources