openapi: 3.2.0 info: title: AI Gateway MCP Server Admin API description: Universal MCP Gateway with OAuth integration - Modern Architecture version: 2.0.3290 tags: - name: Admin paths: /admin/extensions/api/webmcp: get: summary: Webmcp Admin Config Endpoint description: 'GET/POST /admin/extensions/api/webmcp — instance toggle + OT token. GET returns ``{"enabled": bool, "origin_trial_token_set": bool}``. POST accepts ``{"enabled": bool, "origin_trial_token": "..."}`` — either key may be omitted to leave that setting unchanged. The token value itself is never echoed back (it is not secret in the browser, where it ships as a meta tag, but there is no reason to expose it on an admin JSON feed either). SECURITY: no caller-supplied value reaches any response here. Both fields are booleans derived from stored state (``origin_trial_token_set`` is ``bool(token)``, never the token), and the route is behind ``require_admin_user``. Noted explicitly because the anti-pattern check reads the enclosing function for evidence, and it used to find that evidence in the hardcoded success flag this route no longer has — that flag is on the check''s whitelist as a "static success response". It was also exactly the dishonesty #978 removed, so the evidence is now stated as a fact rather than borrowed from a claim that was untrue.' operationId: webmcp_admin_config_endpoint_admin_extensions_api_webmcp_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Webmcp Admin Config Endpoint description: 'GET/POST /admin/extensions/api/webmcp — instance toggle + OT token. GET returns ``{"enabled": bool, "origin_trial_token_set": bool}``. POST accepts ``{"enabled": bool, "origin_trial_token": "..."}`` — either key may be omitted to leave that setting unchanged. The token value itself is never echoed back (it is not secret in the browser, where it ships as a meta tag, but there is no reason to expose it on an admin JSON feed either). SECURITY: no caller-supplied value reaches any response here. Both fields are booleans derived from stored state (``origin_trial_token_set`` is ``bool(token)``, never the token), and the route is behind ``require_admin_user``. Noted explicitly because the anti-pattern check reads the enclosing function for evidence, and it used to find that evidence in the hardcoded success flag this route no longer has — that flag is on the check''s whitelist as a "static success response". It was also exactly the dishonesty #978 removed, so the evidence is now stated as a fact rather than borrowed from a claim that was untrue.' operationId: webmcp_admin_config_endpoint_admin_extensions_api_webmcp_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/logging/level: post: summary: Admin Logging Level description: 'One-click runtime + persistent log-level switch. The Debug toggle on /admin/logging hits this endpoint with ``{"level": "debug"}`` or ``{"level": "info"}``. We persist the choice via ``save_config`` so it survives restarts and apply ``setLevel`` immediately so the running worker (and the ring buffer''s handler) reflects the new level before the next log record fires. Same behaviour as the legacy ``/setup/logging`` form-POST handler, just driven by a single JSON field.' operationId: admin_logging_level_admin_logging_level_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/logging/entries: get: summary: Admin Logging Entries description: 'JSON feed for the in-page log viewer. Reads the in-memory app-log ring buffer (PII-scrubbed at write time) and returns up to ``limit`` records plus buffer stats so the client can show coverage. Admin-only — same gate as every other /admin route.' operationId: admin_logging_entries_admin_logging_entries_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/changelog: get: summary: Admin Changelog operationId: admin_changelog_admin_changelog_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/hooks: get: summary: Admin Hooks2 operationId: admin_hooks2_admin_hooks_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/document-review: get: summary: Admin Document Review2 operationId: admin_document_review2_admin_document_review_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map: get: summary: Admin Context Map2 operationId: admin_context_map2_admin_context_map_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions: get: summary: Admin Extensions2 description: 'Legacy redirect — Extensions was merged into Services in 2.0.653. Anyone with a bookmark to /admin/extensions or /admin/extensions#import lands on Services / the appropriate Import surface. The route stays alive so external bookmarks keep working.' operationId: admin_extensions2_admin_extensions_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/import: get: summary: Admin Import Combined operationId: admin_import_combined_admin_import_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/import/openapi: get: summary: Admin Import Openapi operationId: admin_import_openapi_admin_import_openapi_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/import/mcp: get: summary: Admin Import Mcp operationId: admin_import_mcp_admin_import_mcp_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/services/{service_id}: get: summary: Admin Service Detail description: 'Detail page for a single service — the deep-configuration surface (action management, source-file metadata, lifecycle controls). The Services tile carries only state + Edit- credentials; everything that is "what can I do with this service" lives here, behind a "Manage actions →" link from the tile.' operationId: admin_service_detail_admin_services__service_id__get parameters: - name: service_id in: path required: true schema: type: string title: Service Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/audit: get: summary: Admin Audit2 operationId: admin_audit2_admin_audit_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/destructive: get: summary: Admin Destructive2 operationId: admin_destructive2_admin_destructive_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/notifications: get: summary: Admin Notifications2 operationId: admin_notifications2_admin_notifications_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/throughput: get: summary: Admin Throughput2 description: 'Per-user request-volume dashboard (devs/ai-gateway#762). Wrapped in the unified Admin Panel chrome so the sidebar / header stay consistent with the surrounding compliance pages. Sub-routes (``/entries``, ``/export``, ``/thresholds``) stay direct in ``admin_throughput_routes`` — they return JSON or CSV, no chrome required.' operationId: admin_throughput2_admin_throughput_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/usage: get: summary: Admin Action Usage2 description: 'Action-usage dashboard — which (tool, action) pairs agents call most (devs/ai-gateway#797, Layer 1). The usage lens on the throughput counters, wrapped in the panel chrome so it sits beside Data Usage. Server-rendered; no JSON sub-routes needed.' operationId: admin_action_usage2_admin_usage_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/usage/export: get: summary: Admin Action Usage Export description: '``GET /admin/usage/export`` — CSV of the action-usage table. Mirrors the Data Usage export: same formula-injection guard, same "export is a data-egress event → audit it" rule. Carries the full window''s rows (no per-user data — aggregate (tool, action) counts).' operationId: admin_action_usage_export_admin_usage_export_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/tokens: get: summary: Admin Tokens2 description: 'Operations page — three things an operator actually needs: 1. **Recent activity** — searchable audit feed answering "what just happened?" for compliance + forensics. 2. **Provider health** — per-service connection counts + bulk rotate ("Notion client leaked → force everyone to reconnect"). 3. **Maintenance** — instance-wide cleanup actions for the rare nuclear cases. Per-user token lifecycle lives on /admin/team. The old per-user inspector + stats grid + breakdown views were replaced — they were debug-output, not operator-actions. URL stays /admin/tokens so existing bookmarks survive; the menu label is "Operations".' operationId: admin_tokens2_admin_tokens_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/subscription: get: summary: Admin Subscription2 description: 'Subscription management — clean inline form, no popup. Status on top, key textarea + actions below. Reuses the existing ``/admin/api/subscription`` POST endpoints via fetch in the page script so we don''t duplicate server logic.' operationId: admin_subscription2_admin_subscription_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/diagnostics: get: summary: Admin Diagnostics2 description: 'Legacy redirect — both the "Help & diagnostics" page and the short-lived /admin/help page that replaced it have been retired in 2.0.654. The Help sidebar group itself stays (Support Sessions / Changelog / Documentation), but a dedicated page for "Help" was redundant with those items. Bookmarks land on Changelog, which is the closest "what''s going on with this gateway" surface.' operationId: admin_diagnostics2_admin_diagnostics_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/help: get: summary: Admin Help Redirect description: 'Same redirect as /admin/diagnostics — the standalone Help page was retired in 2.0.654; resources live in the Help sidebar group directly.' operationId: admin_help_redirect_admin_help_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/users: get: summary: Admin Users2 description: 'Legacy /admin/users — folded into Organization. Redirect so anyone with a bookmark lands on the new home.' operationId: admin_users2_admin_users_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/support: get: summary: Admin Support2 description: 'Support Sessions — inline render of the dashboard''s HTML template, wrapped in panel chrome. No HTTP self-call needed: we import the same template the support package exposes so the admin-css link + dashboard scripts load just like before.' operationId: admin_support2_admin_support_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/branding: get: summary: Setup Branding description: 'Step 1: Company branding.' operationId: setup_branding_admin_branding_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Setup Branding Save description: Save branding and advance to next step. operationId: setup_branding_save_admin_branding_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/team: get: summary: Setup Team description: 'Step 2: Team configuration.' operationId: setup_team_admin_team_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Setup Team Save description: Save team config and advance. operationId: setup_team_save_admin_team_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/organization: get: summary: Setup Team description: 'Step 2: Team configuration.' operationId: setup_team_admin_organization_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Setup Team Save description: Save team config and advance. operationId: setup_team_save_admin_organization_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/services: get: summary: Setup Services description: 'Step 3: Service connections. Uses YAML meta as single source of truth (SSOT). Services are grouped by ``meta.category``: gateway, oauth, google.' operationId: setup_services_admin_services_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/sign-in: get: summary: Setup Sign In Overview description: 'Sign-in section landing page: tile-per-provider grid plus a bootstrap-admin card. Mirrors the services tab so the gray / yellow / green semantics + ''Active'' badge are visually consistent across the entire Settings hub.' operationId: setup_sign_in_overview_admin_sign_in_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/sign-in/bootstrap-admin: post: summary: Setup Sign In Bootstrap Admin operationId: setup_sign_in_bootstrap_admin_admin_sign_in_bootstrap_admin_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/sign-in/{provider}: get: summary: Setup Sign In Provider description: 'Per-provider config page. One ``setup-card`` with the provider''s fields, plus a footer-bar with Save / Make active / Test sign-in / Back-to-overview buttons depending on state.' operationId: setup_sign_in_provider_admin_sign_in__provider__get parameters: - name: provider in: path required: true schema: type: string title: Provider responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin post: summary: Setup Sign In Provider Save operationId: setup_sign_in_provider_save_admin_sign_in__provider__post parameters: - name: provider in: path required: true schema: type: string title: Provider responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/sign-in/{provider}/activate: post: summary: Setup Sign In Provider Activate operationId: setup_sign_in_provider_activate_admin_sign_in__provider__activate_post parameters: - name: provider in: path required: true schema: type: string title: Provider responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/logging: get: summary: Setup Logging operationId: setup_logging_admin_logging_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Setup Logging Save operationId: setup_logging_save_admin_logging_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/pii: get: summary: Setup Data Sanitization description: 'Step 5: Customer data sanitization (PII protection layer). Pure render — admin API endpoints under ``/admin/api/pii-sanitization`` do all the state changes; this page is just chrome around them.' operationId: setup_data_sanitization_admin_pii_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/delegation: get: summary: Setup Delegation description: 'Delegated Access lives on the Clients page (it is an inbound-client auth mode). Kept as a redirect so the old /admin/delegation URL still lands somewhere canonical instead of a diverging second surface.' operationId: setup_delegation_admin_delegation_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/clients: get: summary: Setup Clients description: 'Clients — external apps that connect TO the gateway (inbound MCP). Hosts the admin-configurable OAuth redirect-host allowlist. Pure render; ``/admin/api/clients/*`` does the state changes.' operationId: setup_clients_admin_clients_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/data-sanitization: get: summary: Setup Data Sanitization description: 'Step 5: Customer data sanitization (PII protection layer). Pure render — admin API endpoints under ``/admin/api/pii-sanitization`` do all the state changes; this page is just chrome around them.' operationId: setup_data_sanitization_admin_data_sanitization_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/complete: get: summary: Setup Complete description: 'Step 5: Summary and config export.' operationId: setup_complete_admin_complete_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin: get: summary: Setup Complete description: 'Step 5: Summary and config export.' operationId: setup_complete_admin_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/overview/stats: get: summary: Admin Overview Stats description: 'JSON feed for the Overview''s auto-refresh. Returns the same numbers the server-rendered Overview template bakes in (alert counts + Data-today MB). The page polls this every 30 s (paused while the tab is hidden) so the operator sees fresh counts without a full reload. Fail-open: any backend hiccup yields zeros, never a 500 here.' operationId: admin_overview_stats_admin_overview_stats_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/configure-service: post: summary: Setup Configure Service description: Configure a gateway-managed service by setting ENV vars at runtime. operationId: setup_configure_service_admin_configure_service_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/verify-service-credentials: post: summary: Setup Verify Service Credentials description: 'Ask the upstream whether the OPERATOR credentials actually work. The per-user "Verify connection" button next to this one answers a different question — whether the signed-in person''s own connection works — and reaches `_verify_credentials` through the per-user connect routes. A connector credentialed by the operator has no per-user connection to verify, so nothing ran its declared check and the only way to find out was to call one of its actions by hand. Deliberately resolved through ``resolve_secret``, not by reading the submitted form or Redis alone: that is the same lookup an actual call performs (per-user Redis, then server-wide Redis, then the environment), so a green answer here means the credential the connector would really use is the one that worked.' operationId: setup_verify_service_credentials_admin_verify_service_credentials_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/check-provider: post: summary: Setup Check Provider description: 'Ask ONE provider in the routing map whether it actually answers. THE CHECK IS A REAL CALL — one COMPLETION token plus whatever the three-word prompt costs, not one token in total; `max_tokens` caps only the answer — through the ordinary path — `get_executor("ai").execute("chat_completion", …)`. The connector''s own hooks therefore govern it: the routing hook picks the entry and the egress guard confines the request to the endpoint that entry declares. A bespoke HTTP client here would be a second way out of the gateway, with its own copy of those rules to drift from — which is the whole reason a connection check on an operator-entered URL is usually a new SSRF surface. This one adds none: it reaches exactly where a completion for that model would already go. TWO THINGS DO NOT COME WITH THAT PATH, because they live one layer up, in `src/mcp/tool_executor.py`, where an MCP tool call arrives: * The daily token budget is not consulted. Deliberate — a diagnostic has to stay available on the day the budget runs out, which is exactly a day somebody needs to know whether a provider answers. It costs one completion token, it is behind the admin gate, and the operator pressed the button themselves. * The measured consumption is not recorded by that layer, so this records it here. Without it the one call an operator makes by hand would be the one call the Data Usage view cannot see, and a view that is silent about a real spend is worse than one that is absent. An earlier version of this docstring claimed both of them happened. Neither did. THE SAVED MAP, never the form. A model id that is not in the stored registry is refused before anything is sent, so a URL an operator has typed but not saved can never be fetched from here. It also answers the question `/models` cannot: an entry that names a provider has its endpoint deliberately short of the version segment the translation layer adds, so a model listing there asks for a path outside the API. A one-token completion takes the path the real calls take, for both kinds of entry, and costs about that much.' operationId: setup_check_provider_admin_check_provider_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/configure-instance: post: summary: Setup Configure Instance description: 'Configure instance-level settings (BASE_URL, OIDC, ADMIN_USERS). Persists to /data/gateway.env so the config survives restarts. Only admins can access this.' operationId: setup_configure_instance_admin_configure_instance_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/internal-domains: post: summary: Setup Internal Domains Save description: 'Save the colleague-whitelist domain list used by the PII sanitizer. Lives on the PII tab because that''s the only feature that consumes it; routing here means the admin doesn''t have to keep two halves of the same idea (sign-in eligibility vs. PII exemption) cross-linked across tabs.' operationId: setup_internal_domains_save_admin_internal_domains_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/service-scope-gap: post: summary: Setup Service Scope Gap description: 'Scopes this service''s actions need that the consent will not request. Called before saving, with the extra-scopes value as typed, so the answer describes the state the operator is about to be in rather than the one they are leaving. Read-only and advisory: the save is never blocked, because an operator may knowingly run an incomplete set (no board features, say) and only they know that.' operationId: setup_service_scope_gap_admin_service_scope_gap_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/service-scope-plan: get: summary: Setup Service Scope Plan description: What an operator would have to grant for what they switched on. operationId: setup_service_scope_plan_admin_service_scope_plan_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/service-scope-adopt: post: summary: Setup Service Scope Adopt description: 'Add the derived permissions to what this install requests. The operator never types a scope name. What they confirm is the half that cannot be derived: that the provider''s app now allows these — and that confirmation is only as good as the reconnect that follows, which is why the previous value is recorded before the write.' operationId: setup_service_scope_adopt_admin_service_scope_adopt_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/service-scope-rollback: post: summary: Setup Service Scope Rollback description: 'Restore the request set to what it was before the adoption. Exact, not reconstructed: the recorded previous value goes back verbatim, so a scope the operator had added by hand earlier survives an undo of a later automatic addition.' operationId: setup_service_scope_rollback_admin_service_scope_rollback_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/service-scope-settle: post: summary: Setup Service Scope Settle description: 'Drop the undo offer once a grant proves the adoption worked. Deliberately server-checked rather than taken on the client''s word: the dialog cannot see the grants, and "the popup closed" is not evidence that a consent succeeded.' operationId: setup_service_scope_settle_admin_service_scope_settle_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/pii-sanitization: get: summary: Get Pii Sanitization description: 'GET /admin/api/pii-sanitization — slim current state (for Setup Wizard). Returns minimal fields to reduce reconnaissance surface if the response ever leaks (client logs, screenshots). For the full internal config dump use ``GET /admin/api/pii-sanitization/debug``.' operationId: get_pii_sanitization_admin_api_pii_sanitization_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Post Pii Sanitization description: 'POST /admin/api/pii-sanitization — cluster-wide settings update. Body accepts either or both of: - ``display`` (object): ``default_level`` and/or ``services`` — a level per service, or ``"inherit"`` to follow the default again. - ``secret_opt_out`` (object): connector → bool, re-open a secret store. ``enabled``, ``scrub_style`` and a boolean ``services`` map are refused with 400: the display levels replaced them. Values are stored in Redis so every Gunicorn worker picks them up within the flag cache TTL (5s).' operationId: post_pii_sanitization_admin_api_pii_sanitization_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/pii-sanitization/debug: get: summary: Get Pii Sanitization Debug description: 'GET /admin/api/pii-sanitization/debug — full internal state dump. Separate endpoint from the UI-serving GET so we can rate-limit / audit / deprecate it later without breaking the Setup Wizard. Intended for DPO reviews, troubleshooting, and on-call inspection.' operationId: get_pii_sanitization_debug_admin_api_pii_sanitization_debug_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/pii-sanitization/preview: post: summary: Post Pii Preview description: 'POST /admin/api/pii-sanitization/preview — preview every display level. Body: ``{"sample": {...}, "service": "metabase"}``. Returns the input transformed at each display level side by side, cleartext included. Uses a throwaway mapping so the preview doesn''t pollute the real per-user pseudonym counter.' operationId: post_pii_preview_admin_api_pii_sanitization_preview_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/pii-sanitization/reload: post: summary: Post Pii Reload description: 'POST /admin/api/pii-sanitization/reload — reload YAML config. Re-reads ``config/pii_sanitization.yaml``, compiles regexes, and validates the new shape. Returns 200 with the new state on success or 400 with the error on failure (config is left in the previous valid state). Metrics + audit event emitted either way.' operationId: post_pii_reload_admin_api_pii_sanitization_reload_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/document-review/term-lists: get: summary: Get Document Review Terms description: Current vocabulary + allowlist entries for the admin tables. operationId: get_document_review_terms_admin_api_document_review_term_lists_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Post Document Review Terms description: Add or remove a term. Removal affects future documents only. operationId: post_document_review_terms_admin_api_document_review_term_lists_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/document-review/term-lists/export: get: summary: Get Document Review Terms Export description: JSON export of both lists (backup / migration). operationId: get_document_review_terms_export_admin_api_document_review_term_lists_export_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/document-review/feature: post: summary: Post Document Review Feature description: Admin toggle for the document-review feature (Redis-backed, no deploy). operationId: post_document_review_feature_admin_api_document_review_feature_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/document-review/demo: post: summary: Post Document Review Demo description: 'Create a demo review from the embedded sample contract. Real detector, real review page, fictional data — and releases from a demo never write the durable term lists. Works while the feature is disabled so operators can preview the flow before enabling it.' operationId: post_document_review_demo_admin_api_document_review_demo_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/delegation: get: summary: Get Delegation description: Current flag state + the calling admin's own grants. operationId: get_delegation_admin_api_delegation_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Post Delegation description: Toggle the master flag. operationId: post_delegation_admin_api_delegation_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/delegation/all: get: summary: Get All Delegations description: 'Every live delegation grant across ALL subjects (admin overview). Mint-on-demand creates grants for whichever user called, so the per-subject view alone can''t surface them for oversight/revocation. Subjects are masked; the grant_id is the revoke handle (DELETE /admin/api/delegation/grants/{id}).' operationId: get_all_delegations_admin_api_delegation_all_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/delegation/grants: post: summary: Post Grant description: 'Mint a self-delegated grant for the calling admin. The subject is always the authenticated admin — never a different person.' operationId: post_grant_admin_api_delegation_grants_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/delegation/grants/{grant_id}: delete: summary: Delete Grant description: Revoke a grant. Returns 404 if it was not found. operationId: delete_grant_admin_api_delegation_grants__grant_id__delete parameters: - name: grant_id in: path required: true schema: type: string title: Grant Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/delegation/automations: get: summary: Get Automation description: 'The registration (allow-list + declared services) for one automation. ``?automation_id=``. Returns the stored record (subjects masked) or ``{registered: false}`` when none exists.' operationId: get_automation_admin_api_delegation_automations_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Post Automation description: 'Register (or replace) a shared automation''s visibility + requires (admin override). Body: ``{automation_id, visibility, allowed_emails: [...], requires: [...], created_by?}``. ``created_by`` is preserved across an admin edit (so an admin tweaking someone else''s automation does not silently steal ownership); pass it explicitly to re-own. On a brand-new registration with no ``created_by`` the calling admin becomes the owner. Allow-list entries must be known gateway users. Consumed by the run-as-invoker trigger path.' operationId: post_automation_admin_api_delegation_automations_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin delete: summary: Delete Automation description: 'Unregister a shared automation. ``?automation_id=``. Returns 404 if it was not registered.' operationId: delete_automation_admin_api_delegation_automations_delete responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/delegation/automations/pending: get: summary: Get Pending Automations description: Every shared-automation registration awaiting approval (admin review). operationId: get_pending_automations_admin_api_delegation_automations_pending_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/delegation/automations/decision: post: summary: Post Automation Decision description: 'Approve or reject a pending shared-automation registration. Body: ``{automation_id, decision: "approve" | "reject"}``. Same decision core the notification buttons use — one audit trail regardless of surface.' operationId: post_automation_decision_admin_api_delegation_automations_decision_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/clients/redirect-hosts: get: summary: Get Redirect Hosts operationId: get_redirect_hosts_admin_api_clients_redirect_hosts_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Post Redirect Host operationId: post_redirect_host_admin_api_clients_redirect_hosts_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/clients/redirect-hosts/{host}: delete: summary: Delete Redirect Host operationId: delete_redirect_host_admin_api_clients_redirect_hosts__host__delete parameters: - name: host in: path required: true schema: type: string title: Host responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/connections: get: summary: Get Connections description: List connected MCP clients (issued tokens, one row per connection). operationId: get_connections_admin_api_clients_connections_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/clients/connections/{connection_id}: delete: summary: Delete Connection description: Revoke one connection by id (token hash). Returns 404 if not found. operationId: delete_connection_admin_api_clients_connections__connection_id__delete parameters: - name: connection_id in: path required: true schema: type: string title: Connection Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts: get: summary: Get Service Accounts operationId: get_service_accounts_admin_api_clients_service_accounts_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Post Service Account description: Create a service account. Returns the client_secret ONCE (not recoverable). operationId: post_service_account_admin_api_clients_service_accounts_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/clients/service-accounts/{client_id}: delete: summary: Delete Service Account operationId: delete_service_account_admin_api_clients_service_accounts__client_id__delete parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/provider-token: put: summary: Put Service Account Provider Token description: 'Store the service account''s OWN provider token (e.g. a GitLab group-access token) so the gateway acts on that provider AS the account. Body: {service, token, expires_at?}. The token value is stored only in the gateway (never returned) and must be within the account''s scope.' operationId: put_service_account_provider_token_admin_api_clients_service_accounts__client_id__provider_token_put parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin get: summary: Get Service Account Provider Token description: 'Provider-token status for an account (presence + expiry, never the value). `?service=` selects which provider; defaults to the account''s first scoped service.' operationId: get_service_account_provider_token_admin_api_clients_service_accounts__client_id__provider_token_get parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin delete: summary: Delete Service Account Provider Token description: Clear a service account's stored provider token. `?service=` required. operationId: delete_service_account_provider_token_admin_api_clients_service_accounts__client_id__provider_token_delete parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/rotation: put: summary: Put Service Account Rotation description: 'Configure token rotation for (account, service). GitLab (self_rotate): {service, resource, base_url?, auto?, lead_days?} — `resource` is the GitLab token owner: ''personal'' | ''group/'' | ''project/''. GitHub (github_app): {service, kind:''github_app'', app_id, installation_id, api_base?, private_key?, auto?} — the App private key is stored encrypted and never returned.' operationId: put_service_account_rotation_admin_api_clients_service_accounts__client_id__rotation_put parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/provider-token/rotate: post: summary: Post Service Account Rotate description: 'Rotate the account''s provider token now (GitLab self-rotation API). Body: {service}. Requires rotation `resource` configured.' operationId: post_service_account_rotate_admin_api_clients_service_accounts__client_id__provider_token_rotate_post parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/provider-setup: get: summary: Get Service Account Provider Setup description: 'How to give this account a provider credential: can the gateway create it itself, and the deep link to the exact provider page as a fallback.' operationId: get_service_account_provider_setup_admin_api_clients_service_accounts__client_id__provider_setup_get parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/provider-token/create: post: summary: Post Service Account Provider Token Create description: 'Have the gateway create the provider token itself (GitLab access-token API) instead of the admin pasting one. Body: {service, resource?, access_level?}.' operationId: post_service_account_provider_token_create_admin_api_clients_service_accounts__client_id__provider_token_create_post parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/secret: post: summary: Post Service Account Secret description: 'Regenerate the account''s client_secret (returned ONCE). Keeps the client_id, so an existing caller config only needs the new secret.' operationId: post_service_account_secret_admin_api_clients_service_accounts__client_id__secret_post parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/act-as: put: summary: Put Service Account Act As description: 'Grant or withdraw this account''s permission to act as a human. Body: ``{"allowed": true|false}``. Separate from the account''s service scope, and from revoking it: a service can lose "may act as a person" and keep doing its own unattended work. Kept out of the environment on purpose — withdrawing it has to be possible at the speed of an incident, not of a release.' operationId: put_service_account_act_as_admin_api_clients_service_accounts__client_id__act_as_put parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/pii-cleartext: put: summary: Put Service Account Pii Cleartext description: 'Grant or withdraw this account''s permission to receive unmasked PII. Body: ``{"allowed": true|false}``. ``scrub_pii=false`` on a call is an honor-system flag — fine for a person choosing what appears on their own screen, meaningless from a service, which chooses nothing. This is where that request gets its authority: an admin vouching that the data is handled properly wherever this service takes it. Capped by the account''s ``allowed_services``, so it reads as "cleartext within this account''s scope" and is narrowed by narrowing the account.' operationId: put_service_account_pii_cleartext_admin_api_clients_service_accounts__client_id__pii_cleartext_put parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/code-agent: put: summary: Put Service Account Code Agent description: 'Mark or unmark this account as a code agent. Body: ``{"code_agent": true|false}``. A code agent is an AI agent that writes code through this account. The GitLab guard then keeps it away from the CI definition that judges its branch and from the tests: it may not commit CI files, and it may not merge a change that edits CI or removes or edits tests. A person merges those. Admin-only, like every permission on the record: an account that could lift its own restriction would not be restricted.' operationId: put_service_account_code_agent_admin_api_clients_service_accounts__client_id__code_agent_put parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/allowed-services: put: summary: Put Service Account Allowed Services description: 'Replace this account''s service scope. Body: ``{"services": ["gitlab", "slack"]}``. Without this the scope was fixed at creation, so an automation registered without a service it turned out to need could be corrected only by deleting and recreating it — new ``client_id``, new ``client_secret``, every caller updated. The scope is the one field of a service account that routinely turns out to be wrong after the fact, because it is guessed before the automation is written. Replaces rather than merges, so this surface can narrow a scope as well as widen one — see :func:`service_accounts.set_allowed_services`.' operationId: put_service_account_allowed_services_admin_api_clients_service_accounts__client_id__allowed_services_put parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/clients/service-accounts/{client_id}/allowed-services/add: post: summary: Post Service Account Add Service description: 'Add ONE service to this account''s scope. Body: ``{"service": "ai"}``. For the clients page''s "Add to scope" on a recent refusal. Unlike the PUT, it does not send a whole scope read when the page loaded, so an edit made since cannot be undone by it; the add runs inside the same atomic read-modify-write as the scope setter.' operationId: post_service_account_add_service_admin_api_clients_service_accounts__client_id__allowed_services_add_post parameters: - name: client_id in: path required: true schema: type: string title: Client Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/api/subscription: get: summary: Get Subscription Status operationId: get_subscription_status_admin_api_subscription_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Apply Subscription Key operationId: apply_subscription_key_admin_api_subscription_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin delete: summary: Remove Subscription Key operationId: remove_subscription_key_admin_api_subscription_delete responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/subscription/refresh: post: summary: Refresh Subscription Now description: 'Force an immediate refresh attempt. Unlike the periodic background task, this endpoint always tries to pull a new token regardless of how close the current one is to expiring — so operators can verify the refresh path works without waiting for the threshold.' operationId: refresh_subscription_now_admin_api_subscription_refresh_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/quality-metrics/consume: get: summary: Consume Quality Metrics Handler description: 'Consume all quality metrics from Redis (one-time). - Returns all quality metrics - Deletes them from Redis after successful response - Maintainer-only (logged for audit) - Uses Redis lock to prevent concurrent access Use case: Developer wants to review production queries for improvements' operationId: consume_quality_metrics_handler_admin_quality_metrics_consume_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/routing-health-check: post: summary: Routing Health Check Handler description: Manually trigger routing health check (admin only). operationId: routing_health_check_handler_admin_routing_health_check_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/security/session-cleanup: post: summary: Security Session Cleanup description: Clear ALL browser sessions (log out all users) operationId: security_session_cleanup_admin_security_session_cleanup_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/security/oauth-cleanup: post: summary: Security Oauth Cleanup description: Clear OAuth service tokens only (preserves MCP tokens, sessions, instance config) operationId: security_oauth_cleanup_admin_security_oauth_cleanup_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/security/emergency-cleanup: post: summary: Security Emergency Cleanup description: Clear ALL sessions + OAuth service tokens (preserve MCP tokens, instance config) operationId: security_emergency_cleanup_admin_security_emergency_cleanup_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/security/rotate-provider/{name}: post: summary: Security Rotate Provider description: 'Clear all stored tokens for ONE specific OAuth provider. Use case: an OAuth client got rotated (Notion client_id changed) or compromised (leaked credentials). Every user with a saved token for that provider needs to re-OAuth against the new client. Scoped sibling to ``security_oauth_cleanup`` — that one wipes every provider, this one wipes one. Same Redis key shape (``service:{name}:{user_hash}``).' operationId: security_rotate_provider_admin_security_rotate_provider__name__post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/cleanup/ai-tokens: post: summary: Admin Ai Tokens Cleanup description: Clear ALL AI Provider tokens (MCP + ChatGPT) operationId: admin_ai_tokens_cleanup_admin_cleanup_ai_tokens_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/user/{email}/tokens: delete: summary: Clear User Tokens description: 'Clear all OAuth service tokens for a specific user (preserve AI Provider tokens: ChatGPT & Claude Code)' operationId: clear_user_tokens_admin_user__email__tokens_delete parameters: - name: email in: path required: true schema: type: string title: Email responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/user/{email}/delete: delete: summary: Delete User Completely description: 'Delete a user completely including all tokens and sessions. Each cleanup section runs in its own try/except so a failure in one namespace does not block the rest, but failures are now AGGREGATED into ``failed_items`` and returned to the caller. Pre-v2.0.909 the response would say ``"success": true`` with a happy ``deleted_items`` list even when six of eight sections silently failed — admin saw "user deleted", reality was "user mostly intact". Now: HTTP 200 only when ``failed_items`` is empty; otherwise 207 (Multi-Status) so the admin can act.' operationId: delete_user_completely_admin_user__email__delete_delete parameters: - name: email in: path required: true schema: type: string title: Email responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/user/{email}/allowlist-status: get: summary: User Allowlist Status description: 'Report which allowlists currently authorize ``email``. Drives the "Revoke runtime access" confirm dialog: revoking only drops sessions + tokens + tracking records, but if the user is on any allowlist they can sign in again immediately. The dialog needs to enumerate which allowlist(s) so the admin knows where to also edit if they want to fully cut off re-entry. Returns: { "email": , "company_domain": , # email domain in COMPANY_DOMAINS env "admin_users": , # email in ADMIN_USERS env "yaml_domain": , # email''s domain matched a YAML domains: entry "yaml_guest": , # email is in access_control.yaml guests: block "tool_guest": , # email has a runtime tool_guest record "bootstrap_admin": , # email is the OIDC-bootstrap admin principal "re_entry_possible": , # any of the above true }' operationId: user_allowlist_status_admin_user__email__allowlist_status_get parameters: - name: email in: path required: true schema: type: string title: Email responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/team/guests/export.json: get: summary: Guests Export description: 'GET /admin/team/guests/export.json — JSON snapshot of every ``tool_guest:*`` record for backup / DR / migration to another instance. Admin-only. Schema is the same one ``guests.add_guest`` writes plus a top-level envelope. Round-trip-able: ``guests_import`` accepts this exact shape.' operationId: guests_export_admin_team_guests_export_json_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/team/guests/import: post: summary: Guests Import description: 'POST /admin/team/guests/import — restore guests from a previously- exported JSON file. Idempotent (same email_hash overwrites). Admin-only. Expects either a JSON body matching ``guests_export``''s shape OR a ``multipart/form-data`` upload with a ``file`` part containing the same JSON — easier from a browser without curl gymnastics.' operationId: guests_import_admin_team_guests_import_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/data: get: summary: Extensions Api Data description: JSON API returning all extension data. operationId: extensions_api_data_admin_extensions_api_data_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/hooks/api/service-actions/{service_id}: get: summary: Hooks Api Service Actions description: 'Action names + confirmation flags for ONE service. The Hook Builder''s action picker renders the list for the service the operator selected. The page used to carry the whole catalogue to do that — every service''s action names, plus the flag map, about 1 MB of JSON on a full inventory — so that `onServiceChange` could read one entry out of it. Both halves come from the same helpers the page used, so the picker cannot drift from what the page would have shown.' operationId: hooks_api_service_actions_admin_hooks_api_service_actions__service_id__get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/test: post: summary: Extensions Api Test description: Test an extension action by executing it. operationId: extensions_api_test_admin_extensions_api_test_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/remove-action: post: summary: Extensions Api Remove Action description: 'Remove an action from a service''s YAML file and hot-reload. Receives JSON: {"service": "gitlab", "action": "get_project_languages"} Removes the action entry from the YAML file entirely, writes back, and reloads.' operationId: extensions_api_remove_action_admin_extensions_api_remove_action_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/update-action-mode: post: summary: Extensions Api Update Action Mode description: 'Update the read/write mode of an existing action in a YAML extension. Receives JSON: {"service": "p6p_admin", "action": "list_backups_php", "mode": "read"}' operationId: extensions_api_update_action_mode_admin_extensions_api_update_action_mode_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/reload: post: summary: Extensions Api Reload description: Hot-reload all YAML extensions without server restart. operationId: extensions_api_reload_admin_extensions_api_reload_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/import-scan: post: summary: Extensions Api Import Scan description: 'Scan an OpenAPI spec and return grouped endpoints. Accepts JSON: {"url": "https://...", "service": "gitlab"} Returns grouped endpoints with existing-action markers.' operationId: extensions_api_import_scan_admin_extensions_api_import_scan_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/import-apply: post: summary: Extensions Api Import Apply description: 'Apply selected OpenAPI endpoints as YAML extension. Accepts JSON: { "url": "https://...", "service": "gitlab", "actions": ["get_repository_tree", "list_branches", ...] } Generates YAML and writes to the persistent extensions directory.' operationId: extensions_api_import_apply_admin_extensions_api_import_apply_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/import-mcp-scan: post: summary: Extensions Api Import Mcp Scan description: 'Probe an OAuth-protected MCP server and return a proposed YAML config. Accepts JSON: ``{"url": "https://mcp.example.com/", "service": "example_mcp"}`` Discovery: - ``/.well-known/oauth-authorization-server`` → authorize/token/registration endpoints - ``/.well-known/oauth-protected-resource`` → scopes - ``mcp_icon_discovery`` → icon URL (best-effort) Returns the suggested YAML fields the operator can review before save.' operationId: extensions_api_import_mcp_scan_admin_extensions_api_import_mcp_scan_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/import-mcp-apply: post: summary: Extensions Api Import Mcp Apply description: 'Persist a proposed MCP-service YAML extension and hot-reload. Accepts JSON: ``{"service": "example_mcp", "yaml": {...}}`` The ``yaml`` payload is the (possibly operator-edited) proposal returned by ``import-mcp/scan``. Written to a fresh ``_imported.yaml`` under the writable extensions dir.' operationId: extensions_api_import_mcp_apply_admin_extensions_api_import_mcp_apply_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/update-auth: post: summary: Extensions Api Update Auth description: 'Update auth configuration for an existing service extension. Receives JSON: { "service": "petstore", "auth": {"type": "header", "header_name": "Authorization", ...}, "base_url": "https://api.example.com/v1" (optional) } Updates the YAML file''s auth and base_url blocks, then hot-reloads.' operationId: extensions_api_update_auth_admin_extensions_api_update_auth_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/instances: get: summary: Extensions Api Instances List description: 'GET /admin/extensions/api/instances?service=X — list instances. Never returns raw credentials, only whether each instance has one and where it is sourced from (redis|env).' operationId: extensions_api_instances_list_admin_extensions_api_instances_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/instances/save: post: summary: Extensions Api Instances Save description: 'POST /admin/extensions/api/instances/save — create or update an instance. Body: {"service": "...", "name": "...", "url": "...", "auth": "..."} On update an empty ``auth`` keeps the stored credential.' operationId: extensions_api_instances_save_admin_extensions_api_instances_save_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/instances/delete: post: summary: Extensions Api Instances Delete description: 'POST /admin/extensions/api/instances/delete — remove a Redis instance. Body: {"service": "...", "name": "..."}. Env-var instances cannot be deleted here (they live in deployment config).' operationId: extensions_api_instances_delete_admin_extensions_api_instances_delete_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/remove-redis-secret: post: summary: Extensions Api Remove Redis Secret description: 'Remove the redundant Redis copy of a service''s credentials. Body: {"service": "..."}. Drops only Redis keys that also have an env value (safe — env keeps the credential). Unlike the full disconnect path this never clears gateway.env / os.environ. RBAC-gated; the removal is an operator action that changes credential storage → audited.' operationId: extensions_api_remove_redis_secret_admin_extensions_api_remove_redis_secret_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/remove-redis-bulk: post: summary: Extensions Api Remove Redis Bulk description: 'POST /admin/extensions/api/remove-redis-bulk — bulk hardening cleanup. Drops the *redundant* Redis credential copies across all services in one action: the ``both`` fields whose env value is identical, so removal is a behavioural no-op (env keeps the credential). The page-level counterpart to "Export to env" — after an operator migrates the export delta into their deployment env and restarts, this clears the now-duplicate runtime copies in one click instead of one service card at a time. Deliberately scoped to the env-identical set only. Value-mismatched fields (Redis is still the live value) and fields without an env fallback are NEVER touched here — those stay on their per-service card where the operator can review the consequence. RBAC-gated; every dropped key is audited (``redis_copy_removed``), same as the per-service action.' operationId: extensions_api_remove_redis_bulk_admin_extensions_api_remove_redis_bulk_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/credential-storage-summary: get: summary: Extensions Api Credential Storage Summary description: 'GET /admin/extensions/api/credential-storage-summary. Where the shared credentials live (env / Redis / both), for the Credential-storage cockpit. Read-only; returns counts only, never any secret values. Fetched lazily when the operator opens the panel, so it adds no Redis round-trip to page load. Single source of truth shared with the export (``redis`` + ``both_mismatch``) and bulk-remove (``both``) actions, so the displayed numbers match exactly what each action touches.' operationId: extensions_api_credential_storage_summary_admin_extensions_api_credential_storage_summary_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/reveal-secrets-bulk: post: summary: Extensions Api Reveal Secrets Bulk description: 'POST /admin/extensions/api/reveal-secrets-bulk — break-glass bulk export. Body: ``{"token": "..."}``. Returns the cleartext of the *migration set* — every credential whose active value lives in the runtime store and is not yet (correctly) in the environment (``redis``-only, plus ``both`` with a value mismatch) — as an annotated ``.env`` delta the operator merges into their real deployment environment. Same gate as the single reveal (#846): the break-glass ``SECRET_REVEAL_TOKEN`` binds the action to deployment access, which already implies total secret access. Never exports env-only / matched credentials, and never the protected keys (encryption key / session secret / reveal token). The token is never returned or logged; values are returned once and never logged; refused attempts are audited at red severity.' operationId: extensions_api_reveal_secrets_bulk_admin_extensions_api_reveal_secrets_bulk_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/set-token: post: summary: Extensions Api Set Token description: 'Store encrypted auth secrets for a service. Receives JSON (single token): {"service": "petstore", "token": "secret-api-key-value"} Or multiple secrets: {"service": "joan", "secrets": {"JOAN_API_KEY": "...", "JOAN_API_SECRET": "..."}} Encrypts and stores in Redis.' operationId: extensions_api_set_token_admin_extensions_api_set_token_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/delete-token: post: summary: Extensions Api Delete Token description: 'Delete every stored secret of a service (the dashboard''s "remove all stored secrets"), audited. Receives JSON: {"service": "petstore"}' operationId: extensions_api_delete_token_admin_extensions_api_delete_token_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/update-user-fields: post: summary: Extensions Api Update User Fields description: 'Update per-user auth field configuration for a service. Receives JSON: { "service": "grafana", "user_fields": [ {"key": "GRAFANA_TOKEN", "label": "API Token", "type": "password"}, {"key": "GRAFANA_URL", "label": "Grafana URL", "type": "url", "optional": true} ], "verify_endpoint": { "method": "GET", "path": "/api/org", "expect_status": 200 } } Updates the meta: block in the service YAML, then hot-reloads.' operationId: extensions_api_update_user_fields_admin_extensions_api_update_user_fields_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/save-hooks: post: summary: Extensions Api Save Hooks description: 'Save hook changes for a specific action. Accepts JSON: { "service": "gitlab", "action": "get_project_languages", "hooks": {"pre": ["resolve_project_path"], "post": []} } Updates the YAML file and hot-reloads.' operationId: extensions_api_save_hooks_admin_extensions_api_save_hooks_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/hooks: get: summary: Extensions Api Hooks List description: Return all available built-in hooks with descriptions. operationId: extensions_api_hooks_list_admin_extensions_api_hooks_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/disable-file: post: summary: Extensions Api Disable File description: 'Disable an entire extension YAML file by prefixing it with ''_''. Receives JSON: {"filename": "statuspage_imported.yaml"} Renames to _statuspage_imported.yaml and hot-reloads.' operationId: extensions_api_disable_file_admin_extensions_api_disable_file_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/dynamic-discovery: get: summary: Extensions Api Dynamic Discovery description: 'Toggle dynamic-action-discovery (long-tail) per service. GET returns the full per-service enabled state. POST accepts ``{"service": "...", "enabled": true|false}`` and persists through ``save_config`` (which mirrors to Redis pub/sub so blue/green peers reload without restart). Default state for every service is **ON**: the OpenAPI long-tail is visible to ``gateway_search_actions`` unless an operator opts the service out here. This governs search *visibility* only; the curated tools/list surface is unaffected either way.' operationId: extensions_api_dynamic_discovery_admin_extensions_api_dynamic_discovery_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Extensions Api Dynamic Discovery description: 'Toggle dynamic-action-discovery (long-tail) per service. GET returns the full per-service enabled state. POST accepts ``{"service": "...", "enabled": true|false}`` and persists through ``save_config`` (which mirrors to Redis pub/sub so blue/green peers reload without restart). Default state for every service is **ON**: the OpenAPI long-tail is visible to ``gateway_search_actions`` unless an operator opts the service out here. This governs search *visibility* only; the curated tools/list surface is unaffected either way.' operationId: extensions_api_dynamic_discovery_admin_extensions_api_dynamic_discovery_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/read-only-mode: get: summary: Extensions Api Read Only Mode description: 'Toggle global read-only mode. GET returns ``{"enabled": bool}``. POST accepts ``{"enabled": true|false}`` and persists via ``save_config`` (mirrored to Redis pub/sub so peers pick it up without a restart). When ON, every ``*_write_actions`` tool is hidden from ``tools/list`` and any write action is refused at dispatch.' operationId: extensions_api_read_only_mode_admin_extensions_api_read_only_mode_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Extensions Api Read Only Mode description: 'Toggle global read-only mode. GET returns ``{"enabled": bool}``. POST accepts ``{"enabled": true|false}`` and persists via ``save_config`` (mirrored to Redis pub/sub so peers pick it up without a restart). When ON, every ``*_write_actions`` tool is hidden from ``tools/list`` and any write action is refused at dispatch.' operationId: extensions_api_read_only_mode_admin_extensions_api_read_only_mode_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/set-curated: post: summary: Extensions Api Set Curated description: 'Promote a long-tail action to everyday, or demote an everyday action back to on-demand. Persists through the curated-override layer (works for built-in services whose YAML is not writable at runtime) and reloads the extensions so the change reflects in ``tools/list`` + search right away. POST ``{"service": "...", "action": "...", "curated": true|false}``' operationId: extensions_api_set_curated_admin_extensions_api_set_curated_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/apply-action-changes: post: summary: Extensions Api Apply Action Changes description: 'Apply every per-action governance change of one operator Save at once. The service detail page''s Save can promote/demote actions, reclassify their risk badge and flip high-risk approvals in a single gesture. Sending those one request at a time made each one persist, broadcast and re-bake the whole catalogue on its own — the cost of a five-action save was five full rebuilds locally plus five more on the peer. This route persists the lot under one coalesced write (one config-reload broadcast, so the peer re-bakes once) and re-bakes locally once at the end. Per-change granularity is preserved where it carries meaning: every change still writes its own audit row, and the response reports each change individually. POST ``{"service": "...", "changes": [ {"kind": "curated", "action": "...", "value": true}, {"kind": "classification", "action": "...", "value": "high_risk"}, {"kind": "approval", "action": "...", "value": false}]}`` ``success`` is earned, not assumed: it is true only when every change applied, the coalesced write reached disk, the change went out to the peers, every audit row was written and (when the kinds require it) this container re-baked its catalogue. Each of those steps is best-effort in its own layer and reports rather than raises, so anything short of all of them returns 500 with ``problems`` naming what did not happen. An operator who batched five changes can then tell "all five are durable and on their way to the peers" from "applied in this process only".' operationId: extensions_api_apply_action_changes_admin_extensions_api_apply_action_changes_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/set-admin-approval: post: summary: Extensions Api Set Admin Approval description: 'Admin allow-list toggle for a high-risk (``requires_admin_approval``) action. This is the operator-level capability switch the executor''s ``ADMIN_APPROVAL_REQUIRED`` gate consults. Approving adds the exact ``service.action`` key to ``ADMIN_APPROVED_ACTIONS`` (persisted to gateway.env + broadcast via Redis pub/sub, so it hot-reloads across peers); revoking removes it. The default — nothing approved — keeps every flagged action blocked, the safe direction. POST ``{"service": "...", "action": "...", "approved": true|false}``' operationId: extensions_api_set_admin_approval_admin_extensions_api_set_admin_approval_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/set-action-classification: post: summary: Extensions Api Set Action Classification description: 'Set an action''s risk classification (read / write / high_risk). The operator''s per-action badge reclassification. Persists through the classification-override layer (works for built-in services whose YAML is not writable at runtime — replaces the old direct-YAML-write mode toggle that failed for built-ins with "Invalid file path") and reloads so the effective ``mode`` + ``requires_admin_approval`` reflect it everywhere. POST ``{"service": "...", "action": "...", "classification": "read|write|high_risk"}``' operationId: extensions_api_set_action_classification_admin_extensions_api_set_action_classification_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/destructive/api/state: get: summary: Admin Destructive State description: GET /admin/destructive/api/state — the page's data model. operationId: admin_destructive_state_admin_destructive_api_state_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/destructive/api/set-category: post: summary: Admin Destructive Set Category description: 'POST /admin/destructive/api/set-category {category, enabled}. The single new write of this page: one policy per category. Persists via save_config (env + disk + cross-pod hot reload) and re-bakes the local extensions so the gate is live without a restart.' operationId: admin_destructive_set_category_admin_destructive_api_set_category_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/destructive/api/set-gate-scope: post: summary: Admin Destructive Set Gate Scope description: 'POST /admin/destructive/api/set-gate-scope {scope}. Who the high-risk admin-approval gate applies to: ``all`` (secure default) or ``non_admin`` (admins'' own agents exempt). Persisted via save_config; read live at the gate, so no extension re-bake needed.' operationId: admin_destructive_set_gate_scope_admin_destructive_api_set_gate_scope_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/destructive/api/set-human-confirmation: post: summary: Admin Destructive Set Human Confirmation description: 'POST /admin/destructive/api/set-human-confirmation {key, required}. Whether a kind of destructive action needs the person''s own yes in the client''s dialog. Read on every gated call, so no reload is needed.' operationId: admin_destructive_set_human_confirmation_admin_destructive_api_set_human_confirmation_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/destructive/api/reset-defaults: post: summary: Admin Destructive Reset description: 'POST /admin/destructive/api/reset-defaults — factory state for the destructive-governance surface: category policies back to shipped defaults, per-action reclassifications of categorized actions cleared, admin enables for categorized actions revoked. Scoped to categorized actions on purpose — reclassifications of anything else belong to the per-service reset on the service detail page.' operationId: admin_destructive_reset_admin_destructive_api_reset_defaults_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/destructive/api/dismiss-blocked-notice: post: summary: Admin Destructive Dismiss Blocked description: 'POST /admin/destructive/api/dismiss-blocked-notice. Records this admin''s watermark so the overview notice only reappears when a newer blocked attempt arrives. The watermark is derived SERVER-SIDE from the current newest blocked event — the client value is not trusted (a forged far-future timestamp would otherwise suppress the notice permanently via the lexicographic comparison in compute_blocked_notice).' operationId: admin_destructive_dismiss_blocked_admin_destructive_api_dismiss_blocked_notice_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/notifications/api/state: get: summary: Admin Notifications State description: GET /admin/notifications/api/state operationId: admin_notifications_state_admin_notifications_api_state_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/notifications/api/save: post: summary: Admin Notifications Save description: POST /admin/notifications/api/save — persist one channel's settings. operationId: admin_notifications_save_admin_notifications_api_save_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/notifications/api/test: post: summary: Admin Notifications Test description: 'POST /admin/notifications/api/test — fire the real dispatcher. Body may carry ``{"channel": "slack"|"teams"|"email"}`` to test exactly one channel without the fallback chain — so a configured Slack doesn''t mask a broken Teams or Email setup. No body = full chain.' operationId: admin_notifications_test_admin_notifications_api_test_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/notifications/api/teams-app.zip: get: summary: Admin Notifications Teams App description: GET /admin/notifications/api/teams-app.zip — downloadable app package. operationId: admin_notifications_teams_app_admin_notifications_api_teams_app_zip_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/state: get: summary: Admin Context Map State operationId: admin_context_map_state_admin_context_map_api_state_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/page-html: get: summary: Admin Context Map Page Html description: 'Rendered Markdown for ONE page, fetched when the operator opens it. Rendering the whole corpus into the state payload would double a response that already carries every page body (~270 KB at 61 pages) to serve the one page being looked at, so this is a separate read. The id is matched against the index, never against a path — the served corpus is the only thing addressable here, so there is no filesystem for a traversal to reach. The parse runs in a worker thread. Markdown parsing is CPU-bound and the input is untrusted, so leaving it inline would let one page''s cost stall every other request this worker is serving — including the other admins and the MCP tool calls sharing the loop.' operationId: admin_context_map_page_html_admin_context_map_api_page_html_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/save: post: summary: Admin Context Map Save operationId: admin_context_map_save_admin_context_map_api_save_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/sync: post: summary: Admin Context Map Sync description: 'Manual re-pull / reindex trigger. Clears the discovery cache first: this button is an explicit request for a full refresh, so it must also re-scan the configured groups rather than re-pulling whatever the last scan happened to find.' operationId: admin_context_map_sync_admin_context_map_api_sync_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/rotate-secret: post: summary: Admin Context Map Rotate Secret description: 'Generate/rotate the per-tenant webhook + push secret, and (re)configure the push webhooks on the configured repos with the new secret — so the operator doesn''t hand-create them and a rotation can''t silently break existing ones.' operationId: admin_context_map_rotate_secret_admin_context_map_api_rotate_secret_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/gaps: get: summary: Admin Context Map Gaps description: 'Read the gap feed — questions the Map couldn''t answer (#860 2a). A pull surface for the operator (and, later, the external enrichment job): reading does NOT consume. POST with {ack: } removes one (consumer ack).' operationId: admin_context_map_gaps_admin_context_map_api_gaps_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Admin Context Map Gaps description: 'Read the gap feed — questions the Map couldn''t answer (#860 2a). A pull surface for the operator (and, later, the external enrichment job): reading does NOT consume. POST with {ack: } removes one (consumer ack).' operationId: admin_context_map_gaps_admin_context_map_api_gaps_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/expertise-notify: post: summary: Admin Context Map Expertise Notify description: 'Flip the runtime ''DM a named colleague on report'' toggle (Redis-backed, no deploy). Default is on; this lets an operator turn active outbound off.' operationId: admin_context_map_expertise_notify_admin_context_map_api_expertise_notify_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/context-map/api/grade-gate: post: summary: Admin Context Map Grade Gate description: 'Flip the runtime context-memory grade-gate toggle (Redis-backed, no deploy). Default is off; turning it on makes a session settle owed pitfall grades before its next same-family write proceeds (enforcement).' operationId: admin_context_map_grade_gate_admin_context_map_api_grade_gate_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/demo/exit: post: summary: Admin Demo Exit description: 'POST /admin/demo/exit — purge the demo partition. Per-mode semantics: with the flag ``on``, the seeder refuses once the instance has completed real setup, so on a configured instance exiting is permanent; an instance still in bootstrap re-seeds on the next restart (acceptable — the demo only ever lives beside a pristine instance). With ``always`` the partition comes back on the next restart, so exit behaves as a dataset reset.' operationId: admin_demo_exit_admin_demo_exit_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/reset-defaults: post: summary: Extensions Api Reset Defaults description: 'Revert a service''s discovery settings to the shipped defaults. Clears every operator promote/demote override for the service (so each action falls back to its YAML ``curated`` flag), drops its risk classifications, revokes its high-risk approvals and re-enables dynamic discovery (default-on). Persisted through the same override layer, then reloads the extensions so the change reflects everywhere at once. Those four reverts are what "defaults" means here, so the response is only a success when all four happened: one of them failing returns 500 naming it, rather than a 200 for a service that is half reverted. Approvals are included because a high-risk capability SHIPS OFF. Leaving them standing returned everything except the one deviation with a safety consequence, under a button labelled "reset to defaults". POST ``{"service": "..."}``' operationId: extensions_api_reset_defaults_admin_extensions_api_reset_defaults_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/enable-file: post: summary: Extensions Api Enable File description: 'Re-enable a disabled extension YAML file by removing the ''_'' prefix. Receives JSON: {"filename": "_statuspage_imported.yaml"} Renames to statuspage_imported.yaml and hot-reloads.' operationId: extensions_api_enable_file_admin_extensions_api_enable_file_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/delete-file: post: summary: Extensions Api Delete File description: 'Permanently delete an extension YAML file. Receives JSON: {"filename": "statuspage_imported.yaml"} Only allows deleting files with ''_imported'' in the name (safety).' operationId: extensions_api_delete_file_admin_extensions_api_delete_file_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/set-visibility: post: summary: Extensions Api Set Visibility description: 'Toggle an operator-authored visibility override for a service (#732). Receives JSON: ``{"service": "amplitude_mcp", "hidden": true|false}`` The override is persisted in Redis under ``gateway:services:overrides``, fans out to peer workers via ``gateway:overrides:changed``, and writes an audit row in ``override_audit``. Operates orthogonally to the file rename (``_``-prefix) mechanism: file rename is for operator-authored custom imports, the override layer is for vendor-shipped services.' operationId: extensions_api_set_visibility_admin_extensions_api_set_visibility_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/set-audience: post: summary: Extensions Api Set Audience description: 'Set the operators-only audience override for a service. Receives JSON: ``{"service": "google_cloud_logging", "operators_only": true|false}`` When ``operators_only`` is true the service is restricted to operators/admins — hidden from every other user''s tools/list and hard-denied at the shared access gate — while staying available to operators. Orthogonal to the On/Off visibility override: audience narrows *who* sees an on service; visibility decides *whether* it is on at all. Persisted like the visibility override (Redis + peer fan-out + audit row) so it survives restarts and multi-worker setups.' operationId: extensions_api_set_audience_admin_extensions_api_set_audience_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/custom-hooks: get: summary: Custom Hooks List description: 'List all custom YAML hooks (admin scope only in v1). Source of truth is the Redis hash that the gateway loader reads from; disk is consulted only as a fallback when Redis is unreachable.' operationId: custom_hooks_list_admin_extensions_api_custom_hooks_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Custom Hooks Save description: Create or update a custom YAML hook. operationId: custom_hooks_save_admin_extensions_api_custom_hooks_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin delete: summary: Custom Hooks Delete description: Delete a custom YAML hook file. operationId: custom_hooks_delete_admin_extensions_api_custom_hooks_delete responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/custom-hooks/toggle: post: summary: Custom Hooks Toggle description: Enable/disable a custom hook by renaming with/without _ prefix. operationId: custom_hooks_toggle_admin_extensions_api_custom_hooks_toggle_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/custom-hooks/export: get: summary: Custom Hooks Export description: Export all admin hooks as a ZIP file. operationId: custom_hooks_export_admin_extensions_api_custom_hooks_export_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/extensions/api/custom-hooks/import: post: summary: Custom Hooks Import description: Import hooks from uploaded YAML files. operationId: custom_hooks_import_admin_extensions_api_custom_hooks_import_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/reload: post: summary: Admin Reload description: 'Hot-reload all YAML configs without container restart. Requires admin authentication (session cookie or bearer token). Returns a summary of what was reloaded and how long it took.' operationId: admin_reload_admin_reload_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/audit/entries: get: summary: Admin Audit Entries description: 'GET /admin/audit/entries — JSON feed for the audit page viewer. Mirrors the shape of ``/admin/logging/entries``: cursor pager via ``before_iso`` + a generous over-fetch on the server so chip / search filters narrow without a follow-up round-trip. Severity is computed per row via :func:`src.audit.severity.severity_for`.' operationId: admin_audit_entries_admin_audit_entries_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/audit/api: get: summary: Admin Audit Events description: GET /admin/audit/api — legacy unfiltered JSON list of recent events. operationId: admin_audit_events_admin_audit_api_get parameters: - name: limit in: query required: false schema: anyOf: - type: integer - type: 'null' default: 100 title: Limit responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/audit/export: get: summary: Admin Audit Export description: GET /admin/audit/export — CSV download of audit events. operationId: admin_audit_export_admin_audit_export_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/audit/user/{user_email}: get: summary: Admin Audit User Events description: GET /admin/audit/user/{email} — legacy per-user JSON feed. operationId: admin_audit_user_events_admin_audit_user__user_email__get parameters: - name: user_email in: path required: true schema: type: string title: User Email - name: limit in: query required: false schema: anyOf: - type: integer - type: 'null' default: 50 title: Limit responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' tags: - Admin /admin/throughput/entries: get: summary: Admin Throughput Entries description: '``GET /admin/throughput/entries`` — JSON feed for the viewer. Query params: * ``window`` one of ``30m`` / ``1h`` / ``24h`` / ``7d`` / ``30d`` / ``90d`` (default 1h) * ``top`` max rows returned (default 50, hard cap 500) * ``user`` user-hash prefix filter (e.g. first 4 chars) * ``tool`` exact tool-name filter (``metabase_read_actions``) * ``client`` exact client filter (``chatgpt_agent``, ``claude_code``, …) * ``group`` ``detail`` (default), ``user``, or ``model`` * ``model`` exact model filter — honoured by ``group=model`` only' operationId: admin_throughput_entries_admin_throughput_entries_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/throughput/export: get: summary: Admin Throughput Export description: '``GET /admin/throughput/export`` — CSV download, filter-preserving.' operationId: admin_throughput_export_admin_throughput_export_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/throughput/users: get: summary: Admin Throughput Users description: '``GET /admin/throughput/users`` — list of known users for the filter dropdown. Returns ``[{user_hash, user_display}, …]`` sorted by display string. Users are sourced from the throughput store itself (anyone who recorded throughput in the last 30 days) and joined against the audit log''s hash→email index for the masked display. Users without a matching audit-log identity entry fall back to the hash-prefix shape.' operationId: admin_throughput_users_admin_throughput_users_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/throughput/thresholds: get: summary: Admin Throughput Thresholds Get description: '``GET /admin/throughput/thresholds`` — current effective config. Query params: * ``bare=1`` — skip the Redis override and return the bundled YAML defaults instead. Used by the "Reset to defaults" link in the threshold-editor modal to populate the form with the canonical values without first having to delete the override. Calling ``load_thresholds(None)`` skips the Redis-override merge step in the scanner module.' operationId: admin_throughput_thresholds_get_admin_throughput_thresholds_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Admin Throughput Thresholds Post description: '``POST /admin/throughput/thresholds`` — persist a Redis override. Body: JSON object with any subset of the threshold keys (``warning_mb_30m``, ``alert_mb_30m``, ``alert_mb_24h``, ``alert_slack_channel``, ``alert_cooldown_minutes``, ``scan_interval_seconds``). Unknown keys are ignored.' operationId: admin_throughput_thresholds_post_admin_throughput_thresholds_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin /admin/api/client-vault: get: summary: Get Client Vault Switch operationId: get_client_vault_switch_admin_api_client_vault_get responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin post: summary: Post Client Vault Switch operationId: post_client_vault_switch_admin_api_client_vault_post responses: '200': description: Successful Response content: application/json: schema: {} tags: - Admin components: schemas: HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError 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