A 529 means Anthropic's servers can't keep up with the flow of requests this second and are rejecting new ones for a while. The cause is not your connection, your account or the wording of your message. In a chat or in Claude Code a pause and a retry usually do it. In the API you need a retry with a delay.
Developers know the other picture: everything worked in testing, then traffic arrived, and the logs filled with 529 Overloaded and the occasional 500. Some of these errors are yours to fix. Some are Anthropic's, and your only job is to retry intelligently. The trick is knowing which is which, because retrying a 400 forever just burns money and wakes you up at 3 AM for nothing. This guide sorts every Claude API error into the right bucket and gives you working code.
What does the 529 Overloaded error mean?
A 529 means Anthropic's servers are temporarily saturated and can't take your request right now. It's not about your key, your quota, or your prompt: it's about aggregate demand on the platform at that exact moment. Per Anthropic's docs, 529 is the "Overloaded" status, and the correct response is to back off and try again shortly.
This is the single most common error people hit on the raw Anthropic API, and it spikes during peak hours and right after a new model ships. If your 529s cluster in the US working day (evening in Moscow, night in much of Asia), that fits a general spike, but Anthropic doesn't publish a schedule of peaks. You haven't been singled out. You joined the queue with everyone else.
Because it's transient and server-side, retrying is the right move, but retrying immediately and aggressively makes the congestion worse for everyone, including you. It looks different in each place: in the API it is the 529 status, in Claude Code the text "API Error: Repeated 529 Overloaded errors", and in the claude.ai web chat an overload message instead of a reply.
In the API the response looks like this:
{
"type": "error",
"error": {
"type": "overloaded_error",
"message": "Overloaded"
}
}
Cause → fix for 529:
- Cause: platform-wide capacity pressure, often at peak times or post-launch.
- Fix: exponential backoff with jitter (code below), cap your concurrency, and shift non-urgent work to off-peak windows or the Batch API.
- Client control: use a bounded queue, limit concurrency, and return an explicit failure when retries are exhausted. Gateway access does not establish extra capacity.
One honest note: there's no header that tells you when the overload will clear. Unlike 429, a 529 rarely carries a useful retry-after. So your backoff schedule is your only lever. Don't skip the jitter: without it, every client in the world retries on the same beat and re-creates the spike.
What to do when a 529 hits right now
- Wait 10-30 seconds. An overload is usually short, and a retry after half a minute goes through.
- Retry unchanged. The text of your message has nothing to do with it, so don't rewrite it.
- Don't hit send over and over. A burst of quick retries doesn't speed up the answer and adds load.
- If it lasts more than a few minutes, open status.claude.com. It has separate rows for claude.ai, the Claude API and Claude Code, so you can see whether an incident is open.
- Switch model if your interface allows it. According to the Claude Code docs, capacity is tracked per model, so one model can be overloaded while its neighbour answers.
In Claude.ai
You get an overload message instead of a reply. Refreshing the page fixes nothing by itself, but the next send after the refresh is a retry anyway. If the screen is blank and there is no error text at all, suspect your network or browser: a 529 comes with an explicit message or status code.
In Claude Code
Claude Code retries on its own several times before it shows the error. Per Anthropic's docs the default is 10 retries, set by CLAUDE_CODE_MAX_RETRIES. If the error is still on screen:
- Wait a couple of minutes and send your message again. For a 500 the Claude Code docs advise not pasting a long prompt again but typing
try again, because your original message stays in the conversation. - Run
/modeland pick a different model. Claude Code itself suggests this when one model is under heavy load. - Don't worry about limits: per the docs, a 529 doesn't count against your quota.
- If the agent broke off mid-task, run
git statusandgit diffbefore launching the same task again. Part of the edits may already be on disk.
Environment variables control the pauses between retries:
| Variable | Default | What it does |
|---|---|---|
CLAUDE_CODE_RETRY_WATCHDOG | unset | 1 for unattended sessions such as CI: temporary 429 and 529 capacity errors are retried without limit instead of failing after CLAUDE_CODE_MAX_RETRIES attempts. Since v2.1.239 a standard request fails at once when a 429 reports a spend limit or exhausted credits |
CLAUDE_CODE_OVERLOADED_RETRY_BASE_DELAY_MS | 500 | Starting delay between retries on a 529; can be raised up to 32000 |
CLAUDE_CODE_OVERLOADED_RETRY_MAX_DELAY_MS | 32000 | Ceiling on that delay; can be raised up to 64000 |
The last two work only in recent Claude Code versions, and neither has any effect when CLAUDE_CODE_RETRY_WATCHDOG=1.
How to hit 529 less often
One 529 every few days is a normal part of life with a popular API, so don't lose sleep over it. It's different when the error gets in the way every day.
If you work in a chat or in Claude Code, move heavy tasks to hours when you don't see 529s (we have no schedule, so go by your own observations), switch to a neighbouring model with /model, and split long jobs into steps so a break costs less work. If failures persist, write to Anthropic support and say at what times the error repeats most.
If you write code, you have more levers: backoff with jitter, a concurrency cap and the Batch API for non-urgent work are covered above and below. Add one more: ramp traffic up gradually. Anthropic's docs warn that a sharp increase in your organization's usage can trigger 429s through acceleration limits, and steady usage avoids that.
How do I fix a Claude API 500 error?
A 500 Internal Server Error means something broke on Anthropic's side while processing an otherwise valid request. You didn't do anything wrong, and the same request will usually succeed on retry. Treat it like 529: back off and try again.
The difference is intent. A 500 is an unexpected fault; a 529 is expected load-shedding. From your code's perspective they're handled identically: both are retryable server-side errors. Do read the response body, but for one field: error.type on a 500 is either api_error or timeout_error. For api_error there's nothing to change in the request: retry with backoff, and if it keeps failing, save the request_id from the response body (it's also in the request-id header) and attach it to a support ticket. A timeout_error means the request hit the server's time limit before the response started. For long requests Anthropic recommends streaming, which the timeouts section below covers.
{
"type": "error",
"error": {
"type": "api_error",
"message": "Internal server error"
}
}
If 500s persist across many retries over several minutes, that's no longer transient. Check the Anthropic status page and your own request payload for anything malformed enough to trip a server-side edge case (extremely large tool schemas, for instance). But usually a single retry clears it.
What about 503 Service Unavailable?
A 503 means the service is temporarily down or in maintenance: another transient, retryable condition. Same playbook as 500 and 529: exponential backoff, respect any retry-after if present, give up gracefully after a sane number of attempts.
Lump these three together in your head: 529, 500, 503 = retry. They differ in cause (overload, internal fault, unavailability) but not in your response. Your retry layer should catch all three with the same logic.
Why am I getting a 429 rate limit error?
A 429 Too Many Requests means you've hit a limit. Most often it's a rate limit: requests per minute (RPM), input tokens per minute (ITPM) or output tokens per minute (OTPM) for the model. A sharp jump in traffic can also trigger a 429 through acceleration limits. Unlike the 5xx family, this one is about your usage, and the API usually tells you how long to wait via the retry-after header. Not every response carries it.
{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "Number of requests has exceeded your per-minute limit."
}
}
There is also a 429 that retrying won't cure: the monthly spend cap. When spend reaches the monthly ceiling of your Anthropic API usage tier, usage pauses until 00:00 UTC on the first day of the next month, unless you ask for a higher limit sooner. The error type stays rate_limit_error, but there is no retry-after header and error.details.error_code is enforced_spend_limit_reached. Retries, including the SDK's automatic ones, keep failing until access resumes. Tell this case apart by error_code, not by the message text, and don't push it through your retry loop. If you set a spend limit yourself for the organization or a workspace in the console, reaching it usually returns a 400 rather than a 429; that one is covered in the next section.
Read retry-after when present and respect it. Measure token usage, cache repeated context where supported, and bound the request rate in your client. Gateway access does not create independent quotas. The Claude API rate limits guide covers client queues and provider quota handling.
We do carry Haiku: claude-haiku-5.5 and claude-haiku-4.5 are in the catalog, with prices on the models page. They run in the Premium channel, not in Cheap.
What causes a 400 Bad Request error?
A 400 means the request itself is malformed. The API rejected it before doing any work, and retrying the identical request will fail every single time. This is a code bug, not a transient hiccup. Stop, read the message, fix the payload.
One exception: a 400 also comes back when spend reaches a limit you set yourself for the organization or a workspace in the console. The message starts with You have reached your specified API usage limits (for a workspace: You have reached your specified workspace API usage limits) and states when access resumes. There's nothing to fix in the request: raise or remove the limit, or wait for that date.
The most common causes, in rough order of frequency:
| Cause | What it looks like | Fix |
|---|---|---|
Missing max_tokens | max_tokens: Field required | Always include max_tokens, it's mandatory on the Messages API |
| Bad JSON | invalid_request_error on a request that "looks fine" | Validate JSON; watch trailing commas and unescaped quotes |
Prefilled last assistant message | This model does not support assistant message prefill. The conversation must end with a user message. | Claude 4.6 and later don't support prefill: end the conversation with a user message |
| Empty messages array | messages: at least one message required | Send at least one message |
max_tokens too high | max_tokens exceeds model maximum | Keep it under the model's output ceiling |
| Context overflow | prompt is too long | Trim history or split the task: this is a 400, not a 413 |
Pass the system prompt through the top-level system field: that's the one Anthropic's docs describe. Strict user/assistant alternation isn't required for a valid request: the Messages API combines consecutive messages with the same role into one turn.
Here's a 400 from forgetting max_tokens:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "max_tokens: Field required"
}
}
And the correct request in curl, with everything in place:
curl https://api.claudexia.tech/v1/messages \
-H "x-api-key: $CLAUDEXIA_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, Claude."}
]
}'
When you get a 400, log the full error.message. It's specific and usually points straight at the broken field. Don't bury it.
Why does my API key return 401 or 403?
A 401 Unauthorized means your key is missing, malformed, revoked or expired. A 403 Forbidden means the key is valid but isn't allowed to do what you asked. Neither is retryable. Fix the credentials or permissions and move on.
Walk this checklist for 401:
- Is the key actually being sent? Check the
x-api-keyheader (orAuthorization: Beareron the OpenAI-compatible endpoint). - Did an environment variable fail to load, sending an empty string?
- Are you mixing keys and base URLs? An Anthropic
sk-ant-…key won't authenticate against a different base URL, and a Claudexiask_cdx_…key targetshttps://api.claudexia.tech. - Trailing whitespace or newline pasted into the key? It happens more than you'd think.
A separate case: a 401 whose message begins CORS requests are not allowed for this Organization has nothing to do with your key. The request carried an Origin header (browsers add it), and your Anthropic organization can't call the Claude API directly from a browser. Changing the request won't help: send it from your own backend.
For 403, the key works but lacks access, for example when requesting a model your account or plan doesn't have, or hitting an endpoint outside your permissions. Read the message; it names the restriction.
{
"type": "error",
"error": {
"type": "authentication_error",
"message": "invalid x-api-key"
}
}
What is the 413 Request Too Large error?
A 413 with the request_too_large type means the request body exceeded a size limit in bytes. At Anthropic that's 128 MB for the Messages API, 256 MB for the Batch API and 500 MB for the Files API. It has nothing to do with tokens or the context window: if the input alone doesn't fit in the model's window, the API returns 400 invalid_request_error with the message "prompt is too long". Either way, retrying the request as-is is pointless; you have to shrink it.
Common triggers and fixes:
- Context overflow (arrives as a 400, not a 413): you're pushing past the model's input limit. Trim history, summarize old turns, or chunk the work. Our long-context strategy post covers tactics for keeping payloads lean.
- Huge images or PDFs (this is where a 413 shows up): with many images or heavy documents a request can hit the byte limit before the token limit. Downscale images and split large documents before sending.
- Runaway message history: in a chat loop, prune or summarize earlier messages instead of appending forever.
If you're regularly bumping into size limits, that's a design signal: move to retrieval (send only relevant chunks) rather than dumping everything into every call.
How do I handle timeouts and connection errors?
"Timeout" covers three different things, and each is handled differently.
- A client-side timeout. The SDK waits 10 minutes for a response by default. If the time runs out or the connection drops, it raises an exception (
APITimeoutErrorin Python,APIConnectionTimeoutErrorin TypeScript), and this case has no HTTP status at all. - A
504response with thetimeout_errortype. The request didn't finish processing on Anthropic's side. The sametimeout_erroralso comes with other statuses, for example a500when the request reaches the server's time limit before the response starts. 408 Request Timeout. A plain HTTP status. Anthropic's error list doesn't include it, but the SDK retries a 408 by default, and the code below keeps it in the retryable set.
All three are retryable, but blindly repeating the same long request rarely helps. For long outputs Anthropic recommends streaming (and the Batch API for very long jobs): tokens start arriving immediately, so the network is less likely to drop the wait as idle. Also set a sane client timeout: too short and you'll abort valid long responses, too long and a stuck connection hangs your worker.
import anthropic
# Explicit timeout; stream to avoid idle timeouts
client = anthropic.Anthropic(timeout=120.0)
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=4096,
messages=[{"role": "user", "content": "Write a long report."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
Connection errors should fall into the same retry bucket as 529/500/503/504. The implementation below catches them.
The complete status code reference
Keep this table next to your error handler. The "Action" column is the whole game: retry the transient stuff, fix the rest. The table lists the codes from Anthropic's documentation plus two the SDK also retries: 408 and 503.
| Code | Meaning | Retryable? | Action |
|---|---|---|---|
| 400 | invalid_request_error: malformed request | No | Fix the request: max_tokens, JSON, prefill; for a spend limit you set yourself, raise the limit |
| 401 | authentication_error: problem with the key | No | Check the API key and headers; for the CORS error, send the request from your own backend |
| 402 | billing_error: billing or payment problem | No | Check your payment details in the Anthropic console |
| 403 | permission_error: no permission | No | Check the key's access to the model or resource |
| 404 | not_found_error: resource not found | No | Check the endpoint path and any resource IDs in the URL |
| 408 | Request timeout (not in Anthropic's list) | Yes | Retry; raise client timeout; stream |
| 409 | conflict_error: conflicts with the resource's current state | After resolving the conflict | Resolve the conflict, then retry |
| 413 | request_too_large: body over the byte limit | No | Shrink images, documents and history |
| 429 | rate_limit_error: rate limit or monthly spend cap | Yes, except the spend cap | Backoff + honor retry-after; with enforced_spend_limit_reached retrying won't help |
| 500 | api_error: internal server error (sometimes timeout_error) | Yes | Backoff and retry; for timeout_error, stream |
| 503 | Service unavailable | Yes | Backoff and retry |
| 504 | timeout_error: request didn't finish processing in time | Yes | Retry; for long requests switch to streaming |
| 529 | overloaded_error: overloaded | Yes | Backoff with jitter; reduce concurrency |
Exponential backoff with jitter: the production retry
This is the one piece of code every Claude integration needs. It retries the transient errors (429, 500, 503, 504, 529, 408, timeouts, connection drops), waits at least as long as retry-after, adds jitter so clients don't synchronize, and, critically, does not retry the errors that are your bug (400, 401, 403, 413) or a 429 caused by the monthly spend cap.
The SDK can retry on its own: by default it makes two retries on connection errors, 408, 409, 429 and 5xx responses, with a short exponential backoff that honors retry-after. If you leave that on and wrap the call in your own 6-attempt loop, you get up to 18 HTTP requests: each loop attempt retries twice by itself. So the examples switch the SDK's retries off per call (with_options(max_retries=0) in Python, { maxRetries: 0 } in TypeScript). If two SDK retries are enough for you, skip the loop and raise max_retries or maxRetries on the client instead.
Python:
import random
import time
import anthropic
RETRYABLE_STATUS = {408, 429, 500, 503, 504, 529}
SPEND_LIMIT_CODE = "enforced_spend_limit_reached"
def is_spend_limit(e: anthropic.APIStatusError) -> bool:
body = e.body if isinstance(e.body, dict) else {}
details = (body.get("error") or {}).get("details") or {}
return details.get("error_code") == SPEND_LIMIT_CODE
def pause_seconds(attempt: int, retry_after: str | None, base: float, cap: float) -> float:
try:
wait = float(retry_after) if retry_after else None
except ValueError:
wait = None
if wait is not None:
return wait + random.uniform(0, min(1.0, wait * 0.25))
return random.uniform(0, min(cap, base * 2**attempt))
def call_with_retry(
client: anthropic.Anthropic,
*,
max_attempts: int = 6,
base_delay: float = 1.0,
max_delay: float = 60.0,
**kwargs,
) -> anthropic.types.Message:
for attempt in range(max_attempts):
try:
# The SDK retries twice by default; the loop counts attempts, so switch that off
return client.with_options(max_retries=0).messages.create(**kwargs)
except anthropic.APIConnectionError:
if attempt == max_attempts - 1:
raise
retry_after = None
except anthropic.APIStatusError as e:
# Don't retry errors you caused or a spend cap: fix the request or the limit.
if (
e.status_code not in RETRYABLE_STATUS
or is_spend_limit(e)
or attempt == max_attempts - 1
):
raise
retry_after = e.response.headers.get("retry-after")
time.sleep(pause_seconds(attempt, retry_after, base_delay, max_delay))
raise RuntimeError("unreachable")
TypeScript:
import Anthropic from "@anthropic-ai/sdk";
const RETRYABLE = new Set([408, 429, 500, 503, 504, 529]);
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
type ErrorBody = { error?: { details?: { error_code?: string } } };
const isSpendLimit = (err: Anthropic.APIError) =>
(err.error as ErrorBody | undefined)?.error?.details?.error_code ===
"enforced_spend_limit_reached";
function pauseMs(
attempt: number,
retryAfter: string | null | undefined,
baseDelay: number,
maxDelay: number,
): number {
const seconds = retryAfter ? Number.parseFloat(retryAfter) : Number.NaN;
if (Number.isFinite(seconds)) {
const wait = seconds * 1000;
return wait + Math.random() * Math.min(1000, wait * 0.25);
}
return Math.random() * Math.min(maxDelay, baseDelay * 2 ** attempt);
}
export async function callWithRetry(
client: Anthropic,
params: Anthropic.MessageCreateParamsNonStreaming,
{ maxAttempts = 6, baseDelay = 1000, maxDelay = 60000 } = {},
): Promise<Anthropic.Message> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
// The SDK retries twice by default; the loop counts attempts, so switch that off
return await client.messages.create(params, { maxRetries: 0 });
} catch (err) {
const last = attempt === maxAttempts - 1;
let retryAfter: string | null | undefined;
if (err instanceof Anthropic.APIConnectionError) {
if (last) throw err;
} else if (err instanceof Anthropic.APIError && err.status) {
// 400/401/403/413 and the spend cap: surface them immediately.
if (!RETRYABLE.has(err.status) || isSpendLimit(err) || last) throw err;
retryAfter = err.headers?.get("retry-after");
} else {
throw err;
}
await sleep(pauseMs(attempt, retryAfter, baseDelay, maxDelay));
}
}
throw new Error("unreachable");
}
A few design choices worth understanding. Full jitter (a random value in [0, delay]) beats fixed exponential delays at preventing synchronized retries. The AWS architecture blog made this case years ago and it still holds. But it applies only to a pause you compute yourself. When the server sends retry-after, that's a floor: an earlier retry won't succeed, so jitter is only added on top. The max_delay cap stops your backoff from ballooning to absurd waits. And separating retryable from non-retryable status codes is what keeps a 400 from spinning forever.
How does a gateway affect 529 errors?
A gateway changes the endpoint and account used by your app. That does not establish additional request capacity or guarantee that provider overload errors will disappear. Handle 529 Overloaded as a possible failure and use bounded retries for retryable errors.
Claudexia keys select Premium or Cheap, with optional Premium fallback for Cheap under provider policy. Verify model and channel availability before sending traffic. Provider availability and policies still apply; the key settings do not guarantee separate pooled capacity, automatic rerouting, or suppression of upstream 529 errors. Your client must handle these failures.
Switching is a one-line change, with the same SDKs and the same request shapes:
import anthropic
# Same Anthropic SDK, pointed at the gateway.
client = anthropic.Anthropic(
api_key="sk_cdx_your_key",
base_url="https://api.claudexia.tech",
)
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "sk_cdx_your_key",
baseURL: "https://api.claudexia.tech",
});
For Claude Code the same switch is two environment variables:
export ANTHROPIC_BASE_URL="https://api.claudexia.tech"
export ANTHROPIC_API_KEY="sk_cdx_YOUR_KEY"
Installing Claude Code on Windows, macOS and Linux is covered in Claude Code setup without an Anthropic account. Anthropic's overload errors don't go away because of this, so the retry rules above still apply.
There's also an OpenAI-compatible endpoint, so tools like Cursor, Claude Code, and OpenCode plug in unchanged. Billing is pay-per-token with no subscription. In the Premium channel on 10 November 2026, Sonnet 5.5 costs $0.52 per 1M input tokens and $1.98 per 1M output tokens, and Opus 5.5 costs $0.86 and $3.30. At Anthropic, Sonnet 5.5 is $2 and $10, and Opus 5.5 is $4 and $20. Prices change, so check the models page for current ones. For cheap, high-volume work the catalog also has gpt-5.6-luna. You can pay by card, by SBP bank transfer (Russia's instant payment system), with crypto (USDT/BTC/ETH) or through MTS. See pricing and the API reference for details, or accessing Claude from Russia in 2026 for the regional angle.
FAQ
Is the 529 Overloaded error my fault?
No. A 529 means overload on the provider's side, not a problem with your request, key or quota, so checking your connection or reinstalling the client is pointless. Use bounded retries in the client with backoff and jitter, and reduce concurrency during the spike. Gateway access does not guarantee alternate capacity or suppression of the error; return a failure when retries are exhausted.
How long does an overload last?
Usually from a few seconds to a few minutes. Anthropic doesn't give a firm time, and a 529 rarely carries a recovery header. A prolonged overload shows up as an incident on status.claude.com, so check there if the error lasts more than a few minutes.
Does refreshing claude.ai help?
Not by itself. But the next message you send after the refresh is a retry, so it helps in the same way a pause and a retry do.
How is a 529 different from Claude just not responding?
A 529 comes with an explicit error message or status code. A blank screen with no text more often points to your network or browser, not to the Anthropic API.
Should I switch providers because of occasional 529s?
Occasional ones are no reason to. If overload has become a daily nuisance, change how the client is built: a queue, backoff, a concurrency cap and a backup model that you wire into your own code. A gateway does not remove Anthropic's overload.
Should I retry a 400 Bad Request error?
Never. A 400 means the payload is malformed, so an identical retry fails identically and you just waste calls. Read error.message, which usually names the broken field, and fix it. The usual culprits are a missing max_tokens, invalid JSON, or a prefilled last assistant message on a model that doesn't support prefill. There is one exception to "never": a 400 caused by a spend limit you set yourself goes through again once you raise the limit or the date named in the message arrives.
What's the difference between 429 and 529?
A 429 concerns the provider's quota policy; respect retry-after when present and slow the client workload. The exception is a 429 carrying enforced_spend_limit_reached: that's the monthly spend cap, and retrying won't lift it. An Anthropic 529 indicates platform overload. Use bounded retries with backoff for temporary failures and report failure if they persist. A gateway does not guarantee failover to another capacity pool. See the rate limits guide for client controls.
Why does a 429 keep failing after retries?
Most likely it's the monthly spend cap, not a rate limit. Look at error.details.error_code: the value enforced_spend_limit_reached means access is paused until 00:00 UTC on the first day of the next month, or until the limit is raised. Rely on error_code itself: other 429 responses also lack a retry-after header, so its absence doesn't identify a spend cap. Retries, including the SDK's automatic ones, will keep failing until access resumes.
How many times should I retry before giving up?
For transient errors, 5-6 attempts with capped exponential backoff is a sensible default: enough to ride out a brief spike without hanging a request for minutes. Count HTTP attempts: the SDK retries twice on its own by default, so a loop of six calls without max_retries=0 makes up to 18 requests. Always cap the per-attempt delay (e.g. 60 seconds) and surface a clear error to the caller once retries are exhausted, so a stuck request doesn't masquerade as success.
Why do I keep getting 401 even though my key looks right?
Usual suspects: an environment variable that loaded as an empty string, trailing whitespace or a newline pasted into the key, the wrong header (x-api-key vs Authorization: Bearer), or a key/base-URL mismatch: an sk-ant-… key won't authenticate against a non-Anthropic base URL, and an sk_cdx_… key must target https://api.claudexia.tech. Log the exact key length and prefix (never the full value) to spot these fast. If the request goes out from a browser and the message begins CORS requests are not allowed for this Organization, the key isn't the problem: send the request from your own backend.
Keep client retries bounded and distinguish temporary provider failures from invalid requests. Check model and channel availability; gateway access does not guarantee that overload errors disappear. Start with the quickstart. For integration questions, contact us or use Telegram.