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

# HTTP API

> The control-plane HTTP endpoints, their authentication requirements, and their purpose.

The control plane exposes a JSON API used by its own interface. It is documented
here for monitoring, automation, and debugging.

<Note>
  This API is designed for the application's own browser client and is not
  versioned for external consumers. Endpoint shapes can change between releases.
  For automation, prefer `/api/health` and `/api/setup`, which are stable.
</Note>

## Authentication

Most endpoints require a session cookie issued at login and signed with
`BETTER_AUTH_SECRET`. Requests are authorized per record: handlers verify that
the authenticated user owns the object being accessed.

| Access level | Meaning                                          |
| ------------ | ------------------------------------------------ |
| Public       | No authentication                                |
| Session      | Any signed-in user                               |
| Admin        | Signed-in administrator                          |
| Workspace    | Workspace bearer identity, not a browser session |

## Public endpoints

<ResponseField name="GET /api/health" type="Public">
  Liveness and readiness probe. Executes `select 1` against PostgreSQL.

  Returns `200 {"ok":true}` when healthy and `503 {"ok":false}` when the
  database is unreachable.

  ```bash theme={null}
  curl -fsS https://chat.example.com/api/health
  ```
</ResponseField>

<ResponseField name="GET /api/setup" type="Public">
  Reports installation setup state.

  ```json theme={null}
  { "required": false, "claimRequired": true }
  ```

  `required` is true only when setup has not completed and no users exist.
</ResponseField>

<ResponseField name="POST /api/setup" type="Public, one-time">
  Creates the first administrator in a single transaction and permanently closes
  public setup.

  <Expandable title="body">
    <ResponseField name="name" type="string" required>
      2 to 100 characters.
    </ResponseField>

    <ResponseField name="email" type="string" required>
      Valid email, lowercased on save.
    </ResponseField>

    <ResponseField name="password" type="string" required>
      12 to 128 characters, containing a letter and a digit.
    </ResponseField>

    <ResponseField name="claimToken" type="string">
      Required when `SETUP_CLAIM_TOKEN` is configured. May also be sent as the
      `x-setup-claim-token` header.
    </ResponseField>
  </Expandable>

  Returns `403` when the claim token does not match.
</ResponseField>

<ResponseField name="GET /api/public-config" type="Public">
  Branding, setup state, and SSO button configuration needed before login.
  Served with `cache-control: no-store`. Contains no credentials or user data.
</ResponseField>

<ResponseField name="ALL /api/auth/*" type="Public">
  Authentication routes: sign-in, sign-out, session, and OIDC callbacks at
  `/api/auth/oauth2/callback/<provider-key>`.
</ResponseField>

## Session endpoints

<ResponseField name="GET /api/me" type="Session">
  The current user's profile and role.
</ResponseField>

<ResponseField name="GET /api/providers" type="Session">
  Lists the user's providers. API keys are never returned; each provider carries
  a `hasApiKey` flag. Custom headers **are** decrypted and returned so they
  remain editable.
</ResponseField>

<ResponseField name="POST /api/providers" type="Session">
  Creates a provider from a name, base URL, optional API key, and optional
  headers.
</ResponseField>

<ResponseField name="PATCH /api/providers" type="Session">
  Updates a provider. Omitting the API key leaves the stored one unchanged.
</ResponseField>

<ResponseField name="DELETE /api/providers" type="Session">
  Deletes a provider and its models. If it owned the default model, the oldest
  remaining enabled model is promoted.
</ResponseField>

<ResponseField name="POST /api/providers/test" type="Session">
  Validates a provider by requesting `<base URL>/models` through the outbound
  guard.
</ResponseField>

<ResponseField name="POST /api/models" type="Session">
  Creates a model with a provider model ID, display name, context window, and
  maximum output tokens. The first model becomes the default.
</ResponseField>

<ResponseField name="PATCH /api/models" type="Session">
  Updates a model, including making it the default or toggling `enabled`.
  Default changes release the previous holder first.
</ResponseField>

<ResponseField name="DELETE /api/models" type="Session">
  Deletes a model, promoting a replacement default when needed.
</ResponseField>

<ResponseField name="GET /api/chats" type="Session">
  Lists the user's conversations.
</ResponseField>

<ResponseField name="POST /api/chats" type="Session">
  Creates a conversation.
</ResponseField>

<ResponseField name="PATCH /api/chats" type="Session">
  Updates a conversation, such as renaming it or starting a fresh context.
</ResponseField>

<ResponseField name="DELETE /api/chats" type="Session">
  Deletes a conversation and its messages.
</ResponseField>

<ResponseField name="POST /api/chat" type="Session">
  The streaming chat endpoint. Resolves the model, decrypts credentials, ensures
  the workspace is running, and streams text, reasoning, and tool calls back to
  the browser. Messages and tool results are persisted as they complete.
</ResponseField>

<ResponseField name="GET /api/mcp" type="Session">
  Lists configured MCP servers and their known tools.
</ResponseField>

<ResponseField name="POST /api/mcp" type="Session">
  Adds an MCP server, connects to it, lists tools, and records a fingerprint of
  their definitions.
