Skip to content

API Reference

This reference describes the data APIs and WebSocket endpoint registered by src/numi_chat/web/app.py; HTML pages and static assets are outside its scope. In development (ENVIRONMENT=development), FastAPI publishes /openapi.json, /docs, and /redoc. Those routes are disabled outside development. For the turn lifecycle, see Message Flow and Persistence.

Base URL and authentication

The local server listens on http://localhost:4567 by default. The API prefix is /api/v1, except for health checks, generated files, and the WebSocket endpoint.

HTTP requests may authenticate with either:

Authorization: Bearer <access_token>

or the access_token HttpOnly cookie set by the web UI. Refresh tokens are returned by login/register; the server also sets their HttpOnly cookies. An access JWT must match an unexpired, unrevoked session record. See Authentication for session behavior.

Authentication failures return 401; missing admin privileges return 403. Rate-limited endpoints return 429 and may include Retry-After.

Authentication endpoints

All paths below are under /api/v1/auth.

Method Path Auth Purpose
GET /registration-status No Check whether registration is enabled
POST /register No Create the first or an enabled user account
POST /token No Log in with OAuth2 form fields username and password
POST /refresh No Exchange a refresh token for a new token pair
POST /logout No Revoke the refresh cookie and clear auth cookies

POST /register accepts JSON with username and password. Usernames are 3–50 characters matching [A-Za-z0-9_-]+; passwords must be at least 12 characters and also satisfy the configured complexity policy.

POST /token uses application/x-www-form-urlencoded fields. Successful token responses have this shape:

{
  "access_token": "...",
  "refresh_token": "...",
  "token_type": "bearer",
  "expires_in": 1800
}

POST /refresh accepts {"refresh_token": "..."} or the refresh cookie and rotates the token pair. POST /logout revokes the refresh token from the cookie; a bearer header alone does not supply that token.

On an empty database, registration is temporarily allowed for the first user; that user becomes an administrator. Later registration requires the application setting to be enabled by an administrator.

Chat endpoints

All chat endpoints require authentication. {chat_id} is the external chat UUID.

Method Path Purpose
GET /api/v1/chats/ List chats; query offset (default 0), limit (1–100, default 20), and optional archived (boolean)
GET /api/v1/chats/search Search chats with query q and optional archived filter; returns matched message snippets
POST /api/v1/chats/ Create a chat; returns 201 and the chat summary
PATCH /api/v1/chats/{chat_id}/organization Update chat organization (pinned, archived)
DELETE /api/v1/chats/{chat_id} Delete a chat and owned data
GET /api/v1/chats/{chat_id}/messages Load chat messages
POST /api/v1/chats/{chat_id}/title Update the title
POST /api/v1/chats/{chat_id}/model Change the model before messages exist
POST /api/v1/chats/{chat_id}/reasoning Set the reasoning level
POST /api/v1/chats/{chat_id}/tools Set chat-enabled custom tools
POST /api/v1/chats/{chat_id}/abort Abort an active generation
POST /api/v1/chats/{chat_id}/truncate/{message_id} Soft-delete the selected message and all later messages

All create-chat JSON fields are optional: title defaults to New Chat; model, reasoning_level, and enabled_tools use server-selected defaults when omitted. Chat mutation payloads are {"title": "..."}, {"model": "..."}, {"reasoning_level": "..."}, {"enabled_tools": ["..."]}, and {"pinned": boolean, "archived": boolean}.

GET /api/v1/chats/search requires q (at most 200 characters) and returns at most 50 ChatSearchResponse objects. Each contains the normal chat summary plus one plain-text snippet and a nullable message_id. The ID identifies the earliest matching non-deleted user, assistant, or tool message; a title-only match has no message ID. There is no matches array or match_type field. Whitespace-only queries return an empty list. Omitting archived searches both active and archived chats.

Reasoning updates

The chat API accepts off, minimal, low, medium, high, xhigh, and max. Values are normalized to the selected model's capabilities; none is not a valid chat API value. The response contains the saved value, for example {"status":"success","message":"high"}. See Configure Reasoning Effort.

History pagination

GET /api/v1/chats/{chat_id}/messages accepts optional limit (1–100), before_id, around_id, and through_id. limit counts user turns, so a page can contain more messages than that number. before_id excludes that message and later IDs; through_id includes messages up to that ID. around_id anchors the page to a message in the chat.

