# 1.1. REST API All endpoints are versioned under **`/api/v1`** and documented live by Swagger at `/api-docs`. The server speaks JSON; the harness intake endpoint additionally accepts `multipart/form-data` for image uploads. ## Harness (`@Controller('harness')`) The conversation engine's surface. Source: `server/src/modules/harness/controllers/harness.controller.ts` with OpenAPI decorators in `decorators/harness.openapi.ts`. | Method | Path | Purpose | Success | | ------ | ------------------ | ------------------------------------------------------------ | ------- | | `POST` | `/harness` | Submit a prompt (text + images) for streaming processing | `202` | | `POST` | `/harness/cancel` | Signal cancellation of an in-flight job | `202` | | `POST` | `/harness/warm` | Fire-and-forget model warm-up (pre-load weights in Ollama) | `200` | | `GET` | `/harness/models` | List available Ollama models (local or cloud) | `200` | ### `POST /api/v1/harness` The one entry point for all interaction. Accepts `multipart/form-data`: - **Header** `x-harness-llm` — model name (required, `400` otherwise). - **Field** `prompt` — the message array (roles + content), JSON string. - **Files** `images` — zero or more image parts. - **Query** — `requestId`, `sessionId`, `conversationId`, `roomId` (identity/correlation), `stream`, `numCtx` (context window override), `event` (the socket event name the dashboard listens on), `think`, `hasNewImages`, `sessionMetadata` (client-supplied session metadata, e.g. image hashes for dedup), `language` (ISO-639-1 code of the active UI locale — browser-detected or user-selected). Response (synchronous): ```json { "realtime": { "event": "", "roomId": "", "requestId": "" } } ``` Images are hashed/deduplicated against `sessionMetadata.images[*].hash` before becoming job payloads; previously uploaded payloads are re-used from MinIO (see **1.5**). The actual assistant output arrives asynchronously on the Socket.IO room — see **1.4**. ### `POST /api/v1/harness/cancel` Body: `CancelHarnessJobDto` ({ requestId, roomId, event, stream }). Cancels the running job for `requestId`; the UI receives `cancel_result` on the socket (see **1.4**). ### `GET /api/v1/harness/models` Proxies the Ollama model catalogue via `OllamaModelsService` so the dashboard's model selector reflects whatever the configured `OLLAMA_HOST` actually serves — local pull list or Ollama Cloud roster — including each model's default `numCtx` where known. Supports conditional requests: the response carries an `ETag` (built from the catalogue + `numCtx` options) and returns `304 Not Modified` when the client's `If-None-Match` matches. ### `POST /api/v1/harness/warm` Body: `WarmModelDto` (`{ model }`). Fire-and-forget model warm-up: pre-loads a model's weights in Ollama so the first prompt does not stall on a cold load. Failures are logged server-side and never surfaced to the caller (best-effort). ## Playlists (`@Controller('playlists')`) Server-side persisted video playlists, scoped by session and conversation (`HarnessPlaylist` — see **1.5**). Playlist names are user input, so every path segment is URL-encoded. | Method | Path | Purpose | | -------- | -------------------------------------- | ---------------------------------------------- | | `GET` | `/playlists/:sessionId` | List all playlists for a session | | `GET` | `/playlists/:sessionId/:conversationId` | List playlists of a conversation | | `GET` | `/playlists/:sessionId/:conversationId/:name` | Fetch one playlist | | `PUT` | `/playlists/:sessionId/:conversationId/:name` | Upsert a playlist (create/replace) | | `PUT` | `/playlists/:sessionId/:conversationId/:name/rename` | Rename a playlist | | `DELETE` | `/playlists/:sessionId/:conversationId/:name` | Delete a playlist | These back the dashboard's playlist panel, transport bar, and floating playlist (see **2.2**). ## Queue observability (`@Controller('bullmq')`) | Method | Path | Purpose | | ------ | ---------------------- | --------------------------------------------------------------------- | | `GET` | `/bullmq/live` | Live queue status (counts per state) — feeds the SysCtl queue console | | `GET` | `/bullmq/retry-config` | Effective retry/backoff configuration | ## Dead-letter queue (`@Controller('dlq')`) | Method | Path | Purpose | | -------- | ------------------------ | -------------------------------------------------- | | `GET` | `/dlq` | List envelopes with filters (status, queue, requestId, search, limit/offset) | | `GET` | `/dlq/:requestId` | Fetch one DLQ envelope | | `POST` | `/dlq` | Create a DLQ record manually | | `PATCH` | `/dlq/:requestId` | Edit a DLQ record (e.g. fix payload before replay) | | `PATCH` | `/dlq/:requestId/upsert` | Upsert a record | | `DELETE` | `/dlq/:requestId` | Drop a record | | `POST` | `/dlq/reinstate` | Re-queue selected records into BullMQ | | `DELETE` | `/dlq` | Cleanup: purge eligible `Removed` records (retention/amount policy) | These back the dashboard's **DLQ area** (list, edit, reinstate; counts polled every 30 s). ## Storage (`@Controller('storage')`) MinIO object API scoped by session and conversation: | Method | Path | Purpose | | -------- | -------------------------------------------------- | ------------------------------------- | | `GET` | `/storage/info` | Bucket/usage info | | `GET` | `/storage/:sessionId/:conversationId` | List payloads of a conversation | | `GET` | `/storage/:sessionId/:conversationId/:hash` | Fetch one image payload | | `GET` | `/storage/:sessionId/:conversationId/:hash/exists` | Existence probe (dedup before upload) | | `DELETE` | `/storage/:sessionId/:conversationId/:hash` | Remove one payload | | `DELETE` | `/storage/:sessionId/:conversationId` | Remove all payloads of a conversation | ## Provider overrides (`@Controller('provider-overrides')`) | Method | Path | Purpose | | -------- | ------------------------------- | -------------------------------------------------------------------- | | `GET` | `/provider-overrides` | Merged config for all providers (API keys masked) — feeds SysCtl | | `PUT` | `/provider-overrides` | Partial update of provider configs (encrypted at rest; masked keys kept) | | `DELETE` | `/provider-overrides/:provider` | Reset/clear overrides for a provider (sysctl) | Provider keys are `serper`, `brightData`, `youtube`, `sources`, `eodhd`, `layouts`; legacy `webpageFetch` rows are migrated to `scrape` on boot. ## Ollama overrides (`@Controller('ollama-overrides')`) | Method | Path | Purpose | | -------- | --------------------- | ---------------------------------------------------------------- | | `GET` | `/ollama-overrides` | Current Ollama connection config (`host` + `OLLAMA_API_KEY`) | | `PUT` | `/ollama-overrides` | Update the Ollama connection (host / API key) | | `DELETE` | `/ollama-overrides` | Reset the Ollama connection to defaults | ## Memory overrides (`@Controller('memory-overrides')`) Memory system variables (sysctl → system): global settings layered over env defaults, persisted in the shared `provider_overrides` row `memory`, restored on boot. Currently `cognitionLimit` — the cognition profile character cap (env baseline `MEMORY_COGNITION_LIMIT`, default 5000, clamped 500–32000). | Method | Path | Purpose | | -------- | ------------------- | -------------------------------------------------------------- | | `GET` | `/memory-overrides` | Effective value + env baseline + override flag | | `PUT` | `/memory-overrides` | Set a system variable (takes effect on the very next request) | | `DELETE` | `/memory-overrides` | Reset all memory system variables to their env defaults | ## Config (`@Controller('configs')`) Per-session persisted workbench config (`HarnessConfig` — see **1.5**): | Method | Path | Purpose | | -------- | ----------------- | ---------------------------------------------------------------- | | `GET` | `/configs/:sessionId` | Fetch the persisted config for a session (`404` if none) | | `PUT` | `/configs/:sessionId` | Upsert `selectedModel`, `preprocessing`, `providerOverrides`, `memoryPartition`, `memoryCognition` | | `DELETE` | `/configs/:sessionId` | Delete the persisted config for a session | ## Qdrant (`@Controller('qdrant')`) Long-term memory surfaces (vector store — see **1.5**). Two spaces per user: the fact partition (`memory_partition`) and the AI's cognition document (`memory_cognition`). | Method | Path | Purpose | | -------- | ----------------------- | ------------------------------------------------------------------------------------------------ | | `GET` | `/qdrant/status` | Collection status (feature flag, existence, indexes) | | `GET` | `/qdrant/memory` | List records; all query params are optional tightenings (`memoryPartition` / `memoryCognition`, session, role, tags, contains) | | `POST` | `/qdrant/text` | Store a text verbatim as one memory record (sync); `{accepted, id}` envelope | | `DELETE` | `/qdrant/text` | Delete by filters (`text` exact / `contains` / `tags` / `conversationId` / `requestId` — at least one matcher required, capped at 50 matches) or `cognition=true` to purge the AI's cognition space (profile + insights) | | `POST` | `/qdrant/search/text` | Multi-variant semantic search (full text + per-sentence variants) | | `POST` | `/qdrant/search/vector` | Search by a raw query vector | | `DELETE` | `/qdrant/memory` | Prune a whole partition (facts AND cognition) or one conversation of it (facts only) | ## Conversations (`@Controller('conversations')`) Conversation persistence (`HarnessConversation` — see **1.5**): | Method | Path | Purpose | | -------- | ------------------------------------------------- | ---------------------------------------------- | | `GET` | `/conversations/:sessionId` | List conversations for a session | | `GET` | `/conversations/:sessionId/:conversationId` | Get the latest state of one conversation | | `PUT` | `/conversations/:sessionId/:conversationId/:requestId` | Upsert a conversation turn (title + content) | | `DELETE` | `/conversations/:sessionId/:conversationId` | Delete a conversation and all its turns | | `DELETE` | `/conversations/:sessionId/:conversationId/:requestId` | Delete one conversation turn | ## Stock data (`@Controller('stock-data')`) Cached end-of-day market history (see **1.5** for the storage model). Reads come from Postgres first; missing coverage is backfilled from the configured market-data provider (EODHD) on access, so repeating ranges are free. | Method | Path | Purpose | | ------ | ------------------------ | ---------------------------------------------------------------- | | `GET` | `/stock-data/history` | OHLCV bars for a ticker in the inclusive `[from, to]` window (`YYYY-MM-DD`; defaults: 92-day lookback → today). Gap-backfilled on access. | | `GET` | `/stock-data/coverage` | Available date range for a ticker (backfilled to the 10-year retention on first access) — lets the chart's range controls size to real data. | These back the dashboard's stock-market response charts (see **2.2**). ## Sharp overrides (`@Controller('sharp-overrides')`) Image preprocessing configuration and preview (see **1.5** / **PProc**): | Method | Path | Purpose | | -------- | ------------------------ | ---------------------------------------------------------------- | | `GET` | `/sharp-overrides` | Current preprocessing configuration | | `PUT` | `/sharp-overrides` | Update preprocessing configuration overrides | | `POST` | `/sharp-overrides/preview` | Run an uploaded image through the current config, return variants | ## Health (`@Controller('health')`) | Method | Path | Purpose | | ------ | --------------- | ----------------------------------------------- | | `GET` | `/health/live` | Liveness: process memory/disk thresholds | | `GET` | `/health/ready` | Readiness: PostgreSQL + MinIO indicators answer | ## Conventions - Every endpoint is described by `@Api*()` decorator composites (`ApiTags`, `ApiOperation`, `ApiResponse`, body/query schemas) — Swagger stays honest by construction. - Responses are DTO-typed (`HarnessControllerResponse`, `CancelHarnessJobResponseDto`, …) and validated at the boundary. - `202 Accepted` everywhere work is handed to a queue — the API never blocks on model inference.