{"openapi":"3.1.0","info":{"title":"LexPipe ai-assistant","description":"Streaming chat orchestration. Calls the .NET backend for auth/validate + chat persistence + file reads, postgres-dsl-assistant for matter-data search, and the configured LLM providers (Anthropic / OpenAI / etc.) for completions.","version":"0.1.0"},"paths":{"/api/v1/chat":{"post":{"tags":["Chat: v1"],"summary":"Stream an assistant turn in AI SDK UI Message","operationId":"chat_api_v1_chat_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-LexPipe-Tenant-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Lexpipe-Tenant-Id"}},{"name":"X-Workspace-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The workspace the user is currently viewing in the UI. Controls the default `workspace_id` the model hands to `search_matters` and is surfaced to the model in the system prompt so it can reason about 'this matter' style references. Independent of `X-LexPipe-Tenant-Id` (the compliance boundary); users can switch workspaces without losing thread history.","title":"X-Workspace-Id"},"description":"The workspace the user is currently viewing in the UI. Controls the default `workspace_id` the model hands to `search_matters` and is surfaced to the model in the system prompt so it can reason about 'this matter' style references. Independent of `X-LexPipe-Tenant-Id` (the compliance boundary); users can switch workspaces without losing thread history."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workspaces/list":{"get":{"tags":["Chat: v1"],"summary":"List workspaces available to the caller.","description":"Forwards to the .NET main app's ``/api/assistant/workspaces/list`` and re-shapes the response into the FE's expected shape.","operationId":"workspaces_list_api_v1_workspaces_list_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-LexPipe-Tenant-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Lexpipe-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/approve-tool":{"post":{"tags":["Chat: v1"],"summary":"Resolve pending HITL tool-call approval.","description":"Wake the streaming generator that is blocked on this tool call.\n\nReturns ``resolved: false`` when no stream is waiting on this id\n(already resolved, stream was cancelled, or id was bogus). That's\na 200 not a 404 -- the client doesn't need to retry.","operationId":"approve_tool_api_v1_approve_tool_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-LexPipe-Tenant-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Lexpipe-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproveToolRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproveToolResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/greeting":{"post":{"tags":["Chat: v1"],"summary":"Personalised greeting info for the user.","operationId":"greeting_api_v1_greeting_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-LexPipe-Tenant-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Lexpipe-Tenant-Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GreetingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GreetingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/model":{"get":{"tags":["Chat: v1"],"summary":"Object model docs.","description":"Object-model documentation.\n\n``domain=matter`` (default) renders the matter-search DSL schema in\nseven formats:\n\n- ``type=json`` (default) — entity-keyed JSON tree with field\n  metadata (type / enum / aggregatable / sortable / fullText /\n  description) and collection edges (target + cardinality). The\n  canonical agent-facing format: same shape the agent is already\n  composing queries in.\n- ``type=json_lean`` — same shape as ``json`` but ``description``,\n  ``enum`` value lists, and inter-key whitespace are all dropped.\n  Minimum-bytes view for token-conscious LLM consumption.\n- ``type=yaml`` — same content as ``json``, in YAML block style.\n  More readable than JSON for humans browsing the schema.\n- ``type=yaml_lean`` — YAML equivalent of ``json_lean`` (no\n  descriptions, no enum lists). Keeps YAML block formatting.\n- ``type=mermaid`` — ``text/plain`` Mermaid ``classDiagram`` source.\n  Best for humans skimming the structure visually.\n- ``type=csv`` — flat CSV (one row per scalar + one per collection\n  edge) with columns ``Parent, Field, Type, Version, Enum, Full\n  text, Aggregate, Notes``. Useful for pasting into a spreadsheet.\n- ``type=png`` — Mermaid diagram rendered to PNG via the optional\n  ``mermaid-py`` package (lazy import; install with\n  ``pip install mermaid-py``). Requires outbound network access to\n  mermaid.ink. Returns 501 if the package isn't available.\n\nAll matter-domain outputs are generated from ``query_dsl.entities``\n— the same declarations that drive query validation and the response\nwire shape — so they can never drift from the live schema.\n\n``domain=budget`` renders the Phase 0 aggregate budget catalog\n(`mcp_servers.budget_logic.BudgetReference`):\n\n- ``type=json`` / ``type=yaml`` — the catalog content (constraint\n  types, enum value lists, validation/warning rules, phase\n  schemes) plus a ``models`` block with the JSON Schema for\n  every class in the mermaid diagram (BudgetVersion,\n  MatterContext, FeeCap / HoursCap / RateCap, ValidationFailure,\n  etc.).\n- ``type=mermaid`` — Mermaid ``classDiagram`` of the budget domain\n  model: BudgetVersion as the aggregate root, the three flat\n  Constraint leaves (FeeCap / HoursCap / RateCap), MatterContext\n  with its typed enums, the PhaseScheme catalog, and\n  ValidationFailure.\n- ``type=png`` — Mermaid rendered to PNG via mermaid.ink (returns\n  502 if mermaid.ink is unreachable or returns 5xx).\n\n``json_lean`` / ``yaml_lean`` / ``csv`` return 400 for the budget\ndomain (the catalog has no separate lean view, and a flat CSV of\nthe nested catalog model isn't useful).","operationId":"model_docs_api_v1_model_get","parameters":[{"name":"type","in":"query","required":false,"schema":{"enum":["json","json_lean","yaml","yaml_lean","mermaid","csv","png"],"type":"string","default":"json","title":"Type"}},{"name":"domain","in":"query","required":false,"schema":{"enum":["matter","budget"],"type":"string","default":"matter","title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object"},"example":"{\"Matter\": {\"fields\": {\"systemId\": {...}}, ...}}"},"application/yaml":{"schema":{"type":"string"},"example":"Matter:\n  fields:\n    systemId:\n      type: integer\n"},"text/plain":{"schema":{"type":"string"},"example":"classDiagram\n    class Matter {\n        ..."},"text/csv":{"schema":{"type":"string"},"example":"Parent,Field,Type,Version,Enum,Full text,Aggregate,Notes\n..."},"image/png":{"schema":{"type":"string","format":"binary"}}}},"501":{"description":"Optional renderer not installed (``mermaid-py`` for matter png, ``pyyaml`` for yaml)."},"502":{"description":"Upstream mermaid.ink unavailable for domain=budget&type=png."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/live":{"get":{"tags":["health"],"summary":"Live","description":"Liveness probe — process is up.","operationId":"live_live_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/ready":{"get":{"tags":["health"],"summary":"Ready","description":"Readiness probe — settings loaded + chat prompts available.\n\nDoesn't probe the upstream .NET / postgres-dsl-assistant\ndeployments since they're called per-request; a chat request will\nsurface their failures with a more informative error than a\nhealth check would.","operationId":"ready_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"ApproveToolRequest":{"properties":{"toolCallId":{"type":"string","title":"Toolcallid"},"approved":{"type":"boolean","title":"Approved"}},"type":"object","required":["toolCallId","approved"],"title":"ApproveToolRequest"},"ApproveToolResponse":{"properties":{"resolved":{"type":"boolean","title":"Resolved"}},"type":"object","required":["resolved"],"title":"ApproveToolResponse"},"ChatRequest":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/UIMessage"},"type":"array","title":"Messages"},"threadId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Threadid","description":"Either the canonical chat id for an existing chat (opaque -- GUID today, possibly integer-string after a future store change; the AI assistant doesn't inspect the format), or the literal sentinel 'new' for a brand-new chat under the dual-ID pattern (the AI assistant calls POST /api/assistant/chat to mint the canonical id and broadcasts it back via a data-chat-id SSE event). Omit or null for the eval / no-persistence path."}},"additionalProperties":true,"type":"object","title":"ChatRequest"},"GreetingRequest":{"properties":{"local_iso":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Iso","description":"Client's current local time as an ISO 8601 string with timezone offset, e.g. ``2026-04-20T14:30:00-04:00``. Produce with ``date-fns`` ``format(new Date(), \"yyyy-MM-dd'T'HH:mm:ssxxx\")``. Invalid values are silently ignored.","examples":["2026-04-20T14:30:00-04:00"]}},"type":"object","title":"GreetingRequest","description":"Optional request body for ``POST /greeting``.\n\nThe client sends its current local wall-clock time so the Haiku\nprompt can optionally include a time-aware salutation\n(\"Good morning, Alice\" / \"Working late, Alice\"). We pass the raw\nISO string to Haiku rather than bucketing it server-side --\nkeeping the rule fuzzy (\"maybe greet by time of day, maybe don't\")\nlets Haiku vary across repeat visits instead of producing the\nsame salutation every morning.\n\nThere is no standard HTTP header for wall-clock time, so it\nrides in the body. Omit (or post an empty body) to skip the\ntime-aware guidance entirely; Haiku still generates a title."},"GreetingResponse":{"properties":{"title":{"type":"string","minLength":1,"title":"Title"},"subtitle":{"type":"string","minLength":1,"title":"Subtitle"},"pills":{"items":{"type":"string"},"type":"array","maxItems":5,"minItems":2,"title":"Pills"},"provider":{"$ref":"#/components/schemas/ModelProviderEnum","default":"Anthropic"},"model_label":{"type":"string","title":"Model Label","default":""},"tagline":{"type":"string","title":"Tagline","default":""}},"type":"object","required":["title","subtitle","pills"],"title":"GreetingResponse","description":"Structured greeting payload validated directly from the model's\nJSON reply -- no manual field stripping in the parse step.\n\nPydantic does the heavy lifting:\n- ``title`` / ``subtitle`` are stripped and required non-empty.\n- ``pills`` is clamped to its first 5 entries with each one\n  stripped; empty / non-string entries are dropped before the\n  length check fires.\n- The whole model rejects (``ValidationError``) when fewer than\n  two pills survive cleaning -- the UI needs at least a couple of\n  starter prompts to look populated."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ModelProviderEnum":{"type":"string","enum":["Google","OpenAI","Xai","Nvidia","Deepseek","Xiaomi","Anthropic"],"title":"ModelProviderEnum"},"UIMessage":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"role":{"type":"string","title":"Role"},"parts":{"items":{"$ref":"#/components/schemas/UIMessagePart"},"type":"array","title":"Parts"}},"additionalProperties":true,"type":"object","required":["role"],"title":"UIMessage"},"UIMessagePart":{"properties":{"type":{"type":"string","title":"Type"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"toolCallId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Toolcallid"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"input":{"anyOf":[{},{"type":"null"}],"title":"Input"},"output":{"anyOf":[{},{"type":"null"}],"title":"Output"},"errorText":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Errortext"},"providerExecuted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Providerexecuted"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"mediaType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mediatype"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"}},"additionalProperties":true,"type":"object","required":["type"],"title":"UIMessagePart","description":"One part inside a ``UIMessage.parts`` array.\n\nFields are the union across AI SDK v5 part variants -- most are\n``None`` for any given instance. Pre-streaming lifecycle parts\n(``step-start`` etc.) simply carry only the ``type`` and are\nfiltered by the streaming layer."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WorkspaceListResponse":{"properties":{"workspaces":{"items":{"$ref":"#/components/schemas/WorkspaceSummary"},"type":"array","title":"Workspaces"}},"type":"object","required":["workspaces"],"title":"WorkspaceListResponse"},"WorkspaceSummary":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description","default":""},"isDefault":{"type":"boolean","title":"Isdefault","default":false},"matterCount":{"type":"integer","title":"Mattercount","default":0}},"type":"object","required":["id","name"],"title":"WorkspaceSummary","description":"One workspace as the FE expects it — older\n``mock_server`` / ``matter_query`` shape with ``id`` /\n``isDefault`` / ``matterCount``."}}}}