{ "openapi": "3.1.0", "info": { "title": "Clawbolt", "version": "0.1.0" }, "paths": { "/api/health": { "get": { "summary": "Health Check", "description": "Full health check: process is up AND can reach the database.\n\nUse for ops dashboards and richer monitoring. NOT recommended as the\ndeployment platform's healthcheck path: during an incident a\nhealthcheck that waits on the same DB can pile up alongside whatever\nalready broke. Use ``/health/live`` for that.", "operationId": "health_check_api_health_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } } } } }, "/api/health/live": { "get": { "summary": "Health Live", "description": "Liveness probe: the process is up and the event loop is responsive.\n\nNo DB hit, no external calls. Returns instantly when the worker can\nprocess requests. Designed for the deployment platform's healthcheck\nso a stuck DB / external dep / slow query does not also block the\nhealthcheck and prevent traffic from rolling to a fresh container.\nUse ``/health`` for the deeper \"is the system actually working\" check.", "operationId": "health_live_api_health_live_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } } } } }, "/api/app/config": { "get": { "summary": "App Config", "operationId": "app_config_api_app_config_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppConfigResponse" } } } } } } }, "/api/auth/config": { "get": { "summary": "Auth Config", "operationId": "auth_config_api_auth_config_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "additionalProperties": true, "type": "object", "title": "Response Auth Config Api Auth Config Get" } } } } } } }, "/api/oauth/status": { "get": { "summary": "Get Oauth Status", "description": "Return connection status for all OAuth integrations.", "operationId": "get_oauth_status_api_oauth_status_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OAuthStatusResponse" } } } } } } }, "/api/oauth/{integration}/authorize": { "get": { "summary": "Get Authorize Url", "description": "Generate an authorization URL for the given integration.", "operationId": "get_authorize_url_api_oauth__integration__authorize_get", "parameters": [ { "name": "integration", "in": "path", "required": true, "schema": { "type": "string", "title": "Integration" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OAuthAuthorizeResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/oauth/callback": { "get": { "summary": "Oauth Callback", "description": "Handle OAuth provider redirect after user authorization.\n\nThe provider redirects here with an authorization code and the state\nparameter we generated earlier. We exchange the code for tokens,\npersist them, and redirect the user to the frontend success page.\n\nAll parameters are optional because OAuth providers may redirect with\nonly error parameters (no code/state) when the user denies access.\n\nWhen the flow was initiated from chat (source=\"chat\"), a standalone\nHTML page is returned instead of redirecting to the SPA, so users\non SMS/iMessage see a \"you can close this tab\" message.", "operationId": "oauth_callback_api_oauth_callback_get", "parameters": [ { "name": "code", "in": "query", "required": false, "schema": { "type": "string", "default": "", "title": "Code" } }, { "name": "state", "in": "query", "required": false, "schema": { "type": "string", "default": "", "title": "State" } }, { "name": "realmId", "in": "query", "required": false, "schema": { "type": "string", "default": "", "title": "Realmid" } }, { "name": "error", "in": "query", "required": false, "schema": { "type": "string", "default": "", "title": "Error" } }, { "name": "error_description", "in": "query", "required": false, "schema": { "type": "string", "default": "", "title": "Error Description" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/oauth/{integration}": { "delete": { "summary": "Disconnect Integration", "description": "Disconnect an OAuth integration by removing stored tokens.", "operationId": "disconnect_integration_api_oauth__integration__delete", "parameters": [ { "name": "integration", "in": "path", "required": true, "schema": { "type": "string", "title": "Integration" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": { "type": "string" }, "title": "Response Disconnect Integration Api Oauth Integration Delete" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/integrations/servicetitan/connect": { "post": { "summary": "Connect Servicetitan", "description": "Validate ServiceTitan client credentials and persist them.", "operationId": "connect_servicetitan_api_integrations_servicetitan_connect_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServiceTitanConnectRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IntegrationConnectionResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/integrations/servicetitan": { "delete": { "summary": "Disconnect Servicetitan", "description": "Remove the user's stored ServiceTitan credential.", "operationId": "disconnect_servicetitan_api_integrations_servicetitan_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IntegrationConnectionResponse" } } } } } } }, "/api/integrations/appfolio_vendor/connect": { "post": { "summary": "Connect Appfolio", "description": "Exchange a pasted AppFolio magic link for a credential and persist it.", "operationId": "connect_appfolio_api_integrations_appfolio_vendor_connect_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppFolioConnectRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IntegrationConnectionResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/integrations/appfolio_vendor": { "delete": { "summary": "Disconnect Appfolio", "description": "Remove the user's stored AppFolio credential.", "operationId": "disconnect_appfolio_api_integrations_appfolio_vendor_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IntegrationConnectionResponse" } } } } } } }, "/api/media/temp/{token}": { "get": { "summary": "Serve Temp Media", "description": "Serve a temporarily staged media file. No auth required.\n\nThe token stays valid for the full TTL (5 minutes) and can be\nfetched multiple times. External services like CompanyCam may\ndownload the image more than once (original + thumbnails).", "operationId": "serve_temp_media_api_media_temp__token__get", "parameters": [ { "name": "token", "in": "path", "required": true, "schema": { "type": "string", "title": "Token" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/webhooks/telegram": { "post": { "summary": "Telegram Inbound", "description": "Receive inbound messages from Telegram.", "operationId": "telegram_inbound_api_webhooks_telegram_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } } } } }, "/api/user/chat": { "post": { "tags": [ "webchat" ], "summary": "Send Chat Message", "description": "Accept a message, publish to bus, return request_id for SSE.", "operationId": "send_chat_message_api_user_chat_post", "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/Body_send_chat_message_api_user_chat_post" } } } }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/_ChatAccepted" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/chat/events/{request_id}": { "get": { "tags": [ "webchat" ], "summary": "Chat Events", "description": "SSE endpoint: streams tool-call events then the final reply.", "operationId": "chat_events_api_user_chat_events__request_id__get", "parameters": [ { "name": "request_id", "in": "path", "required": true, "schema": { "type": "string", "title": "Request Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/chat/activity": { "get": { "tags": [ "webchat" ], "summary": "Chat Activity", "description": "SSE endpoint: streams agent activity (thinking, tool use) for this user.\n\nDelivers real-time status regardless of which channel (Telegram,\nwebchat, etc.) originated the message being processed. Multiple\nbrowser tabs can subscribe concurrently.", "operationId": "chat_activity_api_user_chat_activity_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } } } } }, "/api/webhooks/linq": { "post": { "summary": "Linq Inbound", "description": "Receive inbound messages from Linq.", "operationId": "linq_inbound_api_webhooks_linq_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } } } } }, "/api/webhooks/bluebubbles": { "post": { "summary": "Bluebubbles Inbound", "description": "Receive inbound messages from BlueBubbles.", "operationId": "bluebubbles_inbound_api_webhooks_bluebubbles_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } } } } }, "/api/webhooks/twilio": { "post": { "summary": "Twilio Inbound", "description": "Receive inbound SMS/MMS from Twilio.\n\nReturns an empty TwiML response immediately; the agent loop\nreplies through the outbound dispatcher via the REST API so\nthis endpoint stays under Twilio's 15s ack window even when\nthe LLM call takes longer.", "operationId": "twilio_inbound_api_webhooks_twilio_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } } } } }, "/api/user/profile": { "get": { "summary": "Get Profile", "description": "Return the current user's profile.", "operationId": "get_profile_api_user_profile_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserProfileResponse" } } } } } }, "put": { "summary": "Update Profile", "description": "Partial update of the current user's profile.\n\nBounded-growth columns (``user_text``, ``soul_text``,\n``heartbeat_text``) are routed through\n:func:`backend.app.agent.markdown_registry.assert_column_within_budget`\nso the dashboard editor cannot bypass the byte cap that the agent's\nworkspace tools and compaction paths already respect. Returns\n``413 Payload Too Large`` with the registry's actual / allowed\nsizes so a client-side editor can show a useful error.", "operationId": "update_profile_api_user_profile_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserProfileUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserProfileResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/data-sharing-consent": { "get": { "summary": "Get Data Sharing Consent", "description": "Return the current user's data sharing consent state.", "operationId": "get_data_sharing_consent_api_user_data_sharing_consent_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataSharingConsentResponse" } } } } } }, "put": { "summary": "Update Data Sharing Consent", "description": "Toggle the current user's data sharing consent.\n\nStamps ``data_sharing_consent_at`` with the current UTC time on every\ncall, regardless of whether the value changed. This makes the column\na \"last toggled at\" timestamp rather than a \"first opted in at\" one,\nwhich is the cheaper guarantee to keep correct: a one-shot accidental\ndouble-PUT can't drift the timestamp.", "operationId": "update_data_sharing_consent_api_user_data_sharing_consent_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataSharingConsentRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataSharingConsentResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/channels/config": { "get": { "summary": "Get Channel Config", "description": "Return server-level channel configuration.", "operationId": "get_channel_config_api_user_channels_config_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelConfigResponse" } } } } } }, "put": { "summary": "Update Channel Config", "description": "Update server-level channel configuration.", "operationId": "update_channel_config_api_user_channels_config_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelConfigUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelConfigResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/channels/routes": { "get": { "summary": "Get Channel Routes", "description": "Return the current user's channel routes with enabled status.", "operationId": "get_channel_routes_api_user_channels_routes_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelRouteListResponse" } } } } } } }, "/api/user/channels/routes/{channel}": { "patch": { "summary": "Update Channel Route", "description": "Toggle enabled status for a channel route.\n\nSingle-channel enforcement: when enabling a channel, all other\nnon-webchat routes for this user are automatically disabled so\nexactly one messaging channel is active at a time.\n\nIf the user has no route and no known identifier for this channel yet\n(fresh onboarding), the selection is persisted via ``preferred_channel``\nonly. The route row is created later when the identifier arrives,\neither via an inbound message (OSS) or via an explicit link call\n(premium). This keeps placeholder rows out of the database and avoids\nleaking the user's internal UUID into identifier-shaped UI fields.", "operationId": "update_channel_route_api_user_channels_routes__channel__patch", "parameters": [ { "name": "channel", "in": "path", "required": true, "schema": { "type": "string", "title": "Channel" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelRouteUpdate" } } } }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelRouteResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/channels/telegram/bot-info": { "get": { "summary": "Get Telegram Bot Info", "description": "Return the Telegram bot username, auto-discovered via getMe.", "operationId": "get_telegram_bot_info_api_channels_telegram_bot_info_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramBotInfoResponse" } } } } } } }, "/api/user/model/config": { "get": { "summary": "Get Model Config", "description": "Return server-level LLM model configuration.", "operationId": "get_model_config_api_user_model_config_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ModelConfigResponse" } } } } } }, "put": { "summary": "Update Model Config", "description": "Update server-level LLM model configuration.\n\nNOTE: In single-tenant (OSS) mode, all authenticated users are\neffectively admins and can modify these settings. The premium layer\nadds role-based guards via AdminConfigGuardMiddleware to restrict\nthis to admin users only.", "operationId": "update_model_config_api_user_model_config_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ModelConfigUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ModelConfigResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/providers": { "get": { "summary": "List Providers", "description": "List available LLM providers from any-llm.", "operationId": "list_providers_api_user_providers_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "items": { "$ref": "#/components/schemas/ProviderInfo" }, "type": "array", "title": "Response List Providers Api User Providers Get" } } } } } } }, "/api/user/providers/{provider}/models": { "get": { "summary": "List Provider Models", "description": "List available models for a provider.\n\n``api_base`` is honored only for a local provider. any-llm resolves a missing\n``api_key`` from the server's environment, so honoring a caller-supplied base\nfor a hosted provider would send that provider's key to whatever host the\ncaller named. Local providers are keyless, so there is nothing to send, and\nthey are the only case the settings UI uses the field for: the API-base input\nand its \"Fetch Models\" button render only when the selected provider is\nlocal, and a hosted provider is always listed with no base.\n\nThis endpoint is gated by ``get_current_user`` alone, which is every user in\na single-tenant deployment but every *tenant* under a multi-tenant auth\nplugin, so the parameter must not be able to redirect a credentialed call.", "operationId": "list_provider_models_api_user_providers__provider__models_get", "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "title": "Provider" } }, { "name": "api_base", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Endpoint to enumerate. Local providers only; a hosted provider always uses the configured LLM_API_BASE.", "title": "Api Base" }, "description": "Endpoint to enumerate. Local providers only; a hosted provider always uses the configured LLM_API_BASE." } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" }, "title": "Response List Provider Models Api User Providers Provider Models Get" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/heartbeat-logs": { "get": { "summary": "Get Heartbeat Logs", "description": "List heartbeat logs for the current user, most recent first.", "operationId": "get_heartbeat_logs_api_user_heartbeat_logs_get", "parameters": [ { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 200, "minimum": 1, "default": 50, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HeartbeatLogListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "delete": { "summary": "Delete Heartbeat Logs", "description": "Delete all heartbeat logs for the current user.", "operationId": "delete_heartbeat_logs_api_user_heartbeat_logs_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteHeartbeatLogsResponse" } } } } } } }, "/api/user/llm-usage": { "get": { "summary": "Get Llm Usage", "description": "Aggregate LLM usage for the current user over the last N days.", "operationId": "get_llm_usage_api_user_llm_usage_get", "parameters": [ { "name": "days", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 365, "minimum": 1, "default": 30, "title": "Days" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LLMUsageSummary" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/conversation": { "get": { "summary": "Get Conversation", "description": "Return the user's conversation transcript, or an empty shape if none yet.\n\nReturning an empty shape (rather than 404) lets the frontend render\nthe chat input without special-casing the first-message-ever flow.\nThe session row is created by the agent pipeline on the first\ninbound message, not by this endpoint.", "operationId": "get_conversation_api_user_conversation_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SessionDetailResponse" } } } } } } }, "/api/user/conversation/system-prompt": { "get": { "summary": "Get Conversation System Prompt", "description": "Return the system prompt that would be sent on the next turn.\n\nReconstructed live from current user state (profile, soul, memory,\nonboarding status, tool availability) so the UI doesn't show a\nstale snapshot from the first turn of the session.\n\nKnown approximations:\n\n* The preview omits specialist tool guidelines that get appended\n mid-turn when the LLM calls ``list_capabilities`` to activate a\n category. It matches the start-of-turn tool list, mirroring how\n the agent itself starts each turn fresh.\n* Tools whose factories require a storage backend or an outbound\n publish hook (currently ``send_media_reply``,\n ``upload_to_storage``, and ``move_file``) are filtered out\n by the registry's dependency gates because the preview can't\n safely construct those runtime hooks. Their usage hints will\n not appear in the Tool Guidelines section.\n* If a user's ``BOOTSTRAP.md`` cannot be created on disk by the\n runtime (rare, requires an OS-level error), the runtime drops\n out of onboarding mode while this preview still reports\n ``is_onboarding=true`` based on the in-memory heuristic.", "operationId": "get_conversation_system_prompt_api_user_conversation_system_prompt_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SessionSystemPromptResponse" } } } } } } }, "/api/user/conversation/messages/batch": { "delete": { "summary": "Delete Messages Batch", "description": "Delete specific messages from the user's conversation by sequence number.", "operationId": "delete_messages_batch_api_user_conversation_messages_batch_delete", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchDeleteRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteMessagesResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/conversation/messages/{seq}": { "delete": { "summary": "Delete Single Message", "description": "Delete a single message from the user's conversation by sequence number.", "operationId": "delete_single_message_api_user_conversation_messages__seq__delete", "parameters": [ { "name": "seq", "in": "path", "required": true, "schema": { "type": "integer", "title": "Seq" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteMessageResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/conversation/messages": { "delete": { "summary": "Delete Conversation History", "description": "Delete all messages from the user's conversation, preserving memory.\n\nResets the initial system prompt so the conversation continues with\na clean slate while retaining compacted memory.", "operationId": "delete_conversation_history_api_user_conversation_messages_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteMessagesResponse" } } } } } } }, "/api/user/memory": { "get": { "summary": "Get Memory", "description": "Return the raw MEMORY.md content.", "operationId": "get_memory_api_user_memory_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MemoryResponse" } } } } } }, "put": { "summary": "Update Memory", "description": "Overwrite MEMORY.md with new content.\n\nReturns ``413 Payload Too Large`` when *body.content* exceeds the\nbounded-growth byte budget for ``MEMORY.md`` (see\n:mod:`backend.app.agent.markdown_registry`). The original message\nfrom the registry includes the actual size and the budget so a\nuser-side editor can show a useful error.", "operationId": "update_memory_api_user_memory_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MemoryUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MemoryResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/permissions": { "get": { "summary": "Get Permissions", "description": "Return the current PERMISSIONS.json content.", "operationId": "get_permissions_api_user_permissions_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PermissionsResponse" } } } } } }, "put": { "summary": "Update Permissions", "description": "Overwrite PERMISSIONS.json with new content.", "operationId": "update_permissions_api_user_permissions_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PermissionsUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PermissionsResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/tools": { "get": { "summary": "Get Tool Config", "description": "Return the current tool configuration for the user.", "operationId": "get_tool_config_api_user_tools_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ToolConfigResponse" } } } } } }, "put": { "summary": "Update Tool Config", "description": "Update tool configuration for the user.\n\nOnly factories that are not ``dashboard_always_enabled`` can be\ntoggled at the factory level. Attempts to disable always-enabled\ntools are silently ignored.\n\nEach entry may include a ``sub_tools`` list with explicit\n``permission_level`` values to override individual sub-tools. Levels\nare persisted to ``user_permissions``; ``\"never\"`` filters the\nsub-tool out of the LLM schema on the next turn.", "operationId": "update_tool_config_api_user_tools_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ToolConfigUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ToolConfigResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/user/calendar/calendars": { "get": { "summary": "List Calendars", "description": "Fetch the user's Google Calendar list from the Google API.", "operationId": "list_calendars_api_user_calendar_calendars_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CalendarListResponse" } } } } } } }, "/api/user/calendar/config": { "get": { "summary": "Get Calendar Config", "description": "Get all enabled calendars for the user.", "operationId": "get_calendar_config_api_user_calendar_config_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CalendarConfigResponse" } } } } } }, "put": { "summary": "Update Calendar Config", "description": "Replace all enabled calendars (delete existing, insert new).\n\nThe ``is_primary`` flag is auto-detected by querying Google's\ncalendarList for the current user, so the frontend does not need to\npass it through. The agent uses it to disambiguate when the LLM\nomits ``calendar_id`` and the user has multiple enabled calendars.", "operationId": "update_calendar_config_api_user_calendar_config_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CalendarConfigUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CalendarConfigResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/auth/refresh": { "post": { "tags": [ "auth" ], "summary": "Refresh Token", "description": "Exchange a valid refresh token for a new access + refresh token pair.", "operationId": "refresh_token_api_auth_refresh_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RefreshRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TokenResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/auth/oauth/google": { "get": { "tags": [ "oauth" ], "summary": "Oauth Google Redirect", "description": "Redirect to Google's authorization endpoint to start the OAuth flow.", "operationId": "oauth_google_redirect_api_auth_oauth_google_get", "responses": { "307": { "description": "Successful Response" } } } }, "/api/auth/oauth/google/callback": { "get": { "tags": [ "oauth" ], "summary": "Oauth Google Callback", "description": "Handle the OAuth callback from Google.\n\nExchanges the authorization code for user info, creates/gets the user,\nissues a refresh token, and redirects to the app with the token in a\nURL hash fragment (never sent to the server or logged by proxies).", "operationId": "oauth_google_callback_api_auth_oauth_google_callback_get", "parameters": [ { "name": "code", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Code" } }, { "name": "state", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "State" } }, { "name": "error", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Error" } }, { "name": "oauth_state", "in": "cookie", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Oauth State" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/auth/oauth/google/state": { "get": { "tags": [ "oauth" ], "summary": "Get Oauth State", "description": "Generate a signed state parameter for OAuth CSRF protection.", "operationId": "get_oauth_state_api_auth_oauth_google_state_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StateResponse" } } } } } } }, "/api/auth/oauth/google/exchange": { "post": { "tags": [ "oauth" ], "summary": "Google Oauth Exchange", "description": "Exchange a Google authorization code for JWT tokens (JSON API).\n\nLegacy endpoint for clients that handle the OAuth redirect themselves.", "operationId": "google_oauth_exchange_api_auth_oauth_google_exchange_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GoogleAuthRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users": { "get": { "tags": [ "admin" ], "summary": "List Users", "description": "List users with pagination, search, sort, and consent filter.\n\nNOTE: This endpoint is O(N) in the total user count today: it loads every\nSubscription, every User row, every Message-this-month aggregate, and the\nfull UserData list from the store before slicing the requested page.\nAcceptable at `` block as ``[admin note: ...]`` to bias how the\nLLM reads the messages, which is useful when the exact failure mode\nis known (e.g. \"ignore prior agent claims about being read-only\").\n\nAudit-logged via ``AdminAction.COMPACT_USER_CONTEXT``; the resulting\n``compaction_events`` row is also linked from ``ctx.detail`` so a\nforensic query can join admin action to the compaction outcome.", "operationId": "compact_user_context_api_admin_users__user_id__compact_now_post", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompactUserContextRequest" } } } }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompactUserContextResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/hygiene-compact-memory": { "post": { "tags": [ "admin" ], "summary": "Hygiene Compact Memory Endpoint", "description": "Re-audit a user's MEMORY.md against the Do-Not-Include list.\n\nRuns the compaction LLM in hygiene-only mode: the model reads the\nuser's current MEMORY.md and removes every line that violates the\nexclusion list (customer IDs, phone numbers, stale bug notes, etc.),\neven if no new conversation triggered the compaction. This is the\n\"clean my memory now\" operation that scrubs pre-existing violations\nthat were written before the compliance rule existed.\n\nUnlike ``POST /admin/users/{user_id}/compact-now``, this endpoint\ndoes not require untrimmed conversation messages and does not\nadvance the trim watermark. It only touches MEMORY.md.\n\nAudit-logged via ``AdminAction.HYGIENE_COMPACT_MEMORY``.", "operationId": "hygiene_compact_memory_endpoint_api_admin_users__user_id__hygiene_compact_memory_post", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HygieneCompactMemoryResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/heartbeat-logs": { "get": { "tags": [ "admin" ], "summary": "Get User Heartbeat Logs", "description": "List heartbeat log metadata for a specific user, most recent first.\n\nSlimmed in #325 work item 2: ``message_text``, ``reasoning``, and\n``tasks`` were stripped from this response. Heartbeat content\nsurfaces only via the consent-gated paths once items 3 + 4 land.", "operationId": "get_user_heartbeat_logs_api_admin_users__user_id__heartbeat_logs_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 200, "minimum": 1, "default": 50, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminHeartbeatLogListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/llm-usage-logs": { "get": { "tags": [ "admin" ], "summary": "Get User Llm Usage Logs", "description": "List per-call LLM usage logs for a specific user, most recent first.\n\nEach row records a single LLM call: provider, model, purpose\n(primary / vision / heartbeat / compaction / etc.), token counts,\nand cost in USD. Purpose lets you spot a runaway compaction loop\nor a heartbeat model burning cache misses.\n\nThe audit dependency writes one row when the route exits, matching\nthe policy on the user-detail endpoint.", "operationId": "get_user_llm_usage_logs_api_admin_users__user_id__llm_usage_logs_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 100, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LLMUsageLogListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/staged-media": { "get": { "tags": [ "admin" ], "summary": "Get User Staged Media", "description": "List staged-media rows for a user, newest first, with cap context.\n\nReturns ``total`` / ``active`` / ``uploaded`` / ``cap`` counts so a\nglance answers \"is this user pinned at the cap, and are all 50\nslots upload receipts?\" -- the shape that proves the receipts-pin\n-the-cap symptom without scrolling the row list.", "operationId": "get_user_staged_media_api_admin_users__user_id__staged_media_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 100, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StagedMediaListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/webhook-events": { "get": { "tags": [ "admin" ], "summary": "Get User Webhook Events", "description": "List webhook dedup events for this user, newest first.\n\nJoins ``idempotency_keys`` to ``messages`` via the external_id so\neach row carries whether a Message persisted. Default behavior is\nto return only events tied to this user; setting\n``include_orphans=true`` adds rows where no Message landed (those\nreveal approval-gate consumption or consumer-side failures, but\ncannot be attributed to a specific user so they are off by\ndefault to avoid cross-tenant noise).", "operationId": "get_user_webhook_events_api_admin_users__user_id__webhook_events_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "since", "in": "query", "required": false, "schema": { "type": "string", "description": "ISO timestamp lower bound. Default returns the most recent rows. Format ``2026-05-21T00:00:00+00:00``.", "default": "", "title": "Since" }, "description": "ISO timestamp lower bound. Default returns the most recent rows. Format ``2026-05-21T00:00:00+00:00``." }, { "name": "channel_prefix", "in": "query", "required": false, "schema": { "type": "string", "description": "Restrict to ``idempotency_keys`` whose ``external_id`` starts with this prefix. Defaults to ``bb_`` (BlueBubbles).", "default": "bb_", "title": "Channel Prefix" }, "description": "Restrict to ``idempotency_keys`` whose ``external_id`` starts with this prefix. Defaults to ``bb_`` (BlueBubbles)." }, { "name": "include_orphans", "in": "query", "required": false, "schema": { "type": "boolean", "description": "Also return idempotency rows in the window that did NOT produce a Message row. Orphans are not user-scoped (the idempotency table has no user_id column) so they may belong to another tenant; only opt in for whole-server investigations.", "default": false, "title": "Include Orphans" }, "description": "Also return idempotency rows in the window that did NOT produce a Message row. Orphans are not user-scoped (the idempotency table has no user_id column) so they may belong to another tenant; only opt in for whole-server investigations." }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 200, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEventListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/llm-payloads": { "get": { "tags": [ "admin" ], "summary": "Export User Llm Payloads", "description": "Download the captured LLM request payloads for one user.\n\nReturns the previous-era and current-era payload snapshots stored\nby ``llm_payload_capture`` for users who have toggled\n``data_sharing_consent``. Non-consenting users are not captured in\nthe first place, so 404 is returned both when no row exists and\nwhen the user has revoked consent (which deletes the row).\n\nResponse is served with ``Content-Disposition: attachment`` so the\nadmin frontend can drop it to disk for offline analysis without\nrendering the JSON in-page.", "operationId": "export_user_llm_payloads_api_admin_users__user_id__llm_payloads_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/usage/{user_id}": { "get": { "tags": [ "admin" ], "summary": "Get User Usage", "description": "Get quota usage and aggregate LLM spend for a specific user.\n\nExistence-checks the user up-front. ``get_usage_summary`` calls\n``get_current_quota`` which inserts a ``UsageQuota`` row for the\nuser; without the check, a bogus ``user_id`` reaches that insert\nand 500s on the FK to ``users.id``. 404 is the right answer for an\nadmin endpoint queried with an unknown user.\n\nCost totals are scoped to the same period as the quota counters\n(``period_cost_usd`` covers the current calendar month, matching\n``messages.used`` / ``tokens.used``). ``lifetime_cost_usd`` lets\nthe admin spot a user whose monthly spend is fine but whose\nall-time spend is an outlier.", "operationId": "get_user_usage_api_admin_usage__user_id__get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminUsageSummary" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/allowed-emails": { "get": { "tags": [ "admin" ], "summary": "List Allowed Emails", "description": "List all pre-approved email addresses.", "operationId": "list_allowed_emails_api_admin_allowed_emails_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AllowedEmailListResponse" } } } } } }, "post": { "tags": [ "admin" ], "summary": "Add Allowed Email", "description": "Add an email address to the approved registration list.", "operationId": "add_allowed_email_api_admin_allowed_emails_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AllowedEmailCreate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AllowedEmailResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/allowed-emails/{email_id}": { "delete": { "tags": [ "admin" ], "summary": "Remove Allowed Email", "description": "Remove an email address from the approved registration list.", "operationId": "remove_allowed_email_api_admin_allowed_emails__email_id__delete", "parameters": [ { "name": "email_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Email Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/waitlist": { "get": { "tags": [ "admin" ], "summary": "List Waitlist Entries", "description": "List waitlist entries, newest first.", "operationId": "list_waitlist_entries_api_admin_waitlist_get", "parameters": [ { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0, "title": "Offset" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 200, "minimum": 1, "default": 50, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WaitlistListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/waitlist/{entry_id}/approve": { "post": { "tags": [ "admin" ], "summary": "Approve Waitlist Entry", "description": "Approve a waitlist entry: add to allowed_emails and remove from waitlist.", "operationId": "approve_waitlist_entry_api_admin_waitlist__entry_id__approve_post", "parameters": [ { "name": "entry_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Entry Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AllowedEmailResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/waitlist/{entry_id}": { "delete": { "tags": [ "admin" ], "summary": "Dismiss Waitlist Entry", "description": "Remove a waitlist entry without approving.", "operationId": "dismiss_waitlist_entry_api_admin_waitlist__entry_id__delete", "parameters": [ { "name": "entry_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Entry Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/stats": { "get": { "tags": [ "admin" ], "summary": "Get Admin Stats", "description": "Return the messaging configuration needed by the admin overview.", "operationId": "get_admin_stats_api_admin_stats_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminStatsResponse" } } } } } } }, "/api/admin/version": { "get": { "tags": [ "admin" ], "summary": "Get Admin Version", "description": "Build metadata for the admin overview card and the client's auto-reload poll.", "operationId": "get_admin_version_api_admin_version_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminVersionResponse" } } } } } } }, "/api/admin/channels/config": { "get": { "tags": [ "admin" ], "summary": "Get Admin Channel Config", "description": "Return full server-level channel configuration for the admin panel.", "operationId": "get_admin_channel_config_api_admin_channels_config_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminChannelConfigResponse" } } } } } }, "put": { "tags": [ "admin" ], "summary": "Update Admin Channel Config", "description": "Update server-level channel configuration (admin only).", "operationId": "update_admin_channel_config_api_admin_channels_config_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminChannelConfigUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminChannelConfigResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/telegram/webhook": { "post": { "tags": [ "admin" ], "summary": "Register Telegram Webhook Endpoint", "description": "Register or update the Telegram webhook URL.\n\nIf webhook_url is empty, constructs it from APP_BASE_URL.", "operationId": "register_telegram_webhook_endpoint_api_admin_telegram_webhook_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramWebhookRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramWebhookResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "delete": { "tags": [ "admin" ], "summary": "Unregister Telegram Webhook Endpoint", "description": "Remove the Telegram webhook.", "operationId": "unregister_telegram_webhook_endpoint_api_admin_telegram_webhook_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramWebhookResponse" } } } } } } }, "/api/admin/config/llm": { "get": { "tags": [ "admin" ], "summary": "Get Admin Llm Config", "description": "Return the global default LLM provider/model used when a user has no override.", "operationId": "get_admin_llm_config_api_admin_config_llm_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminLLMConfigResponse" } } } } } }, "put": { "tags": [ "admin" ], "summary": "Update Admin Llm Config", "description": "Update the global default LLM. Persisted to the settings store.", "operationId": "update_admin_llm_config_api_admin_config_llm_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminLLMConfigUpdate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminLLMConfigResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/llm-config": { "get": { "tags": [ "admin" ], "summary": "Get User Llm Config", "description": "Return per-user LLM override (and the effective values after fallback).", "operationId": "get_user_llm_config_api_admin_users__user_id__llm_config_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminUserLLMOverrideResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "put": { "tags": [ "admin" ], "summary": "Update User Llm Config", "description": "Set the per-user LLM override.\n\nPass an empty string in either field to clear that part of the\noverride and fall back to the global default. Pass null (omit) to\nleave the field unchanged.\n\nDeliberately has no self-action guard. Every other user-targeted mutation\nendpoint (plan, activate, deactivate, reset-quota, delete, compact-now,\nhygiene-compact-memory) still rejects a self-targeted call. For the plan /\nactivate / deactivate / reset-quota / delete set that guard stops an admin\nescalating their own account: raising their own quota, upgrading their own\nplan, locking themselves out. Which model an admin's own agent talks to is a\npersonal preference with no privilege attached, and blocking it stops the\nmost common legitimate use (an admin trying a model on their own account\nbefore rolling it out). ``audit_admin`` still resolves ``get_current_admin``,\nso the role check and the audit record are unaffected.", "operationId": "update_user_llm_config_api_admin_users__user_id__llm_config_put", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminUserLLMOverrideUpdate" } } } }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminUserLLMOverrideResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/users/{user_id}/plan": { "put": { "tags": [ "admin" ], "summary": "Update User Plan", "description": "Change a user's plan and re-cap their active month's quota row.\n\nWithout the quota-row update, a mid-month flip would not take effect\nuntil the next calendar reset because ``UsageQuota`` captures limits\nat row creation. ``messages_used`` / ``tokens_used`` carry over so a\nuser partway through their old cap does not get a free reset.", "operationId": "update_user_plan_api_admin_users__user_id__plan_put", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminUserPlanUpdate" } } } }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminUserPlanResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/config/llm/providers": { "get": { "tags": [ "admin" ], "summary": "List Admin Llm Providers", "description": "List all known LLM providers known to any-llm.", "operationId": "list_admin_llm_providers_api_admin_config_llm_providers_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminLLMProvidersResponse" } } } } } } }, "/api/admin/config/llm/providers/{provider}/models": { "get": { "tags": [ "admin" ], "summary": "List Admin Llm Provider Models", "description": "Return models for ``provider`` plus structured failure context.\n\nEnumerates ``settings.llm_api_base``, matching what the agent loop passes to\n``amessages`` on every call. Previously this called any-llm with no\n``api_base`` at all, so the listing went straight to the provider's own API\nwhile the agent talked to a gateway. On a gateway deployment that failed\noutright, because the gateway's virtual key got presented to the real\nprovider (\"401 invalid x-api-key\"), and an admin could never see the models\nthey were actually able to call.\n\nDeliberately takes no caller-supplied ``api_base``. The OSS sibling\n(``/api/user/providers/{provider}/models``) accepts one because its settings\nform passes it, but the admin UI does not, so the parameter would be\na curl-only surface whose only real effect is to let an admin make the server\ndeliver a provider API key from its environment to an arbitrary host. If the\nadmin form ever needs to preview a candidate endpoint before saving it, add\nthe parameter together with URL validation, not before.\n\nNever raises 4xx/5xx for \"this provider cannot list models\" or \"the\nprovider's API call failed\". Those are normal admin states; the UI\nneeds to render them, not see a generic 502. We only let through\nframework-level errors (e.g. validation), which FastAPI handles.", "operationId": "list_admin_llm_provider_models_api_admin_config_llm_providers__provider__models_get", "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "title": "Provider" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminLLMModelsResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/api-keys": { "get": { "tags": [ "admin" ], "summary": "List Admin Api Keys", "description": "List the admin's own API keys.\n\nIncludes revoked keys (with ``revoked_at`` populated) so the admin\ncan audit their own history. The cleartext token is never\nreturned; only the prefix + metadata.", "operationId": "list_admin_api_keys_api_admin_api_keys_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminApiKeyListResponse" } } } } } }, "post": { "tags": [ "admin" ], "summary": "Create Admin Api Key", "description": "Mint a new API key for the calling admin.\n\nReturns the cleartext token in the response body. The caller must\npersist it: a re-read of the row will only expose the prefix.\n\nRefuses with 409 when the calling admin already has the per-admin\ncap of active (un-revoked) keys. The actionable response is\n\"revoke an old key, then mint again\", surfaced in the error\ndetail so a CLI client can show it verbatim. Revoked keys do not\ncount toward the cap.", "operationId": "create_admin_api_key_api_admin_api_keys_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminApiKeyCreate" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminApiKeyMintResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/api-keys/{key_id}": { "delete": { "tags": [ "admin" ], "summary": "Revoke Admin Api Key", "description": "Revoke one of the admin's own API keys.\n\nIdempotent: revoking an already-revoked key returns 200 ok.\nScoped to the calling admin's own keys; an admin cannot revoke\nanother admin's keys through this endpoint (a separate force-\nrevoke surface would handle that, with stricter audit).", "operationId": "revoke_admin_api_key_api_admin_api_keys__key_id__delete", "parameters": [ { "name": "key_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Key Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatusResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/summary": { "get": { "tags": [ "admin" ], "summary": "Get Shared Data Summary", "description": "Aggregate counts for the Overview \"Research pilot\" panel.\n\nComputes consenting-user totals, weekly activity counts (conversations\n+ heartbeats + errors), open-report count, and a small leaderboard\nof the most active consenting users this week. Cheap enough to run\non every Overview load: a handful of indexed COUNT and GROUP BY\nqueries scoped to consenting user ids.\n\nOnly counts and a small leaderboard surface here. Message bodies,\nmemory text, and per-event content stay behind the existing\nper-conversation endpoints, which already PII-redact and audit-log\nevery read.\n\nThe \"this week\" window is the rolling 7 days ending now (UTC).", "operationId": "get_shared_data_summary_api_admin_shared_data_summary_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataSummaryResponse" } } } } } } }, "/api/admin/shared-data/users": { "get": { "tags": [ "admin" ], "summary": "List Shared Data Users", "description": "List users who have opted into data sharing.\n\nFiltered to ``data_sharing_consent=True`` server-side; non-\nconsenting rows never reach the response. ``conversation_count`` and\n``last_message_at`` are per-user aggregates, so the admin can identify\nrecent conversations without fetching a transcript.", "operationId": "list_shared_data_users_api_admin_shared_data_users_get", "parameters": [ { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0, "title": "Offset" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 200, "minimum": 1, "default": 50, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataUserListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/conversation": { "get": { "tags": [ "admin" ], "summary": "Get Shared Data Conversation", "description": "Return the consenting user's single conversation.\n\nEach user has at most one conversation (enforced by the\n``uq_sessions_user_id`` constraint on OSS). 404s if the user has\nno conversation yet, which is normal for a freshly onboarded user\nwho hasn't sent a first message.", "operationId": "get_shared_data_conversation_api_admin_shared_data_users__user_id__conversation_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataConversationItem" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/conversation/turns": { "get": { "tags": [ "admin" ], "summary": "List Shared Data Conversation Turns", "description": "Return the user's conversation as turn-grouped, redacted records.\n\nPulls every message in the user's single conversation (capped at\n``limit`` rows so a runaway transcript does not OOM the response),\ngroups them into turns via :func:`_group_turns`, and returns each\nturn with its user message, agent reply, and the tool calls fired\nduring the turn. Each tool call is redacted at the leaves: ``args``\nis walked recursively and ``result`` is string-redacted, so a\nquery like ``qb_query(\"...WHERE customer_name='John Smith'\")``\ndoes not surface the customer name even when the conversation is\nopened.\n\nThe consent gate is re-checked server-side, so a user revoking\nconsent mid-investigation immediately starts returning 403.", "operationId": "list_shared_data_conversation_turns_api_admin_shared_data_users__user_id__conversation_turns_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 2000, "minimum": 1, "default": 500, "title": "Limit" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataConversationTurnsResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/profile": { "get": { "tags": [ "admin" ], "summary": "Get Shared Data Profile", "description": "Return the consenting user's soul / user / heartbeat profile text.\n\nThese three fields used to live on ``GET /admin/users/{id}`` until\n#336 dropped them. They are user-authored content (soul = how the\nagent should behave for this user; user_text = synthesized profile;\nheartbeat_text = proactive directives), so they belong behind the\nconsent gate. Strings are passed through ``redact_pii`` even though\nthey're plaintext at rest, because users sometimes paste contact\ninfo into their soul / heartbeat directives.\n\nHeartbeat config (opt-in flag, frequency, max_daily) is metadata\nthat the slim ``/admin/users/{id}`` route already returns; we\nduplicate it here so an admin reviewing one consenting user has\neverything in one response without cross-route hopping.", "operationId": "get_shared_data_profile_api_admin_shared_data_users__user_id__profile_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataProfileResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/heartbeat-logs": { "get": { "tags": [ "admin" ], "summary": "List Shared Data Heartbeat Logs", "description": "Return heartbeat scheduler runs with their content fields.\n\nThe non-consent variant ``/admin/users/{id}/heartbeat-logs`` returns\nonly metadata (id, action_type, channel, created_at) since #336.\nFor consenting users the full content surfaces here: ``message_text``\n(what the agent sent on this tick), ``reasoning`` (why it sent /\nskipped \u2014 often quotes the user back to themselves), and ``tasks``\n(the serialized task state the LLM was deciding from). All three\ncolumns are envelope-encrypted at rest; ORM reads decrypt\ntransparently and we redact PII shapes before serialization.", "operationId": "list_shared_data_heartbeat_logs_api_admin_shared_data_users__user_id__heartbeat_logs_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 100, "title": "Limit" } }, { "name": "start_date", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO-8601 timestamp; lower bound on created_at.", "title": "Start Date" }, "description": "ISO-8601 timestamp; lower bound on created_at." }, { "name": "end_date", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO-8601 timestamp; upper bound on created_at.", "title": "End Date" }, "description": "ISO-8601 timestamp; upper bound on created_at." } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataHeartbeatLogListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/memory": { "get": { "tags": [ "admin" ], "summary": "Get Shared Data Memory", "description": "Return the consenting user's MemoryDocument (memory + history).\n\n``memory_text`` is the agent's working memory file (persistent\nnotes, reminders, current context). ``history_text`` is the\naccumulated output of session compactions: each time a long\nsession compacts, the LLM extracts durable facts and appends them\nhere. Reading ``history_text`` is the closest persisted surface to\na per-event compaction stream; per-event timing lives in\n``logger.info(\"compaction.summary user=...\")`` lines only and is\nnot yet queryable.\n\nBoth columns are envelope-encrypted at rest. A user with no\ndocument yet (never compacted, never wrote memory) returns empty\nstrings rather than 404 \u2014 consenting and \"no memory yet\" is a\nvalid combined state.", "operationId": "get_shared_data_memory_api_admin_shared_data_users__user_id__memory_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataMemoryDocumentResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/compaction-events": { "get": { "tags": [ "admin" ], "summary": "List Shared Data Compaction Events", "description": "Return per-event compaction metadata for one consenting user.\n\nBacked by the OSS ``compaction_events`` table (migrations 023 and\n030). The metadata columns (counts, timings, outcome flags) carry\nno user content, so no redaction is applied. Migration 030 added\neight envelope-encrypted before/after snapshots (memory, history,\nuser, soul); these decrypt to plaintext on read and ride through\nthe response so an admin can see exactly what a compaction event\nrewrote across the four memory files. ``status`` is one of\n``'pending'`` (sync watermark advanced, async LLM call still\nrunning or crashed) or ``'completed'``; legacy rows default to\n``'completed'`` via the migration's server-side default.\n\nSnapshots that exceed\n``settings.compaction_event_snapshot_max_bytes_per_file`` are\nstored as a JSON truncation record. We surface those as a flagged\npayload (``truncated=True`` plus head, tail, size, sha256) instead\nof dumping the JSON verbatim so the UI can render\n\"truncated, N KB\" with the head and tail visible inline.\n\nOrdered ``triggered_at desc`` so the most recent compaction shows\nfirst; that is the question admins almost always ask (\"did this\nuser just compact, and what did it cost?\"). ``limit`` caps the\nresponse so a long-running user does not OOM the wire.", "operationId": "list_shared_data_compaction_events_api_admin_shared_data_users__user_id__compaction_events_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 1000, "minimum": 1, "default": 200, "title": "Limit" } }, { "name": "start_date", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO-8601 timestamp; lower bound on triggered_at.", "title": "Start Date" }, "description": "ISO-8601 timestamp; lower bound on triggered_at." }, { "name": "end_date", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO-8601 timestamp; upper bound on triggered_at.", "title": "End Date" }, "description": "ISO-8601 timestamp; upper bound on triggered_at." } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataCompactionEventListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/approval-events": { "get": { "tags": [ "admin" ], "summary": "List Shared Data Approval Events", "description": "Return per-event tool-approval lifecycle for one consenting user.\n\nBacked by the OSS ``approval_events`` table (migration 028). The\nagent's approval gate writes one row per transition: ``requested``\nwhen a tool with ASK policy fires, ``decided`` (with the\nApprovalDecision) when the user replies, ``timed_out`` if the user\nnever answers, and ``recovered`` if a worker crash left an orphan\nthat the next boot cleaned up. Surfacing this stream lets admins\nsee when the agent was blocked on a permission prompt and how the\nrequest resolved, which the conversation transcript alone cannot\nshow (prompts ride through the messages table indistinguishably\nfrom ordinary replies).\n\n``description`` is the human-readable text shown to the user in\nthe prompt body. It can echo user-pasted content (filenames, URLs,\nquoted message text), so it is PII-redacted before serialization.\n``channel`` and ``chat_id`` are infrastructure metadata that route\nthe prompt; they are not redacted.\n\nOrdered ``created_at asc`` so a request/decided pair stays adjacent\nin the response, matching how the activity feed will render them.", "operationId": "list_shared_data_approval_events_api_admin_shared_data_users__user_id__approval_events_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 2000, "minimum": 1, "default": 500, "title": "Limit" } }, { "name": "start_date", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO-8601 timestamp; lower bound on created_at.", "title": "Start Date" }, "description": "ISO-8601 timestamp; lower bound on created_at." }, { "name": "end_date", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO-8601 timestamp; upper bound on created_at.", "title": "End Date" }, "description": "ISO-8601 timestamp; upper bound on created_at." } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataApprovalEventListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/shared-data/users/{user_id}/export": { "get": { "tags": [ "admin" ], "summary": "Export Shared Data User", "description": "One-shot export bundle for a consenting user.\n\nDesigned for CLI / offline analysis: a single audit-logged request\nreturns identity + profile + memory + heartbeat history + activity\ncounts + tool usage rollup, all PII-redacted. Without this, an\nadmin investigating \"what's wrong with this user's experience?\"\nhas to walk seven separate endpoints by hand.\n\nThe window only applies to time-bucketed sub-resources. The\nprofile / memory / consent fields are always the current value.\n\nBodies live in two places:\n* The agent's persistent text (soul, user, heartbeat directives,\n memory) returns in the corresponding sections.\n* Per-message transcripts only return when ``include_turns=true``.\n They are the heaviest field (one row per message + tool call).", "operationId": "export_shared_data_user_api_admin_shared_data_users__user_id__export_get", "parameters": [ { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string", "title": "User Id" } }, { "name": "days", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 90, "minimum": 1, "description": "Window size in days, ending now. Scopes the time-bucketed subresources (conversations, heartbeat-logs, compaction-events, LLM-usage, reports, and the tool-call rollup). Identity, profile, and memory are point-in-time and not scoped by the window.", "default": 7, "title": "Days" }, "description": "Window size in days, ending now. Scopes the time-bucketed subresources (conversations, heartbeat-logs, compaction-events, LLM-usage, reports, and the tool-call rollup). Identity, profile, and memory are point-in-time and not scoped by the window." }, { "name": "include_turns", "in": "query", "required": false, "schema": { "type": "boolean", "description": "When true, attach turn-grouped transcripts for every conversation in the window. Off by default because turns are expensive both to compute and to ship; flip on when you want full body content.", "default": false, "title": "Include Turns" }, "description": "When true, attach turn-grouped transcripts for every conversation in the window. Off by default because turns are expensive both to compute and to ship; flip on when you want full body content." } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SharedDataExportResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/reported-conversations": { "get": { "tags": [ "admin" ], "summary": "List Reported Conversations", "description": "List reports for triage.\n\nNo status filter returns both open and dismissed; ``status=open``\nor ``status=dismissed`` narrows. Open reports always sort first.", "operationId": "list_reported_conversations_api_admin_reported_conversations_get", "parameters": [ { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0, "title": "Offset" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 200, "minimum": 1, "default": 50, "title": "Limit" } }, { "name": "status", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string", "pattern": "^(open|dismissed)$" }, { "type": "null" } ], "description": "Filter by status: 'open' (not yet dismissed) or 'dismissed'.", "title": "Status" }, "description": "Filter by status: 'open' (not yet dismissed) or 'dismissed'." } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReportedConversationListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/reported-conversations/{report_id}/messages": { "get": { "tags": [ "admin" ], "summary": "Get Reported Conversation Messages", "description": "Return the messages around the report's anchor.\n\nIncludes ``window`` messages on either side of ``anchor_seq``. If\n``anchor_seq`` is NULL (the OSS handler couldn't capture one when\nthe user reported on an empty session), returns the most recent\n``window`` messages instead.\n\nAll bodies pass through PII redaction before serialization.", "operationId": "get_reported_conversation_messages_api_admin_reported_conversations__report_id__messages_get", "parameters": [ { "name": "report_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Report Id" } }, { "name": "window", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 200, "minimum": 1, "description": "How many messages on either side of anchor_seq to include.", "default": 20, "title": "Window" }, "description": "How many messages on either side of anchor_seq to include." } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReportedConversationMessageListResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/admin/reported-conversations/{report_id}/dismiss": { "post": { "tags": [ "admin" ], "summary": "Dismiss Reported Conversation", "description": "Mark a report as dismissed.\n\nStamps ``dismissed_at`` (NOW) and ``reviewed_admin_user_id`` (the\ncalling admin's id). Returns 404 if the report doesn't exist, 400\nif it's already dismissed (idempotent re-dismiss is a no-op but we\nsurface it so the admin UI can show a clear error rather than\nsilently swallowing the click).", "operationId": "dismiss_reported_conversation_api_admin_reported_conversations__report_id__dismiss_post", "parameters": [ { "name": "report_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Report Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DismissReportedConversationResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/api/account/profile": { "get": { "tags": [ "account" ], "summary": "Get Profile", "description": "Return the authenticated user's profile.", "operationId": "get_profile_api_account_profile_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProfileResponse" } } } } } } }, "/api/account/usage": { "get": { "tags": [ "account" ], "summary": "Get Account Usage", "description": "Return current usage for the authenticated user.", "operationId": "get_account_usage_api_account_usage_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UsageSummary" } } } } } } }, "/api/account/export": { "get": { "tags": [ "account" ], "summary": "Export Data", "description": "Export all user data as JSON for GDPR compliance.", "operationId": "export_data_api_account_export_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "additionalProperties": true, "type": "object", "title": "Response Export Data Api Account Export Get" } } } } } } }, "/api/account/delete": { "delete": { "tags": [ "account" ], "summary": "Delete Account", "description": "Delete account: archive usage, cascade delete data, deactivate.\n\nArchives usage totals to prevent quota-reset abuse, deletes all\nuser-generated data, and deactivates the user record.", "operationId": "delete_account_api_account_delete_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatusResponse" } } } } } } }, "/api/channels/telegram": { "get": { "tags": [ "channels" ], "summary": "Get Telegram Link", "description": "Return the current user's linked Telegram user ID.", "operationId": "get_telegram_link_api_channels_telegram_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramLinkResponse" } } } } } }, "put": { "tags": [ "channels" ], "summary": "Set Telegram Link", "description": "Link a Telegram user ID to the current user's account.\n\nCreates or updates the ChannelRoute so inbound Telegram messages\nfrom this ID are routed to this user. Returns 409 if the Telegram\nuser ID is already claimed by another account.", "operationId": "set_telegram_link_api_channels_telegram_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramLinkRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramLinkResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "delete": { "tags": [ "channels" ], "summary": "Remove Telegram Link", "description": "Unlink the Telegram user ID from the current user's account.", "operationId": "remove_telegram_link_api_channels_telegram_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TelegramLinkResponse" } } } } } } }, "/api/channels/linq": { "get": { "tags": [ "channels" ], "summary": "Get Linq Link", "description": "Return the current user's linked phone number for Linq.", "operationId": "get_linq_link_api_channels_linq_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LinqLinkResponse" } } } } } }, "put": { "tags": [ "channels" ], "summary": "Set Linq Link", "description": "Link a phone number to the current user for Linq (iMessage/RCS/SMS).\n\nCreates or updates the ChannelRoute so inbound Linq messages from this\nphone number are routed to this user. Returns 409 if the phone number\nis already claimed by another account.", "operationId": "set_linq_link_api_channels_linq_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LinqLinkRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LinqLinkResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "delete": { "tags": [ "channels" ], "summary": "Remove Linq Link", "description": "Unlink the phone number from the current user's account.", "operationId": "remove_linq_link_api_channels_linq_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LinqLinkResponse" } } } } } } }, "/api/channels/bluebubbles": { "get": { "tags": [ "channels" ], "summary": "Get Bluebubbles Link", "description": "Return the current user's linked phone number or email for BlueBubbles.", "operationId": "get_bluebubbles_link_api_channels_bluebubbles_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BlueBubblesLinkResponse" } } } } } }, "put": { "tags": [ "channels" ], "summary": "Set Bluebubbles Link", "description": "Link a phone number or email to the current user for BlueBubbles.\n\nCreates or updates the ChannelRoute so inbound BlueBubbles messages from this\nidentifier are routed to this user. Returns 409 if the identifier is already\nclaimed by another account.", "operationId": "set_bluebubbles_link_api_channels_bluebubbles_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BlueBubblesLinkRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BlueBubblesLinkResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "delete": { "tags": [ "channels" ], "summary": "Remove Bluebubbles Link", "description": "Unlink the BlueBubbles identifier from the current user's account.", "operationId": "remove_bluebubbles_link_api_channels_bluebubbles_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BlueBubblesLinkResponse" } } } } } } }, "/api/channels/twilio": { "get": { "tags": [ "channels" ], "summary": "Get Twilio Link", "description": "Return the current user's linked phone number for Twilio.", "operationId": "get_twilio_link_api_channels_twilio_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TwilioLinkResponse" } } } } } }, "put": { "tags": [ "channels" ], "summary": "Set Twilio Link", "description": "Link a phone number to the current user for Twilio.\n\nCreates or updates the ChannelRoute so inbound Twilio messages from\nthis phone number are routed to this user. Returns 409 if the phone\nnumber is already claimed by another account.", "operationId": "set_twilio_link_api_channels_twilio_put", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TwilioLinkRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TwilioLinkResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "delete": { "tags": [ "channels" ], "summary": "Remove Twilio Link", "description": "Unlink the Twilio phone number from the current user's account.", "operationId": "remove_twilio_link_api_channels_twilio_delete", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TwilioLinkResponse" } } } } } } }, "/api/channels/linq/welcome": { "post": { "tags": [ "channels" ], "summary": "Send Linq Welcome", "description": "Send the onboarding welcome iMessage/SMS to the linked Linq number.", "operationId": "send_linq_welcome_api_channels_linq_welcome_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WelcomeTextResponse" } } } } } } }, "/api/channels/bluebubbles/welcome": { "post": { "tags": [ "channels" ], "summary": "Send Bluebubbles Welcome", "description": "Send the onboarding welcome iMessage to the linked BlueBubbles identifier.", "operationId": "send_bluebubbles_welcome_api_channels_bluebubbles_welcome_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WelcomeTextResponse" } } } } } } }, "/api/channels/twilio/welcome": { "post": { "tags": [ "channels" ], "summary": "Send Twilio Welcome", "description": "Send the onboarding welcome text to the linked Twilio number.", "operationId": "send_twilio_welcome_api_channels_twilio_welcome_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WelcomeTextResponse" } } } } } } }, "/api/health/detail": { "get": { "tags": [ "health" ], "summary": "Health Detail", "description": "Rich health check: status, database connectivity, uptime.\n\n``/api/health`` answers the same status question and is what the\nplatform healthcheck uses. This adds process uptime, which is what\ndistinguishes \"still degraded\" from \"just restarted\".", "operationId": "health_detail_api_health_detail_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "additionalProperties": true, "type": "object", "title": "Response Health Detail Api Health Detail Get" } } } } } } }, "/api/monitoring/status": { "get": { "tags": [ "health" ], "summary": "Monitoring Status", "description": "Current state of every health probe, plus alerting configuration.\n\nAnswers \"is it working right now\" without waiting for the next email, and\nmakes a misconfigured alert pipeline visible: ``alerts.enabled`` false with\nSMTP set means no recipient resolved.", "operationId": "monitoring_status_api_monitoring_status_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "additionalProperties": true, "type": "object", "title": "Response Monitoring Status Api Monitoring Status Get" } } } } } } }, "/api/monitoring/test-alert": { "post": { "tags": [ "health" ], "summary": "Send Test Alert", "description": "Send a synthetic alert email so SMTP and recipient wiring can be verified.\n\nWorth having because the alert path is, by design, exercised only when\nsomething is already broken. Discovering then that ``ALERT_EMAIL`` had a\ntypo is the worst possible time.\n\nOn failure the transport's own explanation is returned rather than a generic\n\"not sent\", which previously sent the operator to the container logs to\nlearn whether the cause was a typo, a rejected password, or a blocked port.", "operationId": "send_test_alert_api_monitoring_test_alert_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "additionalProperties": true, "type": "object", "title": "Response Send Test Alert Api Monitoring Test Alert Post" } } } } } } }, "/api/monitoring/diagnose-email": { "post": { "tags": [ "health" ], "summary": "Diagnose Email", "description": "Probe the email path from inside this container and explain the result.\n\nAnswers the question a failed test alert leaves open: is the mail server\nsaying no, or is nothing getting out of the container at all? Reports\nper-port TCP reachability plus a full authenticated handshake (EHLO,\nSTARTTLS, login, NOOP) against the configured port, sending no message.", "operationId": "diagnose_email_api_monitoring_diagnose_email_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "additionalProperties": true, "type": "object", "title": "Response Diagnose Email Api Monitoring Diagnose Email Post" } } } } } } }, "/api/monitoring/run-probes": { "post": { "tags": [ "health" ], "summary": "Run Probes", "description": "Start a probe run in the background and return its initial progress.\n\nDeliberately does not await the run. A full pass calls an LLM provider, a\nresidential Mac, and one auth_check per specialist per\nuser; awaiting it held the request open for minutes with nothing to show,\nand any proxy timeout in between lost the result entirely. The caller polls\n``GET /monitoring/status`` and reads ``health_monitor.run`` instead.", "operationId": "run_probes_api_monitoring_run_probes_post", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "additionalProperties": true, "type": "object", "title": "Response Run Probes Api Monitoring Run Probes Post" } } } } } } }, "/api/waitlist/join": { "post": { "tags": [ "waitlist" ], "summary": "Join Waitlist", "description": "Add an email to the waitlist.\n\nAlways returns 200 to prevent email enumeration.\nRate-limited per IP.", "operationId": "join_waitlist_api_waitlist_join_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WaitlistJoinRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatusResponse" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } } }, "components": { "schemas": { "AdminApiKeyCreate": { "properties": { "label": { "type": "string", "title": "Label", "default": "" } }, "type": "object", "title": "AdminApiKeyCreate", "description": "Mint request body. ``label`` is free-form, capped server-side." }, "AdminApiKeyItem": { "properties": { "id": { "type": "integer", "title": "Id" }, "label": { "type": "string", "title": "Label" }, "key_prefix": { "type": "string", "title": "Key Prefix" }, "created_at": { "type": "string", "title": "Created At" }, "last_used_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Last Used At" }, "revoked_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Revoked At" } }, "type": "object", "required": [ "id", "label", "key_prefix", "created_at", "last_used_at", "revoked_at" ], "title": "AdminApiKeyItem", "description": "One row in the admin's key list. Cleartext token never appears." }, "AdminApiKeyListResponse": { "properties": { "items": { "items": { "$ref": "#/components/schemas/AdminApiKeyItem" }, "type": "array", "title": "Items" } }, "type": "object", "required": [ "items" ], "title": "AdminApiKeyListResponse" }, "AdminApiKeyMintResponse": { "properties": { "id": { "type": "integer", "title": "Id" }, "token": { "type": "string", "title": "Token" }, "key_prefix": { "type": "string", "title": "Key Prefix" }, "label": { "type": "string", "title": "Label" }, "created_at": { "type": "string", "title": "Created At" } }, "type": "object", "required": [ "id", "token", "key_prefix", "label", "created_at" ], "title": "AdminApiKeyMintResponse", "description": "Mint response. ``token`` is the only place the cleartext is shown.\n\nThe caller must save the token immediately; subsequent reads of\nthe row only have the prefix. ``key_prefix`` is duplicated here\nso the frontend can echo it back to the admin alongside the\ncleartext for confirmation (\"you minted ``ck_a1b2c3d4...``\")." }, "AdminChannelConfigResponse": { "properties": { "bluebubbles_server_url": { "type": "string", "title": "Bluebubbles Server Url", "default": "" }, "bluebubbles_password_set": { "type": "boolean", "title": "Bluebubbles Password Set", "default": false }, "bluebubbles_imessage_address": { "type": "string", "title": "Bluebubbles Imessage Address", "default": "" }, "bluebubbles_send_method": { "type": "string", "title": "Bluebubbles Send Method", "default": "apple-script" }, "bluebubbles_configured": { "type": "boolean", "title": "Bluebubbles Configured", "default": false }, "telegram_bot_token_set": { "type": "boolean", "title": "Telegram Bot Token Set", "default": false }, "telegram_allowed_chat_id": { "type": "string", "title": "Telegram Allowed Chat Id", "default": "" }, "linq_api_token_set": { "type": "boolean", "title": "Linq Api Token Set", "default": false }, "linq_from_number": { "type": "string", "title": "Linq From Number", "default": "" }, "linq_allowed_numbers": { "type": "string", "title": "Linq Allowed Numbers", "default": "" }, "linq_preferred_service": { "type": "string", "title": "Linq Preferred Service", "default": "iMessage" }, "twilio_account_sid_set": { "type": "boolean", "title": "Twilio Account Sid Set", "default": false }, "twilio_auth_token_set": { "type": "boolean", "title": "Twilio Auth Token Set", "default": false }, "twilio_api_key_sid_set": { "type": "boolean", "title": "Twilio Api Key Sid Set", "default": false }, "twilio_api_key_secret_set": { "type": "boolean", "title": "Twilio Api Key Secret Set", "default": false }, "twilio_configured": { "type": "boolean", "title": "Twilio Configured", "default": false }, "twilio_phone_number": { "type": "string", "title": "Twilio Phone Number", "default": "" }, "twilio_messaging_service_sid": { "type": "string", "title": "Twilio Messaging Service Sid", "default": "" }, "twilio_allowed_numbers": { "type": "string", "title": "Twilio Allowed Numbers", "default": "" } }, "type": "object", "title": "AdminChannelConfigResponse" }, "AdminChannelConfigUpdate": { "properties": { "bluebubbles_server_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Bluebubbles Server Url" }, "bluebubbles_password": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Bluebubbles Password" }, "bluebubbles_imessage_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Bluebubbles Imessage Address" }, "bluebubbles_send_method": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Bluebubbles Send Method" }, "telegram_bot_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Telegram Bot Token" }, "telegram_allowed_chat_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Telegram Allowed Chat Id" }, "linq_api_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Linq Api Token" }, "linq_from_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Linq From Number" }, "linq_allowed_numbers": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Linq Allowed Numbers" }, "linq_preferred_service": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Linq Preferred Service" }, "twilio_account_sid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Twilio Account Sid" }, "twilio_auth_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Twilio Auth Token" }, "twilio_api_key_sid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Twilio Api Key Sid" }, "twilio_api_key_secret": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Twilio Api Key Secret" }, "twilio_phone_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Twilio Phone Number" }, "twilio_messaging_service_sid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Twilio Messaging Service Sid" }, "twilio_allowed_numbers": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Twilio Allowed Numbers" } }, "type": "object", "title": "AdminChannelConfigUpdate" }, "AdminChannelRouteEntry": { "properties": { "channel": { "type": "string", "title": "Channel" }, "channel_identifier": { "type": "string", "title": "Channel Identifier" }, "enabled": { "type": "boolean", "title": "Enabled" }, "last_inbound_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Last Inbound At" } }, "type": "object", "required": [ "channel", "channel_identifier", "enabled", "last_inbound_at" ], "title": "AdminChannelRouteEntry" }, "AdminHeartbeatLogItem": { "properties": { "id": { "type": "integer", "title": "Id" }, "user_id": { "type": "string", "title": "User Id" }, "action_type": { "type": "string", "title": "Action Type", "default": "send" }, "channel": { "type": "string", "title": "Channel", "default": "" }, "created_at": { "type": "string", "title": "Created At" } }, "type": "object", "required": [ "id", "user_id", "created_at" ], "title": "AdminHeartbeatLogItem", "description": "Heartbeat log metadata only.\n\nContent fields (``message_text``, ``reasoning``, ``tasks``) were\nremoved in #325 work item 2 \u2014 they were user-facing content the\nuser-detail response also stripped. They surface only via the\nconsent-gated paths once items 3 + 4 land." }, "AdminHeartbeatLogListResponse": { "properties": { "total": { "type": "integer", "title": "Total" }, "items": { "items": { "$ref": "#/components/schemas/AdminHeartbeatLogItem" }, "type": "array", "title": "Items" } }, "type": "object", "required": [ "total", "items" ], "title": "AdminHeartbeatLogListResponse" }, "AdminLLMConfigResponse": { "properties": { "llm_provider": { "type": "string", "title": "Llm Provider" }, "llm_model": { "type": "string", "title": "Llm Model" }, "llm_api_base": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Llm Api Base" } }, "type": "object", "required": [ "llm_provider", "llm_model" ], "title": "AdminLLMConfigResponse", "description": "Global default LLM (used when a user has no per-user override)." }, "AdminLLMConfigUpdate": { "properties": { "llm_provider": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Llm Provider" }, "llm_model": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Llm Model" }, "llm_api_base": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Llm Api Base" } }, "type": "object", "title": "AdminLLMConfigUpdate", "description": "All fields optional. Pass only what you want to change." }, "AdminLLMModelsResponse": { "properties": { "provider": { "type": "string", "title": "Provider" }, "models": { "items": { "type": "string" }, "type": "array", "title": "Models" }, "supports_listing": { "type": "boolean", "title": "Supports Listing" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Error" } }, "type": "object", "required": [ "provider", "models", "supports_listing" ], "title": "AdminLLMModelsResponse", "description": "Structured result of an ``alist_models`` call, with failure context.\n\nThe admin UI uses this to decide between:\n - rendering a real ``