# API error codes — Y-API

> 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.

This is the markdown representation of https://y-api.bestvirtualgoods.com/docs/errors. Measured against the live gateway 2026-09-01. Generated by `scripts/generate-seo-assets.mjs` from the same copy the page renders — do not edit by hand.

## 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.

## Status codes

Ordered by how often people actually hit them, not by number.

| HTTP | error.code | What actually happened | SDK retries it | What to do |
| --- | --- | --- | --- | --- |
| 401 | _empty string_ | The key does not exist, was revoked, or the Authorization header is missing altogether. | No — fails immediately | 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. |
| 503 | `model_not_found` | 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. | Yes — twice by default | Check the ID against GET /v1/models, which is the authoritative list. Every ID here is vendor/model, for example deepseek/deepseek-v4-flash. |
| 403 | `insufficient_user_quota` | 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. | No — fails immediately | Top up at https://y-api.bestvirtualgoods.com/app/billing. Credit is restored immediately, existing keys keep working, and no code changes. |
| 403 | `pre_consume_token_quota_failed` | 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. | No — fails immediately | 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. |
| 500 | `invalid_request` | A required field is absent from the request body, usually messages. This is a malformed-request error reported as a server error. | Yes — twice by default | The message names the field verbatim, so read it rather than the status code. Fix the body; retrying is guaranteed to fail again. |
| 400 | `invalid_request_error` | 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. | No — fails immediately | 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. |
| 400 | _empty string_ | The request body is not valid JSON. The upstream doubles its own "Invalid request:" prefix, which is cosmetic. | No — fails immediately | Usually a hand-built payload or shell quoting. Validate the JSON before sending; in a shell, keep the body in single quotes. |

- **401** _empty string_ — Message returned: `Invalid token`. Measured on the live endpoint (probes-errors.mjs: invalid-token / missing-auth).
- **503** `model_not_found` — Message returned: `No available channel for model gpt-4o under group y-api (distributor)`. Measured on the live endpoint (probes-errors.mjs: unknown-model).
- **403** `insufficient_user_quota` — Message returned: `用户额度不足, 剩余额度: ＄0.00`. Confirmed in upstream source, not triggered here (service/billing_session.go:355-359).
- **403** `pre_consume_token_quota_failed` — Message returned: `token quota is not enough, token remain quota: ＄0.000002, need quota: ＄0.000074`. Measured on the live endpoint (probes-errors.mjs: quota-exhausted).
- **500** `invalid_request` — Message returned: `field messages is required`. Measured on the live endpoint (probes-errors.mjs: missing-field).
- **400** `invalid_request_error` — Message returned: `` `messages` is required and must be a non-empty array. ``. Measured on the live endpoint (probes-errors.mjs: unsupported-endpoint).
- **400** _empty string_ — Message returned: `Invalid request: Invalid request: invalid JSON request body`. Measured on the live endpoint (probes-errors.mjs: malformed-json).

## 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.

## 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.

**401, no param field**

```json
{"error":{"code":"","message":"Invalid token (request id: 2026090114474514374…)","type":"new_api_error"}}
```

**404, param present but empty**

```json
{"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.

## Three steps that isolate almost anything

In order. Each one rules out a layer, so the answer is wherever the sequence stops.

### 1. 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.

```bash
curl -s https://api.y-api.bestvirtualgoods.com/v1/models -H "Authorization: Bearer $YAPI_KEY" | head -c 200
```

### 2. Reproduce 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.

```bash
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"}]}'
```

### 3. 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.

```python
client = OpenAI(
    base_url="https://api.y-api.bestvirtualgoods.com/v1",
    api_key=os.environ["YAPI_KEY"],
    max_retries=0,  # debugging only
)
```

## Links

- HTML version of this page: https://y-api.bestvirtualgoods.com/docs/errors
- Site index for agents: https://y-api.bestvirtualgoods.com/llms.txt
- Full reference (single file): https://y-api.bestvirtualgoods.com/llms-full.txt
- OpenAPI 3.1 spec: https://y-api.bestvirtualgoods.com/openapi.json
- Model catalog (JSON, no key needed): https://y-api.bestvirtualgoods.com/models.json
- API base URL: `https://api.y-api.bestvirtualgoods.com/v1`
- Contact: support@bestvirtualgoods.com
- Integration guide: https://y-api.bestvirtualgoods.com/docs
- Service status: https://y-api.bestvirtualgoods.com/status
- Model catalog page (a wrong model ID is the most common 503): https://y-api.bestvirtualgoods.com/models.md
