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:
403 graph_disabledforgraphwhileGRAPH_ENABLED=false— checked first, before any store is touched.- The preflight:
503 postgres_unreachable(an export reads the rows, or for the graph only its stale flag, and calls no model service). 422 nothing_to_exportwhen nothing is ingested — or, for the graph, when its workdir holds no file to copy (nothing has been built).500 docs_path_not_writable/graph_docs_path_not_writablewhen the exports folder can't be written.409 corpus_busywhile 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:
403 graph_disabledforgraphwhileGRAPH_ENABLED=false.503 postgres_unreachable/es_unreachable— the stores it writes (both forrag; postgres alone forgraph, for its stale flag).404 bundle_not_found— no such bundle in the folder.409 corpus_busy— another operation holds the corpus. Checked before the bundle is even opened.422 bundle_invalid/bundle_incompatible— the bundle's cheap verdict (the list's own: its central directory andbundle.json, for a graph bundle the engine's file format included), or a damage an earlier restore of this very file found.409 corpus_busy— forrag, 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 itsbundle.jsonmanifest, read alone through the zip's central directory, ormanifest: nullfor a zip that isn't a readable Varagity backup — and the cheap restore verdict:restorable, thereasonwhen it isn't (the same reasons a restore's 422 gives, worded for the panel), anddrift, 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 carriesengine, 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 asapplication/zipwithContent-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
}
]
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"
}
}
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"
}
]
}
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
}
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"
}
}
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"
}
}
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"
}
}
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"
}
}
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"
}
}
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="
Schema of the response body
{
"format": "binary",
"type": "string"
}
{
"error": {
"code": "string",
"message": "string"
}
}
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"
}
}
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"
}
]
}
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"
}
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
}
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"
}
}
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"
}
}
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"
}
}
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"
}
}
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"
}
}
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"
}
]
}
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"
}
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"
}
}
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"
}
}
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"
}
}
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
}
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": {}
}
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"
}
]
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
}
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"
}
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"
}
]
}
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"
}
]
}
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"
}
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"
}
]
}
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
}
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"
}
]
}
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"
}
]
Schema of the response body
{
"items": {
"$ref": "#/components/schemas/DocumentOut"
},
"title": "Response List Documents Api Documents Get",
"type": "array"
}
{
"error": {
"code": "string",
"message": "string"
}
}
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
}
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
}
]
}
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"
}
]
}
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"
]
}
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"
]
}
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"
}
}
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"
}
]
}
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
}
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"
}
}
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"
}
}
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"
}
]
}
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"
}
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
}
]
}
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"
}
]
}
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"
}
}
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"
}
]
}
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"
}
}
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
}
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
}
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"
}
}
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"
}
}
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"
}
]
}
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"
}
}
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
}
]
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"
]
}
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
}
]
}
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"
}
}
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"
}
]
}
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"
}
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"
}
}
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"
}
}
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"
}
]
}
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"
}
]
}
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"
}
}
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"
}
}
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"
}
]
}
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
}
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"
}
}
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"
}
]
}
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
}
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"
}
]
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"
}
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"
}
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"
}
]
}
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"
}
]
}
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
}
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
}
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"
}
}
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"
}
]
}
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"
}
}
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
}
]
}
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": {}
}
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
}
]
}
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"
}
]
}
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 |