# API

> Use Estelle's OpenAI-compatible chat completions endpoint from scripts and services, with bearer authentication, repository scoping, and explicit errors.

The HTTP API lets an OpenAI-compatible client ask Estelle a question without using MCP. It is a good fit for scripts, backend services, and tools that already use the chat completions format and need access to your indexed repositories and memory.

Use the [MCP server](https://docs.fatelabs.ca/mcp) instead when an agent needs discrete navigation, session, monitoring, or job tools.

## Connection values

```toml
base_url = "https://api.fatelabs.ca/v1"
model    = "estelle"
api_key  = "estelle_live_..."
```

Create a key in **Settings → API keys**. It is shown once. Store it on your server in an environment variable or secret manager, never in source control or browser code. Anyone with the key can use your account's data and credits.

| Endpoint | Purpose |
|---|---|
| `POST /v1/chat/completions` | Send a conversation and receive a completed answer. |
| `GET /v1/models` | Return the available model id, `estelle`. |

## Make a request

Send `Authorization: Bearer <key>` and a JSON body containing `model` and `messages`:

```bash
curl https://api.fatelabs.ca/v1/chat/completions \
  -H "Authorization: Bearer $ESTELLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "estelle",
    "messages": [
      { "role": "user", "content": "What changed in auth last week?" }
    ]
  }'
```

With the OpenAI Python SDK:

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.fatelabs.ca/v1",
    api_key=os.environ["ESTELLE_API_KEY"],
)

response = client.chat.completions.create(
    model="estelle",
    messages=[{"role": "user", "content": "Why does /refresh return 401 after deploy?"}],
)
print(response.choices[0].message.content)
```

### Request fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `model` | string | Yes | Use `estelle`. |
| `messages` | array | Yes | Non-empty conversation messages as objects with `role` and `content`. The final message supplies the new turn. |
| `response_format` | object | No | Only `{"type": "text"}` is accepted. JSON modes are refused. |

Send a non-streaming request and read the answer from `choices[0].message.content`.

A successful response has an OpenAI-compatible shape and can include Estelle-specific fields:

```json
{
  "id": "estelle-cmpl",
  "object": "chat.completion",
  "model": "estelle",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "..." },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 20,
    "completion_tokens": 80,
    "total_tokens": 100
  },
  "credits_used": 1
}
```

The token counts in `usage` are estimates. `credits_used` reports the Estelle Credits charged to the request; the value depends on the work performed and is not always the example value above.

### Scope the question to one repository

On a multi-repository account, add the repository as `owner/name`:

```bash
-H "X-Estelle-Repo: your-org/api"
```

This header selects the repository context for the request. Omit it to use the account's normal repository-selection behavior.

### Attach files

You can include OpenAI `file` content blocks in a message. Each file read uses one Estelle Credit. A deployment that cannot read attachments refuses the request rather than pretending the files were inspected.

### Structured output and streaming

This endpoint returns plain-text answers. It does not stream responses, and it does not guarantee JSON output.

- `{"type": "text"}` is accepted.
- `{"type": "json_object"}` and `{"type": "json_schema"}` return `422`.
- Other malformed or unsupported `response_format` values return `400`.

For a strict JSON contract, validate or transform the answer in your application, or use a provider endpoint that supports structured output.

## Check connectivity and authentication

```bash
curl https://api.fatelabs.ca/v1/models
```

```json
{ "object": "list", "data": [{ "id": "estelle", "object": "model" }] }
```

`GET /v1/models` returns this response with or without a key. It proves that the base URL is reachable, not that authentication works. To test a key, make a short chat completion and check for `200` rather than `401`.

## Errors and retries

Errors use an OpenAI-style object:

```json
{ "error": { "message": "...", "code": 429, "wait_s": 3.0 } }
```

| Status | Meaning | Next step |
|---|---|---|
| `400` | The body or a request option is malformed. | Fix the field named in `error.message`. |
| `401` | The key is missing, malformed, or revoked. | Check the bearer header and create or select a valid key. |
| `402` | The account cannot fund the request with its current credits or plan allowance. | Add credits, wait for the applicable usage window, or change plan. |
| `422` | The requested content or option is unsupported, such as structured JSON output. | Change the request. |
| `429` | A request-rate or in-flight limit was reached. | Retry only after the advertised wait, or reduce request volume. |
| `5xx` | The request failed before a usable answer was produced. | Retry with backoff; [contact us](https://www.fatelabs.ca/contact) if it continues. |

A retryable `429` includes `error.wait_s` and the same delay in the `Retry-After` header. Honour that delay. An error without `wait_s` does not instruct you to repeat the identical request immediately.

`401` and `402` are deliberately different: `401` means fix the credential; `402` means the credential was accepted but the account needs funding or allowance.

## Related

- [Keys and models](https://docs.fatelabs.ca/keys-and-models): Create keys and configure model access.
- [MCP server](https://docs.fatelabs.ca/mcp): Give an agent task-specific Estelle tools.
- [Plans and credits](https://docs.fatelabs.ca/plans): Understand usage and funding refusals.
