API error codes
Every failure this gateway returns, measured against the live endpoint on 2026-09-01. Of the 7 failures it returns, 5 do not mean what their status code implies, and 2 of those get retried by the official SDKs before your program ever sees them.
Verified 2026-09-01
Status code misleads
Read error.code, not the status code
A wrong model name comes back as 503. A missing messages field comes back as 500. Both are permanent mistakes in your request — no amount of retrying fixes either — but openai-python, openai-node and the Anthropic SDKs all retry 408, 409, 429 and every 5xx twice by default, with exponential backoff. So one typo becomes three round trips and tens of seconds of waiting before the real reason surfaces. The body always carries the real reason in error.code; the status code is the part that lies.
Reference
Status codes
Ordered by how often people actually hit them, not by number.
- HTTP 401
empty string- What actually happened
- The key does not exist, was revoked, or the Authorization header is missing altogether.
- What to do
- Confirm the key still exists on the API Keys page in the console, or create a new one. One thing this is not: a missing "Bearer " prefix — the gateway accepts the bare key too, so that is never the cause.
- SDK retries it
- No — fails immediately
- Measured on the live endpoint
- probes-errors.mjs: invalid-token / missing-auth
Message returned Invalid token - HTTP 503
model_not_foundStatus code misleads- What actually happened
- The model ID is not in this catalog — almost always copied from another provider’s docs (gpt-4o, claude-sonnet-4-5). Nothing is down; there is simply no route for a model that does not exist here.
- What to do
- Check the ID against GET /v1/models, which is the authoritative list. Every ID here is vendor/model, for example deepseek/deepseek-v4-flash.
- SDK retries it
- Yes — twice by default
- Measured on the live endpoint
- probes-errors.mjs: unknown-model
Message returned No available channel for model gpt-4o under group y-api (distributor) - HTTP 403
insufficient_user_quotaStatus code misleads- What actually happened
- The account balance reached zero. The message is in Chinese and quotes the remaining balance with a fullwidth $, which is why searching for the English text finds nothing.
- What to do
- Top up at https://y-api.bestvirtualgoods.com/app/billing. Credit is restored immediately, existing keys keep working, and no code changes.
- SDK retries it
- No — fails immediately
- Confirmed in upstream source, not triggered here
- service/billing_session.go:355-359
Message returned 用户额度不足, 剩余额度: $0.00 - HTTP 403
pre_consume_token_quota_failedStatus code misleads- What actually happened
- The account still has credit, but this one key hit the per-key cap set when it was created. The message quotes both the key’s remaining quota and the amount this request needed.
- What to do
- Raise or remove that key’s limit on the API Keys page, or switch to a key without a cap. Topping up the account does not help — the cap is per key.
- SDK retries it
- No — fails immediately
- Measured on the live endpoint
- probes-errors.mjs: quota-exhausted
Message returned token quota is not enough, token remain quota: $0.000002, need quota: $0.000074 - HTTP 500
invalid_requestStatus code misleads- What actually happened
- A required field is absent from the request body, usually messages. This is a malformed-request error reported as a server error.
- What to do
- The message names the field verbatim, so read it rather than the status code. Fix the body; retrying is guaranteed to fail again.
- SDK retries it
- Yes — twice by default
- Measured on the live endpoint
- probes-errors.mjs: missing-field
Message returned field messages is required - HTTP 400
invalid_request_errorStatus code misleads- What actually happened
- The endpoint does not exist here — /v1/embeddings, /v1/completions and /v1/images/generations all land in this case. The body then claims messages is required, which points at something entirely unrelated to the real problem.
- What to do
- This gateway serves chat completions and the Anthropic messages endpoint. If a framework calls an embeddings endpoint under the hood, that part needs a different provider.
- SDK retries it
- No — fails immediately
- Measured on the live endpoint
- probes-errors.mjs: unsupported-endpoint
Message returned `messages` is required and must be a non-empty array. - HTTP 400
empty string- What actually happened
- The request body is not valid JSON. The upstream doubles its own "Invalid request:" prefix, which is cosmetic.
- What to do
- Usually a hand-built payload or shell quoting. Validate the JSON before sending; in a shell, keep the body in single quotes.
- SDK retries it
- No — fails immediately
- Measured on the live endpoint
- probes-errors.mjs: malformed-json
Message returned Invalid request: Invalid request: invalid JSON request body
Silent failures
Worse than an error: HTTP 200
Three failures return 200, raise no exception, and let your program carry on. What is wrong is the content or the bill — which is exactly why they are hard to find on your own.
Images are accepted, then ignored
Five models carry an image_ratio flag upstream. That flag is a billing coefficient, not a vision capability. All five accept a data-URL image, return 200, and answer confidently about a picture they never saw — probed with a solid green PNG, none of them named the colour. The recommended model rejects images with a 400 instead, which is the safer of the two failures.
From the client compatibility matrix
max_completion_tokens caps the text, not the bill
Capped at 12 tokens, moonshotai/kimi-k2.5 billed 261 — 21.8× the cap. Truncation itself works (finish_reason=length); the overage is reasoning tokens, which are billed but never appear in content. Any cost estimate derived from the cap will be low by that factor.
From the client compatibility matrix
Anthropic message ids have no msg_ prefix
POST /v1/messages returns a bare hex id. Code that asserts on the msg_ prefix, or parses the id to route a response, will not match — even though the rest of the payload is compliant.
From the client compatibility matrix
Parsing
What an error body looks like
The wrapper is always {"error": {...}}, but the field set is not stable: some errors carry param, others omit it, and code is frequently an empty string. Parse defensively — treat every field as optional.
{"error":{"code":"","message":"Invalid token (request id: 2026090114474514374…)","type":"new_api_error"}}{"error":{"message":"Invalid URL (POST /v1/fine_tuning/jobs)","type":"invalid_request_error","param":"","code":""}}Every message ends with a request id, and it differs on every call. Strip it before comparing messages against this page — and include it when you report a problem, because it is what identifies your exact request in the logs.
Triage
Three steps that isolate almost anything
In order. Each one rules out a layer, so the answer is wherever the sequence stops.
Check the key and the route first
GET /v1/models costs nothing and uses the same key. A 200 with a model list proves the key, the network and the CDN edge are all fine — which means the problem is in your request body, not your credentials.
curl -s https://api.y-api.bestvirtualgoods.com/v1/models -H "Authorization: Bearer $YAPI_KEY" | head -c 200Reproduce it with curl
This takes the SDK out of the picture. If curl succeeds where your code fails, the difference is in how the library builds the request — not in the gateway.
curl -i https://api.y-api.bestvirtualgoods.com/v1/chat/completions \ -H "Authorization: Bearer $YAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"deepseek/deepseek-v4-flash","messages":[{"role":"user","content":"hi"}]}'Turn retries off while debugging
With retries disabled, a 500 or 503 caused by your own request shows up on the first attempt instead of after two pointless ones. Put it back afterwards — retries are worth having against real transient failures.
client = OpenAI( base_url="https://api.y-api.bestvirtualgoods.com/v1", api_key=os.environ["YAPI_KEY"], max_retries=0, # debugging only )
Not one of these?
The status page shows whether the gateway itself is degraded, with the last 30 days of checks. If it is green and you are still stuck, the request id from the error body is the one thing worth sending us.