2026-02-14 21:20:14 -08:00
# REST API Contract
Base URL: `/api` behind web proxy, or server root directly in local/dev.
Authentication:
- If `ADMIN_TOKEN` is set on server, send `Authorization: Bearer <token>` .
- If `ADMIN_TOKEN` is unset, API is open for local/dev use.
Content type:
- Requests with bodies use `application/json` .
- Responses are JSON unless noted otherwise.
## Health + Auth
### `GET /health`
- Response: `{ "ok": true }`
### `GET /v1/auth/session`
- Response: `{ "authenticated": true, "mode": "open" | "token" }`
## Models
### `GET /v1/models`
- Response:
```json
{
"providers": {
"openai": { "models": ["gpt-4.1-mini"], "loadedAt": "2026-02-14T00:00:00.000Z", "error": null },
"anthropic": { "models": ["claude-3-5-sonnet-latest"], "loadedAt": null, "error": null },
"xai": { "models": ["grok-3-mini"], "loadedAt": null, "error": null }
}
}
```
## Chats
### `GET /v1/chats`
- Response: `{ "chats": ChatSummary[] }`
### `POST /v1/chats`
- Body: `{ "title"?: string }`
- Response: `{ "chat": ChatSummary }`
2026-02-14 21:27:44 -08:00
### `PATCH /v1/chats/:chatId`
- Body: `{ "title": string }`
- Response: `{ "chat": ChatSummary }`
- Not found: `404 { "message": "chat not found" }`
### `POST /v1/chats/title/suggest`
- Body:
```json
{
"chatId": "chat-id",
"content": "user request text"
}
```
- Response: `{ "chat": ChatSummary }`
Behavior notes:
- If the chat already has a non-empty title, server returns the existing chat unchanged.
- Server always uses OpenAI `gpt-4.1-mini` to generate a one-line title (up to ~4 words), updates the chat title, and returns the updated chat.
2026-02-14 21:20:14 -08:00
### `DELETE /v1/chats/:chatId`
- Response: `{ "deleted": true }`
- Not found: `404 { "message": "chat not found" }`
### `GET /v1/chats/:chatId`
- Response: `{ "chat": ChatDetail }`
### `POST /v1/chats/:chatId/messages`
- Body:
```json
{
"role": "system|user|assistant|tool",
"content": "string",
"name": "optional",
"metadata": {}
}
```
- Response: `{ "message": Message }`
## Chat Completions (non-streaming)
### `POST /v1/chat-completions`
- Body:
```json
{
"chatId": "optional-chat-id",
"provider": "openai|anthropic|xai",
"model": "string",
"messages": [
{ "role": "system|user|assistant|tool", "content": "string", "name": "optional" }
],
"temperature": 0.2,
"maxTokens": 256
}
```
- Response:
```json
{
"chatId": "chat-id-or-null",
"provider": "openai",
"model": "gpt-4.1-mini",
"message": { "role": "assistant", "content": "..." },
"usage": { "inputTokens": 10, "outputTokens": 20, "totalTokens": 30 },
"raw": {}
}
```
Behavior notes:
- If `chatId` is present, server validates chat existence.
- For `chatId` calls, server stores only * new * non-assistant messages from provided history to avoid duplicates.
- Server persists final assistant output and call metadata (`LlmCall` ) in DB.
2026-02-14 22:06:30 -08:00
- Server updates chat-level model metadata on each call: `lastUsedProvider` /`lastUsedModel` ; first successful/failed call also initializes `initiatedProvider` /`initiatedModel` if unset.
2026-03-02 16:13:34 -08:00
- For `openai` and `xai` , backend enables tool use during chat completion with an internal system instruction.
- Available tool calls for chat: `web_search` and `fetch_url` .
- `web_search` uses Exa and returns ranked results with per-result summaries/snippets.
- `fetch_url` fetches a URL and returns plaintext page content (HTML converted to text server-side).
- When a tool call is executed, backend stores a chat `Message` with `role: "tool"` and tool metadata (`metadata.kind = "tool_call"` ), then stores the assistant output.
- `anthropic` currently runs without server-managed tool calls.
2026-02-14 21:20:14 -08:00
## Searches
### `GET /v1/searches`
- Response: `{ "searches": SearchSummary[] }`
### `POST /v1/searches`
- Body: `{ "title"?: string, "query"?: string }`
- Response: `{ "search": SearchSummary }`
### `DELETE /v1/searches/:searchId`
- Response: `{ "deleted": true }`
- Not found: `404 { "message": "search not found" }`
### `GET /v1/searches/:searchId`
- Response: `{ "search": SearchDetail }`
### `POST /v1/searches/:searchId/run`
- Body:
```json
{
"query": "optional override",
"title": "optional override",
"type": "auto|fast|deep|instant",
"numResults": 10,
"includeDomains": ["example.com"],
"excludeDomains": ["example.org"]
}
```
- Response: `{ "search": SearchDetail }`
Search run notes:
- Backend executes Exa search and Exa answer.
- Persists answer text/citations + ranked results.
- If both search and answer fail, endpoint returns an error.
## Type Shapes
`ChatSummary`
```json
2026-02-14 22:06:30 -08:00
{
"id": "...",
"title": null,
"createdAt": "...",
"updatedAt": "...",
"initiatedProvider": "openai|anthropic|xai|null",
"initiatedModel": "string|null",
"lastUsedProvider": "openai|anthropic|xai|null",
"lastUsedModel": "string|null"
}
2026-02-14 21:20:14 -08:00
```
`Message`
```json
{
"id": "...",
"createdAt": "...",
"role": "system|user|assistant|tool",
"content": "...",
2026-03-02 16:13:34 -08:00
"name": null,
"metadata": null
2026-02-14 21:20:14 -08:00
}
```
`ChatDetail`
```json
{
"id": "...",
"title": null,
"createdAt": "...",
"updatedAt": "...",
2026-02-14 22:06:30 -08:00
"initiatedProvider": "openai|anthropic|xai|null",
"initiatedModel": "string|null",
"lastUsedProvider": "openai|anthropic|xai|null",
"lastUsedModel": "string|null",
2026-02-14 21:20:14 -08:00
"messages": [Message]
}
```
`SearchSummary`
```json
{ "id": "...", "title": null, "query": null, "createdAt": "...", "updatedAt": "..." }
```
`SearchDetail`
```json
{
"id": "...",
"title": "...",
"query": "...",
"createdAt": "...",
"updatedAt": "...",
"requestId": "...",
"latencyMs": 123,
"error": null,
"answerText": "...",
"answerRequestId": "...",
"answerCitations": [],
"answerError": null,
"results": []
}
```
For streaming contracts, see `docs/api/streaming-chat.md` .