Responses include X-History-More and X-History-Before headers. Tool outputs are folded into their assistant tool-call entries for display. This endpoint's representation differs from raw database records and from provider input.

Files and uploads

Method Path Auth Purpose
POST /api/v1/upload?chat_id={chat_id} Yes Upload a chat file as multipart field file
GET /api/v1/chats/{chat_id}/files Yes List files owned by the chat
GET /api/v1/chats/{chat_id}/files/download/{filename} Yes Download one chat file
POST /api/v1/me/avatar Yes Upload a profile avatar as multipart field file
GET /generated-files/{chat_id}/{filename} Yes Read a generated artifact owned by the chat

Upload the file with multipart/form-data; chat_id is a required query parameter on POST /api/v1/upload. Upload limits are controlled by UPLOAD_MAX_UPLOAD_BYTES, UPLOAD_MAX_AVATAR_BYTES, and UPLOAD_MAX_IMAGE_PIXELS. PDF uploads are processed into ordered page context. Images can be used as vision inputs only when the selected model declares image input support.

Generated tool files use the authenticated web route /generated-files/{chat_id}/{filename} and are kept outside /static.

User and settings endpoints

These routes are mounted under /api/v1/me.

Method Path Purpose
GET /api/v1/me/ Return the current user profile
PATCH /api/v1/me/username Change the username and issue refreshed auth cookies
POST /api/v1/me/password Change the password
DELETE /api/v1/me/ Delete your account; JSON body {"password":"..."}
GET /api/v1/me/export Download structured account data as JSON
GET /api/v1/me/settings Read personal settings
POST /api/v1/me/settings Update personal_preferences

POST /password requires current_password and new_password and revokes existing sessions. Account export includes stored message fields and memory data, but not credentials or binary uploads. See Privacy and Self-Hosting.

Example settings update:

{"personal_preferences": "Prefer concise bullet points."}

Memory endpoints

All memory endpoints require authentication.

Method Path Purpose
GET /api/v1/memories/ List explicit durable facts
DELETE /api/v1/memories/{memory_id} Delete one explicit fact
GET /api/v1/memories/chats/{chat_id}/summary Read a chat's rolling summary
DELETE /api/v1/me/memory Clear all saved memory for the current account
GET /api/v1/me/memory/profile Read the structured adaptive-memory profile
PATCH /api/v1/me/memory/profile/section Replace one profile section
DELETE /api/v1/me/memory/profile/section Clear one profile section using section query parameter
GET /api/v1/me/memory/profile/history List profile snapshots; optional limit
GET /api/v1/me/memory/profile/history/{snapshot_id} Read one snapshot
POST /api/v1/me/memory/profile/consolidate Queue manual profile consolidation
GET /api/v1/me/memory/model Read the configured memory model
GET /api/v1/me/memory/observations List raw observations; optional consolidated filter

PATCH /api/v1/me/memory/profile/section accepts JSON fields section and value. The section name is one of identity, communication_style, top_of_mind, recent_history, earlier_context, or long_term_background; the value can contain up to 3000 characters. Snapshot history defaults to 25 entries; the service clamps limit to 1–100.

Metadata endpoints

Method Path Auth Purpose
GET /api/v1/tools Yes List tools and their current schemas
GET /api/v1/models No List models available in the configured catalog
GET /api/v1/instance No Read configured public operator/privacy information
GET /api/v1/favicon Yes Read a citation favicon; required domain (1–2048 characters) and optional size (16–128, default 32)

The model list is filtered by provider configuration and catalog enablement; it is not a remote health check. /api/v1/tools returns the instance registry, including core/custom names and groups. It does not accept a chat ID and does not filter by one chat's active selection. The agent applies that selection when supplying schemas and executing calls.

Admin endpoints

All admin endpoints require an authenticated user with is_admin=true.

