402 insufficient_balance and "your credit balance is too low"
A 402 means authentication succeeded and the request was well-formed — you simply cannot pay for it. Anthropic phrases it as Your credit balance is too low to access the Claude API; OpenAI returns insufficient_quota with You exceeded your current quota; go2llm returns status 402 with the code insufficient_balance and the message not enough spendable balance.
The distinction that matters: this is not a rate limit and not a plan restriction. Nothing about the request needs to change. Money does.
Written 2026-09-12.
What it looks like
{
"error": {
"type": "insufficient_balance",
"code": "insufficient_balance",
"message": "not enough spendable balance",
"request_id": "gw_4f1c8a2e9b7d40c3a15e6f2b8c9d0e1a"
}
}
Anthropic, for the same condition:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "Your credit balance is too low to access the Claude API. Please go to Plans & Billing to upgrade or purchase credits."
}
}
Note that Anthropic reports it as invalid_request_error with status 400, not 402. If you branch on status codes alone, a billing failure will land in your "bad request" path and you will debug the wrong thing.
Why it fires before the model runs
The surprising part for most people is that a 402 can arrive when the balance is clearly positive. That is because cost is reserved up front, not charged afterwards.
go2llm estimates the maximum cost of a request before it calls the provider, using the input you sent plus the max_tokens you asked for at the output rate. That amount is held. After the response comes back, the actual cost is settled and the unused remainder of the hold is released.
The consequence is direct: max_tokens decides whether your request is affordable, not the response you actually get. Asking for max_tokens: 64000 on an expensive model reserves the price of 64000 output tokens even if the model would have replied with forty. Drop max_tokens to something realistic and the same balance suddenly suffices.
This is also why a balance never goes negative and why a failed request costs nothing: the hold is released, and the real number arrives in usage.cost_usd on success.
Fixing it
Check the balance and the price before blaming the code. The public catalog needs no key:
curl -s https://go2llm.tech/api/pricing | head -c 400
claude-sonnet-5 is $0.33 per 1M input tokens and $1.64 per 1M output; deepseek-v4-flash is $0.023 and $0.068. A dollar goes a long way at the bottom of that table and evaporates quickly at the top — a 402 on a 100-token prompt usually means the balance is genuinely near zero, not that the estimate was wrong.
Lower max_tokens to what you actually expect. This is the single most effective fix, and it costs nothing:
# Before: reserves the price of 64k output tokens
{"model": "claude-opus-5", "max_tokens": 64000, "messages": [...]}
# After: reserves the price of 2k, which is all this task needs
{"model": "claude-opus-5", "max_tokens": 2000, "messages": [...]}
Top up. The minimum top-up on go2llm is $1, paid by card through Platega or in crypto through NOWPayments. Registration credits $1 to the balance, which is enough to make a first request without paying anything.
Move the cheap work to a cheap model. Classification, extraction and routing do not need a flagship. At $0.023 per 1M input, deepseek-v4-flash costs about a fourteenth of claude-sonnet-5 and roughly a ninetieth of what the official flagship rate would be.
Handling it correctly in code
Never retry a 402 on a loop. The balance will not change between attempts, and you will burn your 20 RPM limit discovering that. Fail loudly and tell the operator what is wrong:
import httpx
def call(payload, api_key):
r = httpx.post(
"https://go2llm.tech/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json=payload,
timeout=120.0,
)
if r.status_code == 402:
err = r.json().get("error", {})
raise SystemExit(
"Out of balance. Top up at https://go2llm.tech/ or lower max_tokens "
f"(currently {payload.get('max_tokens')}). "
f"request_id={err.get('request_id')}"
)
r.raise_for_status()
return r.json()
For batch jobs, the failure mode worth designing against is dying halfway: three hundred documents processed, four hundred not, and no clean way to tell which. Track spend as you go and stop on your own terms, because usage.cost_usd comes back on every successful call:
budget_usd, spent = 5.00, 0.0
for doc in documents:
if spent >= budget_usd:
print(f"budget reached after {processed} docs; {len(documents) - processed} left")
break
result = call(build_payload(doc), api_key)
spent += result["usage"]["cost_usd"]
A per-key dollar quota does the same job from the server side: issue one key per job with the quota it is allowed to spend, and an overrun becomes a contained 429 quota on that key rather than an empty account.
Related errors
- 429 rate limit and quota — the key is funded but you are calling too fast.
- 401 authentication_error — the key itself is not accepted.
- The full error reference.
FAQ
My balance is $0.40 and the request would cost $0.01. Why 402? Because the reservation is based on max_tokens at the output rate, not on the reply you expect. Lower max_tokens and retry once.
Does a failed request cost money? No. The hold is released and the balance is untouched. Only successful requests settle, and the exact figure is in usage.cost_usd.
Anthropic returns 400 for this, go2llm returns 402. Which is right? Both are in use in the wild, which is exactly why you should branch on error.code rather than on the HTTP status when writing a handler that talks to more than one API.
Get a key and make your first request in 3 minutes → go2llm.tech. Registration credits $1; the minimum top-up is $1. Per-model prices are on the pricing page.