# 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 `. - 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 }` ### `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. ### `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. - Server updates chat-level model metadata on each call: `lastUsedProvider`/`lastUsedModel`; first successful/failed call also initializes `initiatedProvider`/`initiatedModel` if unset. - 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. ## 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 { "id": "...", "title": null, "createdAt": "...", "updatedAt": "...", "initiatedProvider": "openai|anthropic|xai|null", "initiatedModel": "string|null", "lastUsedProvider": "openai|anthropic|xai|null", "lastUsedModel": "string|null" } ``` `Message` ```json { "id": "...", "createdAt": "...", "role": "system|user|assistant|tool", "content": "...", "name": null, "metadata": null } ``` `ChatDetail` ```json { "id": "...", "title": null, "createdAt": "...", "updatedAt": "...", "initiatedProvider": "openai|anthropic|xai|null", "initiatedModel": "string|null", "lastUsedProvider": "openai|anthropic|xai|null", "lastUsedModel": "string|null", "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`.