# HTTP API ## Base Surface The current backend exposes FastAPI routers under: - `/api/aibs/auth` - `/api/aibs/admin/users` - `/api/aibs/workspaces` - `/api/aibs/conversations` - `/api/aibs/documents` - `/api/aibs/chat` The app is served by FastAPI and publishes OpenAPI docs at `/docs`. ## Identity Boundary The API uses an AIBS auth boundary at the interface layer. - `POST /api/aibs/auth/login` validates credentials through the configured OpenSearch identity store. - Successful login requires the `aibs-access` role and sets an HTTP-only session cookie. - Workspace, document, conversation, and chat routes resolve their `UserContext` from that cookie session. - Workspace-local authorization is role based: - `owner`: full control, including workspace delete - `admin`: manage workspace state, non-owner members, and documents - `member`: upload/process documents and ask questions - `viewer`: read workspace state and ask questions - The legacy `X-AIBS-User-Id` header is accepted only when `AIBS_AUTH_DEV_MODE=true`. - Phase 1 sessions use a fixed TTL and process-local memory; single-worker API deployment is required until a persistent session store lands. - AIBS product-user administration is guarded by both `aibs-access` and `aibs-admin`. - Workspace membership is separate from product administration. AIBS admins do not automatically see every workspace. ## Endpoints ### `POST /api/aibs/auth/login` Purpose: - validate credentials against the configured OpenSearch cluster and create a cookie-backed AIBS session Request: - JSON body: - `username` (required) - `password` (required) Success: - `200 OK` - sets an HTTP-only session cookie - returns `{ "success": true, "data": { "expires_at", "user": { "user_id", "provider", "subject", "username", "roles", "apps", "email", "display_name" } } }` ### `POST /api/aibs/auth/logout` Purpose: - revoke the current cookie session Behavior: - reads the configured session cookie - clears the cookie even when the session is missing or already expired Success: - `200 OK` - returns `{ "success": true, "data": {} }` ### `GET /api/aibs/auth/session` Purpose: - return the user profile attached to the current cookie session Behavior: - requires a valid session cookie Success: - `200 OK` - returns the same session payload as login ### `GET /api/aibs/admin/users` Purpose: - list product users from the configured AIBS user directory Query parameters: - `q` (optional username/search text) - `limit` (default `50`, min `1`, max `500`) - `include_non_aibs` (default `false`; when true, include directory users that do not currently have `aibs-access`) Behavior: - requires a principal with both `aibs-access` and `aibs-admin` Success: - `200 OK` - returns `{ "users": [UserDirectoryRecordResponse] }` ### `GET /api/aibs/admin/users/{username}` Purpose: - fetch one user from the configured user directory Behavior: - requires a principal with both `aibs-access` and `aibs-admin` - returns `404` when the user is not found Success: - `200 OK` - returns `UserDirectoryRecordResponse` ### `POST /api/aibs/admin/users` Purpose: - create an AIBS product user in the backing directory Request: - JSON body: - `username` (required) - `password` (required) - `display_name` (optional) - `email` (optional) - `aibs_admin` (optional, default `false`) Behavior: - requires a principal with both `aibs-access` and `aibs-admin` - created users always receive `aibs-access` - created admin users also receive `aibs-admin` Success: - `201 Created` - returns `UserDirectoryRecordResponse` ### `PUT /api/aibs/admin/users/{username}/entitlements` Purpose: - grant or revoke AIBS product roles for an existing directory user Request: - JSON body: - `access` (required boolean) - `admin` (required boolean) Behavior: - requires a principal with both `aibs-access` and `aibs-admin` - preserves non-AIBS directory roles - refuses to remove or disable the last enabled AIBS admin Success: - `200 OK` - returns `UserDirectoryRecordResponse` ### `PUT /api/aibs/admin/users/{username}/password` Purpose: - reset a directory user's password Request: - JSON body: - `new_password` (required) Behavior: - requires a principal with both `aibs-access` and `aibs-admin` Success: - `204 No Content` ### `PUT /api/aibs/admin/users/{username}/disable` Purpose: - disable a directory user when supported by the backing provider Behavior: - requires a principal with both `aibs-access` and `aibs-admin` - refuses to disable the last enabled AIBS admin Success: - `204 No Content` ### `POST /api/aibs/workspaces` Purpose: - create a new backend-owned workspace Request: - JSON body: - `name` (required) - `visibility` (optional, defaults to `private`) Success: - `201 Created` - returns `WorkspaceResponse` ### `GET /api/aibs/workspaces` Purpose: - list workspaces visible to the current identity Query parameters: - `limit` (default `50`, min `1`, max `200`) - `offset` (default `0`) Success: - `200 OK` - returns `WorkspaceListResponse` ### `GET /api/aibs/workspaces/{workspace_id}` Purpose: - fetch one workspace the current identity belongs to Success: - `200 OK` - returns `WorkspaceResponse` ### `PUT /api/aibs/workspaces/{workspace_id}` Purpose: - rename a workspace through the backend-owned workspace service Behavior: - requires workspace owner or admin - rejects rename while workspace delete is already in progress - applies the aggregate rename rule and persists the updated workspace Success: - `200 OK` - returns `WorkspaceResponse` ### `GET /api/aibs/workspaces/{workspace_id}/members` Purpose: - list the members and roles for one workspace Behavior: - requires any workspace role - rejects reads while workspace delete is already in progress Success: - `200 OK` - returns `list[WorkspaceMemberResponse]` ### `POST /api/aibs/workspaces/{workspace_id}/members` Purpose: - add a user to a workspace with a workspace-local role Request: - JSON body: - `username` (preferred) - `user_id` (deprecated compatibility field) - `role` (required: `owner`, `admin`, `member`, or `viewer`) Behavior: - requires workspace owner or admin - at least one of `username` or `user_id` is required - username-based adds resolve through the AIBS user directory - unknown usernames are rejected - if both `username` and `user_id` are supplied, they must resolve to the same user - adding an owner requires the requester to be an owner - rejects duplicate membership through the workspace aggregate - rejects changes while workspace delete is already in progress Success: - `201 Created` - returns `WorkspaceMemberResponse` ### `PUT /api/aibs/workspaces/{workspace_id}/members/{user_id}` Purpose: - change an existing workspace member role Request: - JSON body: - `role` (required: `owner`, `admin`, `member`, or `viewer`) Behavior: - requires workspace owner or admin - promoting to owner or demoting an owner requires the requester to be an owner - preserves the aggregate invariant that a workspace must keep at least one owner - rejects changes while workspace delete is already in progress Success: - `200 OK` - returns `WorkspaceMemberResponse` ### `DELETE /api/aibs/workspaces/{workspace_id}/members/{user_id}` Purpose: - remove a user from a workspace Behavior: - requires workspace owner or admin - removing an owner requires the requester to be an owner - preserves the aggregate invariant that a workspace must keep at least one owner - rejects changes while workspace delete is already in progress Success: - `204 No Content` ### `POST /api/aibs/workspaces/{workspace_id}/archive` Purpose: - archive a workspace through the governance layer Behavior: - requires workspace owner or admin - performs the domain archive transition - persists the archived workspace state Success: - `200 OK` - returns `WorkspaceResponse` ### `DELETE /api/aibs/workspaces/{workspace_id}` Purpose: - destructively delete a workspace and its bounded resources Behavior: - requires workspace ownership - closes the in-process workspace boundary before destructive cleanup starts - rejects deletion if any bounded document is currently processing - deletes bounded documents first - deletes conversations next - removes the workspace record last Success: - `204 No Content` ### `POST /api/aibs/workspaces/{workspace_id}/conversations` Purpose: - create a lightweight persisted conversation bound to one workspace Request: - JSON body: - `title` (optional) Success: - `201 Created` - returns `ConversationResponse` ### `GET /api/aibs/workspaces/{workspace_id}/conversations` Purpose: - list conversations within one workspace for the current identity Query parameters: - `limit` (default `50`, min `1`, max `200`) - `offset` (default `0`) Behavior: - requires active workspace membership - returns conversations created by the current identity - also returns legacy conversations that do not have `created_by` recorded yet Success: - `200 OK` - returns `ConversationListResponse` ### `GET /api/aibs/conversations/{conversation_id}` Purpose: - fetch one conversation if the current identity created it Behavior: - requires workspace membership for the conversation's workspace - requires the conversation creator after workspace membership is confirmed - legacy conversations with missing `created_by` remain readable by workspace members Success: - `200 OK` - returns `ConversationResponse` ### `PUT /api/aibs/conversations/{conversation_id}` Purpose: - rename a workspace-bound conversation record Behavior: - loads the conversation through the backend-owned conversation service - rejects rename while workspace delete is already in progress - requires the conversation creator after workspace membership is confirmed - updates the conversation title and `updated_at` Success: - `200 OK` - returns `ConversationResponse` ### `DELETE /api/aibs/conversations/{conversation_id}` Purpose: - delete one workspace-bound conversation record Behavior: - loads the conversation through the backend-owned conversation service - rejects delete while workspace delete is already in progress - requires the conversation creator after workspace membership is confirmed - removes only the lightweight conversation record Success: - `204 No Content` ### `GET /api/aibs/conversations/{conversation_id}/history` Purpose: - reload one conversation from persisted query and answer records Behavior: - requires workspace membership for the conversation's workspace - requires the conversation creator after workspace membership is confirmed - legacy conversations with missing `created_by` remain readable by workspace members - returns both grounded and insufficient turns truthfully - returns fallback conversation-context turns when published answer lineage exists Success: - `200 OK` - returns `ConversationHistoryResponse` ### `POST /api/aibs/documents` Purpose: - upload, register, and queue a new document for processing Request: - `multipart/form-data` - fields: - `workspace_id` (required) - `file` (required) - `title` (optional) Behavior: - validates upload size before reading - stages the file to disk - computes checksum - stores the managed file - creates a `registered` document record - submits (or reuses an active) ingestion job in OpenSearch before returning - rolls back registration if job submission fails - requires workspace owner, admin, or member Success: - `201 Created` - returns `DocumentResponse` ### `GET /api/aibs/documents/catalog` Purpose: - return a paged workspace document catalog for the source-list UI Query parameters: - `workspace_id` (required) - `limit` (default `100`, min `1`, max `500`) - `offset` (default `0`) - `q` (optional metadata/title/search text, max `200`) - `language` (optional ISO/language filter) - `status` (optional, repeatable document status filter) Behavior: - requires any active workspace role - reads from the OpenSearch-backed document catalog projection - returns lightweight document metadata, not full aggregate JSON - `summary` describes the full workspace; `page.total_matches` describes the current filtered result set Success: - `200 OK` - returns `DocumentCatalogResponse` ### `GET /api/aibs/documents/search` Purpose: - search documents independently from the assistant conversation flow Query parameters: - `workspace_id` (required) - `q` (required for `text`, `semantic`, and `hybrid`; optional metadata fallback) - `mode` (default `metadata`; supported: `metadata`, `text`, `semantic`, `hybrid`) - `limit` (default `50`, min `1`, max `500`) - `offset` (default `0`) - `language` (optional for metadata mode) - `status` (optional, repeatable for metadata mode) - `include_disabled` (default `true`; when false, excludes documents disabled from assistant knowledge retrieval) Behavior: - `metadata` delegates to the catalog projection for title/language/status search - `text` searches indexed chunk text and returns highlighted snippets - `semantic` embeds the query, searches chunk vectors, and returns semantic evidence snippets - `hybrid` is the backend Smart mode: it combines metadata, text, and semantic signals with Reciprocal Rank Fusion (`k=60`), and may use guarded assistant-backed query expansion - Smart query expansion never runs for plain catalog, text, or semantic modes; if expansion fails, deterministic hybrid results are still returned Success: - `200 OK` - returns `DocumentCatalogResponse` ### `GET /api/aibs/documents` Purpose: - list documents within one workspace through the legacy simple list route Query parameters: - `workspace_id` (required) - `limit` (default `50`, min `1`, max `200`) - `offset` (default `0`) Success: - `200 OK` - returns `DocumentListResponse` ### `PATCH /api/aibs/documents/{document_id}/knowledge` Purpose: - enable or disable a document for assistant knowledge retrieval while keeping it visible as an uploaded workspace artifact Request: - JSON body: - `enabled` (required boolean) Behavior: - requires workspace owner or admin - disabled documents remain catalog-searchable and previewable unless the caller explicitly asks search to exclude them with `include_disabled=false` - disabled documents are excluded from assistant knowledge retrieval Success: - `200 OK` - returns `DocumentResponse` ### `GET /api/aibs/documents/{document_id}/snippets` Purpose: - return a small set of indexed chunk previews for a document Query parameters: - `workspace_id` (required) - `n` (default `3`, min `1`, max `10`) Behavior: - requires any active workspace role - returns an empty list for documents that are not indexed/searchable yet - does not call the LLM Success: - `200 OK` - returns `DocumentSnippetListResponse` ### `GET /api/aibs/documents/{document_id}/preview` Purpose: - stream the original uploaded document for inline preview or download Query parameters: - `workspace_id` (required) - `download` (default `false`; when true, uses attachment disposition) Behavior: - requires any active workspace role - disabled knowledge documents remain previewable - inline preview is capped by `AIBS_MAX_UPLOAD_SIZE_BYTES`; download bypasses that preview cap Success: - `200 OK` - returns the file stream ### `GET /api/aibs/documents/{document_id}` Purpose: - fetch one document by id inside a specific workspace Behavior: - enforces workspace membership - loads the document - enforces that the document belongs to the requested workspace Success: - `200 OK` - returns `DocumentResponse` ### `POST /api/aibs/documents/{document_id}/process` Purpose: - submit or reuse a durable ingestion job for a document (manual retry/recovery path) Behavior: - requires workspace owner, admin, or member - rejects documents in draft, uploaded, or other invalid pre-registration states - creates a new ingestion job in OpenSearch or reuses the existing active job for the same document (no duplicate jobs per document) - returns immediately — actual parse/chunk/index work happens asynchronously in the ingestion worker loop Success: - `202 Accepted` - returns `ProcessAcceptedResponse` Important note: - `processing_started=false` means an active job already existed and was reused, not that processing failed - `job_id` in the response identifies the OpenSearch job record for traceability ### `DELETE /api/aibs/documents/{document_id}` Purpose: - delete one document and its bounded indexed content Behavior: - requires workspace owner or admin - rejects deletion if the document is currently in the in-flight processing registry - rejects deletion if the persisted document status is a processing state Success: - `204 No Content` ### `POST /api/aibs/chat/` Purpose: - answer a workspace-scoped question over the current conversation orchestration seam Behavior: - enforces active workspace membership; viewers may ask questions - resolves or creates a real persisted conversation bound to the workspace - rejects explicit document ids that do not belong to the workspace - accepts `execution_mode_override` values `auto`, `creative`, or `grounded` (`casual` is accepted only as a legacy compatibility alias for `creative`) - streams an SSE response Current note: - chat now runs through evidence-regime routing followed by workload planning - public evidence regimes are: - `creative` - evidence optional, with citations when source evidence is used - `grounded` - strict evidence required for factual claims - internal workload strategies are: - `chat` - `chat_map_reduce` - `agent_map_refine_compose` - turns can complete as: - `creative` - `document_grounded` - `conversation_context` - `insufficient` - the SSE contract includes query and answer ids plus answer-mode truth Success: - `200 OK` - returns `text/event-stream` ## Response Models ### `WorkspaceResponse` Fields: - `workspace_id` - `name` - `visibility` - `status` - `member_count` - `owner_count` - `created_at` - `updated_at` - `current_user_role` ### `WorkspaceListResponse` Fields: - `workspaces: list[WorkspaceResponse]` ### `WorkspaceMemberResponse` Fields: - `membership_id` - `user_id` - `role` - `joined_at` ### `ConversationResponse` Fields: - `conversation_id` - `workspace_id` - `title` - `created_by` - `created_at` - `updated_at` ### `ConversationListResponse` Fields: - `conversations: list[ConversationResponse]` ### `ConversationHistoryResponse` Fields: - `conversation_id` - `turns: list[ConversationHistoryTurnResponse]` ### `ConversationHistoryTurnResponse` Fields: - `query_id` - `workspace_id` - `conversation_id` - `question_text` - `query_status` - `answer_id` - `answer_text` - `publication_status` - `confidence_label` - `citations` - `is_fallback` - `grounded` - `asked_at` - `answered_at` ### `DocumentResponse` Fields: - `document_id` - `workspace_id` - `title` - `media_type` - `status` - `is_degraded` - `failure_reason` - `knowledge_enabled` - `ingestion_active` - `language` - `chunk_count` - `page_count` - `search_score` - `search_match_reasons` - `search_snippets` - `created_at` - `updated_at` ### `DocumentListResponse` Fields: - `documents: list[DocumentResponse]` ### `DocumentCatalogResponse` Fields: - `documents: list[DocumentResponse]` - `summary: DocumentCatalogSummaryResponse` - `page: DocumentCatalogPageResponse` Notes: - `summary` is workspace-wide, even when a search/filter is active. - `page.total_matches` is the result denominator for the current catalog/search request. For semantic and hybrid search, it can be bounded by the candidate window rather than an exact full-workspace count. ### `DocumentCatalogSummaryResponse` Fields: - `total` - `ready` - `processing` - `queued` - `review` - `chunks` ### `DocumentSnippetResponse` Fields: - `chunk_id` - `title` - `text` - `page_start` - `page_end` - `match_reason` - `highlights` ### `DocumentSnippetListResponse` Fields: - `document_id` - `snippets: list[DocumentSnippetResponse]` ### `ProcessAcceptedResponse` Fields: - `document_id` - `status` - `processing_started` - `job_id` (the OpenSearch ingestion job id; `null` when the route short-circuits before submission) ### Error payload The app maps `AIBSError` subclasses into JSON responses shaped like: ```json { "error": "Error message description", "code": "stable_error_code" } ``` Typical status mapping: - `400` validation errors - `403` authorization failures - `404` missing entities - `409` duplicates / invalid state transitions - `422` invariant violations - `502` external service errors - `500` persistence and unexpected backend failures ## Current Contract Boundaries - Workspace and conversation truth now live in backend-owned persistence. - Conversation reload is rebuilt from persisted `QueryExecution` and `AnswerArtifact` records rather than a separate transcript store. - Auth is active. Non-dev mode requires cookie-backed sessions from OpenSearch/Logserver credentials and explicit `aibs-access`. - Product administration requires both `aibs-access` and `aibs-admin`. - Workspace membership remains the workspace authorization source of truth. - Ingestion is a durable OpenSearch-backed job API with OCC-based claiming and lease reaping. - Document catalog/search reads are backed by a separate OpenSearch projection; document JSON remains the aggregate source of truth. - Smart document search is `mode=hybrid`; assistant-backed query expansion is guarded and best-effort inside that mode only.