openapi: 3.1.0
info:
  title: Alexandria EE API
  version: 0.1.0
  description: |
    The Alexandria EE Go API — production HTTP surface for the Alexandria Enterprise Edition.
    Handles JWT auth, RBAC, multi-tenancy, agent orchestration, and all administrative operations.
    The API connects to the Rust orchestrator daemon over gRPC (Unix socket).

    ## Authentication

    Most routes require a Bearer JWT in the `Authorization` header.
    Three token types exist:

    - **access** (`TokenType: "access"`) — human user token, 15-minute expiry
    - **agent** — issued by `POST /v1/agent-token`, carries effective tool list
    - **tool** — issued to running tools by the orchestrator

    SCIM endpoints use a separate static bearer token configured at startup.

    ## Permission model

    Effective tools = `agent_allowed_tools ∩ user_allowed_tools ∩ server_ceiling`.
    Per-tenant isolation applies a tenant ceiling before the standard intersection.
    `super_admin` role bypasses all intersection logic.

servers:
  - url: http://localhost:8080
    description: Local development

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    SCIMBearerAuth:
      type: http
      scheme: bearer
      description: Static SCIM provisioning token configured at server startup

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          example: invalid credentials

    TokenResponse:
      type: object
      required: [access_token, refresh_token, token_type, expires_in]
      properties:
        access_token:
          type: string
        refresh_token:
          type: string
        token_type:
          type: string
          example: Bearer
        expires_in:
          type: integer
          description: Access token lifetime in seconds
          example: 900

    AgentDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        backend:
          type: string
          nullable: true
        system_prompt:
          type: string
          nullable: true
        allowed_tools:
          type: array
          items:
            type: string
        approval_required_tools:
          type: array
          items:
            type: string
        model:
          type: string
          nullable: true
        history_limit:
          type: integer
          nullable: true
        group_id:
          type: string
          nullable: true
        capability_role:
          type: string
        permissions_version:
          type: integer
        revoked:
          type: boolean
        on_permission_change:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    UserDTO:
      type: object
      properties:
        id:
          type: string
        username:
          type: string
        role:
          type: string
          enum: [admin, user, viewer, operator, super_admin]
        disabled:
          type: boolean
        created_at:
          type: string
          format: date-time

    ChatDTO:
      type: object
      properties:
        id:
          type: string
        agent_id:
          type: string
        user_id:
          type: string
        title:
          type: string
        label:
          type: string
        memory_strategy:
          type: string
          enum: [plain, overflow, compression]
        compressor:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SessionSummaryDTO:
      type: object
      properties:
        session_id:
          type: string
        agent_id:
          type: string
        turn_count:
          type: integer
        last_active:
          type: string
          format: date-time

    TurnDTO:
      type: object
      properties:
        role:
          type: string
          enum: [user, assistant, tool]
        content:
          type: string
        model_name:
          type: string
        created_at:
          type: string
          format: date-time

    MCPServerDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        url:
          type: string
        transport:
          type: string
          enum: [http, sse, stdio]
        enabled:
          type: boolean
        allowed_tools:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time

    TenantDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        slug:
          type: string
        plan:
          type: string
        disabled:
          type: boolean
        created_at:
          type: string
          format: date-time
        egress_fqdns:
          type: array
          items:
            type: string

    CeilingDTO:
      type: object
      properties:
        tenant_id:
          type: string
        ceiling_tools:
          type: array
          items:
            type: string
        ceiling_models:
          type: array
          items:
            type: string
        ceiling_skills:
          type: array
          items:
            type: string
        updated_at:
          type: string
          format: date-time

    LLMBackendDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        url:
          type: string
        kind:
          type: string
          enum: [open-ai, claude, llama-cpp, custom]
        model:
          type: string
          nullable: true
        has_api_key:
          type: boolean
        is_default:
          type: boolean
        enabled:
          type: boolean
        backend_type:
          type: string
          enum: [external, managed]
        status:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        source_type:
          type: string
          enum: [external, oci, hf]
        image:
          type: string
        image_digest:
          type: string
        hf_repo:
          type: string
        hf_revision:
          type: string
        engine:
          type: string
          enum: [llama-cpp, vllm, custom]
        replicas:
          type: integer
        deployment_status:
          type: string
        deployment_message:
          type: string
        service_url:
          type: string
        capabilities:
          type: array
          items:
            type: string
            enum: [chat, embed, voice, vision, rerank]
        protocol:
          type: string
          enum: [chat, oip]
        cache_strategy:
          type: string
          enum: [native, alexandria, none]
        cache_ttl_seconds:
          type: integer

    PermissionRequestDTO:
      type: object
      properties:
        id:
          type: string
        requester_type:
          type: string
          enum: [agent, user]
        requester_id:
          type: string
        request_type:
          type: string
          enum: [grant_tool, revoke_tool, create_agent, update_agent, delete_agent, set_ceiling, register_mcp, install_tool, tool_call]
        target_id:
          type: string
          nullable: true
        requested_scope:
          type: object
        justification:
          type: string
        required_role:
          type: string
        required_approver_id:
          type: string
          nullable: true
        status:
          type: string
          enum: [pending, approved, rejected, expired]
        reviewed_by:
          type: string
          nullable: true
        reviewed_at:
          type: string
          nullable: true
        expires_at:
          type: string
        created_at:
          type: string

    AuditEntryDTO:
      type: object
      properties:
        id:
          type: string
        principal:
          type: string
        action:
          type: string
        agent:
          type: string
        session:
          type: string
        metadata:
          type: string
        created_at:
          type: string
          format: date-time
        tenant_id:
          type: string
        prev_hash:
          type: string
        row_hash:
          type: string

    SkillDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        version:
          type: string
        description:
          type: string
        system_prompt:
          type: string
        allowed_tools:
          type: array
          items:
            type: string
        model_hint:
          type: string
          nullable: true
        tags:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    RoleDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        tools:
          type: array
          items:
            type: string
        created_at:
          type: string
        updated_at:
          type: string

    GroupDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        ceiling_tools:
          type: array
          items:
            type: string
        permissions_version:
          type: integer
        created_at:
          type: string
          format: date-time

    WorkflowDTO:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        definition:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    WorkflowNodeDTO:
      type: object
      properties:
        id:
          type: string
        workflow_id:
          type: string
        node_type:
          type: string
        agent_id:
          type: string
          nullable: true
        label:
          type: string
        config:
          type: string
        position_x:
          type: number
        position_y:
          type: number
        created_at:
          type: string
          format: date-time

    WorkflowEdgeDTO:
      type: object
      properties:
        id:
          type: string
        workflow_id:
          type: string
        source_node_id:
          type: string
        target_node_id:
          type: string
        edge_type:
          type: string
        condition:
          type: string
          nullable: true
        min_capability_role:
          type: string
        data_labels:
          type: string
        created_at:
          type: string
          format: date-time

    WorkflowGraphDTO:
      type: object
      properties:
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/WorkflowNodeDTO'
        edges:
          type: array
          items:
            $ref: '#/components/schemas/WorkflowEdgeDTO'

    ChangesetEntryDTO:
      type: object
      properties:
        id:
          type: string
        changeset_id:
          type: string
        seq:
          type: integer
        operation:
          type: string
        payload:
          type: string
        created_at:
          type: string
          format: date-time

    ChangesetDTO:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
          enum: [pending, promoted, rolled_back]
        created_by:
          type: string
        notes:
          type: string
        created_at:
          type: string
          format: date-time
        promoted_at:
          type: string
          format: date-time
          nullable: true
        entries:
          type: array
          items:
            $ref: '#/components/schemas/ChangesetEntryDTO'

    ModelObject:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          example: model
        owned_by:
          type: string
          example: alexandria
        capabilities:
          type: array
          items:
            type: string
        protocol:
          type: string
        kind:
          type: string

    EmbeddingObject:
      type: object
      properties:
        object:
          type: string
          example: embedding
        embedding:
          type: array
          items:
            type: number
            format: float
        index:
          type: integer

    SCIMUser:
      type: object
      properties:
        schemas:
          type: array
          items:
            type: string
        id:
          type: string
        userName:
          type: string
        displayName:
          type: string
        active:
          type: boolean
        meta:
          type: object
          properties:
            resourceType:
              type: string
            created:
              type: string
            lastModified:
              type: string
            location:
              type: string

    SCIMGroup:
      type: object
      properties:
        schemas:
          type: array
          items:
            type: string
        id:
          type: string
        displayName:
          type: string
        members:
          type: array
          items:
            type: object
            properties:
              value:
                type: string
              display:
                type: string
        meta:
          type: object

    SCIMListResponse:
      type: object
      properties:
        schemas:
          type: array
          items:
            type: string
        totalResults:
          type: integer
        startIndex:
          type: integer
        itemsPerPage:
          type: integer
        Resources:
          type: array
          items: {}

    ToolAttestationResponse:
      type: object
      x-status: stub
      properties:
        tool:
          type: string
        version:
          type: string
        attested:
          type: boolean
        signature_present:
          type: boolean
        key_fingerprint:
          type: string
        signed_at:
          type: string
          format: date-time
        trusted:
          type: boolean
        reason:
          type: string

    QuotaExceededBody:
      type: object
      properties:
        error:
          type: string
          example: quota_exceeded
        resource:
          type: string
          example: seats
        limit:
          type: integer
        current:
          type: integer

    LicenseDisplay:
      type: object
      properties:
        tier:
          type: string
        customer:
          type: string
        kid:
          type: string
        issued_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        entitlements:
          type: array
          items:
            type: string
        days_remaining:
          type: integer
        customer_id:
          type: string
        expired:
          type: boolean
        expired_grace_remaining_seconds:
          type: integer
        max_seats:
          type: integer
        current_seats:
          type: integer
        max_tenants:
          type: integer
        current_tenants:
          type: integer

    AgentTokenResponse:
      type: object
      properties:
        token:
          type: string
        agent:
          type: string
        effective_tools:
          type: array
          items:
            type: string
        expires_in:
          type: integer
          example: 3600

    PlanRecord:
      type: object
      description: Plan returned by plan management endpoints
      properties:
        id:
          type: string
        tenant_id:
          type: string
        agent_id:
          type: string
        justification:
          type: string
        granted_tools:
          type: array
          items:
            type: string
        granted_models:
          type: array
          items:
            type: string
        granted_skills:
          type: array
          items:
            type: string
        status:
          type: string
          enum: [pending, approved, rejected, active, completed]
        expires_at:
          type: string
          format: date-time
        approved_by:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time

    PasskeyInfo:
      type: object
      properties:
        credential_id:
          type: string
        created_at:
          type: string
        last_used:
          type: string
          nullable: true

    NotificationsConfigDTO:
      type: object
      properties:
        webhook_url:
          type: string
        webhook_secret_set:
          type: boolean
        alertmanager_url:
          type: string
        alertmanager_severity:
          type: string
        reconcile_interval_seconds:
          type: integer

