Skip to content

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits."}}

Your request was well-formed and your key is valid. The account behind the key has no prepaid credit left, so the request was refused before any model ran. That rules out a malformed payload despite the error type, rules out a rate limit (those are 429s), rules out platform capacity (529), and rules out a bad key (401). The more useful thing it does not rule out: on a Pro or Max subscription this message frequently appears on an account that owes nothing at all.

Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.

A billing condition wearing a payload error’s status code

The API has a status code for this. 402 is documented as billing_error, “an issue with your billing or payment information”. Credit exhaustion does not use it. It arrives as 400 invalid_request_error, the same status and the same error.type as a malformed message array or an oversized tool name.

Three practical consequences, and each one costs somebody an hour somewhere:

  • Your error handling is probably misrouting it. Code that buckets invalid_request_error as “my payload is broken” sends you to inspect JSON for a problem that is not in the JSON. Code that keys retry behavior on 429 never sees this at all.
  • Nothing retried it for you, so it appeared instantly. A real rate limit is retried with backoff before you ever see it — the official SDKs retry rate limits and 5xx responses twice by default, and Claude Code retries transient failures up to 10 times before showing an error. A 400 is excluded from both. An error that appeared with no visible delay is evidence against a rate limit, and that is a free diagnostic you already have.
  • There is no retry-after header, because the response is not a rate-limited one. Anything you write that waits for a header-supplied interval will wait forever for a header that is not coming.

The most common cause is not an empty balance

If you pay for a Claude subscription and this message surprised you, check the credential before you check the balance. A stray ANTHROPIC_API_KEY in the environment routes requests through a Console API key instead of your subscription — the subscription is untouched, the Console account has no prepaid credits, and the API says so. The documentation calls this misdiagnosis out by name, and Claude Code prints its own shorter wording, Credit balance is too low, for the same condition.

/status shows which credential is active. That is the first command to run for any “is this my plan or my key?” question, and it is decisive in one line.

What makes this worth a paragraph rather than a footnote: buying credits makes the error go away. You are then paying metered API rates for a subscription you have already bought, the symptom is gone, and nothing tells you. The fix and the misdiagnosis are indistinguishable from the outside, which is why the check comes before the purchase.

Is retrying useful?

Yes — but only in the sense that nothing about your request needs to change. Retrying right now, unchanged, will fail.

This is the exception in a family of errors where retrying is pointless by construction. Every other 400 invalid_request_error is a statement about bytes you sent, and identical bytes are rejected identically forever. This one is a statement about account state, which lives outside your process and can change without you editing anything — someone with billing access adds credits, or the environment variable sending you down the wrong path gets unset. The moment that happens, the same request you already have succeeds unmodified.

So the correct shape is: fix the account or the credential, then re-send the original request. Do not put this in an automatic retry loop. There is no retry-after to honor and no backoff schedule that ends, so a loop is a guaranteed-failing request repeated on a timer — and if the ramp in traffic is sharp enough, you can collect a rate limit on top of a billing failure.

One reading of a spontaneous recovery: if the error clears while you changed nothing, somebody or something topped up the balance. That is a resolved symptom, not a diagnosis, and the next time it empties you will be exactly here again.

Which money thing is refusing you

Several different limits stop requests for money reasons, and they are told apart by the status code and the header — not by the wording, which people paraphrase from memory.

  • This exact message, HTTP 400, invalid_request_error — the organization’s prepaid Console credit balance. Nothing resets on a schedule; it changes only when credits are purchased.
  • HTTP 400, invalid_request_error, message beginning You have reached your specified API usage limits — a spend limit you configured, not an empty balance. Same status code, different sentence; read the first words of the message rather than the status.
  • HTTP 429, rate_limit_error, no retry-after header, and error.details.error_code set to enforced_spend_limit_reached — the organization’s monthly spend cap. Retrying fails until access resumes at 00:00 UTC on the first day of the next month, or until the tier is raised. The caps are $500 USD on Start, $1,000 USD on Build and $200,000 USD on Scale; the Custom tier has none.
  • A message naming a reset time, like You've hit your session limit · resets 3:45pm — a subscription plan allowance, which is a completely separate accounting system from credits. Session and weekly limits are shared across models, so switching model does not restore access; a model-family limit does.
  • A lowercase spend limit reached (daily; resets ... UTC) — a self-hosted Claude apps gateway cap. Those responses are marked x-should-retry: false, so the client shows the message without retrying.

Fix by which one you have

  • Subscriber with a stray API key — unset ANTHROPIC_API_KEY in the shell, the shell profile, and any editor or launcher that injects environment variables, then confirm with /status that the subscription is the active credential. Do not buy credits until that line reads what you expect.
  • Genuinely out of prepaid credits — purchase credits or move to invoiced billing. There is no client-side mitigation, and no amount of prompt trimming changes a balance.
  • A spend limit you set yourself — raise or remove it where you set it, and fix any handler that assumed billing failures arrive as 429.
  • Organization monthly cap — raise the tier, or wait for the reset at the start of the next month. Retry logic here is a loop that spends time reproducing a certainty.
  • Plan allowance rather than API credits — nothing in this page’s numbers applies to you. The Claude Code usage limit calculator is built for the plan side and will tell you whether your consumption is anywhere near the allowance before you start changing billing settings.

Confirming it is actually fixed

A single successful request is a weak signal here, because the thing that changed may not be the thing you changed.

Check the credential first: /status should name the credential you intend to use. Then re-send the request that failed, unmodified — same payload, same model. It should succeed with no edits at all, and that is the point of the test: if it only works after you also changed the request, your diagnosis was wrong and something else was failing underneath. Finally, confirm on the billing side that the balance moved, or that usage is now being recorded against the subscription rather than the API account. A working request tells you the block lifted; only the billing view tells you which account is paying.

If the refusal is a 429 rather than a 400, you are in quota territory, and four different limits print the same rate-limit line — the presence or absence of retry-after is what separates a wait from a dead end. If the message asks specifically for credits to use the 1M context window, that is a capability gate rather than an empty balance, and the fix is different. If the server explicitly says the limit is not your usage limit, no account change will help at all. And if your 400 is genuinely about the payload — a field path, or a character offset — the request-body parse failure page explains why those are deterministic and this one is not.