# API reference

What Saylek's API surface actually accepts, and what it does not. For getting started, use
[Connect your app](/docs/connect-your-app) (local) or [Use your Circle from
anywhere](/docs/connect-openai) (hosted).

## Base URLs

| Surface | Base URL | Auth |
|---|---|---|
| Local daemon | `http://127.0.0.1:8443/v1` | None. Pass any non-empty string if your client insists. |
| Hosted, OpenAI dialect | `https://api.saylek.com/v1` | `Authorization: Bearer <key>` |
| Hosted, Anthropic dialect | `https://api.saylek.com` | `x-api-key` or `Authorization: Bearer`, plus `anthropic-version` |

The Anthropic base URL carries **no** `/v1`, because Anthropic clients append
`/v1/messages` themselves. The OpenAI base URL **does**. Getting this backwards is the most
common cause of a 404.

## Endpoints

The daemon serves the following OpenAI-dialect routes:

| Endpoint | Purpose |
|---|---|
| `GET /v1/models` | What **this daemon** can serve: models discovered on this machine, plus any upstreams you configured. It is **not** the list of what your Circle can reach. For that, run `saylek models --circle`. |
| `POST /v1/chat/completions` | Chat completions. The main path, and the one to build on. |
| `POST /v1/embeddings` | Embeddings. |
| `POST /v1/audio/transcriptions` | Audio transcription. |
| `POST /v1/responses` | Responses-style calls, but **only against a proxy upstream you have configured**. A local model or a Circle Host returns `model_not_found` here. Use the chat path instead. |
| `POST /v1/completions` | **Not implemented.** The route exists and answers 501. It is listed so you do not spend an afternoon wondering why the legacy path 404s differently than you expected. Use the chat path. |

The hosted Anthropic surface adds `POST /v1/messages`, covered on [Claude Code and the
Anthropic SDK](/docs/connect-anthropic).

## Streaming

`stream: true` is supported on the chat path and emits standard OpenAI SSE frames, so a
client that already consumes OpenAI streaming works without changes.

## Model ids are verbatim

Saylek carries the model id your Pool advertises, exactly as advertised. There is **no**
aliasing layer: `gpt-4o`, `claude-3-5-sonnet` and similar names from other providers do not
resolve, even through the Anthropic dialect. Always discover first:

```bash
curl https://api.saylek.com/v1/models -H "Authorization: Bearer YOUR_API_KEY"
```

## Rate limiting and availability

**There is a rate limiter, and it is on by default.** Your local daemon ships with a limit of
**100 requests per second** with a **burst allowance of 200**, and it answers **HTTP 429**
when you exceed it. It is a spike absorber rather than a quota: you are unlikely to meet it
by hand, and quite likely to meet it with a parallel batch job. Handle 429 and back off. The
values live under `[ratelimit]` in `~/.saylek/config.toml` if you need to change them on
your own machine.

What does **not** exist yet is a published **quota or token ceiling** on the hosted surface.
Rather than print a number we have not committed to, we are leaving that unstated until it
is real.

Availability is the failure mode that will actually bite you, because capacity comes from
members' machines:

- A request for a model **no online Host serves** fails, rather than queueing indefinitely.
- Locally, a request for a model with **nothing loaded** returns `model_not_found`.
- A daemon built with no inference backend returns HTTP 503 `no inference backend compiled
  in`.

See [Troubleshooting](/docs/troubleshooting) for the error shapes.

## Limits and status codes a client has to handle

| | |
|---|---|
| **Request body** | **32 MiB** on the chat, embeddings and responses paths. Larger bodies are rejected at the edge with **413**. Audio transcription is bounded separately by the upload itself. |
| **Rate** | 100 requests/second, burst 200. Over that is **429**. |
| **Retry-After** | Sent on **429** and on the **503** you get when the pool is full. Honour it rather than retrying immediately; it is a short hint, because a slot usually frees quickly. |

The status codes worth branching on:

| Code | What it means | What to do |
|---|---|---|
| `400` | The request is malformed. | Fix the request. Retrying will not help. |
| `401` | Key missing, wrong, or revoked. On the **hosted** surface only; a local daemon does not check keys. | Check the key. |
| `404` | Usually the base URL, not the model. See the note at the top of this page. | Check whether your base URL should include `/v1`. |
| `413` | Body over 32 MiB. | Send less. |
| `429` | Rate limited. | Back off, honour `Retry-After`. |
| `501` | The route exists but is not implemented, for example `/v1/completions`. | Use the chat path. |
| `503` | No capacity: pool full, or no inference backend compiled in. | Retry with `Retry-After`, or check that a Host is online. |

A model your Circle cannot serve comes back as `model_not_found` rather than as a
transport error, so branch on the error body and not only on the status.

## Keys

Member API keys are created at `/account/keys`. A key's secret is shown once, at creation.
Keys are member-scoped, so a key reaches every Circle you belong to in its region.

Revoke a device's Circle credential with `saylek circles revoke`; that stops it drawing on
the pool.

## Where requests run

Every hosted call, and every local call your own machine cannot serve, runs on a
Circle-mate's GPU. Build with that in mind. [Privacy and
egress](/docs/privacy-and-egress) is the page to read before sending anything sensitive
through an integration.

## Next steps

- [Use your Circle from anywhere](/docs/connect-openai): hosted setup, end to end.
- [Troubleshooting](/docs/troubleshooting): 401s, 404s, and rejected model ids.
- [Privacy and egress](/docs/privacy-and-egress): what leaves the machine.