paths:
  # ── Health / System ──────────────────────────────────────────────────────────
  /live:
    get:
      operationId: getLive
      summary: Liveness probe
      tags: [system]
      security: []
      responses:
        '200':
          description: Process is running
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok

  /ready:
    get:
      operationId: getReady
      summary: Readiness probe
      tags: [system]
      security: []
      responses:
        '200':
          description: Orchestrator socket is reachable
        '503':
          description: Orchestrator not running

  /health:
    get:
      operationId: getHealth
      summary: Health summary
      tags: [system]
      security: []
      responses:
        '200':
          description: All subsystems healthy
        '503':
          description: Degraded

  /metrics:
    get:
      operationId: getMetrics
      summary: Prometheus metrics
      tags: [system]
      security: []
      responses:
        '200':
          description: Prometheus text format metrics
          content:
            text/plain:
              schema:
                type: string

  /health/config:
    get:
      operationId: getHealthConfig
      summary: Effective loaded configuration (secrets redacted)
      tags: [system]
      description: Requires admin authentication. Returns the parsed TOML config with secret values replaced by their reference shape.
      responses:
        '200':
          description: Configuration object
        '401':
          description: Unauthorized

  /client/ping:
    get:
      operationId: clientPing
      summary: Client bridge ping (reserved)
      tags: [system]
      security: []
      description: Reserved endpoint for the alex-client daemon bridge.
      responses:
        '200':
          description: OK

  # ── Auth ────────────────────────────────────────────────────────────────────
  /auth/login:
    post:
      operationId: login
      summary: Authenticate with username and password
      tags: [auth]
      security: []
      x-ratelimit: "per-IP, configurable"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
              example:
                username: alice
                password: s3cret!
      responses:
        '200':
          description: Token pair
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '401':
          description: Invalid credentials or account disabled
        '429':
          description: Too many login attempts (rate limited or account locked)

  /auth/refresh:
    post:
      operationId: refreshToken
      summary: Rotate refresh token and obtain new access token
      tags: [auth]
      security: []
      x-ratelimit: "per-IP, configurable"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token:
                  type: string
      responses:
        '200':
          description: New token pair
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '401':
          description: Invalid, revoked, or expired refresh token

  /auth/logout:
    post:
      operationId: logout
      summary: Revoke a refresh token
      tags: [auth]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean

  /auth/setup:
    get:
      operationId: getSetupStatus
      summary: Check whether initial admin setup is required
      tags: [auth]
      security: []
      responses:
        '200':
          description: Setup status
          content:
            application/json:
              schema:
                type: object
                properties:
                  needs_setup:
                    type: boolean
    post:
      operationId: setup
      summary: Bootstrap first admin account
      tags: [auth]
      security: []
      description: |
        Only succeeds when no admin account exists. Returns 403 once setup is complete.

        > **Security note:** if all admin accounts are disabled this endpoint
        > re-opens to unauthenticated callers. This is intentional for demo
        > deployments but should be treated as a configuration risk in production.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
      responses:
        '200':
          description: Admin account created and token pair returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '403':
          description: Setup already completed

  /auth/providers:
    get:
      operationId: listProviders
      summary: List configured SSO providers (public)
      tags: [federation]
      security: []
      description: Returns provider name, display name, and kind. No secrets are exposed. Used by login page to render SSO buttons.
      responses:
        '200':
          description: Provider list
          content:
            application/json:
              schema:
                type: object
                properties:
                  providers:
                    type: array
                    items:
                      type: object

  /auth/oidc/{provider}/authorize:
    get:
      operationId: oidcAuthorize
      summary: Begin OIDC Authorization Code + PKCE flow
      tags: [federation]
      security: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
        - name: redirect
          in: query
          schema:
            type: string
          description: Post-login URL
      responses:
        '302':
          description: Redirect to IdP authorization endpoint

  /auth/oidc/{provider}/callback:
    get:
      operationId: oidcCallback
      summary: OIDC callback — exchange code for tokens
      tags: [federation]
      security: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
        - name: code
          in: query
          schema:
            type: string
        - name: state
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Browser redirect with tokens in fragment (or JSON when Accept=application/json)
        '200':
          description: Token response when Accept=application/json
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'

  /auth/oidc/{provider}/cli-start:
    post:
      operationId: oidcCLIStart
      summary: Begin CLI device-code SSO flow
      tags: [federation]
      security: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Auth URL and session ID for polling
          content:
            application/json:
              schema:
                type: object
                properties:
                  auth_url:
                    type: string
                  session_id:
                    type: string

  /auth/oidc/{provider}/cli-poll/{session_id}:
    get:
      operationId: oidcCLIPoll
      summary: Poll for CLI SSO completion
      tags: [federation]
      security: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
        - name: session_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Status — pending, complete (with tokens), or error
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [pending, complete, error]
                  access_token:
                    type: string
                  refresh_token:
                    type: string

  /auth/saml/{provider}/metadata:
    get:
      operationId: samlMetadata
      summary: SAML SP metadata XML
      tags: [federation]
      security: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: SP metadata XML (register with your IdP)
          content:
            application/samlmetadata+xml:
              schema:
                type: string

  /auth/saml/{provider}/sso:
    get:
      operationId: samlSSO
      summary: Initiate SAML SP-initiated SSO
      tags: [federation]
      security: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
        - name: redirect
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to IdP SSO URL

  /auth/saml/{provider}/acs:
    post:
      operationId: samlACS
      summary: SAML Assertion Consumer Service
      tags: [federation]
      security: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                SAMLResponse:
                  type: string
                RelayState:
                  type: string
      responses:
        '302':
          description: Redirect with tokens in fragment
        '401':
          description: SAML response validation failed

  /auth/webauthn/authenticate/begin:
    post:
      operationId: webauthnAuthBegin
      summary: Begin WebAuthn / FIDO2 authentication
      tags: [auth]
      security: []
      x-ratelimit: "20 req/min per IP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username]
              properties:
                username:
                  type: string
      responses:
        '200':
          description: WebAuthn assertion options (PublicKeyCredentialRequestOptions)

  /auth/webauthn/authenticate/finish:
    post:
      operationId: webauthnAuthFinish
      summary: Complete WebAuthn authentication
      tags: [auth]
      security: []
      x-ratelimit: "20 req/min per IP"
      parameters:
        - name: username
          in: query
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: WebAuthn assertion response (AuthenticatorAssertionResponse)
      responses:
        '200':
          description: Token pair on success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '401':
          description: Authentication failed

  /auth/webauthn/register/begin:
    post:
      operationId: webauthnRegisterBegin
      summary: Begin WebAuthn passkey registration
      tags: [auth]
      description: Requires human access token. Agents cannot register passkeys.
      x-ratelimit: "20 req/min per IP"
      responses:
        '200':
          description: WebAuthn creation options (PublicKeyCredentialCreationOptions)

  /auth/webauthn/register/finish:
    post:
      operationId: webauthnRegisterFinish
      summary: Complete WebAuthn passkey registration
      tags: [auth]
      description: Requires human access token.
      x-ratelimit: "20 req/min per IP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: WebAuthn attestation response
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean

  # ── MCP ─────────────────────────────────────────────────────────────────────
  /mcp:
    post:
      operationId: mcpDispatch
      summary: MCP JSON-RPC 2.0 dispatch (unscoped)
      tags: [mcp]
      description: |
        Accepts user access token or agent token. The token type determines
        which tools are accessible. Supports `X-Idempotency-Key` header.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: JSON-RPC 2.0 request
              example:
                jsonrpc: "2.0"
                method: tools/list
                id: 1
      responses:
        '200':
          description: JSON-RPC 2.0 response

  /mcp/agents/{name}:
    post:
      operationId: mcpAgentDispatch
      summary: MCP JSON-RPC 2.0 dispatch scoped to a named agent
      tags: [mcp]
      description: Agent name from path is cross-checked against the token's agent claim.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: JSON-RPC 2.0 request
      responses:
        '200':
          description: JSON-RPC 2.0 response

  # ── Tool attestation ─────────────────────────────────────────────────────────
  /tools/{name}/attestation:
    get:
      operationId: getToolAttestation
      summary: Tool signing attestation (public)
      tags: [system]
      security: []
      x-status: stub
      deprecated: false
      description: |
        Returns signing metadata for the named installed tool. Intentionally unauthenticated —
        intended for auditors verifying tool provenance.

        **Current status:** returns 503. The orchestrator does not yet expose per-tool
        manifest signature metadata over gRPC. The route is registered now for a stable
        URL for integration. When fully implemented the response matches `ToolAttestationResponse`.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Attestation record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToolAttestationResponse'
        '503':
          description: Attestation not yet available (current stub behavior)

  # ── /v1 user routes ──────────────────────────────────────────────────────────
  /v1/query:
    post:
      operationId: query
      summary: Send a prompt to an agent (blocking)
      tags: [query]
      x-ratelimit: "per-IP, configurable"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [prompt]
              properties:
                prompt:
                  type: string
                agent:
                  type: string
                session:
                  type: string
                  default: default
            example:
              prompt: "What is the capital of France?"
              agent: researcher
              session: my-session
      responses:
        '200':
          description: Agent response
          content:
            application/json:
              schema:
                type: object
                properties:
                  response:
                    type: string
                  tool_used:
                    type: string
                    nullable: true
                  session:
                    type: string
                  agent:
                    type: string
        '503':
          description: No default backend or orchestrator unavailable

  /v1/stream:
    post:
      operationId: stream
      summary: Send a prompt to an agent (SSE streaming)
      tags: [query]
      x-ratelimit: "per-IP, configurable"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [prompt]
              properties:
                prompt:
                  type: string
                agent:
                  type: string
                session:
                  type: string
      responses:
        '200':
          description: SSE stream of text chunks
          content:
            text/event-stream:
              schema:
                type: string
                description: |
                  Each SSE event is a JSON object. Possible shapes:
                  - `{"text": "chunk"}` — partial response
                  - `{"error": "message"}` — upstream error
                  - `{"done": true}` — stream complete

  /v1/agents:
    get:
      operationId: listAgents
      summary: List all agents
      tags: [agents]
      responses:
        '200':
          description: Agent list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgentDTO'

  /v1/agents/{name}:
    get:
      operationId: getAgent
      summary: Get agent by name
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDTO'
        '404':
          description: Not found

  /v1/agents/{name}/roles:
    get:
      operationId: listAgentRoles
      summary: List roles assigned to an agent
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Role list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RoleDTO'

  /v1/models:
    get:
      operationId: listModels
      summary: List LLM backends as OpenAI-compatible model objects
      tags: [openai]
      description: No entitlement gate. Extension fields `capabilities`, `protocol`, `kind` are included.
      responses:
        '200':
          description: Model list
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    example: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ModelObject'

  /v1/chat/completions:
    post:
      operationId: chatCompletions
      summary: OpenAI-compatible chat completions
      tags: [openai]
      description: |
        Forwards to the orchestrator via the agent specified in `model`.
        Supports streaming via `stream: true` (SSE, OpenAI chunk format).
        Use `X-Alexandria-Session` header to specify session ID (defaults to `default`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [messages]
              properties:
                model:
                  type: string
                  description: Agent name or `workflow/<name>`
                messages:
                  type: array
                  items:
                    type: object
                    properties:
                      role:
                        type: string
                        enum: [user, assistant, system]
                      content:
                        type: string
                stream:
                  type: boolean
                  default: false
                max_tokens:
                  type: integer
      responses:
        '200':
          description: Chat completion or SSE stream

  /v1/embeddings:
    post:
      operationId: createEmbeddings
      summary: OpenAI-compatible embeddings
      tags: [openai]
      description: |
        Backend resolution:
        1. If `model` is set → use that backend by name.
        2. Otherwise → auto-select first `embed`-capable backend (prefer `is_default`).
        3. If resolved backend lacks `embed` capability → 400.
        4. If no embed backend registered → 503.

        Emits an `model.embed` audit entry per call.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [input]
              properties:
                input:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                  description: Text or array of texts to embed
                model:
                  type: string
                  description: Optional backend name
      responses:
        '200':
          description: Embeddings list
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    example: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/EmbeddingObject'
                  model:
                    type: string
        '400':
          description: Bad request (backend lacks embed capability)
        '503':
          description: No embedding backend registered

  /v1/triggers:
    post:
      operationId: enqueueTrigger
      summary: Enqueue an external trigger for an agent
      tags: [query]
      description: |
        Enqueues an asynchronous agent run from an external source.
        The caller must have access to the target agent (checked against `allowed_agents`).
        Supports `traceparent` header for distributed tracing propagation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [target_agent]
              properties:
                target_agent:
                  type: string
                input:
                  type: string
      responses:
        '202':
          description: Event enqueued
          content:
            application/json:
              schema:
                type: object
                properties:
                  event_id:
                    type: string
                  status:
                    type: string
                    example: pending

  /v1/sessions:
    get:
      operationId: listSessions
      summary: List conversation sessions for current user
      tags: [sessions]
      parameters:
        - name: agent
          in: query
          schema:
            type: string
          description: Filter by agent name
      responses:
        '200':
          description: Session list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SessionSummaryDTO'

  /v1/sessions/{id}:
    get:
      operationId: getSession
      summary: Get session turns (conversation history)
      tags: [sessions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: agent
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
        - name: offset
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: Turn list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TurnDTO'
        '404':
          description: Session not found
    delete:
      operationId: deleteSession
      summary: Delete session and all turns
      tags: [sessions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: agent
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Number of turns deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer

  /v1/agent-token:
    post:
      operationId: issueAgentToken
      summary: Exchange user token for an agent token
      tags: [auth]
      description: |
        Computes effective tools via permission intersection:
        `agent_allowed_tools ∩ user_allowed_tools ∩ server_ceiling ∩ tenant_ceiling`.
        Bakes effective tool list into the JWT. `super_admin` bypasses intersection.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [agent]
              properties:
                agent:
                  type: string
                  description: Agent name
                session_id:
                  type: string
      responses:
        '200':
          description: Agent token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentTokenResponse'
        '403':
          description: User not authorized for this agent
        '404':
          description: Agent not found

  /v1/mcp/servers:
    get:
      operationId: listMCPServers
      summary: List registered MCP servers
      tags: [mcp]
      responses:
        '200':
          description: MCP server list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MCPServerDTO'

  /v1/chats:
    post:
      operationId: createChat
      summary: Create a chat session
      tags: [chats]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [agent_id]
              properties:
                agent_id:
                  type: string
                title:
                  type: string
                label:
                  type: string
                memory_strategy:
                  type: string
                  enum: [plain, overflow, compression]
                  default: plain
      responses:
        '201':
          description: Chat created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatDTO'
    get:
      operationId: listChats
      summary: List chats for the authenticated user
      tags: [chats]
      parameters:
        - name: agent_id
          in: query
          schema:
            type: string
        - name: label
          in: query
          schema:
            type: string
          description: Substring filter on label
      responses:
        '200':
          description: Chat list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ChatDTO'

  /v1/chats/{id}:
    get:
      operationId: getChat
      summary: Get chat by ID
      tags: [chats]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Chat
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatDTO'
        '403':
          description: Chat belongs to another user
        '404':
          description: Not found
    patch:
      operationId: updateChat
      summary: Update chat title and/or label
      tags: [chats]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                label:
                  type: string
      responses:
        '200':
          description: Updated chat
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatDTO'
    delete:
      operationId: deleteChat
      summary: Delete a chat
      tags: [chats]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /v1/skills:
    get:
      operationId: listSkills
      summary: List skills
      tags: [permissions]
      responses:
        '200':
          description: Skill list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SkillDTO'

  /v1/skills/{name}:
    get:
      operationId: getSkill
      summary: Get skill by name
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Skill
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillDTO'

  /v1/groups:
    get:
      operationId: listGroups
      summary: List agent groups
      tags: [permissions]
      responses:
        '200':
          description: Group list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/GroupDTO'

  /v1/groups/{name}:
    get:
      operationId: getGroup
      summary: Get group by name
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupDTO'

  /v1/roles:
    get:
      operationId: listRoles
      summary: List capability roles
      tags: [permissions]
      responses:
        '200':
          description: Role list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RoleDTO'

  /v1/roles/{name}:
    get:
      operationId: getRole
      summary: Get role by name
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Role
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleDTO'

  /v1/workflows:
    get:
      operationId: listWorkflows
      summary: List workflows (read-only)
      tags: [agents]
      responses:
        '200':
          description: Workflow list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WorkflowDTO'

  /v1/ingest/tool_result:
    post:
      operationId: ingestToolResult
      summary: Ingest a tool execution result into session memory
      tags: [ingest]
      description: Requires tool JWT (issued by orchestrator to running tools).
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [session_id, content]
              properties:
                session_id:
                  type: string
                content:
                  type: string
                tool_name:
                  type: string
      responses:
        '201':
          description: Result stored
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean

  /v1/ingest/note:
    post:
      operationId: ingestNote
      summary: Store a note into session memory
      tags: [ingest]
      description: Requires tool JWT.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [session_id, content]
              properties:
                session_id:
                  type: string
                note_type:
                  type: string
                  default: general
                content:
                  type: string
      responses:
        '201':
          description: Note stored with ID and metadata

  /v1/ingest/history:
    get:
      operationId: ingestHistory
      summary: Retrieve notes for a session
      tags: [ingest]
      description: Requires tool JWT.
      security:
        - BearerAuth: []
      parameters:
        - name: session_id
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Note list

  # ── /v1/agent routes (agent JWT) ────────────────────────────────────────────
  /v1/agent/permission-request:
    post:
      operationId: createPermissionRequest
      summary: Agent creates a permission request
      tags: [permissions]
      description: |
        Requires agent JWT. Valid request_type values: `grant_tool`, `revoke_tool`,
        `create_agent`, `update_agent`, `delete_agent`, `set_ceiling`, `register_mcp`,
        `install_tool`, `tool_call`. Default expiry 24h, max 7 days.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [request_type]
              properties:
                request_type:
                  type: string
                target_id:
                  type: string
                  nullable: true
                requested_scope:
                  type: object
                justification:
                  type: string
                required_role:
                  type: string
                  default: admin
                required_approver_id:
                  type: string
                  nullable: true
                expires_in_secs:
                  type: integer
                  maximum: 604800
      responses:
        '201':
          description: Permission request created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionRequestDTO'

  /v1/agent/permission-requests:
    get:
      operationId: listOwnPermissionRequests
      summary: Agent lists its own permission requests
      tags: [permissions]
      description: Requires agent JWT.
      security:
        - BearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, approved, rejected, expired]
      responses:
        '200':
          description: Permission request list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PermissionRequestDTO'

  /v1/agent/plan-request:
    post:
      operationId: createPlanRequest
      summary: Agent requests a temporary elevated capability plan
      tags: [permissions]
      description: |
        Requires agent JWT. Creates a plan requesting temporary access to tools,
        models, and skills. Requires admin approval before the plan becomes active.
        Duration defaults to 24h; max duration is configurable via `multi_tenancy.max_plan_duration`.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [justification, granted_tools]
              properties:
                justification:
                  type: string
                granted_tools:
                  type: array
                  items:
                    type: string
                  minItems: 1
                granted_models:
                  type: array
                  items:
                    type: string
                granted_skills:
                  type: array
                  items:
                    type: string
                duration_hours:
                  type: integer
                  default: 24
      responses:
        '201':
          description: Plan record created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanRecord'

  # ── /admin routes ────────────────────────────────────────────────────────────
  /admin/status:
    get:
      operationId: adminStatus
      summary: Admin status — process health of orchestrator, API, utils
      tags: [admin]
      responses:
        '200':
          description: Status map
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: string
                  enum: [up, down]

  /admin/users:
    get:
      operationId: listUsers
      summary: List all users
      tags: [admin]
      responses:
        '200':
          description: User list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserDTO'
    post:
      operationId: createUser
      summary: Create a user
      tags: [admin]
      description: Validates password against configured policy. Checks seat quota (super_admin bypasses).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
                role:
                  type: string
                  enum: [admin, user, viewer, operator]
                  default: user
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDTO'
        '402':
          description: Seat quota exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuotaExceededBody'

  /admin/users/{id}:
    patch:
      operationId: patchUser
      summary: Update user role or disabled status
      tags: [admin]
      description: Cannot demote or disable the last active admin.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                role:
                  type: string
                  enum: [admin, user, viewer, operator]
                disabled:
                  type: boolean
      responses:
        '200':
          description: OK
        '409':
          description: Cannot demote/disable last active admin
    delete:
      operationId: deleteUser
      summary: Delete a user
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Username (not UUID)
      responses:
        '200':
          description: Deleted
        '409':
          description: Cannot delete last active admin

  /admin/users/{id}/reset-password:
    post:
      operationId: resetPassword
      summary: Reset a user's password
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password:
                  type: string
      responses:
        '200':
          description: OK

  /admin/users/{id}/federation:
    get:
      operationId: listUserFederation
      summary: List federation identity links for a user
      tags: [federation]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Federation identity list

  /admin/users/{id}/federation/{provider}:
    delete:
      operationId: unlinkFederation
      summary: Unlink a federation identity from a user
      tags: [federation]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: provider
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK

  /admin/users/{id}/passkeys:
    get:
      operationId: listUserPasskeys
      summary: List passkeys for a user
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Passkey list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PasskeyInfo'

  /admin/users/{id}/passkeys/{cred_id}:
    delete:
      operationId: deleteUserPasskey
      summary: Revoke a passkey credential
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: cred_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/federation/providers:
    get:
      operationId: adminListProviders
      summary: List federation providers (admin view, requires oidc entitlement)
      tags: [federation]
      description: Gated on `oidc` entitlement. Returns 402 for community deployments.
      responses:
        '200':
          description: Provider list
        '402':
          description: OIDC entitlement not licensed

  /admin/audit:
    get:
      operationId: listAudit
      summary: List audit log entries with cursor-based pagination
      tags: [admin]
      parameters:
        - name: principal
          in: query
          schema:
            type: string
        - name: action
          in: query
          schema:
            type: string
        - name: agent
          in: query
          schema:
            type: string
        - name: since
          in: query
          schema:
            type: string
            format: date-time
        - name: until
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
        - name: after_id
          in: query
          schema:
            type: string
          description: Cursor — resume from this audit entry ID
      responses:
        '200':
          description: Paginated audit entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  entries:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuditEntryDTO'
                  next_cursor:
                    type: string

  /admin/audit/verify:
    get:
      operationId: verifyAuditChain
      summary: Verify HMAC chain integrity of the audit log
      tags: [admin]
      responses:
        '200':
          description: Verification result

  /admin/audit/export:
    get:
      operationId: exportAudit
      summary: Bulk export audit log (requires audit_export entitlement)
      tags: [admin]
      description: |
        Returns up to 50,000 entries. Requires `audit_export` license entitlement.
        `format=csv` returns CSV with `Content-Disposition` attachment header;
        default/`json` returns newline-delimited JSON.
      parameters:
        - name: format
          in: query
          schema:
            type: string
            enum: [json, csv]
            default: json
        - name: principal
          in: query
          schema:
            type: string
        - name: action
          in: query
          schema:
            type: string
        - name: since
          in: query
          schema:
            type: string
            format: date-time
        - name: until
          in: query
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Audit export file
        '402':
          description: audit_export entitlement not licensed

  /admin/audit/{id}/replay:
    post:
      operationId: replayAudit
      summary: Replay a query audit entry (super_admin only)
      tags: [admin]
      description: |
        Re-executes the original query from the audit row's `replay_inputs` block.
        Only `action == "query"` rows are replayable. Cross-tenant access permitted
        for `super_admin`. Emits an `audit.replay` audit row.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Replay output
          content:
            application/json:
              schema:
                type: object
                properties:
                  audit_id:
                    type: string
                  replayed_at:
                    type: string
                    format: date-time
                  output:
                    type: string
                  session_id:
                    type: string
        '400':
          description: Not replayable (action != query)
        '403':
          description: super_admin required
        '422':
          description: audit_inputs_missing — no replay_inputs block

  /admin/license:
    get:
      operationId: getLicense
      summary: Get active license information
      tags: [admin]
      description: Always returns 200. Community license is the default fallback.
      responses:
        '200':
          description: License details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LicenseDisplay'

  /admin/telemetry/preferences:
    get:
      operationId: getTelemetryPreferences
      summary: Get telemetry opt-in preference
      tags: [admin]
      responses:
        '200':
          description: Preference
          content:
            application/json:
              schema:
                type: object
                properties:
                  enabled:
                    type: boolean
    put:
      operationId: putTelemetryPreferences
      summary: Set telemetry opt-in preference
      tags: [admin]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled:
                  type: boolean
      responses:
        '200':
          description: Updated preference

  /admin/diagnostics:
    get:
      operationId: getDiagnostics
      summary: Observability self-report — OTel exporter state, orchestrator socket, sync loop
      tags: [admin]
      responses:
        '200':
          description: Diagnostics

  /admin/agents:
    post:
      operationId: createAgent
      summary: Create an agent
      tags: [agents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                backend:
                  type: string
                system_prompt:
                  type: string
                  nullable: true
                allowed_tools:
                  type: array
                  items:
                    type: string
                model:
                  type: string
                  nullable: true
                history_limit:
                  type: integer
                  nullable: true
      responses:
        '201':
          description: Agent created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDTO'

  /admin/agents/{name}:
    delete:
      operationId: deleteAgent
      summary: Delete an agent
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Deleted
    patch:
      operationId: patchAgent
      summary: Update agent metadata (description, backend, system_prompt, model, history_limit, group_id)
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                description:
                  type: string
                backend:
                  type: string
                  nullable: true
                system_prompt:
                  type: string
                  nullable: true
                model:
                  type: string
                  nullable: true
                history_limit:
                  type: integer
                  nullable: true
                group_id:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Updated agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDTO'

  /admin/agents/{name}/tools:
    post:
      operationId: grantTools
      summary: Grant tools to an agent
      tags: [agents]
      description: Use `["*"]` to grant all tools accessible to the requesting admin (bounded by ceilings).
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tools]
              properties:
                tools:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDTO'
    delete:
      operationId: revokeTools
      summary: Revoke tools from an agent
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tools]
              properties:
                tools:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDTO'

  /admin/agents/{name}/revoke:
    post:
      operationId: revokeAgent
      summary: Revoke an agent (block all new token mints)
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Revoked

  /admin/agents/{name}/unrevoke:
    post:
      operationId: unrevokeAgent
      summary: Unrevoke an agent
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Unrevoked

  /admin/agents/{name}/approval-required-tools:
    put:
      operationId: setApprovalRequiredTools
      summary: Set approval-required tool list for an agent (super_admin only)
      tags: [agents]
      description: |
        Requires `super_admin` role. Bumps `permissions_version` so existing agent
        tokens are invalidated.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tools]
              properties:
                tools:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDTO'
        '403':
          description: super_admin required

  /admin/agents/{name}/roles:
    post:
      operationId: assignAgentRole
      summary: Assign a capability role to an agent
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [role]
              properties:
                role:
                  type: string
      responses:
        '200':
          description: OK

  /admin/agents/{name}/roles/{role}:
    delete:
      operationId: unassignAgentRole
      summary: Unassign a capability role from an agent
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: role
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Unassigned

  /admin/mcp:
    post:
      operationId: registerMCPServer
      summary: Register an MCP server
      tags: [mcp]
      description: SSRF-checked. Wildcard `"*"` is not valid in `allowed_tools`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, url]
              properties:
                name:
                  type: string
                url:
                  type: string
                transport:
                  type: string
                  enum: [http, sse, stdio]
                  default: http
                api_key:
                  type: string
                  nullable: true
                allowed_tools:
                  type: array
                  items:
                    type: string
      responses:
        '201':
          description: MCP server created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPServerDTO'

  /admin/mcp/discover:
    post:
      operationId: discoverMCPServer
      summary: Probe a URL for MCP tools without registering
      tags: [mcp]
      description: SSRF-checked. Times out after 10 seconds.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                transport:
                  type: string
                  enum: [http, sse, stdio]
                  default: http
                api_key:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Tool list from remote MCP server
          content:
            application/json:
              schema:
                type: object
                properties:
                  tools:
                    type: array
                    items:
                      type: object
        '502':
          description: MCP server unreachable

  /admin/mcp/{name}:
    delete:
      operationId: deleteMCPServer
      summary: Delete an MCP server registration
      tags: [mcp]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted
    patch:
      operationId: patchMCPServer
      summary: Update MCP server enabled flag and/or allowed_tools filter
      tags: [mcp]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                  nullable: true
                allowed_tools:
                  type: array
                  items:
                    type: string
                  description: "null = no change; [] = clear filter (expose all)"
                  nullable: true
      responses:
        '200':
          description: Updated server
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPServerDTO'

  /admin/permissions:
    get:
      operationId: listPermissionRequests
      summary: List all permission requests
      tags: [permissions]
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, approved, rejected, expired]
      responses:
        '200':
          description: Permission request list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PermissionRequestDTO'

  /admin/permissions/{id}:
    get:
      operationId: getPermissionRequest
      summary: Get a permission request by ID
      tags: [permissions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Permission request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionRequestDTO'

  /admin/permissions/{id}/approve:
    post:
      operationId: approvePermissionRequest
      summary: Approve a permission request
      tags: [permissions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Updated permission request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionRequestDTO'

  /admin/permissions/{id}/reject:
    post:
      operationId: rejectPermissionRequest
      summary: Reject a permission request
      tags: [permissions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Updated permission request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionRequestDTO'

  /admin/groups:
    post:
      operationId: createGroup
      summary: Create an agent group
      tags: [permissions]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
      responses:
        '201':
          description: Group created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupDTO'

  /admin/groups/{name}:
    delete:
      operationId: deleteGroup
      summary: Delete an agent group
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/groups/{name}/ceiling:
    put:
      operationId: setGroupCeiling
      summary: Set tool ceiling for a group
      tags: [permissions]
      description: Wildcard `"*"` is not valid. Empty list clears the ceiling.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tools]
              properties:
                tools:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupDTO'

  /admin/skills:
    post:
      operationId: createSkill
      summary: Create a skill
      tags: [permissions]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                version:
                  type: string
                  default: 1.0.0
                description:
                  type: string
                system_prompt:
                  type: string
                allowed_tools:
                  type: array
                  items:
                    type: string
                model_hint:
                  type: string
                  nullable: true
                tags:
                  type: array
                  items:
                    type: string
      responses:
        '201':
          description: Skill created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillDTO'

  /admin/skills/{name}:
    patch:
      operationId: patchSkill
      summary: Update a skill
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                description:
                  type: string
                system_prompt:
                  type: string
                allowed_tools:
                  type: array
                  items:
                    type: string
                model_hint:
                  type: string
                  nullable: true
                tags:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated skill
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillDTO'
    delete:
      operationId: deleteSkill
      summary: Delete a skill
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/roles:
    post:
      operationId: createRole
      summary: Create a capability role
      tags: [permissions]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                tools:
                  type: array
                  items:
                    type: string
      responses:
        '201':
          description: Role created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleDTO'

  /admin/roles/{name}:
    patch:
      operationId: patchRole
      summary: Update a role
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                description:
                  type: string
                tools:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated role
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleDTO'
    delete:
      operationId: deleteRole
      summary: Delete a role
      tags: [permissions]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/changesets:
    get:
      operationId: listChangesets
      summary: List changesets
      tags: [admin]
      responses:
        '200':
          description: Changeset list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ChangesetDTO'
    post:
      operationId: createChangeset
      summary: Create a changeset
      tags: [admin]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                notes:
                  type: string
      responses:
        '201':
          description: Changeset created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChangesetDTO'

  /admin/changesets/{id}:
    get:
      operationId: getChangeset
      summary: Get changeset by ID
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Changeset
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChangesetDTO'

  /admin/changesets/{id}/entries:
    post:
      operationId: appendChangesetEntry
      summary: Append an entry to a pending changeset
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operation]
              properties:
                operation:
                  type: string
                payload:
                  type: string
      responses:
        '201':
          description: Entry appended
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChangesetEntryDTO'
        '409':
          description: Changeset is not pending

  /admin/changesets/{id}/promote:
    post:
      operationId: promoteChangeset
      summary: Promote (apply) a changeset
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Promoted
        '409':
          description: Changeset not pending

  /admin/changesets/{id}/rollback:
    post:
      operationId: rollbackChangeset
      summary: Roll back a changeset
      tags: [admin]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Rolled back

  /admin/workflows:
    get:
      operationId: adminListWorkflows
      summary: List workflows (admin)
      tags: [agents]
      responses:
        '200':
          description: Workflow list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WorkflowDTO'
    post:
      operationId: createWorkflow
      summary: Create a workflow
      tags: [agents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                definition:
                  type: string
      responses:
        '201':
          description: Workflow created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowDTO'

  /admin/workflows/{name}:
    get:
      operationId: getWorkflow
      summary: Get workflow by name
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Workflow
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowDTO'
    delete:
      operationId: deleteWorkflow
      summary: Delete a workflow
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/workflows/{name}/graph:
    get:
      operationId: getWorkflowGraph
      summary: Get workflow graph (nodes + edges)
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Workflow graph
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowGraphDTO'

  /admin/workflows/{name}/graph/nodes:
    post:
      operationId: addWorkflowNode
      summary: Add a node to a workflow graph
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [node_type]
              properties:
                node_type:
                  type: string
                agent_id:
                  type: string
                  nullable: true
                label:
                  type: string
                config:
                  type: string
                position_x:
                  type: number
                position_y:
                  type: number
      responses:
        '201':
          description: Node created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowNodeDTO'

  /admin/workflows/{name}/graph/nodes/{node_id}:
    patch:
      operationId: patchWorkflowNode
      summary: Update a workflow node
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: node_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
                config:
                  type: string
                agent_id:
                  type: string
                  nullable: true
                position_x:
                  type: number
                position_y:
                  type: number
      responses:
        '200':
          description: Updated node
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowNodeDTO'
    delete:
      operationId: deleteWorkflowNode
      summary: Delete a workflow node
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: node_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/workflows/{name}/graph/edges:
    post:
      operationId: addWorkflowEdge
      summary: Add an edge to a workflow graph
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [source_node_id, target_node_id]
              properties:
                source_node_id:
                  type: string
                target_node_id:
                  type: string
                edge_type:
                  type: string
                  default: default
                condition:
                  type: string
                  nullable: true
                min_capability_role:
                  type: string
                data_labels:
                  type: string
      responses:
        '201':
          description: Edge created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowEdgeDTO'

  /admin/workflows/{name}/graph/edges/{edge_id}:
    delete:
      operationId: deleteWorkflowEdge
      summary: Delete a workflow edge
      tags: [agents]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: edge_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/tenants:
    get:
      operationId: listTenants
      summary: List tenants (requires multi_tenant entitlement)
      tags: [tenants]
      description: Gated on `multi_tenant` license entitlement. Returns 402 if not licensed.
      responses:
        '200':
          description: Tenant list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TenantDTO'
        '402':
          description: multi_tenant entitlement not licensed
    post:
      operationId: createTenant
      summary: Create a tenant
      tags: [tenants]
      description: |
        Slug must be lowercase alphanumeric with hyphens, 2-64 chars.
        Reserved slugs: `default`, `api`, `admin`, `health`, `metrics`, `www`, `scim`, `auth`.
        Checks tenant quota unless caller is `super_admin`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, slug]
              properties:
                name:
                  type: string
                slug:
                  type: string
                plan:
                  type: string
                  default: standard
      responses:
        '201':
          description: Tenant created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantDTO'
        '402':
          description: Tenant quota exceeded

  /admin/tenants/{id}:
    get:
      operationId: getTenant
      summary: Get tenant by ID
      tags: [tenants]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Tenant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantDTO'
    patch:
      operationId: patchTenant
      summary: Update tenant (name, plan, disabled, ceiling, egress FQDNs)
      tags: [tenants]
      description: Slug is immutable after creation.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                plan:
                  type: string
                disabled:
                  type: boolean
                ceiling_tools:
                  type: array
                  items:
                    type: string
                ceiling_models:
                  type: array
                  items:
                    type: string
                ceiling_skills:
                  type: array
                  items:
                    type: string
                egress_fqdns:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated tenant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantDTO'
    delete:
      operationId: deleteTenant
      summary: Delete a tenant
      tags: [tenants]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Deleted
        '403':
          description: Cannot delete the built-in default tenant

  /admin/tenants/{id}/ceiling:
    get:
      operationId: getTenantCeiling
      summary: Get tenant capability ceiling
      tags: [tenants]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Ceiling (empty = unrestricted)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CeilingDTO'
    put:
      operationId: setTenantCeiling
      summary: Set tenant capability ceiling
      tags: [tenants]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ceiling_tools:
                  type: array
                  items:
                    type: string
                ceiling_models:
                  type: array
                  items:
                    type: string
                ceiling_skills:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: OK
    delete:
      operationId: deleteTenantCeiling
      summary: Remove tenant ceiling (unrestricted)
      tags: [tenants]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Deleted

  /admin/plans:
    get:
      operationId: listPlans
      summary: List agent capability plans
      tags: [permissions]
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, approved, rejected, active, completed]
      responses:
        '200':
          description: Plan list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PlanRecord'

  /admin/plans/{id}:
    get:
      operationId: getPlan
      summary: Get a plan by ID
      tags: [permissions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Plan
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanRecord'

  /admin/plans/{id}/approve:
    post:
      operationId: approvePlan
      summary: Approve and activate a plan
      tags: [permissions]
      description: Activates the plan, creating a temporary agent with the granted capabilities.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Plan approved and activated
          content:
            application/json:
              schema:
                type: object
                properties:
                  plan_id:
                    type: string
                  temp_agent_id:
                    type: string
                  session_id:
                    type: string
                  status:
                    type: string
                    example: active

  /admin/plans/{id}/reject:
    post:
      operationId: rejectPlan
      summary: Reject a plan
      tags: [permissions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Rejected

  /admin/plans/{id}/complete:
    post:
      operationId: completePlan
      summary: Mark a plan as completed (terminates temp agent)
      tags: [permissions]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Completed

  /admin/llm:
    get:
      operationId: listLLMBackends
      summary: List all LLM backends
      tags: [llm]
      responses:
        '200':
          description: Backend list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/LLMBackendDTO'
    post:
      operationId: createLLMBackend
      summary: Register an LLM backend
      tags: [llm]
      description: |
        Supports three source types:
        - `external` — URL-based, SSRF-validated
        - `oci` — OCI image, requires k8s build tag and model controller enabled
        - `hf` — HuggingFace repo, same requirements as OCI

        Cache strategy `"alexandria"` requires the `memcached_cache` entitlement.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                url:
                  type: string
                  description: Required for source_type=external
                kind:
                  type: string
                  enum: [open-ai, claude, llama-cpp, custom]
                  default: open-ai
                model:
                  type: string
                  nullable: true
                completions_path:
                  type: string
                  nullable: true
                api_key:
                  type: string
                  nullable: true
                is_default:
                  type: boolean
                capabilities:
                  type: array
                  items:
                    type: string
                    enum: [chat, embed, voice, vision, rerank]
                protocol:
                  type: string
                  enum: [chat, oip]
                cache_strategy:
                  type: string
                  enum: [native, alexandria, none]
                cache_ttl_seconds:
                  type: integer
                source_type:
                  type: string
                  enum: [external, oci, hf]
                  default: external
                image:
                  type: string
                image_digest:
                  type: string
                hf_repo:
                  type: string
                hf_file:
                  type: string
                hf_revision:
                  type: string
                engine:
                  type: string
                  enum: [llama-cpp, vllm, custom]
                container_port:
                  type: integer
                replicas:
                  type: integer
                resources:
                  type: object
                  additionalProperties:
                    type: string
                env:
                  type: object
                  additionalProperties:
                    type: string
                image_pull_secret:
                  type: string
      responses:
        '201':
          description: Backend created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LLMBackendDTO'
        '403':
          description: cache_strategy=alexandria requires memcached_cache entitlement
        '409':
          description: Backend name already exists

  /admin/llm/sync:
    post:
      operationId: syncLLMConfig
      summary: Sync backend config to orchestrator config file and trigger reload
      tags: [llm]
      responses:
        '200':
          description: Sync result
          content:
            application/json:
              schema:
                type: object
                properties:
                  synced:
                    type: boolean
                  backends_loaded:
                    type: integer

  /admin/llm/{name}:
    get:
      operationId: getLLMBackend
      summary: Get LLM backend by name
      tags: [llm]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Backend
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LLMBackendDTO'
    patch:
      operationId: patchLLMBackend
      summary: Update LLM backend fields
      tags: [llm]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                kind:
                  type: string
                  enum: [open-ai, claude, llama-cpp, custom]
                model:
                  type: string
                completions_path:
                  type: string
                api_key:
                  type: string
                enabled:
                  type: boolean
                capabilities:
                  type: array
                  items:
                    type: string
                protocol:
                  type: string
                cache_strategy:
                  type: string
                cache_ttl_seconds:
                  type: integer
      responses:
        '200':
          description: Updated backend
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LLMBackendDTO'
    delete:
      operationId: deleteLLMBackend
      summary: Delete an LLM backend and remove its API key secret
      tags: [llm]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /admin/llm/{name}/default:
    post:
      operationId: setDefaultLLMBackend
      summary: Mark a backend as the default for routing
      tags: [llm]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Updated backend

  /admin/llm/{name}/ping:
    post:
      operationId: pingLLMBackend
      summary: Check backend reachability (SSRF-safe)
      tags: [llm]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Health check result
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  url:
                    type: string
                  healthy:
                    type: boolean
                  error:
                    type: string
                  status_code:
                    type: integer

  /admin/llm/{name}/logs:
    get:
      operationId: getLLMBackendLogs
      summary: Stream Pod logs for a managed backend
      tags: [llm]
      description: |
        **Build-tag conditional:** only available in the `k8s_enabled` build.
        In non-k8s builds returns 503. Requires k8s client configured in-cluster.
        External backends return 400.

        > **Callout:** the `k8s_enabled` build tag gates this handler.
        > The noop build (`llm_logs_noop.go`) returns 503 with an informational message.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: tail
          in: query
          schema:
            type: integer
            default: 200
            maximum: 5000
        - name: container
          in: query
          schema:
            type: string
            default: engine
      responses:
        '200':
          description: Plain text log stream
          content:
            text/plain:
              schema:
                type: string
        '400':
          description: External backend (no Pod to log)
        '503':
          description: k8s client unavailable (non-k8s build)

  /admin/llm/{name}/deploy:
    post:
      operationId: deployLLMBackend
      summary: Enable a managed backend for k8s deployment (requires backend_autoscaling entitlement)
      tags: [llm]
      description: Flips `enabled=true` so alex-model-controller reconciles a Deployment/Service.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Updated backend with deployment_status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LLMBackendDTO'
        '402':
          description: backend_autoscaling entitlement not licensed

  /admin/llm/{name}/undeploy:
    post:
      operationId: undeployLLMBackend
      summary: Disable a managed backend (controller tears down Deployment/Service)
      tags: [llm]
      description: Requires backend_autoscaling entitlement.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Updated backend
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LLMBackendDTO'
        '402':
          description: backend_autoscaling entitlement not licensed

  /admin/notifications/config:
    get:
      operationId: getNotificationsConfig
      summary: Get notification sink configuration
      tags: [admin]
      description: Secret values are never returned; only their presence is indicated.
      responses:
        '200':
          description: Notifications config
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationsConfigDTO'
    patch:
      operationId: patchNotificationsConfig
      summary: Update notification sink configuration (super_admin only)
      tags: [admin]
      description: Requires `super_admin`. Hot-swaps the notifier without restart.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                webhook_url:
                  type: string
                alertmanager_url:
                  type: string
                alertmanager_severity:
                  type: string
                reconcile_interval_seconds:
                  type: integer
                  minimum: 10
      responses:
        '200':
          description: Updated config
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationsConfigDTO'
        '403':
          description: super_admin required

  /admin/notifications/test:
    post:
      operationId: testNotifications
      summary: Fire test notifications to all configured sinks (super_admin only)
      tags: [admin]
      responses:
        '200':
          description: OK
        '403':
          description: super_admin required

  # ── SCIM 2.0 ────────────────────────────────────────────────────────────────
  /scim/v2/ServiceProviderConfig:
    get:
      operationId: scimServiceProviderConfig
      summary: SCIM 2.0 service provider configuration
      tags: [scim]
      security: []
      description: Per RFC 7644 §4 — no authentication required.
      responses:
        '200':
          description: ServiceProviderConfig

  /scim/v2/Schemas:
    get:
      operationId: scimSchemas
      summary: SCIM 2.0 schema definitions
      tags: [scim]
      security: []
      responses:
        '200':
          description: Schema list
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMListResponse'

  /scim/v2/ResourceTypes:
    get:
      operationId: scimResourceTypes
      summary: SCIM 2.0 resource type definitions
      tags: [scim]
      security: []
      responses:
        '200':
          description: Resource type list
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMListResponse'

  /scim/v2/Users:
    get:
      operationId: scimListUsers
      summary: List SCIM users
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: filter
          in: query
          schema:
            type: string
          description: 'SCIM filter expression, e.g. `userName eq "alice"`'
        - name: startIndex
          in: query
          schema:
            type: integer
            default: 1
        - name: count
          in: query
          schema:
            type: integer
            default: 100
      responses:
        '200':
          description: User list
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMListResponse'
    post:
      operationId: scimCreateUser
      summary: Provision a SCIM user
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              required: [userName]
              properties:
                userName:
                  type: string
                displayName:
                  type: string
                password:
                  type: string
                  description: Optional. Random placeholder used if absent (user authenticates via IdP).
                active:
                  type: boolean
      responses:
        '201':
          description: User created
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMUser'
        '409':
          description: userName already exists

  /scim/v2/Users/{id}:
    get:
      operationId: scimGetUser
      summary: Get a SCIM user by ID
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: SCIM user
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMUser'
    put:
      operationId: scimReplaceUser
      summary: Replace a SCIM user (full update)
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              properties:
                userName:
                  type: string
                active:
                  type: boolean
                password:
                  type: string
      responses:
        '200':
          description: Updated user
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMUser'
    patch:
      operationId: scimPatchUser
      summary: Patch a SCIM user
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      description: Supports `active` (enable/disable) and `password` operations. `userName` changes are rejected.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              properties:
                schemas:
                  type: array
                  items:
                    type: string
                Operations:
                  type: array
                  items:
                    type: object
                    properties:
                      op:
                        type: string
                        enum: [add, replace, remove]
                      path:
                        type: string
                      value: {}
      responses:
        '200':
          description: Updated user
    delete:
      operationId: scimDeleteUser
      summary: Deprovision a SCIM user
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

  /scim/v2/Groups:
    get:
      operationId: scimListGroups
      summary: List SCIM groups
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: filter
          in: query
          schema:
            type: string
        - name: startIndex
          in: query
          schema:
            type: integer
            default: 1
        - name: count
          in: query
          schema:
            type: integer
            default: 100
      responses:
        '200':
          description: Group list
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMListResponse'
    post:
      operationId: scimCreateGroup
      summary: Provision a SCIM group
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              required: [displayName]
              properties:
                displayName:
                  type: string
                members:
                  type: array
                  items:
                    type: object
                    properties:
                      value:
                        type: string
      responses:
        '201':
          description: Group created
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMGroup'

  /scim/v2/Groups/{id}:
    get:
      operationId: scimGetGroup
      summary: Get a SCIM group by ID
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: SCIM group
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SCIMGroup'
    put:
      operationId: scimReplaceGroup
      summary: Replace a SCIM group
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              properties:
                displayName:
                  type: string
                members:
                  type: array
                  items:
                    type: object
      responses:
        '200':
          description: Updated group
    patch:
      operationId: scimPatchGroup
      summary: Patch a SCIM group
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              properties:
                schemas:
                  type: array
                  items:
                    type: string
                Operations:
                  type: array
                  items:
                    type: object
      responses:
        '200':
          description: Updated group
    delete:
      operationId: scimDeleteGroup
      summary: Delete a SCIM group
      tags: [scim]
      security:
        - SCIMBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted

tags:
  - name: auth
    description: Authentication, token issuance, WebAuthn
  - name: federation
    description: OIDC / SAML SSO federation
  - name: agents
    description: Agent lifecycle and workflow management
  - name: query
    description: Query and streaming endpoints
  - name: openai
    description: OpenAI-compatible routes
  - name: mcp
    description: MCP JSON-RPC bridge
  - name: chats
    description: Persistent chat sessions
  - name: sessions
    description: Conversation session history
  - name: permissions
    description: RBAC — skills, groups, roles, permission requests, plans
  - name: tenants
    description: Multi-tenant management
  - name: llm
    description: LLM backend management
  - name: admin
    description: Administrative operations
  - name: scim
    description: SCIM 2.0 provisioning (RFC 7644)
  - name: ingest
    description: Tool memory ingestion endpoints
  - name: system
    description: Health, metrics, attestation
