> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bettertoken.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# BetterToken API errors, status codes, and rate limits

> Diagnose BetterToken API errors using HTTP status, response body, and request ID. Find fixes for authentication, rate limits, timeouts, and retry handling.

Diagnose a BetterToken API error from its HTTP status and response body together. Record the request ID from `error.message`, fix authentication or configuration errors before resending, and retry only failures identified as temporary.

## Before troubleshooting

Have your Model ID, Base URL, and failed response available. Check the Key status and current balance in the [BetterToken Dashboard](https://bettertoken.ai/workspace). If you have not completed a model request yet, start with the [SDK examples](/en/guides/sdk-examples.md) or your [tool setup guide](/en/ai-tools/overview.md).

## Read the error response

BetterToken returns API errors in a JSON envelope. For example, a request without a Key can return:

```json theme={null}
{
  "error": {
    "code": "",
    "message": "Token not provided (request id: …)",
    "type": "new_api_error"
  }
}
```

Read `error.message` even when `error.code` is empty. The request ID in that message helps identify the failed attempt. A request to `/v1/models` without a Key returns `401`; it does not test whether your model can generate a response.

Record these details before contacting support:

| Detail | What to record |
| - | - |
| HTTP status | The response status, or the client's timeout/network exception if no HTTP response arrived |
| Response body | The error JSON or other returned body, with secrets removed |
| Request ID | The identifier in `error.message`, when present |
| Time | Date, time, and timezone of the attempt |
| Model ID | The exact value sent as `model` |
| Base URL | The configured Base URL and the final request path |

Keep your BetterToken API Key out of screenshots, support messages, and shared logs.

## Check the HTTP status

The table uses general HTTP/API meanings and the linked setup guidance. It is not a list of BetterToken-specific error codes; read the actual response before choosing a fix.

| Status | Common meaning | Check first | Retry? |
| - | - | - | - |
| `400` | Invalid request, field, or parameter combination | JSON shape, required fields, model, and endpoint contract | Correct the request first |
| `401` | Missing, invalid, expired, or revoked credential | Key status, whitespace, and the authentication header for the protocol | Correct authentication first |
| `403` | Request refused | Response message, account balance, Key status, and client configuration | Resolve the refusal first |
| `404` | Route or requested resource not found | Base URL, duplicated/missing `/v1`, final path, and exact Model ID | Correct the URL or resource first |
| `408` / client timeout | Server did not receive a complete request in time, or your client stopped waiting | Whether an HTTP status was received, timeout settings, network, and Dashboard request status | Check whether the request completed before deciding |
| `409` / `422`, if returned | Generally a conflict or content that cannot be processed | Response details and the submitted state or fields | Resolve the cause first |
| `429` | A limit was reached | Response body and any retry headers; use the [limit diagnosis FAQ](/en/faq/codex/usage-limit-vs-api-rate-limit.md) | Only if identified as a temporary rate limit |
| `500` | Server-side failure | Request record, message, and whether failure persists | Bounded retry for a temporary failure |
| `502` / `503` | Gateway/upstream failure or temporary unavailability | Response body and any `Retry-After` header | Bounded retry for a temporary failure |
| `504` | Gateway timed out waiting for an upstream response | Dashboard record and whether the request completed | Check completion before retrying |

For protocol and suffix mistakes, see the [Base URL FAQ](/en/faq/model-calling/base-url-config.md). For cache questions, use the [cache billing FAQ](/en/faq/service-assurance/cache-billing.md).

## Retry temporary failures

Use bounded exponential backoff with jitter after identifying a temporary `429` or server failure. Honor `Retry-After` when returned; if it exceeds your retry budget, stop and wait outside the loop. SDK retries and your own loop share one attempt budget, so disable SDK retries when implementing your own policy.

Do not retry unchanged `400`, `401`, `403`, balance, or configuration failures. A client timeout or broken stream does not prove the request failed before processing. Check the **Dashboard** first, especially for image generation or a workflow that can repeat an action.

The following examples make a non-streaming Chat Completions request with at most four attempts. They retry HTTP `500`, `502`, and `503`. They leave `429` retries off until you confirm the response is a temporary rate limit, and stop on timeouts, network exceptions, and `504` so you can check completion. The maximum retry wait is 30 seconds per attempt.

### Prepare the request

Copy a current Model ID from the [model plaza](https://bettertoken.ai/pricing) and replace both placeholders. Use a text model available through [Chat Completions](/en/api-reference/chat-completions.md).

```bash theme={null}
export BETTERTOKEN_API_KEY="YOUR_API_KEY"
export BETTERTOKEN_MODEL_ID="YOUR_MODEL_ID"
```

The Python example uses the standard library. Save the JavaScript example as an `.mjs` file and run it with a Node.js version that provides `fetch` and `AbortSignal.timeout`.

**Python**

```python theme={null}
import json
import os
import random
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

def retry_after_seconds(value):
    try:
        return max(0.0, float(value))
    except ValueError:
        return max(0.0, (
            parsedate_to_datetime(value) - datetime.now(timezone.utc)
        ).total_seconds())

retry_temporary_429 = False  # Enable only after diagnosing the returned limit.
payload = json.dumps({
    "model": os.environ["BETTERTOKEN_MODEL_ID"],
    "messages": [{"role": "user", "content": "Reply with one word: OK"}],
}).encode()
for attempt in range(4):
    request = urllib.request.Request(
        "https://bettertoken.ai/v1/chat/completions", data=payload,
        headers={
            "Authorization": "Bearer " + os.environ["BETTERTOKEN_API_KEY"],
            "Content-Type": "application/json",
        },
    )
    try:
        with urllib.request.urlopen(request, timeout=120) as response:
            print(json.load(response)["choices"][0]["message"]["content"])
        break
    except urllib.error.HTTPError as error:
        body = error.read().decode("utf-8", errors="replace")
        retryable = error.code in (500, 502, 503) or (
            error.code == 429 and retry_temporary_429
        )
        if not retryable or attempt == 3:
            raise RuntimeError(f"HTTP {error.code}: {body}") from error
        delay = random.uniform(0, min(8.0, 2.0 ** attempt))
        retry_after = error.headers.get("Retry-After")
        if retry_after:
            delay = max(delay, retry_after_seconds(retry_after))
        if delay > 30:
            raise RuntimeError("Retry-After exceeds this retry budget") from error
        time.sleep(delay)
```

**JavaScript**

```javascript theme={null}
const retryTemporary429 = false; // Enable only after diagnosing the returned limit.
const payload = {
  model: process.env.BETTERTOKEN_MODEL_ID,
  messages: [{ role: "user", content: "Reply with one word: OK" }],
};
for (let attempt = 0; attempt < 4; attempt++) {
  const response = await fetch("https://bettertoken.ai/v1/chat/completions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BETTERTOKEN_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(120000),
  });
  const body = await response.text();
  if (response.ok) {
    console.log(JSON.parse(body).choices[0].message.content);
    break;
  }
  const retryable = [500, 502, 503].includes(response.status) ||
    (response.status === 429 && retryTemporary429);
  if (!retryable || attempt === 3) {
    throw new Error(`HTTP ${response.status}: ${body}`);
  }
  let delay = Math.random() * Math.min(8000, 1000 * 2 ** attempt);
  const retryAfter = response.headers.get("Retry-After");
  if (retryAfter !== null) {
    const seconds = Number(retryAfter);
    const wait = Number.isFinite(seconds)
      ? seconds * 1000 : Date.parse(retryAfter) - Date.now();
    if (!Number.isFinite(wait)) throw new Error("Invalid Retry-After header");
    delay = Math.max(delay, wait, 0);
  }
  if (delay > 30000) throw new Error("Retry-After exceeds this retry budget");
  await new Promise(resolve => setTimeout(resolve, delay));
}
```

If the response body identifies a lasting configuration, balance, or access problem, stop the loop and fix it even if the status alone looks retryable. Adapt this short text example to your application's completion checks before using it for other operations.

## Find the guide for your symptom

| Symptom | Go to |
| - | - |
| Claude Code authentication, URL, or model error | [Claude Code common errors](/en/ai-tools/claude-code.md#common-errors) |
| Codex asks for sign-in or ignores provider changes | [Codex CLI / App common errors](/en/ai-tools/codex.md#common-errors) |
| Cursor Key cannot be enabled or built-in models stop working | [Cursor common errors](/en/ai-tools/cursor.md#common-errors) |
| Cline authentication, `404`, or model error | [Cline common errors](/en/ai-tools/cline.md#common-errors) |
| OpenCode provider, model, or project override error | [OpenCode common errors](/en/ai-tools/opencode.md#common-errors) |
| Claude Desktop Gateway cannot connect | [Claude Desktop common errors](/en/faq/claude-desktop-bettertoken-api.md#common-errors) |
| Official Codex usage warning or API `429` | [Usage limit vs API rate limit](/en/faq/codex/usage-limit-vs-api-rate-limit.md) |
| Changes appear to have no effect | [Clear conflicting environment variables](/en/best-practices.md#clear-conflicting-environment-variables) |
| Truncated output or an output-limit error | [Output token limits](/en/faq/model-calling/max-tokens.md) |

## Confirm the fix

Send one short, non-critical request through the same client, Base URL, and Model ID that failed. Confirm a normal response, then find the matching record in the **Dashboard** and check the model, status, Token usage, and charge before resuming a larger workflow.

## Common questions

### Does every 429 mean I should wait and retry?

Use the [usage-limit diagnosis FAQ](/en/faq/codex/usage-limit-vs-api-rate-limit.md) to decide what to check and whether retrying applies to your response.

### Should I increase the timeout after one failure?

First check whether your client, a proxy, or an upstream request stopped waiting. Use the [timeout guidance](/en/best-practices.md#set-a-generous-timeout), then verify with a small request.

## Related

* [SDK request examples](/en/guides/sdk-examples.md)
* [Streaming and tool calling](/en/guides/streaming-and-tool-calling.md)
* [API reference](/en/api-reference/introduction.md)
* [Supported tools](/en/ai-tools/overview.md)
* [Cache billing FAQ](/en/faq/service-assurance/cache-billing.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.