> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.beatapi.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.beatapi.io/_mcp/server.

# Connection and usage

> Diagnosing base URLs, authentication failures, 404s, timeouts, and requests that never reach BeatAPI.

# Connection and usage

## Which base URL should I use?

`https://api.beatapi.io`. Whether a `/v1` belongs on the end depends on what the client appends for you.

| Client style                           | Base URL                                                     | Because it appends   |
| -------------------------------------- | ------------------------------------------------------------ | -------------------- |
| OpenAI SDK and OpenAI-compatible tools | `https://api.beatapi.io/v1`                                  | `/chat/completions`  |
| Anthropic SDK and Claude-native tools  | `https://api.beatapi.io`                                     | `/v1/messages`       |
| Gemini SDK and Gemini-native tools     | `https://api.beatapi.io`                                     | `/v1beta/models/...` |
| Raw HTTP                               | Full path, e.g. `https://api.beatapi.io/v1/chat/completions` | nothing              |

Getting this wrong produces a `404` on every request, which most clients report as "cannot connect" rather than as a bad URL.

## Everything returns 404

In order of likelihood:

1. **A doubled or missing `/v1`.** See the table above. `/v1/v1/chat/completions` does not exist.
2. **A trailing slash** on the base URL, which some clients concatenate literally.
3. **An endpoint that is not part of the published surface.** BeatAPI publishes a deliberate list — text, tasks, files, usage, webhooks, realtime. Endpoints outside it return `404` rather than an error, on purpose. `/v1/embeddings`, `/v1/rerank`, and `/v1/audio/*` are not served.
4. **A model ID that does not exist.** IDs are case-sensitive and carry no vendor prefix — `claude-fable-5-1`, not `anthropic/claude-fable-5-1`.

## 401 missing_api_key or invalid_api_key

`missing_api_key` means no key reached BeatAPI. `invalid_api_key` means a key arrived but is wrong, expired, disabled, or exhausted; the message says which. Older clients may still show the legacy name `unauthorized` for either.

* The header is `Authorization: Bearer <key>`. A missing `Bearer ` prefix reads as a malformed key.
* Whitespace pasted around the key counts as part of the key.
* Confirm the key is still active in [Dashboard → API Keys](https://beatapi.io/dashboard/apikeys). A revoked key fails immediately and permanently.
* Some clients send an `x-api-key` or `x-goog-api-key` header instead; that is fine — all of them identify the same account and the same balance.

## 403 forbidden

Authentication succeeded but the operation is not allowed for this account or this key. Check the key's own restrictions — its IP allowlist and the group of models it may call — and the account's status before looking at the request. On an account that has only free credit, models outside the free list answer `403 forbidden` with a message saying so; a top-up unlocks them.

A `403` from BeatAPI is always BeatAPI's own decision; a model's answer is never passed through as a `403`. (A `403` whose body is `error code: 1010` came from the network edge instead: set an explicit `User-Agent`.) A prompt refused by content moderation is `400 content_policy_violation` at create, or `content_policy_violation` in `error_code` on an accepted task — see [Errors](/errors).

## 402 insufficient_credits

The balance is zero or below the price of the request. Every paid operation on the account fails with `402` until credits are added — this is not per-key. Insufficient balance is always `402`, on every endpoint. Worth alerting on if anything you run is user-facing.

## 429 rate_limit_exceeded

Two different limits return a `429`, and they need opposite responses:

* **Request rate** — too many requests per minute for this key. Honour `Retry-After` or `retry_after_seconds`. The allowance rises with lifetime top-ups, except on free models, which have their own fixed limit: 1 successful request per minute before your first top-up, up to 10 per minute after any top-up, counted separately from the paid allowance.

A polling loop is the usual cause of the first. The task endpoint allows up to 120 requests per minute per key, which a fixed one-second poll across a few tasks will exhaust on its own — see [Development Guide](/development).

## A request times out

Text calls with `stream: false` return nothing until the model has finished, and a long reasoning response can take a while. Two things help:

* **Stream.** With `stream: true` the first bytes arrive quickly and the connection stays active throughout.
* **Raise the client timeout.** Many SDKs default to 30 or 60 seconds, which is short for a long non-streaming completion.

## A task never finishes

Check the status rather than the clock. `GET /v1/tasks/{task_id}` returns one of a fixed set of states, and two of them are not "still working":

* `storyboard_ready` and `requires_action` on Music Video tasks are waiting **for you**, not for the model. Polling them forever will not advance them.

Everything else is either terminal (`succeeded`, `failed`) or genuinely in progress. See the [Quick Guide](/quick-guide) for the full table.

## Connections fail from a server but work from my laptop

Test from the machine that actually makes the call. Self-hosted platforms — Dify, AnythingLLM in Docker, CI runners — have their own egress rules. Confirm the container or host can reach `api.beatapi.io`, and allow it through any corporate proxy.

## What do I include in a bug report?

The `request_id` from the failing response. Every BeatAPI error carries one, and it identifies the exact call in the logs. Add the model ID, the endpoint, and the approximate time. Open the ticket from the [dashboard](https://beatapi.io/dashboard).