{"openapi":"3.1.0","info":{"title":"Tiberius API","version":"0.1.0","description":"API-first reply-drafting agent for chat-based teams.\n\n**Pipeline per `/reply` call:** hybrid retrieval (HNSW vectors + tsvector FTS, fused via RRF) + metadata-filtered search + entity-triggered lookup → LLM listwise rerank → structured-slot prompt → grounded generation → multi-signal confidence (retrieval coverage + intent confidence + LLM groundedness). Below-threshold drafts return `suggested_tool: \"flag_for_review\"`.\n\n**Auth:** every request needs `Authorization: Bearer tib_…`. Keys are either **agent-scope** (created from the agent's API-keys page, can only act on that one agent) or **workspace-scope** (created via `/api/v1/keys`, used by MCP clients that need to span every agent — passes auth for any agent id).\n\n**Response envelope:** all responses are `{ data, error }`. On success `error` is `null`; on failure `data` is `null` and `error` is `{ code, message, details? }`."},"servers":[{"url":"https://asktiberius.de","description":"Production"},{"url":"/","description":"Same origin as the docs"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"tib_*","description":"Bearer token. Use `tib_live_…` (agent-scope) or `tib_ws_…` (workspace-scope)."}},"schemas":{"AgentConfig":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"confidence_threshold":{"default":0.6,"type":"number","minimum":0,"maximum":1},"calendly_url":{"type":"string","format":"uri"},"available_documents":{"default":[],"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"storage_path":{"type":"string"}},"required":["name","description"],"additionalProperties":false}}},"required":["confidence_threshold","available_documents"],"additionalProperties":false},"Agent":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"config":{"type":"object","properties":{"confidence_threshold":{"default":0.6,"type":"number","minimum":0,"maximum":1},"calendly_url":{"type":"string","format":"uri"},"available_documents":{"default":[],"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"storage_path":{"type":"string"}},"required":["name","description"],"additionalProperties":false}}},"required":["confidence_threshold","available_documents"],"additionalProperties":false},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","config","created_at","updated_at"],"additionalProperties":false},"AgentCreate":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":1000},"config":{"type":"object","properties":{"confidence_threshold":{"default":0.6,"type":"number","minimum":0,"maximum":1},"calendly_url":{"type":"string","format":"uri"},"available_documents":{"default":[],"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"storage_path":{"type":"string"}},"required":["name","description"],"additionalProperties":false}}},"additionalProperties":false}},"required":["name"],"additionalProperties":false},"AgentPatch":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}]},"config":{"type":"object","properties":{"confidence_threshold":{"default":0.6,"type":"number","minimum":0,"maximum":1},"calendly_url":{"type":"string","format":"uri"},"available_documents":{"default":[],"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"storage_path":{"type":"string"}},"required":["name","description"],"additionalProperties":false}}},"additionalProperties":false}},"additionalProperties":false},"FileTextCreate":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255},"content":{"type":"string","minLength":1},"file_type":{"default":"product_doc","type":"string","enum":["product_doc","sop","glossary","chat_history","convo_snippet","tov_example","transcript"]},"folder_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["filename","content","file_type"],"additionalProperties":false},"ChunkPatch":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"content":{"type":"string","minLength":1}},"required":["content"],"additionalProperties":false},"ReplyRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"trigger_message":{"type":"string","minLength":1,"maxLength":4000},"history":{"default":[],"maxItems":100,"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"},"timestamp":{"type":"string"}},"required":["role","content"],"additionalProperties":false}},"config_override":{"type":"object","properties":{"confidence_threshold":{"default":0.6,"type":"number","minimum":0,"maximum":1},"calendly_url":{"type":"string","format":"uri"},"available_documents":{"default":[],"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"storage_path":{"type":"string"}},"required":["name","description"],"additionalProperties":false}}},"additionalProperties":false}},"required":["trigger_message","history"],"additionalProperties":false},"ReplyResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reply_text":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1},"confidence_breakdown":{"type":"object","properties":{"retrieval":{"type":"number","minimum":0,"maximum":1},"intent":{"type":"number","minimum":0,"maximum":1},"groundedness":{"type":"number","minimum":0,"maximum":1},"consistency":{"type":"number","minimum":0,"maximum":1}},"required":["retrieval","intent","groundedness","consistency"],"additionalProperties":false},"detected_stage":{"type":"string"},"detected_intent":{"type":"string"},"detected_intents":{"type":"array","items":{"type":"string"}},"suggested_tool":{"type":"string","enum":["send_calendly_link","attach_document","flag_for_review","none"]},"tool_args":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"reasoning":{"type":"string"},"retrieved_chunk_ids":{"type":"array","items":{"type":"string"}},"reply_log_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"below_threshold":{"type":"boolean"},"tree_trace":{"type":"object","properties":{"distribution":{"type":"object","properties":{"engaged":{"type":"number","minimum":0,"maximum":1},"fit_mismatch":{"type":"number","minimum":0,"maximum":1},"qualifying":{"type":"number","minimum":0,"maximum":1},"scheduling":{"type":"number","minimum":0,"maximum":1},"ghosting":{"type":"number","minimum":0,"maximum":1},"reasoning":{"type":"string","maxLength":400}},"required":["engaged","fit_mismatch","qualifying","scheduling","ghosting","reasoning"],"additionalProperties":false},"hypotheses":{"minItems":3,"maxItems":3,"type":"array","items":{"type":"string","enum":["engaged","fit_mismatch","qualifying","scheduling","ghosting"]}},"drafts":{"minItems":3,"maxItems":3,"type":"array","items":{"type":"object","properties":{"hypothesis":{"type":"string","enum":["engaged","fit_mismatch","qualifying","scheduling","ghosting"]},"hypothesis_probability":{"type":"number","minimum":0,"maximum":1},"reply":{"type":"object","properties":{"reply_text":{"type":"string","minLength":1,"maxLength":2000},"reasoning":{"type":"string","maxLength":500,"description":"Brief rationale: which SOPs/facts guided the reply and how you matched the customer's tone."},"detected_intent":{"type":"string","description":"Primary intent you inferred (matches state.intents)."},"suggested_tool":{"type":"string","enum":["send_calendly_link","attach_document","flag_for_review","none"],"description":"A tool to attach to the reply, or 'none'. 'flag_for_review' = don't send, route to human."},"tool_args":{"type":"object","properties":{"calendly_url":{"anyOf":[{"type":"string"},{"type":"null"}]},"document_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"review_reason":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["calendly_url","document_name","review_reason"],"additionalProperties":false},"used_chunk_refs":{"type":"array","items":{"type":"string"},"description":"IDs of the chunks (kb-N / sop-N / tov-N / convo-N) you actually cited. Empty array if you made no factual claim."}},"required":["reply_text","reasoning","detected_intent","suggested_tool","tool_args","used_chunk_refs"],"additionalProperties":false},"critics":{"type":"object","properties":{"stage_appropriateness":{"type":"number","minimum":0,"maximum":1},"groundedness":{"type":"number","minimum":0,"maximum":1},"tone_match":{"type":"number","minimum":0,"maximum":1},"intent_match":{"type":"number","minimum":0,"maximum":1},"inferred_stage":{"type":"string","enum":["engaged","fit_mismatch","qualifying","scheduling","ghosting"]},"notes":{"type":"string","maxLength":300}},"required":["stage_appropriateness","groundedness","tone_match","intent_match","inferred_stage","notes"],"additionalProperties":false},"latency_ms":{"type":"number","minimum":0}},"required":["hypothesis","hypothesis_probability","reply","critics","latency_ms"],"additionalProperties":false}},"judge":{"type":"object","properties":{"chosen":{"anyOf":[{"type":"number","const":0},{"type":"number","const":1},{"type":"number","const":2},{"type":"string","const":"synthesis"}]},"reasoning":{"type":"string","maxLength":600},"synthesis_plan":{"anyOf":[{"type":"string","maxLength":600},{"type":"null"}]}},"required":["chosen","reasoning","synthesis_plan"],"additionalProperties":false},"synthesis_used":{"type":"boolean"},"synthesis_text":{"anyOf":[{"type":"string"},{"type":"null"}]},"tone_polish":{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"reply_log_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"content":{"type":"string"},"origin":{"type":"string","enum":["reply_logs","tov_examples"]}},"required":["reply_log_id","content","origin"],"additionalProperties":false}},"before":{"type":"string"},"after":{"type":"string"},"skipped":{"type":"boolean"}},"required":["sources","before","after","skipped"],"additionalProperties":false}},"required":["distribution","hypotheses","drafts","judge","synthesis_used","synthesis_text","tone_polish"],"additionalProperties":false}},"required":["reply_text","confidence","confidence_breakdown","detected_stage","detected_intent","detected_intents","suggested_tool","tool_args","reasoning","retrieved_chunk_ids","reply_log_id","below_threshold"],"additionalProperties":false},"FeedbackSubmit":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reply_log_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"feedback_text":{"type":"string","minLength":3,"maxLength":4000}},"required":["reply_log_id","feedback_text"],"additionalProperties":false},"ApiError":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}}}}},"tags":[{"name":"Agents","description":"Create, list, configure, clone, delete agents."},{"name":"Files","description":"Upload knowledge — multipart, raw text, or signed direct-to-Storage upload."},{"name":"Folders","description":"Organize files into a folder tree per agent."},{"name":"Chunks","description":"Inspect and edit the chunks produced by the ingestion pipeline. Editing re-embeds."},{"name":"Reply","description":"The headline endpoint — draft a grounded reply."},{"name":"Feedback","description":"Closed-loop chunk improvement: submit feedback on a draft, the system proposes an edit, you apply or dismiss."},{"name":"Graph","description":"Read-only knowledge-graph view (entities + chunk relationships) for the dashboard graph page."},{"name":"Keys","description":"Manage API keys. Agent-scope keys live under an agent; workspace-scope keys live at the top level for MCP clients."}],"paths":{"/api/v1/agents":{"get":{"tags":["Agents"],"summary":"List agents","description":"Returns every agent the authenticated principal can see. Session-scope auth sees the whole workspace; api-key-scope auth sees only the agent the key is pinned to.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{"$ref":"#/components/schemas/Agent"},"error":{"type":["object","null"]}}}}}}}},"post":{"tags":["Agents"],"summary":"Create agent","description":"Creates a new agent with a default `AgentConfig`. The body only needs a name and use-case; everything else (retrieval params, confidence thresholds, voice) can be patched later.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{"$ref":"#/components/schemas/Agent"},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"get":{"tags":["Agents"],"summary":"Get agent","description":"Returns the full agent record including `config`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"patch":{"tags":["Agents"],"summary":"Update agent","description":"Partial update. Patching `config` deep-merges into the existing config — provide only the fields you want to change.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPatch"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"delete":{"tags":["Agents"],"summary":"Delete agent","description":"Permanently deletes the agent. Cascades to files, chunks, keys, reply logs, and feedback.","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/clone":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"post":{"tags":["Agents"],"summary":"Clone an agent","description":"Creates a new agent that copies the source agent's config, files, and chunks. Use this to fork a tuned setup for a new team or use case.","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/files":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"get":{"tags":["Files"],"summary":"List files","description":"Returns every file uploaded to this agent with status (`uploading | pending | processing | ready | error`).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"post":{"tags":["Files"],"summary":"Upload file (multipart)","description":"Multipart upload — the API streams the file to Storage and inserts a `files` row, then the worker picks it up. Use this for files up to a few MB. For larger files, prefer the signed-upload flow (`/files/sign` + `/files/{id}/commit`).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"file_type":{"type":"string","description":"Drives the chunker. `glossary` chunks per term, `chat_history` per conversation, `sop` per rule, `tov_example` keeps short tone snippets intact, etc.","enum":["product_doc","sop","glossary","chat_history","convo_snippet","tov_example","transcript"]},"folder_id":{"type":"string","format":"uuid","description":"Optional folder to attach the file to."}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/files/text":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"post":{"tags":["Files"],"summary":"Upload raw text as a file","description":"Bypasses extraction. The text is stored as a synthetic file and immediately handed to the chunk/enrich/embed pipeline. Useful for pasted snippets, n8n outputs, or transcripts already in plain text.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileTextCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/files/sign":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"post":{"tags":["Files"],"summary":"Get a signed direct-upload URL","description":"Step 1 of the large-file flow. Returns a one-shot signed URL the client can `PUT` the file to directly (bypasses the API function's body-size limits and 100MB cap). After the PUT, call `POST /files/{fileId}/commit`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","size_bytes"],"properties":{"filename":{"type":"string","maxLength":255},"size_bytes":{"type":"integer","maximum":104857600,"description":"100 MB cap."},"mime_type":{"type":["string","null"]},"file_type":{"type":"string","enum":["product_doc","sop","glossary","chat_history","convo_snippet","tov_example","transcript"],"default":"product_doc"},"folder_id":{"type":["string","null"],"format":"uuid"}}}}}},"responses":{"200":{"description":"Signed URL + file id — PUT the file to `signed_url`, then POST to `/files/{file_id}/commit`.","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/files/{fileId}":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"},{"in":"path","name":"fileId","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Files"],"summary":"Get file metadata","description":"Returns the file row including current `status` and any error message.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"post":{"tags":["Files"],"summary":"Reprocess file","description":"Re-queues the file for the worker. Old chunks are cleared first — useful after the chunker or enrichment prompt changes.","responses":{"200":{"description":"Reprocessing","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"delete":{"tags":["Files"],"summary":"Delete file","description":"Cascades to all chunks produced from this file.","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/files/{fileId}/commit":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"},{"in":"path","name":"fileId","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Files"],"summary":"Commit a signed upload","description":"Step 2 of the large-file flow. Call this after the client successfully PUT the bytes to the signed URL from `/files/sign`. Flips the file from `uploading` → `pending` so the worker picks it up.","responses":{"200":{"description":"Committed — pipeline started","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/folders":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"get":{"tags":["Folders"],"summary":"List folders","description":"Returns the folder tree for this agent. Used by the dashboard knowledge view.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"post":{"tags":["Folders"],"summary":"Create folder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"parent_id":{"type":["string","null"],"format":"uuid","description":"Optional parent for nesting."},"description":{"type":["string","null"]}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/folders/{folderId}":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"},{"in":"path","name":"folderId","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"tags":["Folders"],"summary":"Rename / describe folder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"description":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"delete":{"tags":["Folders"],"summary":"Delete folder","description":"Files inside the folder are detached, not deleted.","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/chunks":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"},{"in":"query","name":"file_id","schema":{"type":"string","format":"uuid"},"description":"Filter to one source file."},{"in":"query","name":"content_type","schema":{"type":"string"},"description":"Filter by enriched content type (e.g. `pricing`, `policy`, `tov_example`)."},{"in":"query","name":"limit","schema":{"type":"integer","default":50}},{"in":"query","name":"offset","schema":{"type":"integer","default":0}}],"get":{"tags":["Chunks"],"summary":"List chunks","description":"Returns the chunks the retrieval system can pull from. Each chunk has its enriched metadata (content type, entities, summary).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/chunks/{id}":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Chunks"],"summary":"Get chunk","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"patch":{"tags":["Chunks"],"summary":"Edit chunk content","description":"Saves the new content and **re-embeds** synchronously. Use this from the inline editor when a chunk is wrong or stale — no need to reprocess the whole file.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChunkPatch"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"delete":{"tags":["Chunks"],"summary":"Delete chunk","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/reply":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"post":{"tags":["Reply"],"summary":"Draft a reply for an incoming message","description":"The headline endpoint. Given a trigger message and (optional) chat history, returns a grounded draft reply with a confidence score and citations to the chunks it used.\n\nPipeline: hybrid retrieval (semantic + FTS, fused via RRF) + metadata-filtered search + entity-triggered lookup → LLM listwise rerank over the merged top-25 → structured-slot prompt (`kb_facts`, `sops`, `tov_examples`, `similar_past_convos`, `state`, `history`, `instructions`) → grounded generation with `[kb-N]` / `[sop-N]` citation tags → multi-signal confidence (weighted avg of retrieval coverage + intent classifier + LLM groundedness).\n\nIf the confidence is below the agent's `confidence_threshold`, the response includes `suggested_tool: \"flag_for_review\"` so callers (n8n, the Gmail extension, etc.) can route the draft to a human instead of sending it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{"$ref":"#/components/schemas/ReplyResponse"},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/feedback":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"get":{"tags":["Feedback"],"summary":"List feedback for this agent","description":"Returns submitted feedback rows with their analysis status. Use the `status` query param (`pending | analyzing | analyzed | applying | applied | dismissed | failed | all`) to filter.","parameters":[{"in":"query","name":"status","schema":{"type":"string","enum":["pending","analyzing","analyzed","applying","applied","dismissed","failed","all"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"post":{"tags":["Feedback"],"summary":"Submit feedback on a reply","description":"Attaches free-text feedback to a previous `reply_log` (returned in the reply response). The mini-model then analyzes it against the chunks the reply cited and proposes a concrete `edit_chunk` / `add_chunk` / `deprecate_chunk` action — visible via the list endpoint, applied via `/feedback/{id}/apply`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackSubmit"}}}},"responses":{"200":{"description":"Submitted — analysis is async","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/feedback/{id}/apply":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Feedback"],"summary":"Apply the proposed chunk edit","description":"Executes the analyzer's `edit_chunk` / `add_chunk` / `deprecate_chunk` action against the knowledge base. Re-embeds touched chunks.","responses":{"200":{"description":"Applying","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/feedback/{id}/dismiss":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Feedback"],"summary":"Dismiss feedback","description":"Marks the feedback row as `dismissed` without applying any change.","responses":{"200":{"description":"Dismissed","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/feedback/{id}/retry":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Feedback"],"summary":"Retry analysis","description":"Resets the row to `pending` so the analyzer re-runs. Use after a `failed` analysis or after editing the underlying chunks.","responses":{"200":{"description":"Pending","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/graph":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"},{"in":"query","name":"fresh","schema":{"type":"string","enum":["1"]},"description":"Set `fresh=1` to bypass the cache and recompute."},{"in":"query","name":"topK","schema":{"type":"integer","minimum":1,"maximum":15,"default":5},"description":"Top-K neighbors per node when building edges (1–15)."}],"get":{"tags":["Graph"],"summary":"Get knowledge-graph data","description":"Returns nodes (entities + chunks) and edges for the dashboard graph view. Cached server-side; pass `fresh=1` to recompute.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/keys":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"}],"get":{"tags":["Keys"],"summary":"List API keys for an agent","description":"Returns active keys. Only the prefix is exposed — the full key value is shown exactly once at creation time.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"post":{"tags":["Keys"],"summary":"Create an agent-scope API key","description":"Creates a key pinned to this one agent. The full `tib_…` value is in the response and **cannot be retrieved later** — store it now.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Human-readable label (e.g. 'n8n production', 'Gmail extension')."}}}}}},"responses":{"201":{"description":"Created — full key in response","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/agents/{id}/keys/{keyId}":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent UUID"},{"in":"path","name":"keyId","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["Keys"],"summary":"Revoke API key","description":"Permanently revokes the key. Subsequent requests with it return 401.","responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/keys":{"get":{"tags":["Keys"],"summary":"List workspace-scope keys","description":"Workspace keys (`agent_id = null`) span every agent in the workspace. Used by MCP clients (Claude Desktop, ChatGPT) that need one connector for the whole workspace. Session-auth only — api-key callers cannot manage keys.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}},"post":{"tags":["Keys"],"summary":"Create a workspace-scope key","description":"Creates a key with `agent_id = null` that passes auth for any agent. Session-auth only.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":120}}}}}},"responses":{"201":{"description":"Created — full key in response","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}},"/api/v1/keys/{keyId}":{"parameters":[{"in":"path","name":"keyId","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["Keys"],"summary":"Revoke a workspace key","description":"Session-auth only.","responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","required":["data","error"],"properties":{"data":{},"error":{"type":["object","null"]}}}}}}}}}}}