</ResponseField>

<ResponseField name="PATCH /api/mcp" type="Session">
  Updates an MCP server, including re-approving changed tool definitions.
</ResponseField>

<ResponseField name="DELETE /api/mcp" type="Session">
  Removes an MCP server.
</ResponseField>

## Workspace endpoints

<ResponseField name="GET /api/workspace/status" type="Session">
  Current workspace phase: `running`, `starting`, `stopped`, or `disabled`, plus
  a readiness flag and the mode.
</ResponseField>

<ResponseField name="POST /api/workspace/status" type="Session">
  Starts the workspace and waits for readiness.
</ResponseField>

<ResponseField name="DELETE /api/workspace/status" type="Session">
  Requests an immediate scale to zero, clearing active leases.
</ResponseField>

<ResponseField name="GET /api/workspace/files" type="Session">
  Lists or previews files under the home directory. Confined to canonical paths;
  traversal and symlink escapes are rejected.
</ResponseField>

<ResponseField name="POST /api/workspace/files" type="Session">
  Uploads a file, normally into `~/artifacts`.
</ResponseField>

<ResponseField name="GET /api/workspace/download" type="Session">
  Authenticated download of a workspace file, proxied through the control plane.
</ResponseField>

## Admin endpoints

<ResponseField name="GET /api/admin/users" type="Admin">
  Lists all users.
</ResponseField>

<ResponseField name="POST /api/admin/users" type="Admin">
  Creates a user account directly.
</ResponseField>

<ResponseField name="PATCH /api/admin/users" type="Admin">
  Updates a user's role or enabled state.
</ResponseField>

<ResponseField name="GET /api/admin/config" type="Admin">
  Returns branding and OIDC configuration. Client secrets are not returned in
  plaintext.
</ResponseField>

<ResponseField name="POST /api/admin/config" type="Admin">
  Updates branding and OIDC configuration.

  <Expandable title="brand">
    <ResponseField name="productName" type="string">
      1 to 60 characters.
    </ResponseField>

    <ResponseField name="tagline" type="string">
      Up to 140 characters.
    </ResponseField>

    <ResponseField name="logoDataUrl" type="string">
      PNG, JPEG, WebP, SVG, or icon data URL.
    </ResponseField>

    <ResponseField name="faviconDataUrl" type="string">
      Same formats as the logo.
    </ResponseField>

    <ResponseField name="primaryColor" type="string">
      Six-digit hex color.
    </ResponseField>

    <ResponseField name="primaryForegroundColor" type="string">
      Six-digit hex color.
    </ResponseField>
  </Expandable>

  <Expandable title="oidc">
    <ResponseField name="enabled" type="boolean" />

    <ResponseField name="providerKey" type="string">Matches `^[a-z0-9][a-z0-9-]{1,38}$`.</ResponseField>

    <ResponseField name="label" type="string" />

    <ResponseField name="issuer" type="string" />

    <ResponseField name="discoveryUrl" type="string" />

    <ResponseField name="clientId" type="string" />

    <ResponseField name="clientSecret" type="string" />

    <ResponseField name="scopes" type="string">Defaults to `openid profile email`.</ResponseField>

    <ResponseField name="autoProvision" type="boolean" />

    <ResponseField name="linkByEmail" type="boolean" />

    <ResponseField name="allowedDomains" type="array">Up to 50 domains.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="POST /api/admin/oidc-test" type="Admin">
  Validates OIDC discovery and client credentials against the real provider
  before enabling it.
</ResponseField>

<ResponseField name="POST /api/invitations" type="Admin">
  Creates an invitation for a new account.
</ResponseField>

<ResponseField name="PUT /api/invitations" type="Public">
  Redeems an invitation to create an account.
</ResponseField>

## Workspace-authenticated endpoints

<ResponseField name="GET /api/internal/conversations" type="Workspace">
  Backs the `r5dchat conversation` commands. Authenticates with the workspace
  bearer identity, then re-checks that the workspace's user owns the requested
  conversation. Supports overview, single-turn, and full-read modes in Markdown
  or JSON.
</ResponseField>

## Errors

Errors return a JSON body with a machine-readable code:

```json theme={null}
{ "error": { "message": "Workspace did not become ready in time" } }
```

| Status | Code                       | Meaning                                             |
| ------ | -------------------------- | --------------------------------------------------- |
| 401    | `unauthorized`             | Missing or invalid session or workspace identity    |
| 403    | —                          | Claim token mismatch or insufficient role           |
| 404    | `not_found`                | Record does not exist or is not owned by the caller |
| 503    | `workspace_disabled`       | `WORKSPACE_MODE=disabled`                           |
| 503    | `workspace_start_timeout`  | Workspace not ready within 90 seconds               |
| 502    | `workspace_request_failed` | The workspace daemon returned an error              |

## Monitoring

Use `/api/health` for liveness and readiness. It is unauthenticated and checks
database connectivity, making it a meaningful signal rather than a static
response.

```bash theme={null}
curl -fsS -o /dev/null -w '%{http_code}\n' https://chat.example.com/api/health
```
