{"openapi":"3.1.0","info":{"title":"SentinelX Hub","description":"MCP server + WebSocket bridge to sentinelx-core agents","version":"0.57.24"},"paths":{"/.well-known/oauth-authorization-server":{"get":{"summary":"Oauth Authorization Server","description":"RFC 8414 metadata. Claude reads this to discover the OAuth flow.","operationId":"oauth_authorization_server__well_known_oauth_authorization_server_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Oauth Authorization Server  Well Known Oauth Authorization Server Get"}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"summary":"Oauth Protected Resource","description":"RFC 9728 — tells clients which authorization server protects this resource.","operationId":"oauth_protected_resource__well_known_oauth_protected_resource_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Oauth Protected Resource  Well Known Oauth Protected Resource Get"}}}}}}},"/.well-known/oauth-protected-resource/{resource_path}":{"get":{"summary":"Oauth Protected Resource Pathed","description":"RFC 9728 path-suffixed variant.\n\nSome MCP clients (e.g. VS Code) derive the metadata URL from the resource\npath — /.well-known/oauth-protected-resource/<path> — instead of using the\nWWW-Authenticate `resource_metadata` value, and validate that the returned\n`resource` matches the exact URL they connected to (e.g. .../mcp/mcp).\nServing this variant lets those clients complete discovery and delegate to\nthe authorization server (Keycloak), where DCR is same-origin and works.","operationId":"oauth_protected_resource_pathed__well_known_oauth_protected_resource__resource_path__get","parameters":[{"name":"resource_path","in":"path","required":true,"schema":{"type":"string","title":"Resource Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Oauth Protected Resource Pathed  Well Known Oauth Protected Resource  Resource Path  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/dcr":{"post":{"summary":"Dcr Public Proxy","description":"DCR proxy that forces PUBLIC clients for loopback/desktop redirects.\n\nKeycloak's Dynamic Client Registration defaults new clients to confidential\n(client_secret_basic). Desktop MCP clients (VS Code, Android Studio, Cursor)\nuse loopback redirects + PKCE, and some (Android Studio) mishandle\nconfidential+PKCE, breaking the token exchange. For loopback redirects we\nset token_endpoint_auth_method=none (public). Web clients (claude.ai,\nchatgpt.com) are forwarded untouched — they register confidential and work.\nnginx routes Keycloak's public registration endpoint here; we forward to\nKeycloak's internal endpoint to avoid looping through that same intercept.","operationId":"dcr_public_proxy_auth_dcr_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/auth/enroll/start":{"get":{"tags":["auth"],"summary":"Enroll Start","description":"Kick off the enrollment OAuth flow.","operationId":"enroll_start_auth_enroll_start_get","parameters":[{"name":"host_id","in":"query","required":true,"schema":{"type":"string","minLength":8,"maxLength":64,"title":"Host Id"}},{"name":"callback","in":"query","required":true,"schema":{"type":"string","description":"Local URL the installer is listening on","title":"Callback"},"description":"Local URL the installer is listening on"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/enroll/callback":{"get":{"tags":["auth"],"summary":"Enroll Callback","description":"Keycloak redirects back here. We mint a JWT and bounce to the installer.","operationId":"enroll_callback_auth_enroll_callback_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","title":"Code"}},{"name":"state","in":"query","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/dashboard/enroll":{"get":{"tags":["auth"],"summary":"Dashboard Enroll","description":"Like /enroll/start, but instead of redirecting back to localhost, the\ncallback renders an HTML page with the enrollment token for the user to copy.","operationId":"dashboard_enroll_auth_dashboard_enroll_get","parameters":[{"name":"host_id","in":"query","required":true,"schema":{"type":"string","minLength":8,"maxLength":64,"title":"Host Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/dashboard/enroll/callback":{"get":{"tags":["auth"],"summary":"Dashboard Enroll Callback","description":"Keycloak redirects here after login. Render the token in HTML.","operationId":"dashboard_enroll_callback_auth_dashboard_enroll_callback_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","title":"Code"}},{"name":"state","in":"query","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard":{"get":{"tags":["dashboard"],"summary":"Dashboard Home","description":"Main dashboard page. Renders the hosts list, or the login page.","operationId":"dashboard_home_dashboard_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/stats":{"get":{"tags":["dashboard"],"summary":"Dashboard Stats","description":"User's own stats: hosts + activity, scoped strictly to their account.\n\nReuses the same per-user aggregates the admin panel computes, but keyed\nto the authenticated user's own sub — never a path/query param — so a\nuser can only ever see their own data. Metadata only; no command payloads.","operationId":"dashboard_stats_dashboard_stats_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/plans":{"get":{"tags":["dashboard"],"summary":"Dashboard Plans","description":"Plans / upgrade page. Shows the founder ladder as anchor cards and the\nuser's current plan. Reached from the Upgrade CTA and as the checkout\nsuccess_url (?upgraded=1); ?changed=1 after an in-app tier change.","operationId":"dashboard_plans_dashboard_plans_get","parameters":[{"name":"upgraded","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Upgraded"}},{"name":"changed","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Changed"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/login":{"get":{"tags":["dashboard"],"summary":"Dashboard Login","description":"Start OAuth PKCE against Keycloak.\n\n``next`` records where the user was heading before being asked to sign in.\nIt is kept server-side under the state key rather than echoed through the\nbrowser, so it can never bounce a user to a third-party site.","operationId":"dashboard_login_dashboard_login_get","parameters":[{"name":"next","in":"query","required":false,"schema":{"type":"string","default":"","title":"Next"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/callback":{"get":{"tags":["dashboard"],"summary":"Dashboard Callback","description":"Exchange the authorization code for tokens, mint a session cookie.","operationId":"dashboard_callback_dashboard_callback_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","title":"Code"}},{"name":"state","in":"query","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/logout":{"get":{"tags":["dashboard"],"summary":"Dashboard Logout","description":"Log the user out completely.\n\nTwo-step process:\n  1. Drop the local session cookie (so the hub forgets the user).\n  2. Redirect to Keycloak's RP-initiated logout endpoint so it\n     tears down the SSO session as well. Otherwise the next \"Sign\n     in\" click would silently re-authenticate using the surviving\n     Keycloak cookie, never showing the Google/GitHub picker.\n\nWhen we have a cached id_token (set during login), we pass it as\n`id_token_hint` and Keycloak performs the logout silently. Without\nit (Redis miss, expired TTL, or pre-existing sessions from before\nthis feature), Keycloak shows its built-in \"Are you sure?\" page —\nthat's the safe fallback, not an error.","operationId":"dashboard_logout_dashboard_logout_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts":{"get":{"tags":["dashboard"],"summary":"Api List Hosts","description":"Return all hosts owned by the authenticated user (active + offline).","operationId":"api_list_hosts_dashboard_api_hosts_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/system":{"get":{"tags":["dashboard"],"summary":"Api System","description":"Canonical version info: hub, protocol, and the recommended agent version.\nSurfaced in the dashboard so operators (and third-party protocol\nimplementers) have a single authoritative reference.","operationId":"api_system_dashboard_api_system_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/profile":{"get":{"tags":["dashboard"],"summary":"Api Get Profile","description":"Current MCP toolset profile for the authenticated user ('compact'|'full').","operationId":"api_get_profile_dashboard_api_profile_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["dashboard"],"summary":"Api Set Profile","description":"Set the user's MCP toolset profile. Takes effect on the next tools/list\n(the MCP client may need to refresh/reconnect the connector).","operationId":"api_set_profile_dashboard_api_profile_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/tool-bridge":{"get":{"tags":["dashboard"],"summary":"Api Get Tool Bridge","description":"Whether the caller has opted into the experimental POST /v1/tool bridge.","operationId":"api_get_tool_bridge_dashboard_api_tool_bridge_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["dashboard"],"summary":"Api Set Tool Bridge","description":"Opt the caller in/out of the experimental /v1/tool bridge. Off by default;\nwhile on, an API key can drive ANY MCP tool (integrations, host-management)\nover REST, not just the agent ops /v1/op exposes.","operationId":"api_set_tool_bridge_dashboard_api_tool_bridge_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/audit":{"get":{"tags":["dashboard"],"summary":"Api User Audit","description":"Return the authenticated user's own recent audit entries.\n\nServed from the retained Postgres store when available (user-scoped),\nso this mirrors the durable trace the admin panel has — the correlation\nids (hub/mcp request, conversation ref, transport, client, sentinel\ncontext) travel with each entry. Still metadata only: never the command\ntext, script body, path, or any payload. Filtered strictly to this user's\nactivity; a user never sees anyone else's -- except, with host sharing on,\nwhat members did on the hosts this user shared with them (trimmed rows,\nactor=member + actor_email; see storage.audit). host_id is enriched with the\nuser's label/hostname so the UI can show a friendly name instead of the\nopaque id.","operationId":"api_user_audit_dashboard_api_audit_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/apikeys":{"get":{"tags":["dashboard"],"summary":"Api List Apikeys","description":"List the caller's own REST API keys (metadata only) plus whether the\noperator has the feature enabled. Never returns the secret or the hash.","operationId":"api_list_apikeys_dashboard_api_apikeys_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["dashboard"],"summary":"Api Mint Apikey","description":"Create a named API key for the caller. The plaintext key is in THIS\nresponse only; it is never stored or shown again. Policy (only monitoring\nkeys may never expire; a full key expires within 90 days) lives in\ntokens.mint_named_key.","operationId":"api_mint_apikey_dashboard_api_apikeys_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintKeyBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/postman-collection":{"get":{"tags":["dashboard"],"summary":"Api Postman Collection","description":"The REST API as a Postman collection (two folders: Monitoring and Full\naccess). baseUrl defaults to production; the file carries no key.","operationId":"api_postman_collection_dashboard_api_postman_collection_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/apikeys/{key_id}":{"delete":{"tags":["dashboard"],"summary":"Api Revoke Apikey","description":"Revoke one of the caller's own keys. 404 if it is not theirs, so a user\ncan never probe for or revoke another user's key.","operationId":"api_revoke_apikey_dashboard_api_apikeys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/instructions":{"post":{"tags":["dashboard"],"summary":"Api Set Instructions","description":"Instructions for the assistant about this host (issue #52). Dashboard only:\nno MCP tool writes them, so a model can never set its own instructions. An\nempty text removes them.","operationId":"api_set_instructions_dashboard_api_hosts__host_id__instructions_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstructionsBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["dashboard"],"summary":"Api Remove Instructions","operationId":"api_remove_instructions_dashboard_api_hosts__host_id__instructions_delete","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/label":{"post":{"tags":["dashboard"],"summary":"Api Set Label","operationId":"api_set_label_dashboard_api_hosts__host_id__label_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["dashboard"],"summary":"Api Remove Label","operationId":"api_remove_label_dashboard_api_hosts__host_id__label_delete","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/hosts/{host_id}/config":{"get":{"tags":["dashboard"],"summary":"Dashboard Host Config","description":"Full-page view of a host's live policy (owner-only).\n\nThis is the owner-only counterpart to the admin panel's config_summary\ncounts. The full lists are sensitive — they describe exactly how the\nuser locked down their own server — so they are NEVER persisted in the\nhub and NEVER shown to admins. We fetch them live from the agent, in\nresponse to the owner navigating here from their own dashboard, and\nrender them without storing anything.\n\nSecurity: resolve_session(user_id, host_id) keys on the authenticated\nuser's id, so a user can only reach their own hosts — asking for\nsomeone else's host_id simply finds no session, never another user's\ndata.","operationId":"dashboard_host_config_dashboard_hosts__host_id__config_get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/hosts/{host_id}/activity":{"get":{"tags":["dashboard"],"summary":"Dashboard Host Activity","description":"Full-page view of a host's own on-host audit log (owner-only).\n\nUnlike the Activity tab — which shows metadata from the hub's shared ring\nbuffer — this fetches the host's local audit log live via read_audit. That\nlog is the only place the actual command/payload is kept; it lives on the\nhost and is pulled on demand here, never stored in the hub. Same\nowner-only security model as the config view: resolve_session(user_id,\nhost_id) keys on the authenticated user, so a user can only ever read\ntheir own host's log.","operationId":"dashboard_host_activity_dashboard_hosts__host_id__activity_get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/hosts/{host_id}/config/edit":{"get":{"tags":["dashboard"],"summary":"Dashboard Host Config Edit","description":"Full-page editor for a host's config.yaml (owner-only).\n\nRenders the current YAML into an editable page. This GET only *shows*\nthe editor; the actual write is a separate POST that requires the arm\ntoken. If the host is offline or lacks the self-edit tools, we fall\nback to the read-only config page rather than showing a dead editor.","operationId":"dashboard_host_config_edit_dashboard_hosts__host_id__config_edit_get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/config/arm":{"post":{"tags":["dashboard"],"summary":"Api Arm Config Edit","description":"Arm the config editor for one host: mint a short-lived edit token.\n\nThis is the deliberate, explicit action that gates config writes. The\nclient calls it only after the user confirms they understand they're\nabout to modify the security policy. The returned token must accompany\nthe later write; a session cookie alone is not enough.\n\nRejects if the host is offline or lacks the self-edit tools in its\nallowlist — no point arming an editor that can't write.","operationId":"api_arm_config_edit_dashboard_api_hosts__host_id__config_arm_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/config":{"post":{"tags":["dashboard"],"summary":"Api Write Config","description":"Write a new config.yaml to a host. Requires session + arm token.\n\nTwo independent authorizations are required:\n  1. A valid session cookie (proves WHO you are).\n  2. A valid config-edit arm token for THIS host (proves a deliberate,\n     recent intent to edit — a stolen cookie alone can't get here).\n\nThe write itself validates YAML, backs up the current file, and only\nthen replaces it — all server-side on the host, via script_run.","operationId":"api_write_config_dashboard_api_hosts__host_id__config_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteConfigBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/restart":{"post":{"tags":["dashboard"],"summary":"Api Restart Agent","description":"Restart the SentinelX agent on a host (owner-only).\n\nLow-risk: a restart just re-reads the existing config — it changes no\npermissions and touches no allowlist. It's how a config edit takes\neffect. Only the session cookie is required (no arm token): restarting\ncan't escalate anything on its own.\n\nThe agent restarts *itself*, so its WebSocket drops mid-command and the\ncall raises a disconnect. That's the EXPECTED success signal, not a\nfailure — the agent reconnects to the hub on its own within seconds.","operationId":"api_restart_agent_dashboard_api_hosts__host_id__restart_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/disable":{"post":{"tags":["dashboard"],"summary":"Api Disable Host","description":"Disable a host: mark it as blocked AND close the active session.\n\nDisable is persistent — once disabled, the agent's reconnect\nattempts are rejected at handshake until the user re-enables.\nThe host stays visible in the dashboard with a \"Disabled\" badge.\n\nDoes NOT revoke the agent's identity_token. If the user wants a\nhard revoke (e.g. server compromised, token leaked), that's a\nseparate operation (not implemented yet).","operationId":"api_disable_host_dashboard_api_hosts__host_id__disable_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DisableBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/enable":{"post":{"tags":["dashboard"],"summary":"Api Enable Host","description":"Re-enable a previously disabled host.\n\nIdempotent — calling enable on an already-enabled host is fine\n(returns ok=True, was_disabled=False). The agent will be free to\nreconnect on its next attempt; we don't actively wake it up here.","operationId":"api_enable_host_dashboard_api_hosts__host_id__enable_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}":{"delete":{"tags":["dashboard"],"summary":"Api Forget Host","description":"Permanently remove a host from the dashboard.\n\n- Closes the active session if connected (agent will reconnect — this is a\n  soft revoke, not a hard one)\n- Deletes the persisted metadata (host_meta in Redis)\n- Deletes the label if any (host_label in Redis)\n\nThe host will reappear if the agent reconnects with a still-valid\nidentity_token. To prevent that, the user needs to uninstall the agent\non the host (which deletes /etc/sentinelx/identity.json).\n\nThis is documented in the UI: \"Forget host\" with a clear explanation\nthat it's only meaningful after uninstalling the agent.","operationId":"api_forget_host_dashboard_api_hosts__host_id__delete","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/enroll":{"post":{"tags":["dashboard"],"summary":"Api Generate Enrollment","description":"Mint a fresh enrollment JWT for a new host.\n\nIf host_id is omitted, a random one is generated on the agent side at\ninstall time; the user can also pass a specific host_id.","operationId":"api_generate_enrollment_dashboard_api_enroll_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrollBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/sharing":{"get":{"tags":["dashboard"],"summary":"Api Host Sharing","operationId":"api_host_sharing_dashboard_api_hosts__host_id__sharing_get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/sharing/invites":{"post":{"tags":["dashboard"],"summary":"Api Invite","operationId":"api_invite_dashboard_api_hosts__host_id__sharing_invites_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/sharing/invites/{invite_id}":{"delete":{"tags":["dashboard"],"summary":"Api Cancel Invite","operationId":"api_cancel_invite_dashboard_api_hosts__host_id__sharing_invites__invite_id__delete","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"invite_id","in":"path","required":true,"schema":{"type":"string","title":"Invite Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/sharing/members/{member_id}/role":{"post":{"tags":["dashboard"],"summary":"Api Member Role","operationId":"api_member_role_dashboard_api_hosts__host_id__sharing_members__member_id__role_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"member_id","in":"path","required":true,"schema":{"type":"string","title":"Member Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/hosts/{host_id}/sharing/members/{member_id}":{"delete":{"tags":["dashboard"],"summary":"Api Revoke","operationId":"api_revoke_dashboard_api_hosts__host_id__sharing_members__member_id__delete","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"member_id","in":"path","required":true,"schema":{"type":"string","title":"Member Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/sharing":{"get":{"tags":["dashboard"],"summary":"Api My Sharing","operationId":"api_my_sharing_dashboard_api_sharing_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/sharing/invites/{invite_id}/accept":{"post":{"tags":["dashboard"],"summary":"Api Accept","operationId":"api_accept_dashboard_api_sharing_invites__invite_id__accept_post","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"string","title":"Invite Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/sharing/invites/{invite_id}/decline":{"post":{"tags":["dashboard"],"summary":"Api Decline","operationId":"api_decline_dashboard_api_sharing_invites__invite_id__decline_post","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"string","title":"Invite Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/sharing/hosts/{host_id}":{"delete":{"tags":["dashboard"],"summary":"Api Leave","operationId":"api_leave_dashboard_api_sharing_hosts__host_id__delete","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/integrations":{"get":{"tags":["integrations"],"summary":"Integrations Page","description":"Serve the integrations management UI.\n\nMirrors the auth pattern of /dashboard: if there's no session, bounce\nto /dashboard/login (which kicks off the Keycloak flow). We use a\nredirect here rather than HTTPException(401) because this endpoint\nis meant to be browsed to directly — a JSON error would be a\nconfusing experience.","operationId":"integrations_page_dashboard_integrations_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/integrations":{"get":{"tags":["integrations"],"summary":"Api List Integrations","description":"Return all integrations attached to the calling user.","operationId":"api_list_integrations_dashboard_api_integrations_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["integrations"],"summary":"Api Create Integration","description":"Create or replace one of the calling user's integrations.\n\nIdempotent on (user_id, name) — re-posting with the same name\noverwrites the previous secret. That's intentional: it's how a\nuser rotates a credential without needing a separate endpoint.","operationId":"api_create_integration_dashboard_api_integrations_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIntegrationRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/integrations/{name}":{"delete":{"tags":["integrations"],"summary":"Api Delete Integration","description":"Remove an integration. 200 with {deleted: false} if it didn't exist.","operationId":"api_delete_integration_dashboard_api_integrations__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/support":{"get":{"tags":["support"],"summary":"Support Page","operationId":"support_page_dashboard_support_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/support":{"get":{"tags":["support"],"summary":"Api Support State","operationId":"api_support_state_dashboard_api_support_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/support/policy":{"post":{"tags":["support"],"summary":"Api Set Policy","operationId":"api_set_policy_dashboard_api_support_policy_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/support/drafts/{draft_id}/approve":{"post":{"tags":["support"],"summary":"Api Approve Draft","operationId":"api_approve_draft_dashboard_api_support_drafts__draft_id__approve_post","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/api/support/drafts/{draft_id}/discard":{"post":{"tags":["support"],"summary":"Api Discard Draft","operationId":"api_discard_draft_dashboard_api_support_drafts__draft_id__discard_post","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/files/d/{token}":{"get":{"tags":["file_links"],"summary":"Download Info","operationId":"download_info_files_d__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["file_links"],"summary":"Download","operationId":"download_files_d__token__post","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"}}}}}}},"/files/u/{token}":{"get":{"tags":["file_links"],"summary":"Upload Info","operationId":"upload_info_files_u__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["file_links"],"summary":"Upload Whole","description":"curl -T <file> <url>: the whole file in one request (Cloudflare caps a\nrequest at 100 MB; larger files go through the page). The hub hashes the\nbytes as they pass and the agent must arrive at the same sha256.","operationId":"upload_whole_files_u__token__put","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"}}}}}}},"/files/u/{token}/begin":{"post":{"tags":["file_links"],"summary":"Upload Begin","operationId":"upload_begin_files_u__token__begin_post","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"}}}}}}},"/files/u/{token}/part":{"put":{"tags":["file_links"],"summary":"Upload Part","operationId":"upload_part_files_u__token__part_put","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"index","in":"query","required":true,"schema":{"type":"integer","title":"Index"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/files/u/{token}/finish":{"post":{"tags":["file_links"],"summary":"Upload Finish","operationId":"upload_finish_files_u__token__finish_post","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"}}}}}}},"/admin/":{"get":{"tags":["admin"],"summary":"Admin Home","operationId":"admin_home_admin__get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin":{"get":{"tags":["admin"],"summary":"Admin Home","operationId":"admin_home_admin_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users":{"get":{"tags":["admin"],"summary":"Admin Users Page","operationId":"admin_users_page_admin_users_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts":{"get":{"tags":["admin"],"summary":"Admin Hosts Page","operationId":"admin_hosts_page_admin_hosts_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts/{host_id}":{"get":{"tags":["admin"],"summary":"Admin Host Detail Page","operationId":"admin_host_detail_page_admin_hosts__host_id__get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/audit":{"get":{"tags":["admin"],"summary":"Admin Audit Page","operationId":"admin_audit_page_admin_audit_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/reports":{"get":{"tags":["admin"],"summary":"Admin Reports Page","operationId":"admin_reports_page_admin_reports_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/reports/{report_id}":{"get":{"tags":["admin"],"summary":"Admin Report Detail Page","operationId":"admin_report_detail_page_admin_reports__report_id__get","parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"string","title":"Report Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/billing":{"get":{"tags":["admin"],"summary":"Admin Billing Page","operationId":"admin_billing_page_admin_billing_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hub":{"get":{"tags":["admin"],"summary":"Admin Hub Page","description":"Hub operational health + status. Read-only metadata only.","operationId":"admin_hub_page_admin_hub_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/rejections":{"get":{"tags":["admin"],"summary":"Admin Rejections Page","description":"Recent /agent/connect rejections across all users. Read-only metadata.\n\nDefaults to the invalid-token view. host_disabled is about 94% of the\nwindow and is a setting working correctly, so showing everything by\ndefault hides the few people who cannot enrol behind hundreds of rows\nthat need nothing.","operationId":"admin_rejections_page_admin_rejections_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/prepare-restart":{"post":{"tags":["admin"],"summary":"Api Prepare Restart","description":"Drain this worker's agents ahead of a planned restart.\n\nCalled by the deploy before stopping the process, because by the time a\nshutdown hook runs uvicorn has already closed the sockets: the hint would\nhave nobody left to tell. Here the sessions are still up, so each agent is\ngiven its own retry_after and comes back spread over a window rather than\nthe whole fleet arriving at once while the hub is still starting.\n\nDraining also stops the per-session Redis cleanup that a mass disconnect\ntriggers. That cleanup is three or four round trips per session, it\nexhausts a pool of 100 within seconds, and it fails for nearly every\nsession anyway -- 10,280 \"Too many connections\" in a single restart. What\nit would have written expires by itself in 90 seconds.\n\nOnly this worker's own sessions are reachable from here; with several\nworkers the deploy calls it once per worker, or simply accepts that the\nremainder fall back to their own schedule.","operationId":"api_prepare_restart_admin_api_prepare_restart_post","parameters":[{"name":"spread_seconds","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spread Seconds"}},{"name":"refuse_new_work","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Refuse New Work"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/hub/resume":{"post":{"tags":["admin"],"summary":"Api Hub Resume","description":"Accept new work again, for a deploy that was aborted after phase one.\n\nNot strictly required: the flag carries a TTL and clears itself. This is for\nthe case where someone stops a deploy and does not want to wait it out.","operationId":"api_hub_resume_admin_api_hub_resume_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/rejections/outreach":{"post":{"tags":["admin"],"summary":"Api Rejection Outreach","description":"Email the owner of a stuck host the diagnosis and the fix.\n\nDeliberately operator-triggered rather than automatic: the classifier is\ngood but a wrong diagnosis mailed at scale is worse than a slow one, and\nthe person sending it should have looked at the row first. Recording the\ncontact also stops the daily alert for that host.\n\n`to` overrides the recipient, which is how the copy gets proofread by\nsending it to yourself before it goes to a user.","operationId":"api_rejection_outreach_admin_api_rejections_outreach_post","parameters":[{"name":"sub","in":"query","required":true,"schema":{"type":"string","title":"Sub"}},{"name":"host_id","in":"query","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"}},{"name":"self_copy","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Self Copy"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/clients":{"get":{"tags":["admin"],"summary":"Admin Clients Page","description":"MCP client coverage + context continuity (last 24h). Split off the\noverview because its count(DISTINCT) aggregations were its heaviest\nquery; here it only runs on demand (still briefly cached).","operationId":"admin_clients_page_admin_clients_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/_debug/memory":{"get":{"tags":["admin"],"summary":"Admin Debug Memory","description":"In-process leak diagnostic: RSS + tracemalloc growth vs baseline.\n\nWARNING: tracemalloc adds per-allocation CPU overhead. On a busy hub\nprefer the low-overhead gc path (no ?enable), and keep frames small.\n\nParams (runtime-controllable, no restart):\n  ?gc=1             one-shot gc object-type histogram (heaviest read;\n                    run it twice a few min apart and diff gc_top_types)\n  ?enable=1         start tracemalloc + baseline (default frames=1)\n  ?frames=N         tracemalloc stack depth, 1-10 (higher = more CPU)\n  ?disable=1        stop tracemalloc (turn overhead back off)\n  ?reset_baseline=1 re-zero the growth baseline (measure from now)\n  ?top=N            cap reported sites (default 20)\n\nRecommended low-risk run: GET ?gc=1 twice ~5 min apart, compare the\ntype whose count grew. Only if inconclusive, GET ?enable=1&frames=1\nfor a SHORT window (2-5 min), GET (read top_growth), then ?disable=1.","operationId":"admin_debug_memory_admin__debug_memory_get","parameters":[{"name":"enable","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Enable"}},{"name":"disable","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Disable"}},{"name":"reset_baseline","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Reset Baseline"}},{"name":"gc","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Gc"}},{"name":"frames","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Frames"}},{"name":"top","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Top"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}":{"get":{"tags":["admin"],"summary":"Admin User Detail Page","operationId":"admin_user_detail_page_admin_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/overview":{"get":{"tags":["admin"],"summary":"Api Overview","operationId":"api_overview_admin_api_overview_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/users":{"get":{"tags":["admin"],"summary":"Api Users","operationId":"api_users_admin_api_users_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/errors":{"get":{"tags":["admin"],"summary":"Api Errors","description":"Top error signatures grouped from the audit ring buffer.","operationId":"api_errors_admin_api_errors_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/users/{user_id}":{"get":{"tags":["admin"],"summary":"Api User Detail","operationId":"api_user_detail_admin_api_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/hosts":{"get":{"tags":["admin"],"summary":"Api Hosts","operationId":"api_hosts_admin_api_hosts_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/hosts/{host_id}":{"get":{"tags":["admin"],"summary":"Api Host Detail","operationId":"api_host_detail_admin_api_hosts__host_id__get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/audit":{"get":{"tags":["admin"],"summary":"Api Audit","operationId":"api_audit_admin_api_audit_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/reports":{"get":{"tags":["admin"],"summary":"Admin Api Reports","operationId":"admin_api_reports_admin_api_reports_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Admin Api Reports Admin Api Reports Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/reports/{report_id}":{"get":{"tags":["admin"],"summary":"Admin Api Report Detail","operationId":"admin_api_report_detail_admin_api_reports__report_id__get","parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"string","title":"Report Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Api Report Detail Admin Api Reports  Report Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Admin Api Report Update","operationId":"admin_api_report_update_admin_api_reports__report_id__post","parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"string","title":"Report Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Api Report Update Admin Api Reports  Report Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/health":{"get":{"tags":["admin"],"summary":"Api Health","description":"Lightweight health summary for the admin UI: live counters.","operationId":"api_health_admin_api_health_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/users/{user_id}/suspend":{"post":{"tags":["admin"],"summary":"Api Suspend User","operationId":"api_suspend_user_admin_api_users__user_id__suspend_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/users/{user_id}/unsuspend":{"post":{"tags":["admin"],"summary":"Api Unsuspend User","operationId":"api_unsuspend_user_admin_api_users__user_id__unsuspend_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/hosts/{host_id}/kick":{"post":{"tags":["admin"],"summary":"Api Kick Host","description":"Force-close the WebSocket for a host. The agent will reconnect.","operationId":"api_kick_host_admin_api_hosts__host_id__kick_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/hosts/{host_id}/disable":{"post":{"tags":["admin"],"summary":"Api Admin Disable Host","description":"Admin-initiated disable of a host (potentially owned by another user).\n\nUnlike kick (which is a one-shot connection drop), disable is persistent:\nthe host stays rejected at handshake until someone re-enables it.\n\nThe `reason` field on the disabled meta is prefixed with \"[admin: <email>]\"\nso the owner sees in their own dashboard that the action was administrative,\nnot their own.\n\nReason for not accepting a free-text reason from the admin here: the admin\npanel is the operator's tool, and we keep its actions terse. If audit\ndetail is needed, it lives in the structured logs (admin_email + target).","operationId":"api_admin_disable_host_admin_api_hosts__host_id__disable_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/hosts/{host_id}/enable":{"post":{"tags":["admin"],"summary":"Api Admin Enable Host","description":"Admin-initiated enable. Idempotent: enabling an already-enabled host\nreturns ok with was_disabled=False.\n\nNote: this enables ANY disabled host, including ones the OWNER themselves\ndisabled. That's intentional — the admin has policy override. The audit\nlog captures both who disabled and who enabled, so the trail is intact.","operationId":"api_admin_enable_host_admin_api_hosts__host_id__enable_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","title":"Host Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/apikeys":{"get":{"tags":["admin"],"summary":"Admin Apikeys Page","operationId":"admin_apikeys_page_admin_apikeys_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/apikeys":{"get":{"tags":["admin"],"summary":"Api Apikeys State","operationId":"api_apikeys_state_admin_api_apikeys_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/apikeys/enable":{"post":{"tags":["admin"],"summary":"Api Apikeys Enable","operationId":"api_apikeys_enable_admin_api_apikeys_enable_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/apikeys/disable":{"post":{"tags":["admin"],"summary":"Api Apikeys Disable","operationId":"api_apikeys_disable_admin_api_apikeys_disable_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/campaigns":{"get":{"tags":["admin"],"summary":"Admin Campaigns Page","operationId":"admin_campaigns_page_admin_campaigns_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/broadcasts":{"get":{"tags":["admin"],"summary":"Api List Broadcasts","operationId":"api_list_broadcasts_admin_api_broadcasts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Api Publish Broadcast","operationId":"api_publish_broadcast_admin_api_broadcasts_post","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/broadcasts/{bcast_id}":{"delete":{"tags":["admin"],"summary":"Api Retract Broadcast","operationId":"api_retract_broadcast_admin_api_broadcasts__bcast_id__delete","parameters":[{"name":"bcast_id","in":"path","required":true,"schema":{"type":"string","title":"Bcast Id"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/agent/enroll":{"post":{"tags":["hub"],"summary":"Agent Enroll","description":"Exchange a one-time enrolment token for a host credential.\n\nThe installer calls this with the bootstrap token and writes what comes back\ninto identity.json, so the agent only ever holds a session credential. Every\nrefusal carries a stable `error` so the installer can tell the operator\nwhat happened instead of leaving a host that silently never connects.","operationId":"agent_enroll_agent_enroll_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/agent/rotate":{"post":{"tags":["hub"],"summary":"Agent Rotate","description":"Exchange a current, valid host credential for a fresh one.\n\nThe agent calls this past the half-life of its own credential. The new jti\nbecomes current (the old one stays valid as `previous` for one more\nrotation, so a lagging reconnect on the old credential is not rejected),\nwhich is what makes a stolen copy stop working at the next rotation.","operationId":"agent_rotate_agent_rotate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/op":{"post":{"tags":["api"],"summary":"Post Op","description":"Authenticate an API key and run one op through the shared ``forward()``.\n\n``client_request_id`` is optional. When present it creates an at-most-once\nexecution boundary in Redis: an identical retry replays the stored response,\na conflicting reuse is rejected, and concurrent duplicates cannot both run.\nEvery authenticated request also gets a Hub-generated ``hub_request_id`` for\ncorrelation across response + audit.","operationId":"post_op_v1_op_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Post Op V1 Op Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tool":{"post":{"tags":["api"],"summary":"Post Tool","description":"Dispatch one MCP tool through the REST bridge with request identity.\n\nAuthenticated calls always receive a Hub-generated correlation id. Supplying\n``client_request_id`` additionally creates the same at-most-once/replay\nboundary used by POST /v1/op, including concurrent-duplicate protection.","operationId":"post_tool_v1_tool_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Post Tool V1 Tool Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/ops":{"get":{"tags":["api"],"summary":"Get Ops","operationId":"get_ops_v1_ops_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Ops V1 Ops Get"}}}}}}},"/v1/ops/{op}":{"get":{"tags":["api"],"summary":"Get Op","operationId":"get_op_v1_ops__op__get","parameters":[{"name":"op","in":"path","required":true,"schema":{"type":"string","title":"Op"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Op V1 Ops  Op  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tools":{"get":{"tags":["api"],"summary":"Get Tools","operationId":"get_tools_v1_tools_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Tools V1 Tools Get"}}}}}}},"/v1/openapi.json":{"get":{"tags":["api"],"summary":"Get Openapi","operationId":"get_openapi_v1_openapi_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Openapi V1 Openapi Json Get"}}}}}}},"/v1/contract":{"get":{"tags":["api"],"summary":"Get Contract","description":"The full REST manual for the calling key, rendered from the same template\nthe mint tool uses. This is what a `terse` mint's contract_ref points at: a\ncold caller fetches it once, then re-mints in `none` mode for the session.","operationId":"get_contract_v1_contract_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Contract V1 Contract Get"}}}}}}},"/v1/keys":{"get":{"tags":["api"],"summary":"List Keys","description":"List the caller's live keys — metadata only, never the secret.","operationId":"list_keys_v1_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response List Keys V1 Keys Get"}}}}}},"post":{"tags":["api"],"summary":"Mint Key","description":"Mint a scoped API key for the authenticated OAuth user. Shown ONCE.","operationId":"mint_key_v1_keys_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/MintRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Mint Key V1 Keys Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/keys/{key_id}":{"delete":{"tags":["api"],"summary":"Revoke Key","description":"Revoke one of the caller's own keys. 404 if it isn't theirs.","operationId":"revoke_key_v1_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Revoke Key V1 Keys  Key Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/checkout":{"get":{"tags":["billing"],"summary":"Billing Checkout","description":"Start a checkout for `tier` (?tier=plus&billing=monthly|annual).\n\nRequires a dashboard session; redirects to login if absent. On success\n303-redirects to Polar's hosted checkout URL.","operationId":"billing_checkout_billing_checkout_get","parameters":[{"name":"tier","in":"query","required":true,"schema":{"type":"string","title":"Tier"}},{"name":"billing","in":"query","required":false,"schema":{"type":"string","default":"monthly","title":"Billing"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/portal":{"get":{"tags":["billing"],"summary":"Billing Portal","description":"Open the Polar customer portal for the logged-in user, where an existing\nsubscriber changes tier / billing period or cancels. Requires a dashboard\nsession. 303-redirects to Polar's hosted portal.","operationId":"billing_portal_billing_portal_get","parameters":[{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/change":{"post":{"tags":["billing"],"summary":"Billing Change","description":"Change an existing subscriber's tier in-app (no portal round-trip).\n\nCalled via fetch from the plans page, so it returns JSON (not a redirect).\nSwitches the tier within the SAME billing interval via the Polar merchant\nAPI (proration applied immediately); switching monthly<->annual stays in\nthe portal. Uncancels first if the sub was scheduled to cancel.","operationId":"billing_change_billing_change_post","parameters":[{"name":"tier","in":"query","required":true,"schema":{"type":"string","title":"Tier"}},{"name":"billing","in":"query","required":false,"schema":{"type":"string","default":"monthly","title":"Billing"}},{"name":"sentinelx_dash_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentinelx Dash Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/polar/webhook":{"post":{"tags":["billing"],"summary":"Polar Webhook","description":"Receive a Polar webhook: verify signature, apply subscription state,\nreconcile live host slots. Returns 2xx on accept, 4xx on a bad signature.\n\nWe read the RAW body before any parsing (signatures cover exact bytes).","operationId":"polar_webhook_billing_polar_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"BroadcastCreate":{"properties":{"type":{"type":"string","title":"Type"},"severity":{"type":"string","title":"Severity","default":"info"},"title":{"type":"string","title":"Title"},"body":{"type":"string","title":"Body","default":""},"target":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target"},"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds"}},"type":"object","required":["type","title"],"title":"BroadcastCreate"},"CreateIntegrationRequest":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_\\-]{0,63}$","title":"Name","description":"Short user-chosen label, e.g. 'personal-cf'. Letters, digits, underscore and hyphen only; must start with an alphanumeric."},"provider":{"type":"string","enum":["cloudflare","telegram","resend","compute"],"title":"Provider","description":"Which third-party service this credential is for."},"secret_value":{"type":"string","maxLength":8192,"minLength":1,"title":"Secret Value","description":"The plaintext credential (API token, bot token, etc.). Sent encrypted-at-rest immediately; never logged."},"metadata":{"additionalProperties":{"type":"string"},"type":"object","title":"Metadata","description":"Non-secret hints to the playbook layer, e.g. {'zone': 'pensa.ar'} for Cloudflare. Must not contain secret data — anything in here is returned by list."}},"additionalProperties":false,"type":"object","required":["name","provider","secret_value"],"title":"CreateIntegrationRequest","description":"Body for POST /dashboard/api/integrations.\n\n`secret_value` is the only place plaintext credentials enter the\nsystem. It is consumed immediately by storage.set_integration,\nencrypted with Fernet, and never persisted in plaintext nor\nreturned in any response."},"DisableBody":{"properties":{"reason":{"type":"string","maxLength":200,"title":"Reason","default":""}},"type":"object","title":"DisableBody","description":"Body for disable. `reason` is optional free text the user can\nleave for themselves — appears in the dashboard next to the badge."},"EnrollBody":{"properties":{"host_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Host Id"}},"type":"object","title":"EnrollBody"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InstructionsBody":{"properties":{"text":{"type":"string","maxLength":4000,"title":"Text"}},"type":"object","required":["text"],"title":"InstructionsBody"},"InviteBody":{"properties":{"email":{"type":"string","maxLength":254,"minLength":3,"title":"Email"},"role":{"type":"string","pattern":"^(view|operate)$","title":"Role"}},"type":"object","required":["email","role"],"title":"InviteBody"},"LabelBody":{"properties":{"label":{"type":"string","maxLength":32,"minLength":1,"title":"Label"}},"type":"object","required":["label"],"title":"LabelBody"},"MintKeyBody":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name"},"scope":{"type":"string","pattern":"^(monitoring|full)$","title":"Scope"},"expiry":{"type":"string","pattern":"^(1d|7d|30d|90d|never)$","title":"Expiry"}},"type":"object","required":["name","scope","expiry"],"title":"MintKeyBody"},"MintRequest":{"properties":{"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"type":"object","title":"MintRequest","description":"Body of ``POST /v1/keys``. ``scope`` is fixed to the user's full access\nin v1 (see the spec LOCKED block), so only TTL and a label are accepted."},"OpRequest":{"properties":{"op":{"type":"string","title":"Op"},"host_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Host Id"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"client_request_id":{"anyOf":[{"type":"string","maxLength":128,"minLength":1,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"},{"type":"null"}],"title":"Client Request Id","description":"Optional caller-generated idempotency key. Reusing it with the same logical request replays the prior result without re-executing; reusing it with different op/host/payload is rejected."}},"type":"object","required":["op"],"title":"OpRequest","description":"Body of ``POST /v1/op`` — the same triple an MCP tool resolves to."},"PolicyBody":{"properties":{"policy":{"type":"string","title":"Policy"}},"type":"object","required":["policy"],"title":"PolicyBody"},"ReportUpdateRequest":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"external_issue_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Issue Provider"},"external_issue_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Issue Url"},"fixed_in_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fixed In Version"},"resolution_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Summary"}},"type":"object","title":"ReportUpdateRequest"},"RoleBody":{"properties":{"role":{"type":"string","pattern":"^(view|operate)$","title":"Role"}},"type":"object","required":["role"],"title":"RoleBody"},"ToolRequest":{"properties":{"tool":{"type":"string","title":"Tool"},"args":{"additionalProperties":true,"type":"object","title":"Args"},"host_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Host Id"},"client_request_id":{"anyOf":[{"type":"string","maxLength":128,"minLength":1,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"},{"type":"null"}],"title":"Client Request Id","description":"Optional caller-generated idempotency key. Reusing it with the same effective tool invocation replays the prior result without calling the tool again; conflicting reuse is rejected."}},"type":"object","required":["tool"],"title":"ToolRequest","description":"Body of ``POST /v1/tool`` — call any registered MCP tool by name.\n\n``args`` are the tool's own parameters (its JSON Schema is in GET\n/v1/tools). ``host_id`` is a convenience: for agent tools it's merged into\n``args`` so the caller can keep it out of the args blob, matching /v1/op."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WriteConfigBody":{"properties":{"content":{"type":"string","maxLength":512000,"title":"Content"},"token":{"type":"string","title":"Token","description":"config-edit arm token"}},"type":"object","required":["content","token"],"title":"WriteConfigBody"}}}}