Skip to content

HTTP API

The FastAPI service (varagity/api/, port 8000) is the system's single backend surface: the web GUI talks only to it, and the CLI is its peer over the same Prefect flows (spec_v2 §4). The layer is async at the edge, sync flows underneath — routes run the pipeline in a worker threadpool rather than rewriting it (spec_v2 §4.1). The wire contract lives in varagity/api/schemas.py as pydantic models; everything below the hand-written sections is auto-rendered from openapi.json, a checked-in snapshot of the app's schema (see Keeping this page honest).

Two things OpenAPI cannot express are documented by hand here: the SSE event protocols and the error-envelope conventions.

The error envelope

Every non-2xx response carries the same structured body (spec_v2 §4.1) — the GUI maps code to an actionable banner, and the shape holds for every failure, including unhandled exceptions (a pure-ASGI middleware seated inside CORS envelopes those as internal_error, so cross-origin browsers see the real error instead of TypeError: Failed to fetch):

{"error": {"code": "es_unreachable", "message": "elasticsearch unreachable — is the stack up? (…)"}}

The code vocabulary, as built:

Code Status Raised by
validation_error 422 Any request-body/parameter validation failure (field errors ride in message).
unknown_retrieval_method 422 POST /api/chat override naming no registered retriever.
unknown_chat_engine 422 POST /api/chat override naming no registered chat engine.
unknown_setting / invalid_settings 422 PATCH /api/settings — unknown name / value failing the Settings validators (linked settings validate as a merged whole).
no_file_stored 422 POST /api/documents when every file in the batch was rejected.
paths_mismatch 422 POST /api/documents when the paths form field doesn't pair 1:1 with files (the folder-upload positional contract is checked, not trusted).
modified_mismatch 422 POST /api/documents when the modified form field (per-file last-modified epoch-ms; restamps each stored file's mtime so file_modified_at provenance survives the upload) doesn't pair 1:1 with files. Malformed entries are ignored per file, not errors.
too_many_files / batch_too_large 422 POST /api/documents when the batch busts UPLOAD_MAX_FILES / UPLOAD_MAX_TOTAL_MB — rejected before any byte is written. Per-file problems (invalid_path, path_too_deep, extension_not_allowed, file_too_large, reserved_path — a path under, or a flat file named, the reserved varagity-exports backup folder — …) are not errors: they ride UploadedFileOut.reason inside the 201.
nothing_to_export 422 POST /api/backups/{corpus}/export when the corpus holds no documents — or, for the graph, nothing has been built — an empty bundle is not a backup.
bundle_invalid 422 POST /api/backups/{corpus}/restore for a bundle that isn't sound: no readable bundle.json, an unsafe member (.., an absolute path, a symlink), an unexpected, missing or unrecorded member, a member whose recorded size differs from the zip's, a vector matrix that disagrees with the chunk count, a graph bundle with no files — or one an earlier restore found damaged (a member whose bytes don't match their recorded sha256, or a line no row can be made from: the restore run checks those as it loads, fails with the corpus untouched, and the list then shows that bundle as damaged until the file changes). Nothing was touched.
bundle_incompatible 422 POST /api/backups/{corpus}/restore for a sound bundle this stack must not restore: another bundle format version, another corpus, vectors from another embedding model or dimension — or a graph bundle another graph engine, package or major.minor version wrote (Written by lightrag-hku 1.4.9 — this stack runs 1.5.4, whose files differ.). Refused, never converted — neither store checks the model, so a mismatch would restore cleanly and then retrieve the wrong passages, and a graph's files are copied verbatim.
bundle_not_found 404 GET /api/backups/{corpus}/files/{name} and POST /api/backups/{corpus}/restore for any name that is not a bundle file inside the corpus's varagity-exports/ folder — hidden and in-flight .partial files, non-.zip names, and names (or symlinks) leading out of the folder included.
conversation_not_found 404 POST /api/chat (pre-stream) and the conversation routes.
document_not_found 404 DELETE /api/documents/{doc_id} and the two …/preview/* routes — the bulk POST /api/documents/delete reports unknown ids in not_found rather than failing the batch.
preview_disabled, unsupported_type, file_missing, file_changed, conversion_unavailable, conversion_failed, page_out_of_range 404 GET /api/documents/{doc_id}/preview/page/{page} only — an <img> can't read a JSON envelope, so the page route turns each degrade reason into a 404 code. The locate route reports the same conditions inside a 200 envelope instead (below).
ingest_already_running 409 POST /api/ingest while a run is in flight (one run at a time).
corpus_busy 409 One writer per corpus. POST /api/backups/{corpus}/export and …/restore while another operation holds the corpus's write gate — a second backup run included; POST /api/ingest while a backup export or restore holds the RAG gate; the two document delete routes while a backup export or restore holds it; POST /api/graph/build while a graph backup holds the graph gate; DELETE /api/graph/documents/{name} while a graph restore holds it (the restore ends by writing the bundle's own stale flag). A RAG restore is also refused while an ingest in another process (a CLI main.py ingest / chat) holds the corpus's advisory lock. The message names the holder (a restore is running).
corpus_restoring 503 POST /api/chat, before the stream opens, while a backup restore is replacing the corpus that would answer the turn — a graph turn before the graph's session is opened. A graph turn the graph answers is unaffected by a document restore, and a document turn by a graph restore. Ask again once the restore finishes.
<service>_unreachable 503 Dependency preflights and per-request store construction: postgres_unreachable, es_unreachable, llamacpp_unreachable, infinity_unreachable.
docs_path_not_writable 500 POST /api/documents when DOCS_PATH rejects writes (the container-UID gotcha — see the runbook); POST /api/backups/rag/export when docs/varagity-exports/ can't be created or written (the same gotcha, or a file squatting on the folder's name).
internal_error 500 Any unhandled exception (enveloped by the middleware).
pipeline_error — In-band SSE only: the chat stream's error event when the pipeline fails after the 200 flushed.
bad_request, not_found, method_not_allowed, service_unavailable, http_<status> 400/404/405/503/other Status-derived fallbacks for framework-raised errors (unknown path, wrong method, …).

The chat stream — POST /api/chat

One question in, one text/event-stream out (spec_v2 §4.3). The transport is a POST — the browser EventSource API is GET-only, so clients consume it with fetch plus an SSE parser (the web app uses eventsource-parser); each frame is standard SSE framing, event: <name> + data: <json>.

Event order:

retrieval → (reasoning | token | stats)* → done | error

The retrieval frame fires once retrieval (+rerank) completes, before any answer token — evidence before prose (spec_v2 §4.3). That ordering is the transparency story: the browser's provenance panel is populated while the answer is still streaming, and it is the flow's natural shape (on_retrieved fires before generation starts). reasoning and token deltas then interleave in stream order — a reasoning model's <think>…</think> content is classified fragment-by-fragment (ThinkStreamSplitter) into reasoning events, everything after into token events. Throttled stats frames (live decode throughput) may interleave with the deltas — only when the model server reports its own timings. llama.cpp does; a backend that doesn't simply produces a stream with zero stats frames, so clients must treat their absence as normal.

Event Payload Fields
retrieval RetrievalEvent chunks (list of RetrievedChunk, best first, each with metadata and — when the method fills it — a RetrievalTrace), method, top_k, reranked_to (RERANK_TOP_N when the reranked method narrowed the list, else null), condensed_query (v3 — the standalone search query the chat engine actually retrieved with, null whenever the turn wasn't condensed: a first turn, the simple engine, the kill switch, or the raw-query fallback; ADR-011).
reasoning DeltaEvent delta — the next <think> fragment, stream order.
token DeltaEvent delta — the next answer fragment.
stats StatsEvent tokens_per_second (decode throughput so far, cumulative average — it settles rather than jitters), completion_tokens (tokens decoded so far). Warmup-gated (no frame before 8 decoded tokens — the model server's first readings compute absurd rates) and throttled to ≥250 ms apart.
done DoneEvent message_id, conversation_id (the one just created when the request named none), answer (full, <think>-stripped — authoritative; streamed deltas are best-effort display), usage (prompt_tokens, completion_tokens, latency_ms keyed retrieval/generation/total, tokens_per_second — the model server's own final decode rate, null when it reports no timings).
error ErrorEvent code (pipeline_error), message.

Failure surfaces split by stream state. Everything detectable before streaming is a clean structured status, checked cheapest-first: body shape (422 validation_error) → retrieval-method / chat-engine resolution (422 unknown_retrieval_method / unknown_chat_engine) → the corpus that would answer being mid-restore (503 corpus_restoring) → dependency preflight (503 <service>_unreachable, probing postgres, elasticsearch, llamacpp, infinity — prefect is deliberately absent: without a server, flows fall back to an ephemeral in-process API, so chat works untracked) → conversation existence (404 conversation_not_found). Once the 200 flushed, HTTP status can't change — anything the pipeline raises mid-stream arrives as the in-band error event instead.

Disconnect semantics. A client disconnect cancels the response generator, which flips an abort flag the flow polls between tokens; the LLM stream closes and the GPU is freed (spec_v2 §4.3 cancellation). Aborted turns persist nothing — the turn (both messages, evidence snapshots, timings) is persisted at done, whose ids prove it. Conversation auto-titling runs fire-and-forget after done and never delays it.

The ingest status stream — GET /api/ingest/status

POST /api/ingest returns 202 with a run handle (IngestRunOut) immediately; the flow — the same tracked Prefect ingest flow the CLI runs — executes on a background thread (spec_v2 §4.2). One run at a time: a second POST while one is in flight is a 409 ingest_already_running. The preflight 503-checks postgres, elasticsearch, infinity, plus llamacpp only when CONTEXTUALIZE is on.

GET /api/ingest/status streams the run's feed with replay-from-frame-one semantics: a subscriber always gets the full backlog first, then live events while the run is still going — connecting mid-run or after completion renders the same picture the CLI's rich display showed live. With no run ever started in this API process, the stream is a single idle status frame (run: null) and closes.

Event Payload Fields
status IngestStatusEvent run (IngestRunOut: run_id, state running/completed/failed, reingest, timestamps, terminal summary counters, flow-level error) — always the first frame (snapshot) and the last (terminal).
progress IngestProgressEvent stage (discover | parse | chunk | contextualize | embed | store | file_done), file, outcome (file_done only: ingested/skipped/no_text/failed), current, total, files_done, files_total.
log IngestLogEvent level, message — relayed varagity.ingest records (skips, no-text warnings, failure heads), pinned to INFO for the run.

One progress frame per stage transition — except contextualize, the ingest's long pole (one LLM blurb per chunk), which additionally ticks per-chunk via current/total, so the browser's progress bar moves at the same granularity as the terminal's.

The backup status stream — GET /api/backups/{corpus}/status

POST /api/backups/{corpus}/export writes one backup bundle into the corpus folder's reserved varagity-exports/ — a zip of the corpus's processed rows for rag, of the engine's working directory, verbatim, for graph (see Data model). It returns 202 with a run handle (BackupRunOut) at once, and the tracked rag-export or graph-export flow runs on a background thread, the same shape as ingest. Everything that can refuse an export is checked before the 202, so a refusal is a clean status, never a failed run:

  1. 403 graph_disabled for graph while GRAPH_ENABLED=false — checked first, before any store is touched.
  2. The preflight: 503 postgres_unreachable (an export reads the rows, or for the graph only its stale flag, and calls no model service).
  3. 422 nothing_to_export when nothing is ingested — or, for the graph, when its workdir holds no file to copy (nothing has been built).
  4. 500 docs_path_not_writable / graph_docs_path_not_writable when the exports folder can't be written.
  5. 409 corpus_busy while any operation holds the corpus.

The last one is a per-corpus write gate, not a per-runner flag: one operation per corpus at a time — ingest (RAG), build (graph), export and restore all claim their corpus's gate, so a second backup run is refused too, and so is a graph build during a graph export. Each corpus has its own gate: a graph export never waits for a RAG one. {corpus} is rag or graph; anything else is a 422 validation_error. The graph's export holds the graph's write lock while it copies, so graph chat and /graph keep answering.

POST /api/backups/{corpus}/restore with {"name": "<bundle>"} starts a restore the same way (the tracked rag-restore or graph-restore flow). A restore replaces the corpus, with no model call. For rag, its rows in both stores become the bundle's, re-rooted onto this stack's DOCS_PATH, and the BM25 index is rebuilt from them. For graph, the engine's working directory becomes the bundle's: extracted beside the live one, swapped in, and opened — the previous graph comes back if the restored one won't open. Its refusals, in order, again all before the 202:

  1. 403 graph_disabled for graph while GRAPH_ENABLED=false.
  2. 503 postgres_unreachable / es_unreachable — the stores it writes (both for rag; postgres alone for graph, for its stale flag).
  3. 404 bundle_not_found — no such bundle in the folder.
  4. 409 corpus_busy — another operation holds the corpus. Checked before the bundle is even opened.
  5. 422 bundle_invalid / bundle_incompatible — the bundle's cheap verdict (the list's own: its central directory and bundle.json, for a graph bundle the engine's file format included), or a damage an earlier restore of this very file found.
  6. 409 corpus_busy — for rag, an ingest in another process holds the corpus's advisory lock.

Neither the list nor this route hashes a member. The run checks the sha256 of every member as it loads the bundle — the restore's one read of it, with progress — and nothing takes effect until every member checked out (the RAG transaction commits after the load, the graph's staged copy is swapped in after the extraction). A bundle damaged inside an intact zip therefore fails its first run with the corpus untouched; that failure is remembered in-process by name, size and mtime, so the list then reports the bundle as damaged and the route refuses it with 422 bundle_invalid.

While a restore runs, POST /api/chat against that corpus answers 503 corpus_restoring — a graph turn before the graph's session is opened. For rag, every route that reads the tables — GET /api/documents and the two preview routes — answers 503 corpus_restoring too (the restore's TRUNCATE holds them until it commits, so a query would hang for the whole load), and the document deletes answer 409 corpus_busy; for graph, the source delete answers 409 corpus_busy, and the status poll and the /graph reads that arrive mid-swap wait for it (seconds), then read the restored graph.

GET /api/backups/{corpus}/status has the ingest stream's replay-from-frame-one semantics. A subscriber gets the run's whole backlog, then live events until the terminal frame. With no backup run in this API process it is a single idle status frame (run: null). It is the one graph backup route a disabled graph still answers, like the build's own stream.

Event Payload Fields
status BackupStatusEvent run (BackupRunOut: run_id, corpus, operation export/restore, state running/completed/failed, timestamps, run-level error). A completed export adds bundle (the file name), saved_to (docs/varagity-exports/<name> or graph-docs/varagity-exports/<name>, the checkout-relative path the panel's Copy path copies) and summary (counts, size_bytes, wall_clock_s; a graph export's counts are thread_days, messages, entities, relations). A restore names its bundle from the first frame; its summary counts what it wrote — documents, chunks, bm25 for rag, the restored graph's thread_days, messages, entities, relations for graph — and adds stale, the stale flag it left. The first frame is the snapshot and the last is the terminal state.
progress BackupProgressEvent stage — a RAG export's snapshot | documents | chunks | finalize, a graph export's snapshot | pack | finalize, a RAG restore's validate | load | index | bm25, a graph restore's validate | extract | swap | open — plus current/total for the counted stages: rows for documents and chunks, chunks for load and bm25, and bytes for pack and extract (the workdir's files copied, or extracted, of their total). Throttled to about 50 frames per stage, so a large corpus doesn't flood the stream.
log BackupLogEvent level, message: relayed varagity.backup records. These include what was exported or restored, a swept partial from an interrupted export, a warning when rows record an embedding model other than EMBEDDING_MODEL, and a warning naming any graph workdir entry the export had to skip.

The terminal transition is atomic: the run turns terminal, its last frame joins the backlog, and the corpus is released together. A client that starts the next export as soon as it sees completed never races a spurious corpus_busy.

The other two backup routes are plain (both answer 403 graph_disabled for a disabled graph):

  • GET /api/backups/{corpus} lists the folder's bundles, newest first. Each entry carries its bundle.json manifest, read alone through the zip's central directory, or manifest: null for a zip that isn't a readable Varagity backup — and the cheap restore verdict: restorable, the reason when it isn't (the same reasons a restore's 422 gives, worded for the panel), and drift, the reingest-affecting settings the bundle was made under that differ from this stack's (restoring it flags the corpus stale; drift never refuses; always empty for the graph). The verdict reads only the zip directory and the manifest, so a bundle damaged inside an intact zip lists as restorable until its first restore refuses it. A graph bundle's manifest also carries engine, the format its files are in, which the verdict holds to this stack's engine (same engine, package and major.minor version).
  • GET /api/backups/{corpus}/files/{name} returns one bundle as application/zip with Content-Disposition: attachment. The panel's Download is a plain link, so no cross-origin fetch (and no CORS) is involved.

The graph build learns the gate too: POST /api/graph/build answers 409 corpus_busy while a graph backup holds the corpus (a second build keeps its 409 graph_build_running), and the graph's corpus routes treat the reserved folder as not-corpus — GET /api/graph/documents never lists a bundle, and DELETE /api/graph/documents/{name} answers 404 graph_document_not_found for anything under varagity-exports/ (and 409 corpus_busy for any file while a graph restore runs).

The preview pair — degradable by design

The evidence panel's page previews (ADR-010) ride two routes with an asymmetric error convention. POST /api/documents/{doc_id}/preview/locate answers 200 for every degradable condition — available:false plus a machine reason (preview_disabled | unsupported_type | file_missing | file_changed | conversion_unavailable | conversion_failed | no_match) — reserving 404 for an unknown doc_id; the GUI maps any available:false to its full-text fallback, so a missing LibreOffice or an edited-on-disk file is a per-document degrade, never a failed request. GET /api/documents/{doc_id}/preview/page/{page} serves the rendered PNG with Cache-Control: public, max-age=31536000, immutable — sound because doc_id is content-hashed and the route re-verifies the on-disk content_hash before rendering (a drifted file 404s file_changed rather than serving a lying image). Clients only request pages a successful locate named, so this route's 404s are the honest edge signal, carrying the degrade reason as the error code.

Keeping this page honest

The route reference below renders openapi.json, a snapshot regenerated by uv run python scripts/export_openapi.py (it pins METRICS_ENABLED=true, because that setting gates /metrics' presence in the schema) and drift-guarded by tests/unit/test_openapi_snapshot.py — the unit suite fails whenever the app's live schema stops matching the checked-in copy. The frontend's web/lib/types.ts is generated from the same schema (bun run gen:types), and the app factory merges the SSE payload models into components/schemas even though no route returns them directly — the schemas named in the tables above are all in the reference below. A running stack also serves the live interactive docs at http://localhost:8000/docs.

Route reference (auto-rendered)

Varagity API 0.0.0

Contextual-Retrieval RAG over local GPUs (spec_v2 §4).


backups


GET /api/backups/{corpus}

List Backups

Description

List the backup bundles in a corpus's varagity-exports/ folder.

A directory read plus one small member per bundle — the manifest is found through each zip's central directory, so a folder of large bundles lists in milliseconds. Each entry carries the cheap restore verdict (no hashing — a restore hashes, and a damage it finds is remembered here).

Args: corpus: The corpus whose backups to list. damaged: The process's remembered restore failures.

Returns: One entry per bundle file, newest first (empty when the folder does not exist yet).

Raises: HTTPException: 403 graph_disabled for the graph while GRAPH_ENABLED is false.

Input parameters

Parameter In Type Default Nullable Description
corpus path string No

Responses

[
    {
        "drift": [
            "string"
        ],
        "manifest": null,
        "modified_at": "2022-04-13T15:42:05.901Z",
        "name": "string",
        "reason": null,
        "restorable": true,
        "size_bytes": 0
    }
]
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "items": {
        "$ref": "#/components/schemas/BundleOut"
    },
    "title": "Response List Backups Api Backups  Corpus  Get",
    "type": "array"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

POST /api/backups/{corpus}/export

Start Export

Description

Start a background export and return its handle.

The bundle is written into <corpus root>/varagity-exports/ as varagity-<corpus>-<UTC yyyymmdd-HHMM>.zip (a same-minute repeat gets a -2 suffix — never a replace): for RAG, from one consistent snapshot of the corpus; for the graph, the engine's workdir copied under the graph's write lock, while graph chat keeps answering.

Args: corpus: The corpus to export. runners: The process-wide backup runners. preflight: The awaitable reachability check (structured 503). store: The per-request vector store (the RAG nothing-to-export check).

Returns: The new run's snapshot (state running).

Raises: HTTPException: 403 graph_disabled for the graph while GRAPH_ENABLED is false; 503 postgres_unreachable when the database is down; 422 nothing_to_export when nothing is ingested (or, for the graph, nothing has been built); 500 docs_path_not_writable / graph_docs_path_not_writable when the exports folder can't be written; 409 corpus_busy while any operation — another export, an ingest or build, a restore — holds the corpus (the message names it).

Input parameters

Parameter In Type Default Nullable Description
corpus path string No

Responses

{
    "bundle": null,
    "corpus": "rag",
    "error": null,
    "finished_at": null,
    "operation": "export",
    "run_id": "string",
    "saved_to": null,
    "started_at": "2022-04-13T15:42:05.901Z",
    "state": "running",
    "summary": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "One backup run's state (``POST /api/backups/{corpus}/export`` or ``/restore``).\n\nAttributes:\n    run_id: The run handle.\n    corpus: The corpus being backed up or restored.\n    operation: What the run does — ``export`` writes a new bundle,\n        ``restore`` replaces the corpus with one.\n    state: ``running`` | ``completed`` | ``failed``.\n    bundle: An export's: the file it wrote (set once it is published —\n        an export names nothing until its bundle is whole). A restore's:\n        the bundle it restores, from the start.\n    saved_to: Where an export's bundle landed, relative to the checkout\n        (``docs/varagity-exports/<name>``, or\n        ``graph-docs/varagity-exports/<name>`` for the graph) — the path\n        \"Copy path\" copies, right in both host and compose mode.\n        ``None`` for a restore.\n    started_at: When the run started.\n    finished_at: When it reached a terminal state (``None`` while\n        running).\n    error: The run-level failure (``failed`` only).\n    summary: The final counters (``completed`` only).",
    "properties": {
        "bundle": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Bundle"
        },
        "corpus": {
            "enum": [
                "rag",
                "graph"
            ],
            "title": "Corpus",
            "type": "string"
        },
        "error": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Error"
        },
        "finished_at": {
            "anyOf": [
                {
                    "format": "date-time",
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Finished At"
        },
        "operation": {
            "enum": [
                "export",
                "restore"
            ],
            "title": "Operation",
            "type": "string"
        },
        "run_id": {
            "title": "Run Id",
            "type": "string"
        },
        "saved_to": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Saved To"
        },
        "started_at": {
            "format": "date-time",
            "title": "Started At",
            "type": "string"
        },
        "state": {
            "enum": [
                "running",
                "completed",
                "failed"
            ],
            "title": "State",
            "type": "string"
        },
        "summary": {
            "anyOf": [
                {
                    "$ref": "#/components/schemas/BackupSummaryOut"
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "run_id",
        "corpus",
        "operation",
        "state",
        "started_at"
    ],
    "title": "BackupRunOut",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

GET /api/backups/{corpus}/files/{name}

Download Backup

Description

Return one bundle as a file download.

A plain GET answering with Content-Disposition: attachment, so the panel's Download is an ordinary link — the browser saves the file (and offers its own "Show in folder") without a cross-origin fetch.

Args: corpus: The corpus the bundle belongs to. name: The bundle's file name in the exports folder.

Returns: The bundle's bytes as application/zip.

Raises: HTTPException: 403 graph_disabled for the graph while GRAPH_ENABLED is false; 404 bundle_not_found when no such bundle lives in the folder — which covers a name trying to escape it.

Input parameters

Parameter In Type Default Nullable Description
corpus path string No
name path string No

Responses

"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ="
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "format": "binary",
    "type": "string"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

POST /api/backups/{corpus}/restore

Start Restore

Description

Start a background restore of one bundle and return its handle.

Restore replaces, with no model call. For RAG, the corpus's rows in both stores become the bundle's, re-rooted onto this stack's DOCS_PATH — nothing parsed, chunked, contextualized or embedded — and the BM25 index is rebuilt from the restored rows. For the graph, the engine's working directory becomes the bundle's — extracted beside the live one and swapped in, the previous graph put back if the restored one won't open — and nothing is re-extracted. Everything a cheap look can see refuses the bundle here, before the 202: the list's verdict, and a damage an earlier restore of this very file found. The sha256 of every member is the run's job — it reads the bundle once, to load it, and nothing takes effect until every member checked out — so a bundle damaged inside an intact zip fails its first run (the corpus untouched) and is refused here from then on.

Args: corpus: The corpus to restore. payload: The bundle to restore, by name. runners: The process-wide backup runners. preflight: The awaitable reachability check (structured 503). store: The per-request vector store (the RAG corpus-lock check). damaged: The process's remembered restore failures (a bundle an earlier restore found damaged is refused here, before the run).

Returns: The new run's snapshot (state running).

Raises: HTTPException: 403 graph_disabled for the graph while GRAPH_ENABLED is false; 503 postgres_unreachable / es_unreachable when a store the restore writes is down; 404 bundle_not_found for a name that is not a bundle in the folder; 409 corpus_busy while another operation holds the corpus, or (RAG) an ingest in another process holds the corpus lock; 422 bundle_invalid for an unsound bundle, or one an earlier restore found damaged; 422 bundle_incompatible for one from another embedding model or dimension, corpus, or format version — or, for the graph, another engine or engine major.minor version.

Input parameters

Parameter In Type Default Nullable Description
corpus path string No

Request body

{
    "name": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "additionalProperties": false,
    "description": "Body of ``POST /api/backups/{corpus}/restore`` (ADR-019).\n\nAttributes:\n    name: The bundle to restore — a file name in the corpus's\n        ``varagity-exports/`` folder, as the backups list reports it.",
    "properties": {
        "name": {
            "minLength": 1,
            "title": "Name",
            "type": "string"
        }
    },
    "required": [
        "name"
    ],
    "title": "BackupRestoreRequest",
    "type": "object"
}

Responses

{
    "bundle": null,
    "corpus": "rag",
    "error": null,
    "finished_at": null,
    "operation": "export",
    "run_id": "string",
    "saved_to": null,
    "started_at": "2022-04-13T15:42:05.901Z",
    "state": "running",
    "summary": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "One backup run's state (``POST /api/backups/{corpus}/export`` or ``/restore``).\n\nAttributes:\n    run_id: The run handle.\n    corpus: The corpus being backed up or restored.\n    operation: What the run does — ``export`` writes a new bundle,\n        ``restore`` replaces the corpus with one.\n    state: ``running`` | ``completed`` | ``failed``.\n    bundle: An export's: the file it wrote (set once it is published —\n        an export names nothing until its bundle is whole). A restore's:\n        the bundle it restores, from the start.\n    saved_to: Where an export's bundle landed, relative to the checkout\n        (``docs/varagity-exports/<name>``, or\n        ``graph-docs/varagity-exports/<name>`` for the graph) — the path\n        \"Copy path\" copies, right in both host and compose mode.\n        ``None`` for a restore.\n    started_at: When the run started.\n    finished_at: When it reached a terminal state (``None`` while\n        running).\n    error: The run-level failure (``failed`` only).\n    summary: The final counters (``completed`` only).",
    "properties": {
        "bundle": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Bundle"
        },
        "corpus": {
            "enum": [
                "rag",
                "graph"
            ],
            "title": "Corpus",
            "type": "string"
        },
        "error": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Error"
        },
        "finished_at": {
            "anyOf": [
                {
                    "format": "date-time",
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Finished At"
        },
        "operation": {
            "enum": [
                "export",
                "restore"
            ],
            "title": "Operation",
            "type": "string"
        },
        "run_id": {
            "title": "Run Id",
            "type": "string"
        },
        "saved_to": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Saved To"
        },
        "started_at": {
            "format": "date-time",
            "title": "Started At",
            "type": "string"
        },
        "state": {
            "enum": [
                "running",
                "completed",
                "failed"
            ],
            "title": "State",
            "type": "string"
        },
        "summary": {
            "anyOf": [
                {
                    "$ref": "#/components/schemas/BackupSummaryOut"
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "run_id",
        "corpus",
        "operation",
        "state",
        "started_at"
    ],
    "title": "BackupRunOut",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

GET /api/backups/{corpus}/status

Backup Status

Description

Stream the corpus's current (or last) backup run as SSE.

Frames: status (snapshot; also terminal, with the summary — and an export's saved path), progress (a RAG export's snapshot → documents → chunks → finalize; a graph export's snapshot → pack, in bytes → finalize; a RAG restore's validate → load → index → bm25; a graph restore's validate → extract, in bytes → swap → open), log (relayed varagity.backup lines). With no run in this API process the stream is a single idle status frame; after a terminal run it replays that run and closes. Never refused for a disabled graph: a tab must still render what ran.

Args: corpus: The corpus whose backup run to follow. runners: The process-wide backup runners.

Yields: The framed events, oldest first, then live until terminal.

Input parameters

Parameter In Type Default Nullable Description
corpus path string No

Responses

Schema of the response body

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

chat


POST /api/chat

Chat

Description

Answer one question as a typed SSE stream (spec_v2 §4.3).

Event order: retrieval (the provenance payload) → reasoning/ token deltas, interleaved with throttled stats frames while the model server reports throughput → done (ids, full answer, usage + per-stage latency). A failure after the stream opened emits an in-band error event instead. Aborted turns (client disconnect) persist nothing — the turn is persisted at done.

stats is optional in the protocol: it appears only when the model server reports its own decode counters (llama.cpp does; see :class:~varagity.models.llm.GenerationTimings), so clients must treat zero stats frames as normal rather than as a stalled stream.

Args: plan: The validated request plan.

Yields: The framed SSE events, in protocol order.

Request body

{
    "conversation_id": null,
    "corpus": null,
    "overrides": null,
    "query": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "description": "Body of ``POST /api/chat`` (spec_v2 §4.2, spec_graphrag §4.2).\n\nAttributes:\n    query: The user's question.\n    conversation_id: Existing conversation to append the turn to; a new\n        conversation is created when omitted.\n    overrides: Optional per-request setting overrides.\n    corpus: Which corpus to answer from — ``\"rag\"`` (the chunk corpus,\n        the default when omitted) or ``\"graph\"`` (the message archive).\n        The **router-fillable** field: automatic routing is out of scope\n        (spec_graphrag §6), so today the composer sets it and later a\n        router fills the same field, changing no contract.",
    "properties": {
        "conversation_id": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Conversation Id"
        },
        "corpus": {
            "anyOf": [
                {
                    "enum": [
                        "rag",
                        "graph"
                    ],
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Corpus"
        },
        "overrides": {
            "anyOf": [
                {
                    "$ref": "#/components/schemas/ChatOverrides"
                },
                {
                    "type": "null"
                }
            ]
        },
        "query": {
            "minLength": 1,
            "title": "Query",
            "type": "string"
        }
    },
    "required": [
        "query"
    ],
    "title": "ChatRequest",
    "type": "object"
}

Responses

Schema of the response body

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

system


GET /api/config

Config

Description

Expose the registered capabilities and valid ranges.

Mostly static registry contents; the two upload constraints (upload_max_mb, allowed_extensions) are read from the effective settings per request so the dropzone always validates against what the server enforces (ALLOWED_EXTENSIONS is runtime-overridable).

Returns: Registry contents (retrievers, chunkers, OCR engines, model types), the numeric knobs' valid ranges, and the upload constraints.

Responses

{
    "allowed_extensions": [
        "string"
    ],
    "chunkers": [
        "string"
    ],
    "llm_model_types": [
        "string"
    ],
    "model_types": [
        "string"
    ],
    "ocr_engines": [
        "string"
    ],
    "preview_enabled": true,
    "ranges": {},
    "retrievers": [
        "string"
    ],
    "upload_max_mb": 0
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/config`` — static capabilities (spec_v2 §4.2).\n\nThe GUI builds its settings controls from this, so a newly registered\nimplementation (one file + one import) appears in the UI automatically.\n\nAttributes:\n    retrievers: Registered retrieval method names.\n    chunkers: Registered chunking strategy names.\n    ocr_engines: Available OCR engine names.\n    model_types: Valid ``get_model`` types.\n    llm_model_types: The chat-capable subset of ``model_types`` — the\n        ``CHAT_MODEL_TYPE`` vocabulary the composer quick-toggle offers.\n    ranges: Valid ranges for the numeric query-time knobs, keyed by\n        setting name (lowercase).\n    upload_max_mb: Effective per-file upload cap (``UPLOAD_MAX_MB``) —\n        the dropzone validates against it client-side.\n    allowed_extensions: Effective ingestable extensions\n        (``ALLOWED_EXTENSIONS``, normalized with leading dots), sorted.\n    preview_enabled: Whether the page-preview endpoints are on\n        (``PREVIEW_ENABLED``, read-only here — the knob is env-only) —\n        off, the GUI skips preview eligibility entirely.",
    "properties": {
        "allowed_extensions": {
            "items": {
                "type": "string"
            },
            "title": "Allowed Extensions",
            "type": "array"
        },
        "chunkers": {
            "items": {
                "type": "string"
            },
            "title": "Chunkers",
            "type": "array"
        },
        "llm_model_types": {
            "items": {
                "type": "string"
            },
            "title": "Llm Model Types",
            "type": "array"
        },
        "model_types": {
            "items": {
                "type": "string"
            },
            "title": "Model Types",
            "type": "array"
        },
        "ocr_engines": {
            "items": {
                "type": "string"
            },
            "title": "Ocr Engines",
            "type": "array"
        },
        "preview_enabled": {
            "title": "Preview Enabled",
            "type": "boolean"
        },
        "ranges": {
            "additionalProperties": {
                "$ref": "#/components/schemas/NumericRange"
            },
            "title": "Ranges",
            "type": "object"
        },
        "retrievers": {
            "items": {
                "type": "string"
            },
            "title": "Retrievers",
            "type": "array"
        },
        "upload_max_mb": {
            "title": "Upload Max Mb",
            "type": "integer"
        }
    },
    "required": [
        "retrievers",
        "chunkers",
        "ocr_engines",
        "model_types",
        "llm_model_types",
        "ranges",
        "upload_max_mb",
        "allowed_extensions",
        "preview_enabled"
    ],
    "title": "ConfigResponse",
    "type": "object"
}

GET /api/health

Health

Description

Report API liveness and per-dependency reachability.

Always 200 while the API process is alive — the compose healthcheck gates on the process, and per-dependency state lives in the body so a flapping backing service doesn't flap the api container.

Returns: The aggregate flag and each service's probe outcome.

Responses

{
    "ok": true,
    "services": {}
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/health``.\n\nThe endpoint answers ``200`` whenever the API process itself is alive —\nper-dependency state lives in the body, so a flapping backing service\ndoesn't mark the ``api`` container unhealthy.\n\nAttributes:\n    ok: ``True`` when every dependency probe succeeded.\n    services: Probe result per service name (``llamacpp``,\n        ``infinity``, ``postgres``, ``elasticsearch``, ``prefect``).",
    "properties": {
        "ok": {
            "title": "Ok",
            "type": "boolean"
        },
        "services": {
            "additionalProperties": {
                "$ref": "#/components/schemas/ServiceHealth"
            },
            "title": "Services",
            "type": "object"
        }
    },
    "required": [
        "ok",
        "services"
    ],
    "title": "HealthResponse",
    "type": "object"
}

conversations


GET /api/conversations

List Conversations

Description

List every conversation, most recently updated first.

Args: store: The per-request conversation store.

Returns: Summaries with message counts.

Responses

[
    {
        "conversation_id": "string",
        "created_at": "2022-04-13T15:42:05.901Z",
        "group_id": null,
        "message_count": 0,
        "title": "string",
        "updated_at": "2022-04-13T15:42:05.901Z"
    }
]
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "items": {
        "$ref": "#/components/schemas/ConversationSummaryOut"
    },
    "title": "Response List Conversations Api Conversations Get",
    "type": "array"
}

POST /api/conversations

Create Conversation

Description

Start a conversation.

Args: payload: Optional explicit title (the first chat turn auto-titles otherwise). store: The per-request conversation store.

Returns: The created conversation's summary.

Request body

{
    "title": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "description": "Body of ``POST /api/conversations``.\n\nAttributes:\n    title: Optional explicit title; defaults to the placeholder the\n        first chat turn auto-replaces.",
    "properties": {
        "title": {
            "anyOf": [
                {
                    "maxLength": 200,
                    "minLength": 1,
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Title"
        }
    },
    "title": "ConversationCreateRequest",
    "type": "object"
}

Responses

{
    "conversation_id": "string",
    "created_at": "2022-04-13T15:42:05.901Z",
    "group_id": null,
    "message_count": 0,
    "title": "string",
    "updated_at": "2022-04-13T15:42:05.901Z"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "One conversation in the ``GET /api/conversations`` list.\n\nAttributes:\n    conversation_id: The app-generated id.\n    title: Current title.\n    created_at: Creation timestamp.\n    updated_at: Last-turn timestamp (list ordering key).\n    message_count: Number of persisted messages.\n    group_id: Sidebar group the conversation is filed under (``None``\n        = ungrouped).",
    "properties": {
        "conversation_id": {
            "title": "Conversation Id",
            "type": "string"
        },
        "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
        },
        "group_id": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Group Id"
        },
        "message_count": {
            "title": "Message Count",
            "type": "integer"
        },
        "title": {
            "title": "Title",
            "type": "string"
        },
        "updated_at": {
            "format": "date-time",
            "title": "Updated At",
            "type": "string"
        }
    },
    "required": [
        "conversation_id",
        "title",
        "created_at",
        "updated_at",
        "message_count"
    ],
    "title": "ConversationSummaryOut",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

DELETE /api/conversations/{conversation_id}

Delete Conversation

Description

Delete a conversation; messages and sources cascade.

Args: conversation_id: The conversation to delete. store: The per-request conversation store.

Returns: An empty 204 response.

Raises: HTTPException: 404 conversation_not_found for an unknown id.

Input parameters

Parameter In Type Default Nullable Description
conversation_id path string No

Responses

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

GET /api/conversations/{conversation_id}

Get Conversation

Description

Fetch a full transcript: messages plus each answer's stored sources.

Args: conversation_id: The conversation to fetch. store: The per-request conversation store.

Returns: The transcript, oldest message first.

Raises: HTTPException: 404 conversation_not_found for an unknown id.

Input parameters

Parameter In Type Default Nullable Description
conversation_id path string No

Responses

{
    "conversation_id": "string",
    "created_at": "2022-04-13T15:42:05.901Z",
    "messages": [
        {
            "chat_engine": null,
            "condensed_query": null,
            "content": "string",
            "created_at": "2022-04-13T15:42:05.901Z",
            "graph_evidence": null,
            "latency_ms": null,
            "message_id": "string",
            "reasoning": null,
            "retrieval_method": null,
            "role": "string",
            "sources": [
                {
                    "chunk_id": "string",
                    "rank": 0,
                    "trace": {}
                }
            ],
            "target_corpus": null
        }
    ],
    "title": "string",
    "updated_at": "2022-04-13T15:42:05.901Z"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/conversations/{id}`` — the full transcript.\n\nAttributes:\n    conversation_id: The app-generated id.\n    title: Current title.\n    created_at: Creation timestamp.\n    updated_at: Last-turn timestamp.\n    messages: All messages, oldest first, each with its sources.",
    "properties": {
        "conversation_id": {
            "title": "Conversation Id",
            "type": "string"
        },
        "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
        },
        "messages": {
            "items": {
                "$ref": "#/components/schemas/MessageOut"
            },
            "title": "Messages",
            "type": "array"
        },
        "title": {
            "title": "Title",
            "type": "string"
        },
        "updated_at": {
            "format": "date-time",
            "title": "Updated At",
            "type": "string"
        }
    },
    "required": [
        "conversation_id",
        "title",
        "created_at",
        "updated_at",
        "messages"
    ],
    "title": "ConversationDetailOut",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

PATCH /api/conversations/{conversation_id}

Update Conversation

Description

Move a conversation into a sidebar group, or out of one.

Deliberately not a re-order: the move never bumps updated_at, so the recency-sorted list keeps its order.

Args: conversation_id: The conversation to move. payload: The target group (group_id: null ungroups). store: The per-request conversation store.

Returns: An empty 204 response.

Raises: HTTPException: 404 group_not_found for an unknown target group; 404 conversation_not_found for an unknown conversation.

Input parameters

Parameter In Type Default Nullable Description
conversation_id path string No

Request body

{
    "group_id": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "additionalProperties": false,
    "description": "Body of ``PATCH /api/conversations/{conversation_id}``.\n\nUnknown fields are rejected so a typo'd patch fails loudly instead of\nsilently doing nothing.\n\nAttributes:\n    group_id: Sidebar group to file the conversation under, or ``None``\n        to ungroup it. Required — it is the route's only mutable field,\n        so an empty patch is a validation error, not a silent no-op.",
    "properties": {
        "group_id": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Group Id"
        }
    },
    "required": [
        "group_id"
    ],
    "title": "ConversationUpdateRequest",
    "type": "object"
}

Responses

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

documents


GET /api/documents

List Documents

Description

List every ingested document (the corpus-management table).

Files uploaded but not yet ingested don't appear here — the documents table records ingests; the GUI pairs this list with the upload outcomes it already holds. Each entry carries its relative_path under DOCS_PATH (None for sources living elsewhere) so the GUI can group the table by the folders a folder upload created.

Args: store: The per-request vector store.

Returns: One entry per document, newest ingest first.

Raises: HTTPException: 503 corpus_restoring while a backup restore replaces the corpus (its TRUNCATE holds the table until it commits).

Responses

[
    {
        "content_hash": "string",
        "doc_id": "string",
        "extraction_mix": {},
        "file_name": "string",
        "file_type": "string",
        "ingested_at": "2022-04-13T15:42:05.901Z",
        "n_chunks": 0,
        "relative_path": null,
        "source": "string"
    }
]
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "items": {
        "$ref": "#/components/schemas/DocumentOut"
    },
    "title": "Response List Documents Api Documents Get",
    "type": "array"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

POST /api/documents

Upload Documents

Description

Upload file(s) into DOCS_PATH (no auto-ingest).

Each file is validated and stored independently; rejected files are reported per-file so a mixed batch partially succeeds. A same-named existing file is replaced (the re-upload flow) — its changed content hash makes the next ingest re-process it.

With paths (folder uploads, spec_v3 §5.2), each file lands at its sanitized relative path under DOCS_PATH, preserving nested structure — structure is identity: doc_id hashes the relative path, so q3/notes.md and q4/notes.md must not collapse onto one name. Entries pair with files positionally; an empty-string entry keeps that file on the flat path. Without paths the flat contract is untouched.

With modified (same positional contract), each stored file's mtime is restamped to the client's declared last-modified time (epoch milliseconds — the browser's File.lastModified), so the file_modified_at provenance on the next ingest's chunks carries the document's clock rather than the upload instant. An empty-string entry keeps that file's write time; a malformed entry is ignored per file.

Args: files: The multipart file parts. paths: Optional relative path per file, positionally aligned with files. modified: Optional last-modified epoch-milliseconds per file, positionally aligned with files.

Returns: Per-file outcomes, in upload order.

Raises: HTTPException: 422 paths_mismatch / 422 modified_mismatch when paths / modified is present with a different length than files (a positional contract must be checked, not trusted); 422 too_many_files / 422 batch_too_large when the batch busts UPLOAD_MAX_FILES / UPLOAD_MAX_TOTAL_MB (checked before anything is written); 422 no_file_stored when every file was rejected on its own merits (extension/size/name/path); 500 docs_path_not_writable when nothing landed because the server couldn't write DOCS_PATH (e.g. the ./docs bind mount is not writable by the api container's user).

Request body

{
    "files": [
        "string"
    ],
    "modified": null,
    "paths": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "properties": {
        "files": {
            "items": {
                "contentMediaType": "application/octet-stream",
                "type": "string"
            },
            "title": "Files",
            "type": "array"
        },
        "modified": {
            "anyOf": [
                {
                    "items": {
                        "type": "string"
                    },
                    "type": "array"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Modified"
        },
        "paths": {
            "anyOf": [
                {
                    "items": {
                        "type": "string"
                    },
                    "type": "array"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Paths"
        }
    },
    "required": [
        "files"
    ],
    "title": "Body_upload_documents_api_documents_post",
    "type": "object"
}

Responses

{
    "files": [
        {
            "file_name": "string",
            "reason": null,
            "relative_path": null,
            "replaced": true,
            "size_bytes": 0,
            "stored": true
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``POST /api/documents``.\n\nAttributes:\n    files: Per-file outcomes, in upload order.",
    "properties": {
        "files": {
            "items": {
                "$ref": "#/components/schemas/UploadedFileOut"
            },
            "title": "Files",
            "type": "array"
        }
    },
    "required": [
        "files"
    ],
    "title": "UploadResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

POST /api/documents/delete

Delete Documents

Description

Remove several documents' chunks from both stores (bulk GUI-driven GC).

The corpus table's multi-select delete (spec_v2 §4.2). A POST sub-path rather than a body on DELETE — the set to delete is the request's subject, and bodies on DELETE are the kind of thing intermediaries feel free to drop.

Same store ordering as the single-document delete, applied set-wise: one terms delete_by_query against Elasticsearch first, one DELETE … WHERE doc_id = ANY(…) against pgvector last. So the invariant holds for the batch exactly as it does for one document — if Elasticsearch fails, no marker row is gone, every document still lists, and the whole batch is retryable. Unknown ids are reported in not_found rather than raising: a concurrent delete must not fail the rest of the batch.

Args: payload: The requested doc_ids (duplicates collapse; order is preserved). store: The per-request vector store. bm25: The per-request BM25 store. gates: The process's per-corpus write gates. remove_file: Also unlink each source file — honored per document, only inside DOCS_PATH.

Returns: Per-document outcomes plus the ids that had no documents row.

Raises: HTTPException: 409 corpus_busy while a backup export or restore holds the corpus; 503 es_unreachable when Elasticsearch is down (nothing deleted — retry when it returns).

Input parameters

Parameter In Type Default Nullable Description
remove_file query boolean False No

Request body

{
    "doc_ids": [
        "string"
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "additionalProperties": false,
    "description": "Body of ``POST /api/documents/delete`` (spec_v2 §4.2).\n\nAttributes:\n    doc_ids: The documents to remove, at least one. Duplicates collapse\n        and unknown ids are reported rather than fatal, so the corpus\n        table's selection can be posted as-is.",
    "properties": {
        "doc_ids": {
            "items": {
                "type": "string"
            },
            "minItems": 1,
            "title": "Doc Ids",
            "type": "array"
        }
    },
    "required": [
        "doc_ids"
    ],
    "title": "DocumentBulkDeleteRequest",
    "type": "object"
}

Responses

{
    "deleted": [
        {
            "chunks_deleted": 0,
            "doc_id": "string",
            "file_removed": true
        }
    ],
    "not_found": [
        "string"
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``POST /api/documents/delete`` (spec_v2 §4.2).\n\nAttributes:\n    deleted: Per-document outcomes, in requested order (duplicates\n        collapsed) — the same shape a single delete returns.\n    not_found: Requested ids that no longer had a ``documents`` row.\n        Reported, never fatal: a concurrent delete (another tab, a\n        reingest) must not fail the rest of the batch.",
    "properties": {
        "deleted": {
            "items": {
                "$ref": "#/components/schemas/DocumentDeleteResponse"
            },
            "title": "Deleted",
            "type": "array"
        },
        "not_found": {
            "items": {
                "type": "string"
            },
            "title": "Not Found",
            "type": "array"
        }
    },
    "required": [
        "deleted",
        "not_found"
    ],
    "title": "DocumentBulkDeleteResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

DELETE /api/documents/{doc_id}

Delete Document

Description

Remove a document's chunks from both stores (GUI-driven GC).

Elasticsearch first, pgvector last: the pgvector documents row is the marker — if the ES delete fails, the document still lists and the delete can be retried; a marker deleted first would strand ES chunks invisibly (the exact v1 gap this route closes).

Args: doc_id: The document to remove. store: The per-request vector store. bm25: The per-request BM25 store. gates: The process's per-corpus write gates. remove_file: Also unlink the source file — honored only when it resolves inside DOCS_PATH (otherwise the next ingest would simply re-add the document).

Returns: The deletion counts and whether the file was removed.

Raises: HTTPException: 409 corpus_busy while a backup export or restore holds the corpus; 404 document_not_found for an unknown id; 503 es_unreachable when Elasticsearch is down (nothing deleted — retry when it returns).

Input parameters

Parameter In Type Default Nullable Description
doc_id path string No
remove_file query boolean False No

Responses

{
    "chunks_deleted": 0,
    "doc_id": "string",
    "file_removed": true
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``DELETE /api/documents/{doc_id}`` (spec_v2 §4.2).\n\nAlso one entry of a bulk delete's ``deleted`` list.\n\nAttributes:\n    doc_id: The deleted document.\n    chunks_deleted: Chunks removed (the pgvector count; the\n        Elasticsearch ``delete_by_query`` removes the same identity-\n        addressed set).\n    file_removed: Whether the source file was also deleted from\n        ``DOCS_PATH`` (requested via ``?remove_file=true`` and only\n        honored inside the corpus directory).",
    "properties": {
        "chunks_deleted": {
            "title": "Chunks Deleted",
            "type": "integer"
        },
        "doc_id": {
            "title": "Doc Id",
            "type": "string"
        },
        "file_removed": {
            "title": "File Removed",
            "type": "boolean"
        }
    },
    "required": [
        "doc_id",
        "chunks_deleted",
        "file_removed"
    ],
    "title": "DocumentDeleteResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

POST /api/documents/{doc_id}/preview/locate

Locate Preview

Description

Find the source page containing a chunk's text, with highlight rects.

The evidence panel's locate step (ADR-010): resolve the document to an openable PDF (containment + content-hash verified; PPTX via the cached conversion), score its pages by word coverage, and return the best page plus normalized highlight rectangles. Every degradable condition — kill switch, unsupported format, missing/edited file, unavailable converter, no textual match — answers 200 available:false with its reason; the GUI falls back to the full-text view.

Args: doc_id: The chunk's parent document. payload: The chunk text to locate. store: The per-request vector store.

Returns: The located page, rects, and coverage — or the degrade reason.

Raises: HTTPException: 404 document_not_found for an unknown id (the one non-degradable condition: there is nothing to fall back to — the GUI shows full text without asking again); 503 corpus_restoring while a backup restore replaces the corpus (the GUI falls back to full text, as for any failed locate).

Input parameters

Parameter In Type Default Nullable Description
doc_id path string No

Request body

{
    "text": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "description": "Body of ``POST /api/documents/{doc_id}/preview/locate`` (ADR-010).\n\nAttributes:\n    text: The chunk content to locate — both wire shapes (the live\n        ``retrieval`` event and persisted ``message_sources`` snapshots)\n        already deliver it to the client, so history previews work\n        without any migration.",
    "properties": {
        "text": {
            "maxLength": 20000,
            "minLength": 1,
            "title": "Text",
            "type": "string"
        }
    },
    "required": [
        "text"
    ],
    "title": "PreviewLocateRequest",
    "type": "object"
}

Responses

{
    "available": true,
    "coverage": null,
    "page": null,
    "page_count": null,
    "reason": null,
    "rects": [
        {
            "x0": 10.12,
            "x1": 10.12,
            "y0": 10.12,
            "y1": 10.12
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Where a chunk's text lives in its source document (ADR-010).\n\n``available=False`` + ``reason`` covers every degradable condition\n(``preview_disabled`` | ``unsupported_type`` | ``file_missing`` |\n``file_changed`` | ``conversion_unavailable`` | ``conversion_failed`` |\n``no_match``) — the GUI falls back to the full-text view on any of\nthem, never a dead panel.\n\nAttributes:\n    available: Whether a page was located (the fields below are set).\n    reason: The degradable condition when ``available`` is false.\n    page: Best-matching page, 1-based.\n    page_count: Total pages in the document (also set on ``no_match``).\n    rects: Highlight rectangles for the chunk's text on that page.\n    coverage: The winning page's containment score in ``[0, 1]`` (also\n        set on ``no_match`` — the score that stayed below the floor).",
    "properties": {
        "available": {
            "title": "Available",
            "type": "boolean"
        },
        "coverage": {
            "anyOf": [
                {
                    "type": "number"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Coverage"
        },
        "page": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Page"
        },
        "page_count": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Page Count"
        },
        "reason": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Reason"
        },
        "rects": {
            "default": [],
            "items": {
                "$ref": "#/components/schemas/PreviewRect"
            },
            "title": "Rects",
            "type": "array"
        }
    },
    "required": [
        "available"
    ],
    "title": "PreviewLocateResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

GET /api/documents/{doc_id}/preview/page/{page}

Preview Page

Description

Serve one rendered page of a source document as a PNG.

The image half of the preview pair (ADR-010), immutable-cacheable: doc_id is content-hashed and the resolution step re-verifies the on-disk content_hash before rendering, so a drifted file 404s (file_changed) rather than serving a lying image. Degradable reasons are 404 codes here — an <img> can't read a JSON envelope, and the client only requests pages a successful locate named.

Args: doc_id: The document to render. page: 1-based page number (a locate response's page). store: The per-request vector store.

Returns: The PNG at PREVIEW_RENDER_WIDTH pixels wide, marked Cache-Control: public, max-age=31536000, immutable.

Raises: HTTPException: 404 document_not_found for an unknown id; 404 with the degrade reason as its code (preview_disabled | unsupported_type | file_missing | file_changed | conversion_unavailable | conversion_failed) when the document can't resolve; 404 page_out_of_range beyond the last page; 503 corpus_restoring while a backup restore replaces the corpus.

Input parameters

Parameter In Type Default Nullable Description
doc_id path string No
page path integer No

Responses

Schema of the response body

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

graph


POST /api/graph/build

Start Graph Build

Description

Trigger a background graph build and return its handle.

This is also how a killed build resumes. The engine keeps document statuses on disk and re-selects every in-flight or failed document, so pressing Build again after a crash finishes the work instead of repeating it — the run's summary reports what it inherited as resumed. That is why reingest is a separate, explicit flag: it is the one path that throws the working directory away.

Args: payload: reingest (wipe and re-index — the only thing that clears the graph-stale flag), plus the message_limit / since bounds that make a minutes-long spot check possible on an archive whose full build runs for days. runner: The process-wide graph build runner. preflight: The awaitable reachability check (structured 503).

Returns: The new run's snapshot (state running).

Raises: HTTPException: 403 graph_disabled when the kill switch is off; 409 graph_build_running while a build is in flight; 409 corpus_busy while a graph backup holds the corpus (ADR-019 — the message names it); 503 <service>_unreachable when a required model service is down.

Request body

{
    "message_limit": null,
    "reingest": true,
    "since": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "additionalProperties": false,
    "description": "Body of ``POST /api/graph/build`` (spec_graphrag §5.2).\n\nAttributes:\n    reingest: Wipe the engine's working directory and index from\n        scratch. The **only** thing that clears the graph-stale flag\n        (stage-2 decision #16) — and the only reason to pay a full\n        backfill twice.\n    message_limit: Index at most this many messages, newest first — the\n        bounded escape hatch that makes a minutes-long spot check\n        possible on an archive whose full build runs for days. A bounded\n        build never prunes: its render is partial by construction, so\n        documents it does not mention are kept, not deleted.\n    since: Ignore messages older than this date (the other half of the\n        bound; same no-prune rule).",
    "properties": {
        "message_limit": {
            "anyOf": [
                {
                    "minimum": 1.0,
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Message Limit"
        },
        "reingest": {
            "default": false,
            "title": "Reingest",
            "type": "boolean"
        },
        "since": {
            "anyOf": [
                {
                    "format": "date",
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Since"
        }
    },
    "title": "GraphBuildRequest",
    "type": "object"
}

Responses

{
    "bounded": true,
    "error": null,
    "finished_at": null,
    "message_limit": null,
    "reingest": true,
    "run_id": "string",
    "since": null,
    "started_at": "2022-04-13T15:42:05.901Z",
    "state": "string",
    "summary": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "One graph build run's state (``POST /api/graph/build`` + its stream).\n\nAttributes:\n    run_id: The run handle.\n    state: ``running`` | ``completed`` | ``failed``.\n    reingest: Whether the run wiped the working directory first.\n    bounded: Whether ``message_limit``/``since`` narrowed the render (a\n        bounded run deliberately skips the removal pass).\n    message_limit: The requested message cap, if any.\n    since: The requested date floor, if any.\n    started_at: When the run started.\n    finished_at: When it reached a terminal state (``None`` while\n        running).\n    summary: The final counters (terminal states only; a run that died\n        before the engine returned carries ``None``).\n    error: The run-level failure (``failed`` only; per-document failures\n        ride in ``summary.failures`` and the document statuses).",
    "properties": {
        "bounded": {
            "title": "Bounded",
            "type": "boolean"
        },
        "error": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Error"
        },
        "finished_at": {
            "anyOf": [
                {
                    "format": "date-time",
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Finished At"
        },
        "message_limit": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Message Limit"
        },
        "reingest": {
            "title": "Reingest",
            "type": "boolean"
        },
        "run_id": {
            "title": "Run Id",
            "type": "string"
        },
        "since": {
            "anyOf": [
                {
                    "format": "date",
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Since"
        },
        "started_at": {
            "format": "date-time",
            "title": "Started At",
            "type": "string"
        },
        "state": {
            "title": "State",
            "type": "string"
        },
        "summary": {
            "anyOf": [
                {
                    "$ref": "#/components/schemas/GraphBuildSummaryOut"
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "run_id",
        "state",
        "reingest",
        "bounded",
        "started_at"
    ],
    "title": "GraphBuildRunOut",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

GET /api/graph/build/status

Graph Build Status

Description

Stream the current (or last) graph build's progress as SSE.

Frames: status (snapshot; also terminal, with the summary), progress (scan → parse per file → bound/reset → index → sampled process ticks), log (relayed varagity.graph lines). The stream replays from the start, so a browser opened six hours into a backfill renders the same picture one opened at the start did.

Args: runner: The process-wide graph build runner.

Yields: The framed events, oldest first, then live until terminal.

Responses

Schema of the response body


GET /api/graph/documents

List Graph Documents

Description

List every file in the graph corpus directory.

A directory scan, not a table read: the graph's own record of what it indexed lives in the engine's workdir, and what this tab must show is what has been dropped in — sidecars and contacts files included. Files the last build parsed also carry their doc_id and a parse summary; files no build has scanned carry None for both, because parsing a multi-gigabyte database is a build's job. The reserved varagity-exports/ folder's backup bundles are never listed.

Args: runner: The process-wide graph build runner (holds the last scan).

Returns: One entry per file, in path order.

Responses

[
    {
        "doc_id": null,
        "modified_at": "2022-04-13T15:42:05.901Z",
        "name": "string",
        "parse": null,
        "relative_path": "string",
        "size_bytes": 0
    }
]
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "items": {
        "$ref": "#/components/schemas/GraphDocumentOut"
    },
    "title": "Response List Graph Documents Api Graph Documents Get",
    "type": "array"
}

POST /api/graph/documents

Upload Graph Documents

Description

Upload message-source file(s) into GRAPH_DOCS_PATH (no auto-build).

Each file is written independently and then sniffed: a file no registered message source claims is deleted again and reported as unsupported_graph_source, so a mixed drop partially succeeds and the corpus directory never fills with unreadable files. The chunk-RAG ALLOWED_EXTENSIONS list does not apply — contents decide.

Copy the whole SQLite set — chat.db and its -wal/-shm sidecars: a live database keeps its most recent messages in the write-ahead log, and uploading the .db alone silently loses them. Sidecars are stored beside their database without being sniffed.

Args: files: The multipart file parts.

Returns: Per-file outcomes, in upload order.

Raises: HTTPException: 403 graph_disabled when the kill switch is off; 422 too_many_files when the batch busts UPLOAD_MAX_FILES; 422 no_file_stored when every file was rejected on its own merits; 500 graph_docs_path_not_writable when nothing landed because the server couldn't write GRAPH_DOCS_PATH (in compose, the ./graph-docs bind mount must be writable by the api container's user).

Request body

{
    "files": [
        "string"
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "properties": {
        "files": {
            "items": {
                "contentMediaType": "application/octet-stream",
                "type": "string"
            },
            "title": "Files",
            "type": "array"
        }
    },
    "required": [
        "files"
    ],
    "title": "Body_upload_graph_documents_api_graph_documents_post",
    "type": "object"
}

Responses

{
    "files": [
        {
            "file_name": "string",
            "reason": null,
            "relative_path": null,
            "replaced": true,
            "size_bytes": 0,
            "stored": true
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``POST /api/graph/documents``.\n\nAttributes:\n    files: Per-file outcomes, in upload order (the corpus upload's\n        shape, plus its ``unsupported_graph_source`` rejection).",
    "properties": {
        "files": {
            "items": {
                "$ref": "#/components/schemas/UploadedFileOut"
            },
            "title": "Files",
            "type": "array"
        }
    },
    "required": [
        "files"
    ],
    "title": "GraphUploadResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

DELETE /api/graph/documents/{name}

Delete Graph Document

Description

Remove one file from the graph corpus directory and flag the graph stale.

Deleting the source does not retract what the graph already extracted from it — that costs a rebuild — so this sets the graph-stale flag and the tab surfaces it, rather than letting the graph and its corpus diverge silently (stage-2 decision #16).

Args: name: The file's path relative to GRAPH_DOCS_PATH. store: The per-request app-settings store (the stale flag). gates: The process's per-corpus write gates.

Returns: What was removed and whether the graph is now flagged stale.

Raises: HTTPException: 409 corpus_busy while a backup restore holds the graph (it ends by writing the bundle's own stale flag, which would undo this one — checked before anything is touched); 404 graph_document_not_found when no such file lives inside GRAPH_DOCS_PATH — which covers a path trying to escape it, since an escaping path is not a file this route has, and anything in the reserved varagity-exports/ folder (a backup bundle is not a graph source, whatever path reaches it).

Input parameters

Parameter In Type Default Nullable Description
name path string No

Responses

{
    "graph_stale": true,
    "relative_path": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``DELETE /api/graph/documents/{name}``.\n\nAttributes:\n    relative_path: The removed file's path under ``GRAPH_DOCS_PATH``.\n    graph_stale: Whether the graph is now flagged stale — it keeps the\n        deleted file's messages until the next rebuild, and saying so is\n        the honest alternative to silently diverging (stage-2 decision\n        #16). ``False`` only when the flag could not be persisted.",
    "properties": {
        "graph_stale": {
            "title": "Graph Stale",
            "type": "boolean"
        },
        "relative_path": {
            "title": "Relative Path",
            "type": "string"
        }
    },
    "required": [
        "relative_path",
        "graph_stale"
    ],
    "title": "GraphDocumentDeleteResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

GET /api/graph/entities/{name}

Graph Entity

Description

Inspect one entity: its summary, its neighbourhood, its source days.

The graph view's click-through (spec_graphrag §4.4): a name in the picture resolves to what the engine merged about it, the relations around it, and — the drill-down that makes the graph answerable — the transcript days it was extracted from.

Args: name: The entity's name (path-encoded; extracted names may contain slashes). Canonical spelling resolves directly; any other casing resolves through a whole-graph lookup, because the engine's own label match is exact. service: The process-wide graph service.

Returns: The entity with its depth-1 relations and its source days.

Raises: HTTPException: 403 graph_disabled when the kill switch is off; 404 entity_not_found when the graph holds no such entity (which includes a graph that has not been built yet); 503 graph_unavailable when the engine will not open.

Input parameters

Parameter In Type Default Nullable Description
name path string No

Responses

{
    "entity": {
        "degree": 0,
        "description": null,
        "entity_type": null,
        "id": "string"
    },
    "relations": [
        {
            "description": null,
            "id": "string",
            "label": null,
            "source": "string",
            "target": "string"
        }
    ],
    "sources": [
        {
            "doc_key": "string",
            "message_count": 0,
            "span": "string",
            "thread_name": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/graph/entities/{name}`` — the inspect panel.\n\nAttributes:\n    entity: The entity itself, with the summary the engine merged from\n        every mention.\n    relations: Every edge touching it in a depth-1 slice, i.e. its\n        immediate neighbourhood.\n    sources: The transcript days it was extracted from — the drill-down\n        from a name in the graph back to the conversations behind it.\n        Empty when the engine recorded no document provenance.",
    "properties": {
        "entity": {
            "$ref": "#/components/schemas/GraphExportNodeOut"
        },
        "relations": {
            "default": [],
            "items": {
                "$ref": "#/components/schemas/GraphExportEdgeOut"
            },
            "title": "Relations",
            "type": "array"
        },
        "sources": {
            "default": [],
            "items": {
                "$ref": "#/components/schemas/GraphTranscriptRefOut"
            },
            "title": "Sources",
            "type": "array"
        }
    },
    "required": [
        "entity"
    ],
    "title": "GraphEntityDetailOut",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

GET /api/graph/export

Export Graph

Description

Read a drawable slice of the graph (spec_graphrag §4.4).

The graph view's whole data source. The default is the whole graph, degree-ordered so the cap keeps the most connected entities, and truncated says when the cap bit — the view surfaces that rather than implying it drew everything.

Args: service: The process-wide graph service. label: Entity name to centre the slice on; "*" (the default) takes the whole graph. max_depth: Hops to walk out from label (ignored for "*"). max_nodes: Node cap, defaulting to its own ceiling :data:MAX_EXPORT_NODES (the view draws the fullest slice the contract allows). Above it the request is a 422 rather than a silent clamp: a caller asking for more than the view can render should hear so.

Returns: The slice; empty (not an error) when nothing has been indexed yet.

Raises: HTTPException: 403 graph_disabled when the kill switch is off; 503 graph_unavailable when the engine will not open.

Input parameters

Parameter In Type Default Nullable Description
label query string * No
max_depth query integer 3 No
max_nodes query integer 5000 No

Responses

{
    "edges": [
        {
            "description": null,
            "id": "string",
            "label": null,
            "source": "string",
            "target": "string"
        }
    ],
    "nodes": [
        {
            "degree": 0,
            "description": null,
            "entity_type": null,
            "id": "string"
        }
    ],
    "truncated": true
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/graph/export`` — what the graph view draws.\n\nAttributes:\n    nodes: The exported entities, engine order (degree-descending for a\n        whole-graph export, so the cap keeps the most connected).\n    edges: Every edge between exported nodes.\n    truncated: Whether the cap hid nodes the graph actually holds. The\n        view **must** surface this rather than implying it drew\n        everything — an honest partial picture, per §4.4's scale ceiling.",
    "properties": {
        "edges": {
            "default": [],
            "items": {
                "$ref": "#/components/schemas/GraphExportEdgeOut"
            },
            "title": "Edges",
            "type": "array"
        },
        "nodes": {
            "default": [],
            "items": {
                "$ref": "#/components/schemas/GraphExportNodeOut"
            },
            "title": "Nodes",
            "type": "array"
        },
        "truncated": {
            "default": false,
            "title": "Truncated",
            "type": "boolean"
        }
    },
    "title": "GraphExportOut",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

GET /api/graph/status

Graph Status

Description

Report the graph's size, staleness, and build state (the tab's poll).

Reads the workdir's summary sidecar rather than the graph itself, so a poll never walks a multi-megabyte graphml (stage-2 decision #18), and skips the engine entirely when nothing has been built here — an unbuilt graph must not make a status poll open a session and pay an engine's startup. workdir_files lists the workdir (a directory read, no file opened) by the graph export's own rule, so the backup panel's "nothing built yet" can never disagree with the export route's nothing_to_export.

Args: runner: The process-wide graph build runner (build state). service: The process-wide graph service (document statuses). store: The per-request app-settings store (the stale flag).

Returns: The status snapshot; every size field is None/empty when nothing has been indexed, never a zero that would read like an empty graph.

Responses

{
    "building": true,
    "documents": {},
    "enabled": true,
    "entities": null,
    "last_build": null,
    "message_guids": null,
    "relations": null,
    "stale": true,
    "workdir_files": 0
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/graph/status`` — the graph tab's poll surface.\n\nEvery size field is optional because \"nothing has been built here yet\"\nand \"the engine would not say\" are both real states, and reporting them\nas ``0`` would read like an empty graph (the honesty rule\n:class:`~varagity.graph.records.GraphStats` sets).\n\nAttributes:\n    enabled: The ``GRAPH_ENABLED`` kill switch.\n    stale: Whether a source file was deleted since the last rebuild.\n    building: Whether a build is running in this API process.\n    documents: The engine's document counts by status (its own\n        vocabulary: ``pending``, ``processing``, ``processed``,\n        ``failed``, …); empty when nothing has been indexed.\n    entities: Entity count from the workdir's summary sidecar.\n    relations: Relation count from the same.\n    message_guids: Messages the workdir's manifest accounts for.\n    workdir_files: Files in the engine's working directory that a\n        backup would copy (ADR-019) — ``0`` when nothing has been built.\n        The backup panel's \"nothing built yet\": the export route's own\n        ``nothing_to_export`` rule, so the two can never disagree (an\n        unopenable graph, or a first build killed before its summary,\n        still has files to back up and to replace).\n    last_build: The current (or last) build run in this API process.",
    "properties": {
        "building": {
            "title": "Building",
            "type": "boolean"
        },
        "documents": {
            "additionalProperties": {
                "type": "integer"
            },
            "default": {},
            "title": "Documents",
            "type": "object"
        },
        "enabled": {
            "title": "Enabled",
            "type": "boolean"
        },
        "entities": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Entities"
        },
        "last_build": {
            "anyOf": [
                {
                    "$ref": "#/components/schemas/GraphBuildRunOut"
                },
                {
                    "type": "null"
                }
            ]
        },
        "message_guids": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Message Guids"
        },
        "relations": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Relations"
        },
        "stale": {
            "title": "Stale",
            "type": "boolean"
        },
        "workdir_files": {
            "default": 0,
            "title": "Workdir Files",
            "type": "integer"
        }
    },
    "required": [
        "enabled",
        "stale",
        "building"
    ],
    "title": "GraphStatusOut",
    "type": "object"
}

groups


GET /api/groups

List Groups

Description

List every conversation group, name order.

Args: store: The per-request conversation store.

Returns: All groups, including empty ones.

Responses

[
    {
        "created_at": "2022-04-13T15:42:05.901Z",
        "group_id": "string",
        "name": "string"
    }
]
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "items": {
        "$ref": "#/components/schemas/GroupOut"
    },
    "title": "Response List Groups Api Groups Get",
    "type": "array"
}

POST /api/groups

Create Group

Description

Create a conversation group.

Args: payload: The new group's display name. store: The per-request conversation store.

Returns: The created group.

Request body

{
    "name": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "additionalProperties": false,
    "description": "Body of ``POST /api/groups``.\n\nAttributes:\n    name: Display name of the new group.",
    "properties": {
        "name": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Name",
            "type": "string"
        }
    },
    "required": [
        "name"
    ],
    "title": "GroupCreateRequest",
    "type": "object"
}

Responses

{
    "created_at": "2022-04-13T15:42:05.901Z",
    "group_id": "string",
    "name": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "One sidebar conversation group (a user-created folder).\n\nAttributes:\n    group_id: The app-generated id.\n    name: Display name.\n    created_at: Creation timestamp.",
    "properties": {
        "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
        },
        "group_id": {
            "title": "Group Id",
            "type": "string"
        },
        "name": {
            "title": "Name",
            "type": "string"
        }
    },
    "required": [
        "group_id",
        "name",
        "created_at"
    ],
    "title": "GroupOut",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

DELETE /api/groups/{group_id}

Delete Group

Description

Delete a group; its conversations survive, ungrouped.

Args: group_id: The group to delete. store: The per-request conversation store.

Returns: An empty 204 response.

Raises: HTTPException: 404 group_not_found for an unknown id.

Input parameters

Parameter In Type Default Nullable Description
group_id path string No

Responses

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

ingest


POST /api/ingest

Start Ingest

Description

Trigger a background ingest run and return its handle.

Args: payload: reingest=true re-processes unchanged files (the stale-corpus action; setting changes don't change content hashes). runner: The process-wide ingest runner. preflight: The awaitable reachability check (structured 503).

Returns: The new run's snapshot (state running).

Raises: HTTPException: 409 ingest_already_running while a run is in flight; 409 corpus_busy while a backup export or restore holds the corpus (ADR-019 — the message names it); 503 <service>_unreachable when a required backing service is down.

Request body

{
    "reingest": true
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "description": "Body of ``POST /api/ingest``.\n\nAttributes:\n    reingest: Delete each discovered document's previous ingest and\n        re-process it (required after reingest-affecting setting\n        changes — content hashes don't change, so unchanged files are\n        otherwise skipped).",
    "properties": {
        "reingest": {
            "default": false,
            "title": "Reingest",
            "type": "boolean"
        }
    },
    "title": "IngestStartRequest",
    "type": "object"
}

Responses

{
    "error": null,
    "finished_at": null,
    "reingest": true,
    "run_id": "string",
    "started_at": "2022-04-13T15:42:05.901Z",
    "state": "string",
    "summary": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "One ingest run's state (``POST /api/ingest`` + the status stream).\n\nAttributes:\n    run_id: The run handle.\n    state: ``running`` | ``completed`` | ``failed``.\n    reingest: Whether the run re-processes unchanged files.\n    started_at: When the run started.\n    finished_at: When it reached a terminal state (``None`` while\n        running).\n    summary: The final counters (terminal states only; ``failed`` runs\n        may carry ``None`` when the flow died before summarizing).\n    error: The flow-level failure (``failed`` only; per-file failures\n        ride in ``summary.failed`` and the ``log`` events instead).",
    "properties": {
        "error": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Error"
        },
        "finished_at": {
            "anyOf": [
                {
                    "format": "date-time",
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Finished At"
        },
        "reingest": {
            "title": "Reingest",
            "type": "boolean"
        },
        "run_id": {
            "title": "Run Id",
            "type": "string"
        },
        "started_at": {
            "format": "date-time",
            "title": "Started At",
            "type": "string"
        },
        "state": {
            "title": "State",
            "type": "string"
        },
        "summary": {
            "anyOf": [
                {
                    "$ref": "#/components/schemas/IngestSummaryOut"
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "run_id",
        "state",
        "reingest",
        "started_at"
    ],
    "title": "IngestRunOut",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

{
    "error": {
        "code": "string",
        "message": "string"
    }
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "The structured error envelope every non-2xx response carries.\n\nAttributes:\n    error: The error body.",
    "properties": {
        "error": {
            "$ref": "#/components/schemas/ErrorBody"
        }
    },
    "required": [
        "error"
    ],
    "title": "ErrorResponse",
    "type": "object"
}

GET /api/ingest/status

Ingest Status

Description

Stream the current (or last) ingest run's progress as SSE.

Frames: status (snapshot; also terminal, with the summary), progress (per stage/file/chunk), log (relayed pipeline lines). With no run in this API process the stream is a single idle status frame; after a terminal run it replays that run and closes.

Args: runner: The process-wide ingest runner.

Yields: The framed events, oldest first, then live until terminal.

Responses

Schema of the response body

settings


GET /api/settings

Read Settings

Description

Report the effective settings and the corpus-stale flag.

Args: store: The per-request app-settings store.

Returns: The overridable catalog with effective (env + override) values.

Responses

{
    "corpus_stale": true,
    "graph_stale": true,
    "settings": [
        {
            "choices": null,
            "group": "string",
            "name": "string",
            "overridden": true,
            "reingest_affecting": true,
            "value": null
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/settings`` and ``PATCH /api/settings``.\n\nAttributes:\n    settings: The full overridable catalog with effective values.\n    corpus_stale: Whether a reingest-affecting setting changed since the\n        corpus was last (re)ingested — the \"Re-ingest to apply\" banner.\n    graph_stale: Whether the graph corpus changed since the graph was\n        last (re)built — the graph tab's \"Re-build to apply\" banner\n        (stage-2 decision #16).",
    "properties": {
        "corpus_stale": {
            "title": "Corpus Stale",
            "type": "boolean"
        },
        "graph_stale": {
            "default": false,
            "title": "Graph Stale",
            "type": "boolean"
        },
        "settings": {
            "items": {
                "$ref": "#/components/schemas/SettingOut"
            },
            "title": "Settings",
            "type": "array"
        }
    },
    "required": [
        "settings",
        "corpus_stale"
    ],
    "title": "SettingsResponse",
    "type": "object"
}

PATCH /api/settings

Patch Settings

Description

Apply, validate, and persist runtime setting overrides.

A None value clears that override (reverting to the env value). Validation runs on the merged whole through every config validator, so linked constraints (the fusion weight pair, the rerank bounds) hold exactly as they do for .env — an invalid patch changes nothing.

Args: payload: The overrides to set or clear. store: The per-request app-settings store (persistence + flag). vector_store: The vector store (the corpus-emptiness check). gates: The process's per-corpus write gates — during a backup restore the emptiness check is skipped (see below).

Returns: The full post-patch catalog; corpus_stale reports whether a reingest is now needed.

Raises: HTTPException: 422 unknown_setting for a name outside the catalog; 422 invalid_settings when the merged settings fail validation; 503 postgres_unreachable if persistence fails (the overrides are rolled back — the process never diverges from the table).

Request body

{
    "overrides": {}
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "additionalProperties": false,
    "description": "Body of ``PATCH /api/settings``.\n\nAttributes:\n    overrides: Setting name → new value, or ``None`` to clear that\n        override (reverting to the env value). Linked settings (the\n        fusion weight pair) must be patched together — validation runs\n        on the merged whole.",
    "properties": {
        "overrides": {
            "additionalProperties": {
                "anyOf": [
                    {
                        "type": "boolean"
                    },
                    {
                        "type": "integer"
                    },
                    {
                        "type": "number"
                    },
                    {
                        "type": "string"
                    },
                    {
                        "type": "null"
                    }
                ]
            },
            "minProperties": 1,
            "title": "Overrides",
            "type": "object"
        }
    },
    "required": [
        "overrides"
    ],
    "title": "SettingsPatchRequest",
    "type": "object"
}

Responses

{
    "corpus_stale": true,
    "graph_stale": true,
    "settings": [
        {
            "choices": null,
            "group": "string",
            "name": "string",
            "overridden": true,
            "reingest_affecting": true,
            "value": null
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "description": "Response of ``GET /api/settings`` and ``PATCH /api/settings``.\n\nAttributes:\n    settings: The full overridable catalog with effective values.\n    corpus_stale: Whether a reingest-affecting setting changed since the\n        corpus was last (re)ingested — the \"Re-ingest to apply\" banner.\n    graph_stale: Whether the graph corpus changed since the graph was\n        last (re)built — the graph tab's \"Re-build to apply\" banner\n        (stage-2 decision #16).",
    "properties": {
        "corpus_stale": {
            "title": "Corpus Stale",
            "type": "boolean"
        },
        "graph_stale": {
            "default": false,
            "title": "Graph Stale",
            "type": "boolean"
        },
        "settings": {
            "items": {
                "$ref": "#/components/schemas/SettingOut"
            },
            "title": "Settings",
            "type": "array"
        }
    },
    "required": [
        "settings",
        "corpus_stale"
    ],
    "title": "SettingsResponse",
    "type": "object"
}

{
    "detail": [
        {
            "ctx": {},
            "input": null,
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
        }
    },
    "title": "HTTPValidationError",
    "type": "object"
}

observability


GET /metrics

Metrics

Description

Expose the metric catalog for a Prometheus scrape.

Returns: The default registry rendered in the Prometheus text format (text/plain; version=0.0.4).

Responses

Schema of the response body


Schemas

BackupLogEvent

Name Type Description
level string
message string

BackupProgressEvent

Name Type Description
current
stage string
total

BackupRestoreRequest

Name Type Description
name string

BackupRunOut

Name Type Description
bundle
corpus string
error
finished_at
operation string
run_id string
saved_to
started_at string(date-time)
state string
summary

BackupStatusEvent

Name Type Description
run

BackupSummaryOut

Name Type Description
counts
size_bytes integer
stale
wall_clock_s number

Body_upload_documents_api_documents_post

Name Type Description
files Array<string>
modified
paths

Body_upload_graph_documents_api_graph_documents_post

Name Type Description
files Array<string>

BundleManifestOut

Name Type Description
corpus string
counts
created_at string(date-time)
embedding_dim integer
embedding_model string
engine
format string
ingest_settings
members Array<BundleMember>
stale boolean
version integer

BundleMember

Name Type Description
name string
sha256 string
size_bytes integer

BundleOut

Name Type Description
drift Array<string>
manifest
modified_at string(date-time)
name string
reason
restorable boolean
size_bytes integer

ChatOverrides

Name Type Description
chat_engine
retrieval_method
top_k

ChatRequest

Name Type Description
conversation_id
corpus
overrides
query string

ConfigResponse

Name Type Description
allowed_extensions Array<string>
chunkers Array<string>
llm_model_types Array<string>
model_types Array<string>
ocr_engines Array<string>
preview_enabled boolean
ranges
retrievers Array<string>
upload_max_mb integer

ConversationCreateRequest

Name Type Description
title

ConversationDetailOut

Name Type Description
conversation_id string
created_at string(date-time)
messages Array<MessageOut>
title string
updated_at string(date-time)

ConversationSummaryOut

Name Type Description
conversation_id string
created_at string(date-time)
group_id
message_count integer
title string
updated_at string(date-time)

ConversationUpdateRequest

Name Type Description
group_id

DeltaEvent

Name Type Description
delta string

DocumentBulkDeleteRequest

Name Type Description
doc_ids Array<string>

DocumentBulkDeleteResponse

Name Type Description
deleted Array<DocumentDeleteResponse>
not_found Array<string>

DocumentDeleteResponse

Name Type Description
chunks_deleted integer
doc_id string
file_removed boolean

DocumentOut

Name Type Description
content_hash string
doc_id string
extraction_mix
file_name string
file_type string
ingested_at string(date-time)
n_chunks integer
relative_path
source string

DoneEvent

Name Type Description
answer string
conversation_id string
message_id string
usage UsageInfo

EngineFormat

Name Type Description
name string
package string
version string

ErrorBody

Name Type Description
code string
message string

ErrorEvent

Name Type Description
code string
message string

ErrorResponse

Name Type Description
error ErrorBody

GraphBuildLogEvent

Name Type Description
level string
message string

GraphBuildProgressEvent

Name Type Description
current
docs_done
docs_total
file
stage string
total

GraphBuildRequest

Name Type Description
message_limit
reingest boolean
since

GraphBuildRunOut

Name Type Description
bounded boolean
error
finished_at
message_limit
reingest boolean
run_id string
since
started_at string(date-time)
state string
summary

GraphBuildStatusEvent

Name Type Description
run

GraphBuildSummaryOut

Name Type Description
documents
failures Array<string>
files_failed integer
files_parsed integer
files_scanned integer
messages_indexed integer
messages_parsed integer
messages_seen integer
resumed integer
wall_clock_s number

GraphDocumentDeleteResponse

Name Type Description
graph_stale boolean
relative_path string

GraphDocumentOut

Name Type Description
doc_id
modified_at string(date-time)
name string
parse
relative_path string
size_bytes integer

GraphEntityDetailOut

Name Type Description
entity GraphExportNodeOut
relations Array<GraphExportEdgeOut>
sources Array<GraphTranscriptRefOut>

GraphEntityOut

Name Type Description
name string
summary
type

GraphExportEdgeOut

Name Type Description
description
id string
label
source string
target string

GraphExportNodeOut

Name Type Description
degree integer
description
entity_type
id string

GraphExportOut

Name Type Description
edges Array<GraphExportEdgeOut>
nodes Array<GraphExportNodeOut>
truncated boolean

GraphParseSummary

Name Type Description
first
last
messages integer
threads integer

GraphRelationOut

Name Type Description
description
label
source string
target string

GraphRetrievalPayload

Name Type Description
entities Array<GraphEntityOut>
mode string
relations Array<GraphRelationOut>
transcripts Array<GraphTranscriptOut>

GraphStatusOut

Name Type Description
building boolean
documents
enabled boolean
entities
last_build
message_guids
relations
stale boolean
workdir_files integer

GraphTranscriptOut

Name Type Description
doc_key string
excerpt string
message_count integer
span string
thread_name string

GraphTranscriptRefOut

Name Type Description
doc_key string
message_count integer
span string
thread_name string

GraphUploadResponse

Name Type Description
files Array<UploadedFileOut>

GroupCreateRequest

Name Type Description
name string

GroupOut

Name Type Description
created_at string(date-time)
group_id string
name string

HealthResponse

Name Type Description
ok boolean
services

HTTPValidationError

Name Type Description
detail Array<ValidationError>

IngestLogEvent

Name Type Description
level string
message string

IngestProgressEvent

Name Type Description
current
file
files_done
files_total
outcome
stage string
total

IngestRunOut

Name Type Description
error
finished_at
reingest boolean
run_id string
started_at string(date-time)
state string
summary

IngestStartRequest

Name Type Description
reingest boolean

IngestStatusEvent

Name Type Description
run

IngestSummaryOut

Name Type Description
chunks integer
discovered integer
failed integer
ingested integer
no_text integer
skipped integer
unsupported integer

MessageOut

Name Type Description
chat_engine
condensed_query
content string
created_at string(date-time)
graph_evidence
latency_ms
message_id string
reasoning
retrieval_method
role string
sources Array<MessageSourceOut>
target_corpus

MessageSourceOut

Name Type Description
chunk_id string
rank integer
trace

NumericRange

Name Type Description
max
min

PreviewLocateRequest

Name Type Description
text string

PreviewLocateResponse

Name Type Description
available boolean
coverage
page
page_count
reason
rects Array<PreviewRect>

PreviewRect

Name Type Description
x0 number
x1 number
y0 number
y1 number

RetrievalEvent

Name Type Description
chunks Array<RetrievedChunk>
condensed_query
corpus string
graph
method string
reranked_to
top_k integer

RetrievalTrace

Name Type Description
bm25_rank
bm25_score
final_rank integer
fused_rank integer
fused_score number
rerank_delta
rerank_score
semantic_rank
semantic_score

RetrievedChunk

Name Type Description
chunk_id string
content string
context
doc_id string
metadata
original_index integer
score number
trace

ServiceHealth

Name Type Description
detail
ok boolean

SettingOut

Name Type Description
choices
group string
name string
overridden boolean
reingest_affecting boolean
value

SettingsPatchRequest

Name Type Description
overrides

SettingsResponse

Name Type Description
corpus_stale boolean
graph_stale boolean
settings Array<SettingOut>

StatsEvent

Name Type Description
completion_tokens integer
tokens_per_second number

UploadedFileOut

Name Type Description
file_name string
reason
relative_path
replaced boolean
size_bytes integer
stored boolean

UploadResponse

Name Type Description
files Array<UploadedFileOut>

UsageInfo

Name Type Description
completion_tokens
latency_ms
prompt_tokens
tokens_per_second

ValidationError

Name Type Description
ctx
input
loc Array<>
msg string
type string