Method Path Purpose
GET /api/v1/admin/stats Application statistics
GET /api/v1/admin/users List users and usage statistics
POST /api/v1/admin/users/{user_id}/reset-password Set a user's password
DELETE /api/v1/admin/users/{user_id} Delete a user and owned data
GET /api/v1/admin/app-settings Read application settings
POST /api/v1/admin/app-settings Update application settings
GET /api/v1/admin/model-settings/{name} Read a model override
PUT /api/v1/admin/model-settings/{name} Update a model override
DELETE /api/v1/admin/model-settings/{name} Reset a model override
GET, POST /api/v1/admin/models List or add configured models
PATCH, DELETE /api/v1/admin/models/{model_id} Edit or safely delete a model
POST /api/v1/admin/models/default Set the enabled catalog default
POST /api/v1/admin/models/import Merge or replace a complete JSON catalog
GET /api/v1/admin/models/export Export portable model JSON
POST /api/v1/admin/models/reload Reload an externally edited catalog
GET /api/v1/admin/models/discovery Search a configured provider's models
GET /api/v1/admin/models/discovery/detail Enrich a provider model selection

Model-override {name} is either chat-title or memory. Override updates and default-model updates accept {"model":"catalog-model-id"}. Catalog imports accept {"mode":"merge","catalog":{...}}, with replace as the other mode. Model IDs in edit/delete paths can contain /.

Discovery requires provider; it accepts query (up to 200 characters) and limit (1–100, default 30). Detail lookup requires provider and model_id (1–256 characters). These calls contact the configured provider and can return 502 for discovery failures.

Health endpoints

Health routes are intentionally unversioned:

Method Path Meaning
GET /health Basic service health
GET /health/ready Database readiness check
GET /health/live Process liveness check

WebSocket protocol

Connect to /ws/{chat_id} for a chat owned by the authenticated user. Use a bearer header or the access_token cookie. A supplied Origin must match a configured trusted origin or the request's own origin. Without an Origin, bearer-header authentication is required.

Sending a message

Send a JSON object in a text frame:

{
  "message": "Summarize these files",
  "vision_files": ["diagram.png"],
  "attached_files": ["notes.pdf"],
  "request_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}

Attachment filenames must already belong to that chat. The arrays and request_id are optional. If supplied, request_id must match [a-f0-9-]{32,36}; retain it when retrying the same submission so the server can recognize an already-saved message. Text can be empty when permitted attachments provide the input.

For keepalive, send {"message":"ping"} without request ID or attachments. The response is {"type":"pong"}. A raw non-JSON ping frame is invalid.

Reconnecting to a turn

Both query parameters are needed to resume from a retained event boundary:

  • turn_id: the ID of the turn being resumed, up to 64 characters.
  • last_event_id: the last applied event ID, a nonnegative integer.

The server sends stream_sync. Its reset flag tells the client whether to replace the displayed stream with the supplied snapshot or apply retained events after the requested boundary. For an active turn, it also carries active, turn_id, event_id, request_id, before_message_id, events, and snapshot_lost. With no active turn, it returns active: false, reset: true, and an empty events array. A missing/mismatched turn ID cannot resume solely from last_event_id.

Replay is bounded and in memory. After a restart, or when a snapshot is lost, reconcile with saved history rather than assuming every streamed chunk can be replayed. A browser disconnect does not itself cancel the turn.

Server events

Broadcast turn events include event_id and turn_id for ordering.

Type Main fields Meaning
stream_sync active, reset, events, IDs Reconnect state or snapshot
turn_start before_message_id, request_id A generation turn began
accepted request_id, message Submitted user message was persisted
request_resolved request_id, message_id Retry matched an already-saved request
rejected request_id, content Submission could not start
status content Status such as thinking or compacting
content content Answer text chunk
reasoning content Returned reasoning text or summary chunk
tool_preparing name, tool_call_id Tool call recognized before arguments complete
tool_start name, input, tool_call_id Tool invocation announced
tool_end name, output, tool_call_id Tool result available
title_update title Optional title update after a successful answer
done message_id Final assistant response persisted without more tool calls
error content Failure, cancellation, or limit
turn_end Generation ended, including after errors; title and memory work can follow

turn_end is not a success signal. Connection and payload limits are controlled by WEBSOCKET_*; agent turn budgets add further limits. Invalid JSON or request IDs close with code 1007, binary frames with 1003, oversized payloads with 1009, and connection capacity with 1013. An untrusted origin or inaccessible chat is rejected with 1008; missing or expired authentication closes with 4001. An unavailable turn slot produces a rejected event.

Error and status behavior

Typical responses use FastAPI's standard shape:

{"detail": "Error message"}

Ownership failures intentionally look like 404 so resources are not disclosed. Use the generated schema on a development instance for exact response models and validation constraints. Source schemas live in src/numi_chat/web/schemas/ and routes in src/numi_chat/web/routes/.