openapi: 3.1.0
info:
  title: Agent Orc Conductor API
  version: 0.1.0
  description: |
    HTTP control plane for the agent-orc conductor. Every operation is
    tenant-scoped via the `X-Tenant-ID` header; omit it to use `default`.
x-fern-global-headers:
  - header: X-Tenant-ID
    name: tenantId
    optional: true
    x-fern-default: default
servers:
  - url: http://localhost:8080
    description: Local dev conductor (make conductor)
  - url: '{baseUrl}'
    description: Custom
    variables:
      baseUrl:
        default: http://localhost:8080
security:
  - BearerAuth: []
tags:
  - name: Profiles
  - name: Pools
  - name: Sessions
  - name: Runs
  - name: Workflows
  - name: Skills
  - name: MCP
  - name: Storage
  - name: Memory
  - name: Tenants
  - name: Secrets
  - name: Stats
  - name: Topology
  - name: Billing
  - name: Sandbox
  - name: ConnectedApps
  - name: Capabilities
  - name: Publishing
  - name: Misc
paths:
  /healthz:
    get:
      tags: [Misc]
      x-fern-sdk-group-name: misc
      operationId: health
      x-fern-sdk-method-name: health
      summary: Liveness probe
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                type: string
                example: ok

  /api/profiles:
    get:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: listProfiles
      x-fern-sdk-method-name: list
      summary: List agent profiles
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: All profiles registered for the active tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileList'
    post:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: createProfile
      x-fern-sdk-method-name: create
      summary: Create a profile
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Profile'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Error'

  /api/profiles/{name}:
    get:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: getProfile
      x-fern-sdk-method-name: get
      summary: Get a profile by name
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: updateProfile
      x-fern-sdk-method-name: update
      summary: Update or rename a profile
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Profile'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Error'
    delete:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: deleteProfile
      x-fern-sdk-method-name: delete
      summary: Delete a profile
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/profiles/{name}/changes:
    get:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: listProfileChanges
      x-fern-sdk-method-name: listChanges
      summary: List profile change-history entries (newest first)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Audit-style log of create / update / delete events.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileChangeList'

  /api/profiles/{name}/usage:
    get:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: getProfileUsage
      x-fern-sdk-method-name: usage
      summary: Get finalized all-time token usage for a profile
      description: >-
        Returns durable token totals and finished-run counts for the active
        tenant's profile. Profile totals always span all finalized run usage.
        When `sessions` is supplied, the response contains one row per
        requested session, including explicit zero rows. Without it, the most
        recently recorded sessions are returned with a bounded limit.
        `available=false` distinguishes an unavailable usage store/read from a
        verified zero.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: sessions
          in: query
          required: false
          description: Comma-separated session IDs to aggregate (up to 500).
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Maximum recent session rows when `sessions` is omitted.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        '200':
          description: Finalized all-time profile and per-session token usage.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileUsageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/pools:
    get:
      tags: [Pools]
      x-fern-sdk-group-name: pools
      operationId: listPools
      x-fern-sdk-method-name: list
      summary: List agent pools
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: All pools registered for the active tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolList'
    post:
      tags: [Pools]
      x-fern-sdk-group-name: pools
      operationId: createPool
      x-fern-sdk-method-name: create
      summary: Create a pool
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pool'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pool'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Error'

  /api/pools/{name}:
    patch:
      tags: [Pools]
      x-fern-sdk-group-name: pools
      operationId: updatePool
      x-fern-sdk-method-name: update
      summary: Patch a pool (partial update)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pool'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pool'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Pools]
      x-fern-sdk-group-name: pools
      operationId: deletePool
      x-fern-sdk-method-name: delete
      summary: Delete a pool
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/pools/{name}/members/{profile}:
    post:
      tags: [Pools]
      x-fern-sdk-group-name: pools
      operationId: addPoolMember
      x-fern-sdk-method-name: addMember
      summary: Add a profile to a pool (idempotent)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: profile
          in: path
          required: true
          schema:
            type: string
        - name: role
          in: query
          required: false
          description: 'Pool role: lead | member | observer (default member).'
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Pools]
      x-fern-sdk-group-name: pools
      operationId: removePoolMember
      x-fern-sdk-method-name: removeMember
      summary: Remove a profile from a pool (idempotent)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: profile
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/sessions:
    get:
      tags: [Sessions]
      x-fern-sdk-group-name: sessions
      operationId: listSessions
      x-fern-sdk-method-name: list
      summary: List sessions
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Sessions visible to the active tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionList'
    post:
      tags: [Sessions]
      x-fern-sdk-group-name: sessions
      operationId: createSession
      x-fern-sdk-method-name: create
      summary: Create a session for a profile
      description: |
        Mints a runtime session bound to the named profile. Used by the
        public chat-gateway's "first turn of a new conversation" path:
        the gateway needs a sess_... id before dispatching the run so
        the conversations row can be inserted with a real session_id.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [profile]
              properties:
                profile:
                  type: string
                  description: AgentProfile.Name to bind the session to.
      responses:
        '201':
          description: Session minted.
          content:
            application/json:
              schema:
                type: object
                required: [sessionId, profile]
                properties:
                  sessionId:
                    type: string
                  profile:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/sessions/{id}:
    get:
      tags: [Sessions]
      x-fern-sdk-group-name: sessions
      operationId: retrieveSession
      x-fern-sdk-method-name: retrieve
      summary: Get a session by id
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Session detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Sessions]
      x-fern-sdk-group-name: sessions
      operationId: deleteSession
      x-fern-sdk-method-name: delete
      summary: Shut a session down
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/sessions/{id}/runs:
    get:
      tags: [Sessions]
      x-fern-sdk-group-name: sessions
      operationId: listSessionRuns
      x-fern-sdk-method-name: listRuns
      summary: List runs for a session (newest first)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Run summaries for the session.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunSummaryList'

  /api/profiles/{name}/runs:
    get:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: listProfileRuns
      x-fern-sdk-method-name: listRuns
      summary: List runs for a profile (newest first)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Run summaries for the profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunSummaryList'

  /api/orca/seed:
    post:
      tags: [Misc]
      x-fern-sdk-group-name: misc
      operationId: seedOrca
      x-fern-sdk-method-name: seedOrca
      summary: Idempotently re-create the canonical "orca" helper profile
      description: |
        Used by the dashboard's Quickstart "Restore Orca" button. The
        endpoint is safe to call repeatedly; it has no body and
        always returns the resulting profile snapshot.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Orca profile state after the call.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/clerk/webhook:
    post:
      tags: [Misc]
      x-fern-sdk-group-name: misc
      operationId: clerkWebhook
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: webhook
      x-fern-sdk-method-name: clerkWebhook
      summary: Clerk identity webhook (Svix-signed)
      description: |
        Ingests Clerk user/organization/membership events and projects
        them into the identity tables (users + memberships). This route is
        NOT authenticated with a Clerk session JWT; it is verified by the
        Svix HMAC signature Clerk attaches (svix-id, svix-timestamp,
        svix-signature headers) against CLERK_WEBHOOK_SECRET. Returns 404
        when the secret is unset (feature disabled) and 400 on a bad
        signature. Not intended to be called by SDK clients.
      responses:
        '200':
          description: Event accepted (or ignored for unhandled types).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/polar/webhook:
    post:
      tags: [Misc]
      x-fern-sdk-group-name: misc
      operationId: polarWebhook
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: webhook
      x-fern-sdk-method-name: polarWebhook
      summary: Polar billing webhook (standard-webhooks signed)
      description: |
        Ingests Polar order events; order.paid triggers the purchase-
        confirmation email (pack, amount, fresh wallet balance) through
        Resend. NOT authenticated with a Clerk session JWT; verified by
        the standard-webhooks HMAC Polar attaches (webhook-id,
        webhook-timestamp, webhook-signature headers) against
        POLAR_WEBHOOK_SECRET. Returns 404 when the secret is unset
        (feature disabled) and 400 on a bad signature. Duplicate
        deliveries are acknowledged without re-sending (order-id claim).
        Not intended to be called by SDK clients.
      responses:
        '200':
          description: Event accepted (or ignored for unhandled types).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/capabilities/discover:
    post:
      tags: [Capabilities]
      operationId: discoverCapabilities
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: run-facade
      summary: Discover actions for the authenticated run
      description: |
        Provider-neutral capability facade used by an active run. The run
        token supplies the tenant, profile, session, and revision authority;
        callers cannot override that authorization context in the body.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapabilityDiscoverRequest'
      responses:
        '200':
          description: Ranked actions available to the run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityDiscoverResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/Error'

  /api/capabilities/actions/{actionId}/run:
    post:
      tags: [Capabilities]
      operationId: runCapabilityAction
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: run-facade
      summary: Execute a discovered action for the authenticated run
      description: |
        Executes the opaque action card selected by the run. Authorization is
        bound to the route action ID, the authenticated run, and the current
        profile revision. Confirmation authority is never accepted here.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: actionId
          in: path
          required: true
          schema:
            type: string
            pattern: '^act_[A-Za-z0-9_-]{32,}$'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapabilityRunRequest'
      responses:
        '200':
          description: Execution result, input request, confirmation request, or denial.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityRunResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          description: Unknown, expired, revoked, detached, or cross-tenant action.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityRunResponse'
        '502':
          $ref: '#/components/responses/Error'

  /api/capabilities/confirmations/{challengeId}:
    post:
      tags: [Capabilities]
      operationId: decideCapabilityConfirmation
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: human-confirmation
      summary: Approve or deny a durable capability challenge
      description: |
        A stable authenticated human principal may decide a router-owned
        challenge. Tenant, principal, operation, and trace ownership are loaded
        from the durable challenge and cannot be supplied in the body. Service
        principals are not confirmation authorities. In phase one, denial is
        durable but approval execution is disabled: an approve request returns
        `confirmation_execution_unavailable` without consuming the challenge.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: challengeId
          in: path
          required: true
          schema:
            type: string
            minLength: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapabilityDecisionRequest'
      responses:
        '200':
          description: Terminal result of a denied challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityRunResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          description: Unknown challenge or mismatched tenant or principal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityRunResponse'
        '409':
          description: Approval execution is disabled and the challenge remains unconsumed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          $ref: '#/components/responses/Error'

  /api/runs:
    get:
      tags: [Runs]
      x-fern-sdk-group-name: runs
      operationId: listRuns
      x-fern-sdk-method-name: list
      summary: List runs (newest first)
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Run summaries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunSummaryList'
    post:
      tags: [Runs]
      x-fern-sdk-group-name: runs
      operationId: createRun
      x-fern-sdk-method-name: create
      summary: Start a run
      description: |
        Accepts a SubTask, mints a sessionId when missing, and kicks off a
        background run. Returns 202 with the assigned `runId` and
        `sessionId` immediately; events stream via the SSE endpoints.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunCreateRequest'
      responses:
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                type: object
                required: [runId, sessionId]
                properties:
                  runId:
                    type: string
                  sessionId:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/Error'

  /api/runs/{id}:
    get:
      tags: [Runs]
      x-fern-sdk-group-name: runs
      operationId: retrieveRun
      x-fern-sdk-method-name: retrieve
      summary: Get a run with its buffered event tail
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Run detail (summary + events).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunDetail'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Runs]
      x-fern-sdk-group-name: runs
      operationId: cancelRun
      x-fern-sdk-method-name: cancel
      summary: Cancel an in-flight run
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '202':
          description: Cancellation requested
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/runs/{id}/terminate:
    post:
      tags: [Runs]
      x-fern-sdk-group-name: runs
      operationId: terminateRun
      x-fern-sdk-method-name: terminate
      summary: Force-stop a run that cooperative cancel could not stop
      description: |
        Last-resort kill switch. `DELETE /api/runs/{id}` is the cooperative
        stop and is enough for a healthy run; this endpoint assumes nothing
        about the run's health and performs three independent best-effort
        steps, reporting each one:

        1. Cancel the run context, when this conductor owns the run.
        2. Force-release the session's run claim on the owning runner, so a
           leaked claim cannot keep failing later runs with `session busy`.
        3. Flip the durable run row to `cancelled`, so the run stops showing
           as `running` even if its own finish path never completes.

        Unlike cancel this does not 404 for a run missing from the
        conductor's in-memory registry: a run stranded by a restart is
        exactly the case that most needs steps 2 and 3. Idempotent.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Termination attempted; body reports what each step achieved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TerminateRunResult'

  /api/runs/{id}/events:
    get:
      tags: [Runs]
      x-fern-sdk-group-name: runs
      operationId: replayRunEvents
      x-fern-sdk-method-name: replayEvents
      summary: Replay a run's events as newline-delimited JSON
      description: |
        Newline-delimited JSON. First line is a header object; subsequent
        lines are run events; final line is a finish marker. See
        `runtime/httpapi/run_events_handler.go` for the frame shape.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: |
            Newline-delimited JSON. First line is a header object;
            subsequent lines are run events; final line is a finish
            marker. See `runtime/httpapi/run_events_handler.go` for the
            frame shape.
          content:
            application/x-ndjson:
              schema:
                type: string
        '404':
          $ref: '#/components/responses/NotFound'

  /api/runs/{id}/stream:
    get:
      tags: [Runs]
      x-fern-sdk-group-name: runs
      operationId: streamRun
      x-fern-sdk-method-name: stream
      summary: Subscribe to a live run (SSE)
      description: |
        Server-sent events stream. Replays buffered events first, then
        fans out new events as they arrive. Each frame is `data: <json>\n\n`
        where the body is a single `Event`. Heartbeat lines (`: ping`)
        every ~15s keep proxies from buffering. The connection closes
        once the run reaches a terminal status.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: SSE stream of `Event` JSON frames.
          content:
            text/event-stream:
              schema:
                type: string
        '404':
          $ref: '#/components/responses/NotFound'

  /api/workflows/runs:
    post:
      tags: [Workflows]
      operationId: createWorkflowRun
      summary: Create a workflow run
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanCreateRequest'
      responses:
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    get:
      tags: [Workflows]
      operationId: listWorkflowRuns
      summary: List workflow runs
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: orchestratorRunId
          in: query
          required: false
          schema:
            type: string
        - name: status
          in: query
          required: false
          schema:
            type: string
        - name: since
          in: query
          required: false
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Matching workflow runs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanList'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/workflows/runs/{id}:
    get:
      tags: [Workflows]
      operationId: retrieveWorkflowRun
      summary: Get a workflow run snapshot
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Workflow run
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Plan'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/workflows/runs/{id}/start:
    post:
      tags: [Workflows]
      operationId: startWorkflowRun
      summary: Start a workflow run
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/workflows/runs/{id}/cancel:
    post:
      tags: [Workflows]
      operationId: cancelWorkflowRun
      summary: Cancel a workflow run
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '202':
          description: Cancellation accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/workflows/runs/{id}/repair:
    post:
      tags: [Workflows]
      operationId: repairWorkflowRun
      summary: Repair a paused workflow run
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RepairAction'
      responses:
        '202':
          description: Repair accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/workflows/runs/{id}/nodes/{nodeId}/status:
    post:
      tags: [Workflows]
      operationId: updateWorkflowRunNodeStatus
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: service-callback
      summary: Update a workflow run node's terminal status
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NodeStatusUpdate'
      responses:
        '202':
          description: Updated workflow run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Plan'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/workflows/runs/{id}/stream:
    get:
      tags: [Workflows]
      operationId: streamWorkflowRun
      summary: Subscribe to workflow run status changes
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: SSE stream of workflow-run status frames.
          content:
            text/event-stream:
              schema:
                type: string
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/workflows/definitions:
    post:
      tags: [Workflows]
      operationId: createWorkflowDefinition
      summary: Create a workflow definition
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Object'
      responses:
        '201':
          description: Created workflow definition.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
    get:
      tags: [Workflows]
      operationId: listWorkflowDefinitions
      summary: List workflow definitions
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Workflow definitions.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Object'

  /api/workflows/definitions/{id}:
    get:
      tags: [Workflows]
      operationId: retrieveWorkflowDefinition
      summary: Get a workflow definition
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Workflow definition.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Workflows]
      operationId: updateWorkflowDefinition
      summary: Update a workflow definition
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Object'
      responses:
        '200':
          description: Updated workflow definition.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Workflows]
      operationId: deleteWorkflowDefinition
      summary: Delete a workflow definition
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/workflows/schedules:
    post:
      tags: [Workflows]
      operationId: createWorkflowSchedule
      summary: Create a workflow schedule
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Object'
      responses:
        '201':
          description: Created workflow schedule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
    get:
      tags: [Workflows]
      operationId: listWorkflowSchedules
      summary: List workflow schedules
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Workflow schedules.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Object'

  /api/workflows/schedules/{id}:
    get:
      tags: [Workflows]
      operationId: retrieveWorkflowSchedule
      summary: Get a workflow schedule
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Workflow schedule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Workflows]
      operationId: deleteWorkflowSchedule
      summary: Delete a workflow schedule
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/workflows/schedules/{id}/pause:
    post:
      tags: [Workflows]
      operationId: pauseWorkflowSchedule
      summary: Pause a workflow schedule
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Paused workflow schedule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/workflows/schedules/{id}/resume:
    post:
      tags: [Workflows]
      operationId: resumeWorkflowSchedule
      summary: Resume a workflow schedule
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Resumed workflow schedule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/skills/import:
    post:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: importSkills
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: server-maintenance
      x-fern-sdk-method-name: import
      summary: Walk known roots and import SKILL.md files into the catalog
      description: |
        Walks AGENT_ORC_IMPORT_SKILLS_FROM (when set), then the project's
        `.claude/skills` and `.agents/skills` trees, then `~/.claude/skills`
        and `~/.agents/skills`, then any installed plugin skill dirs. Each
        SKILL.md's YAML frontmatter is parsed and validated, and the body
        upserted with `source: "imported"`. Existing skills are skipped
        unless `?force=1` is supplied.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: force
          in: query
          required: false
          description: Set `1` to overwrite existing entries with the imported version.
          schema:
            type: string
      responses:
        '200':
          description: Import result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillImportResult'

  /api/skills/import-package:
    post:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: importSkillPackage
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: multipart-upload
      summary: Validate and stage an uploaded Open Agent Skills package
      description: |
        Accepts repeated multipart `files` parts whose filenames are relative
        package paths, for example `my-skill/SKILL.md` and
        `my-skill/scripts/run.sh`. Dry-run is required; commit the returned
        stagingId through `/api/skills/import-package/commit`.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: dryRun
          in: query
          required: true
          schema:
            type: string
            enum: ['1']
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [files]
              properties:
                files:
                  type: array
                  items:
                    type: string
                    format: binary
      responses:
        '200':
          description: Staged package preview.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillPackagePreview'
        '400':
          $ref: '#/components/responses/BadRequest'
        '413':
          $ref: '#/components/responses/Error'

  /api/skills/import-package/commit:
    post:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: commitSkillPackage
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: staged-upload
      summary: Register a previously staged skill package
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SkillPackageCommitRequest'
      responses:
        '201':
          description: Registered skill.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Skill'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Error'

  /api/skills:
    get:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: listSkills
      x-fern-sdk-method-name: list
      summary: List skills
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: All skills in the catalog.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillList'
    post:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: createSkill
      x-fern-sdk-method-name: create
      summary: Create a skill
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Skill'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Skill'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Error'

  /api/skills/{name}:
    get:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: retrieveSkill
      x-fern-sdk-method-name: retrieve
      summary: Get a skill by name
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Skill detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Skill'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: updateSkill
      x-fern-sdk-method-name: update
      summary: Update or rename a skill
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Skill'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Skill'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Error'
    delete:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: deleteSkill
      x-fern-sdk-method-name: delete
      summary: Delete a skill
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/skills/{name}/resources/{path}:
    get:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: getSkillResource
      x-fern-sdk-method-name: getResource
      summary: Read a skill resource file (raw bytes)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: path
          in: path
          required: true
          description: |
            Resource path under the skill root, e.g. `scripts/run.sh`. The
            registered route uses Go 1.22's `{path...}` wildcard so the
            path may include nested directories. Bruno and other clients
            should pass the full multi-segment value verbatim.
          schema:
            type: string
      responses:
        '200':
          description: Resource bytes (Content-Type derived from the file extension).
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: putSkillResource
      x-fern-sdk-method-name: putResource
      summary: Upload or replace a skill resource file
      description: |
        Bytes are stored under the tenant prefix in the artifact bucket;
        a `skill_resources` row is inserted/updated with size and content
        type. Returns 503 when DB+S3 are not wired.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: path
          in: path
          required: true
          description: 'Multi-segment resource path (Go 1.22 `{path...}` wildcard).'
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: Resource metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillResource'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '413':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      tags: [Skills]
      x-fern-sdk-group-name: skills
      operationId: deleteSkillResource
      x-fern-sdk-method-name: deleteResource
      summary: Delete a skill resource file
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: path
          in: path
          required: true
          description: 'Multi-segment resource path (Go 1.22 `{path...}` wildcard).'
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/capability-bundles:
    get:
      tags: [Misc]
      x-fern-sdk-group-name: misc
      operationId: listCapabilityBundles
      x-fern-sdk-method-name: listCapabilityBundles
      summary: List capability-bundle metadata
      description: |
        Returns the static toolkit-bundle catalog (id, label, description,
        member tools). Used by the dashboard's capability picker.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Bundles
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/mcp-servers:
    get:
      tags: [MCP]
      x-fern-sdk-group-name: mcp
      operationId: listMcpServers
      x-fern-sdk-method-name: list
      summary: List MCP catalog entries
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: All catalog entries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPServerList'
    post:
      tags: [MCP]
      x-fern-sdk-group-name: mcp
      operationId: createMcpServer
      x-fern-sdk-method-name: create
      summary: Register a new MCP catalog entry
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MCPServer'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPServer'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Error'

  /api/mcp-servers/test:
    post:
      tags: [MCP]
      x-fern-sdk-group-name: mcp
      operationId: testMcpServer
      x-fern-sdk-method-name: test
      summary: Probe an MCP server (does not persist)
      description: |
        Resolves header references (`${VAR}` env, `secret://name`) when
        a secrets manager is wired, then opens a short-lived MCP probe
        and reports tool count + latency.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MCPServer'
      responses:
        '200':
          description: Probe result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPTestResult'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/mcp-servers/{name}:
    get:
      tags: [MCP]
      x-fern-sdk-group-name: mcp
      operationId: retrieveMcpServer
      x-fern-sdk-method-name: retrieve
      summary: Get an MCP catalog entry by name
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Catalog entry
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPServer'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [MCP]
      x-fern-sdk-group-name: mcp
      operationId: updateMcpServer
      x-fern-sdk-method-name: update
      summary: Update or rename an MCP catalog entry
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MCPServer'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPServer'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Error'
    delete:
      tags: [MCP]
      x-fern-sdk-group-name: mcp
      operationId: deleteMcpServer
      x-fern-sdk-method-name: delete
      summary: Delete an MCP catalog entry
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/storage/info:
    get:
      tags: [Storage]
      x-fern-sdk-group-name: storage
      operationId: getStorageInfo
      x-fern-sdk-method-name: info
      summary: Storage health, capacity, and per-prefix usage
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Bucket-wide stats and breakdown.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StorageInfo'

  /api/storage/objects:
    get:
      tags: [Storage]
      x-fern-sdk-group-name: storage
      operationId: listStorageObjects
      x-fern-sdk-method-name: list
      summary: List artefact objects under a prefix
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: prefix
          in: query
          required: false
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Defaults to 100; capped at 1000.
          schema:
            type: integer
      responses:
        '200':
          description: Listing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StorageObjectList'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/storage/objects/{key}:
    get:
      tags: [Storage]
      x-fern-sdk-group-name: storage
      operationId: retrieveStorageObject
      x-fern-sdk-method-name: retrieve
      summary: Read a single object inline
      description: |
        Returns the bytes inline. Text bodies are returned UTF-8; non-UTF-8
        bodies are base64-encoded in the response (the `encoding` field
        signals which).
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: key
          in: path
          required: true
          description: 'Multi-segment object key (Go 1.22 `{key...}` wildcard).'
          schema:
            type: string
      responses:
        '200':
          description: Object body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StorageObject'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    put:
      tags: [Storage]
      x-fern-sdk-group-name: storage
      operationId: putStorageObject
      x-fern-sdk-method-name: put
      summary: Upload (or overwrite) an object
      description: |
        The raw request body is stored under the key from the path; the
        `Content-Type` request header, when present, is preserved. Inline
        cap mirrors the artefact client's `MaxInlineSize`.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: key
          in: path
          required: true
          description: 'Multi-segment object key (Go 1.22 `{key...}` wildcard).'
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: Upload metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoragePutResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '413':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      tags: [Storage]
      x-fern-sdk-group-name: storage
      operationId: deleteStorageObject
      x-fern-sdk-method-name: delete
      summary: Delete an object (or prefix when key ends with `/`)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: key
          in: path
          required: true
          description: 'Multi-segment object key (Go 1.22 `{key...}` wildcard). When trailing `/`, treated as a prefix and every object under it is deleted (capped at 1000).'
          schema:
            type: string
      responses:
        '200':
          description: Delete result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StorageDeleteResult'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/memories:
    get:
      tags: [Memory]
      x-fern-sdk-group-name: memory
      operationId: listProfileMemories
      x-fern-sdk-method-name: list
      summary: List memories for a profile
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          required: false
          schema:
            type: integer
        - name: offset
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Page of memories.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryListResponse'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      tags: [Memory]
      x-fern-sdk-group-name: memory
      operationId: createProfileMemory
      x-fern-sdk-method-name: create
      summary: Save a new memory under a profile
      description: |
        `rawInput` alone triggers LLM-driven extraction of
        `processedContent`, `summary`, `category`, and `confidence`.
        Supplying `processedContent` skips the LLM path.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemoryCreateRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/memories/search:
    get:
      tags: [Memory]
      x-fern-sdk-group-name: memory
      operationId: searchProfileMemories
      x-fern-sdk-method-name: search
      summary: Search memories by relevance
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: q
          in: query
          required: false
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Defaults to 8.
          schema:
            type: integer
        - name: minScore
          in: query
          required: false
          description: Defaults to 0.05.
          schema:
            type: number
      responses:
        '200':
          description: Scored results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemorySearchResponse'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/memories/{id}:
    get:
      tags: [Memory]
      x-fern-sdk-group-name: memory
      operationId: retrieveProfileMemory
      x-fern-sdk-method-name: retrieve
      summary: Get a single memory by id
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Memory
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      tags: [Memory]
      x-fern-sdk-group-name: memory
      operationId: deleteProfileMemory
      x-fern-sdk-method-name: delete
      summary: Delete a memory
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Delete acknowledgement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/memory-bank:
    get:
      tags: [Memory]
      x-fern-sdk-group-name: memory
      operationId: getMemoryBank
      x-fern-sdk-method-name: getBank
      summary: Cross-profile snapshot of every memory in the bank
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: All memories grouped by profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryBankSnapshot'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/memory-bank/stats:
    get:
      tags: [Memory]
      x-fern-sdk-group-name: memory
      operationId: getMemoryBankStats
      x-fern-sdk-method-name: getBankStats
      summary: Bank-wide totals + per-profile counts
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryBankStats'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/tenants:
    get:
      tags: [Tenants]
      x-fern-sdk-group-name: tenants
      operationId: listTenants
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: operator
      x-fern-sdk-method-name: list
      summary: List tenants
      description: |
        Operator-only cross-tenant surface. Enabled only when the conductor
        sets `AGENT_ORC_ADMIN_TENANT` and the caller's verified tenant matches
        it; otherwise every `/api/tenants*` request returns 404 (kept opaque so
        non-admins cannot enumerate the endpoint). Ordinary tenant API keys
        should expect 404 here.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: includeDeleted
          in: query
          required: false
          description: Set `1` to include soft-deleted rows.
          schema:
            type: string
      responses:
        '200':
          description: All tenants visible to the caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantList'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      tags: [Tenants]
      x-fern-sdk-group-name: tenants
      operationId: createTenant
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: operator
      x-fern-sdk-method-name: create
      summary: Create a tenant
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantCreateRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tenant'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/tenants/{id}:
    get:
      tags: [Tenants]
      x-fern-sdk-group-name: tenants
      operationId: retrieveTenant
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: operator
      x-fern-sdk-method-name: retrieve
      summary: Get a tenant by id
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Tenant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tenant'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    put:
      tags: [Tenants]
      x-fern-sdk-group-name: tenants
      operationId: updateTenant
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: operator
      x-fern-sdk-method-name: update
      summary: Update a tenant
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantUpdateRequest'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tenant'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      tags: [Tenants]
      x-fern-sdk-group-name: tenants
      operationId: deleteTenant
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: operator
      x-fern-sdk-method-name: delete
      summary: Soft-delete a tenant (status='deleted'; the `default` tenant cannot be deleted)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Delete acknowledgement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/secrets:
    get:
      tags: [Secrets]
      x-fern-sdk-group-name: secrets
      operationId: listSecrets
      x-fern-sdk-method-name: list
      summary: List secret metadata for the active tenant
      description: |
        Plaintext is NEVER returned. The list is a metadata-only
        projection (name, description, algorithm, timestamps).
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Secret metadata listing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecretList'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      tags: [Secrets]
      x-fern-sdk-group-name: secrets
      operationId: createSecret
      x-fern-sdk-method-name: create
      summary: Create a secret (envelope-encrypted server-side)
      description: |
        Plaintext is encrypted under the active tenant's master key and
        persisted; only metadata is returned. Plaintext is never logged.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecretCreateRequest'
      responses:
        '201':
          description: Created (metadata only — never plaintext).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Secret'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/secrets/{name}:
    put:
      tags: [Secrets]
      x-fern-sdk-group-name: secrets
      operationId: updateSecret
      x-fern-sdk-method-name: update
      summary: Replace a secret's plaintext (envelope-encrypted server-side)
      description: |
        Same shape as POST. The body's `name`, when present, must match
        the path. Plaintext is never returned on the response.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecretCreateRequest'
      responses:
        '200':
          description: Updated (metadata only — never plaintext).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Secret'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      tags: [Secrets]
      x-fern-sdk-group-name: secrets
      operationId: deleteSecret
      x-fern-sdk-method-name: delete
      summary: Delete a secret
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Delete acknowledgement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/connected-apps/providers:
    get:
      tags: [ConnectedApps]
      x-fern-sdk-group-name: connectedApps
      operationId: listConnectedAppsProviders
      x-fern-sdk-method-name: listProviders
      summary: List Connected Apps providers (Composio, Pipedream, ...)
      description: |
        Routes always register; unconfigured providers still appear in
        the list (so the UI can show a setup hint) but the catalog and
        connect endpoints below return 503 for them.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Provider list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectedAppProviderList'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/connected-apps/providers/{provider}/apps:
    get:
      tags: [ConnectedApps]
      x-fern-sdk-group-name: connectedApps
      operationId: listConnectedApps
      x-fern-sdk-method-name: listApps
      summary: List apps available from a provider
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: provider
          in: path
          required: true
          schema:
            type: string
        - name: search
          in: query
          required: false
          schema:
            type: string
        - name: category
          in: query
          required: false
          schema:
            type: string
        - name: cursor
          in: query
          required: false
          description: Provider-supplied opaque pagination cursor.
          schema:
            type: string
      responses:
        '200':
          description: Provider-shaped paged listing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/connected-apps/providers/{provider}/connect:
    post:
      tags: [ConnectedApps]
      x-fern-sdk-group-name: connectedApps
      operationId: connectConnectedApp
      x-fern-sdk-method-name: connect
      summary: Initiate an app connection (returns a redirect URL when applicable)
      description: |
        Mints an `AppConnection` row and asks the provider to begin the
        OAuth/credentials handshake. When the provider returns a
        deterministic MCP server URL (Composio) and the connection lands
        ACTIVE in the same call, the catalog is upserted immediately.
        Otherwise the row stays PENDING until the next GET on the
        connection picks up the post-redirect status.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: provider
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConnectAppRequest'
      responses:
        '201':
          description: Connection initiated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InitiateConnectResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/connected-apps/providers/{provider}/toolkits/{slug}/tools:
    get:
      tags: [ConnectedApps]
      operationId: listConnectedAppTools
      summary: List tools exposed by an active connected app
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: provider
          in: path
          required: true
          schema: {type: string}
        - name: slug
          in: path
          required: true
          schema: {type: string}
      responses:
        '200':
          description: Tool descriptors including input schemas.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '403':
          $ref: '#/components/responses/Forbidden'
        '502':
          $ref: '#/components/responses/Error'

  /api/connected-apps/providers/{provider}/toolkits/{slug}/call:
    post:
      tags: [ConnectedApps]
      operationId: callConnectedAppTool
      summary: Execute a tool through the tenant-isolated MCP bridge
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: provider
          in: path
          required: true
          schema: {type: string}
        - name: slug
          in: path
          required: true
          schema: {type: string}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Object'
      responses:
        '200':
          description: MCP tool result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '502':
          $ref: '#/components/responses/Error'

  /api/connected-apps/connections:
    get:
      tags: [ConnectedApps]
      x-fern-sdk-group-name: connectedApps
      operationId: listAppConnections
      x-fern-sdk-method-name: listConnections
      summary: List existing app connections for the active tenant
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Connections
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppConnectionList'

  /api/connected-apps/connections/{id}:
    get:
      tags: [ConnectedApps]
      x-fern-sdk-group-name: connectedApps
      operationId: retrieveAppConnection
      x-fern-sdk-method-name: retrieveConnection
      summary: Get a connection (polls the provider for fresh status)
      description: |
        Reads the persisted row, then polls the provider for the current
        status (when configured). Status / metadata changes are persisted
        and the managed `mcp_servers` row is upserted when the
        connection flips ACTIVE.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: AppConnection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppConnection'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      tags: [ConnectedApps]
      x-fern-sdk-group-name: connectedApps
      operationId: deleteAppConnection
      x-fern-sdk-method-name: deleteConnection
      summary: Revoke a connection (provider + mcp_servers row)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Delete acknowledgement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/connected-apps/connections/{id}/repair:
    post:
      tags: [ConnectedApps]
      x-fern-sdk-group-name: connectedApps
      operationId: repairAppConnection
      x-fern-sdk-method-name: repairConnection
      summary: Re-run auth_config + MCP server provisioning for an active connection
      description: |
        Heals the runtime "Auth config(s) X bound to this MCP server
        could not be resolved" failure mode without forcing the user to
        disconnect and reconnect. Provider must implement the
        `Repairer` interface; otherwise returns 400.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Repair succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RepairConnectionResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/spend-cap:
    get:
      tags: [SpendCap]
      x-fern-sdk-group-name: spendCap
      operationId: getSpendCap
      x-fern-sdk-method-name: retrieve
      summary: Get the tenant's spend cap and accrued spend
      description: >-
        Returns the calling tenant's effective daily/monthly spend limits
        (tenant-set value or system default), accrued spend in each window,
        remaining budget, reset times, and the billing email.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Spend cap and accrued spend.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
    put:
      tags: [SpendCap]
      x-fern-sdk-group-name: spendCap
      operationId: updateSpendCap
      x-fern-sdk-method-name: update
      summary: Set the tenant's monthly spend budget
      description: >-
        Sets the calling tenant's self-serve monthly spend budget and/or
        billing email. Omitted fields are left unchanged; an explicit null
        monthly_cap_usd_cents clears the override and reverts to the system
        default. The daily cap is an ops runaway guard and is not settable
        here.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Object'
      responses:
        '200':
          description: Updated spend cap and accrued spend.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/billing/wallet:
    get:
      tags: [Billing]
      x-fern-sdk-group-name: billing
      operationId: getBillingWallet
      x-fern-sdk-method-name: wallet
      summary: Credit wallet balance
      description: |
        The tenant's live credit balance (Polar is the source of truth) plus the
        purchasable top-up packs. Returns 503 when billing is not configured.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Wallet balance and purchasable packs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingWallet'
        '502':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/billing/checkout:
    post:
      tags: [Billing]
      x-fern-sdk-group-name: billing
      operationId: startBillingCheckout
      x-fern-sdk-method-name: checkout
      summary: Start a credit-pack checkout
      description: |
        Opens a Polar checkout for a credit pack and returns its URL for the
        browser to redirect to. Admin-only. Returns 503 when billing is not
        configured.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BillingCheckoutRequest'
      responses:
        '200':
          description: Checkout URL to redirect the browser to.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingCheckoutResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/admin/pricing:
    get:
      tags: [Billing]
      operationId: getAdminPricing
      summary: Model pricing floor and live reconciler rates
      description: |
        Admin-only. Returns the compiled-in per-MTok pricing floor plus the
        live reconciler rates from the global model_rates table.
        reconcilerAvailable is false (with an empty reconciler list) when no
        database is wired, so callers can render the floor alone.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Floor and reconciler pricing rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '502':
          $ref: '#/components/responses/Error'

  /api/topology:
    get:
      tags: [Topology]
      x-fern-sdk-group-name: topology
      operationId: getTopology
      x-fern-sdk-method-name: retrieve
      summary: Cluster topology snapshot
      description: |
        Live view of the runner pool, one entry per runner, each carrying the
        runner's own process footprint, its per-runtime session breakdown, and
        the agent-worker instances observed beneath it.

        Available in every deployment shape, including single-runner: the
        conductor always builds a pool. It still 404s when the server was not
        built with one at all.

        Reading this triggers on-demand health probes down the tree. That is
        intentional: probing is driven by observation rather than a background
        ticker, so an idle fleet stays idle and scale-to-zero is preserved.
        The fan-out is cached for a short TTL (tens of seconds) and shared
        across concurrent callers, so repeated reads within that window do
        not re-probe the fleet; this route also sits behind its own tighter
        per-tenant rate-limit tier (AGENT_ORC_RATELIMIT_TOPOLOGY_RPM) on top
        of the general per-tenant ceiling.

        Two fields are easy to misread. `observedInstances` is a lower bound
        on worker replicas, not a count. Null stats mean "not yet probed",
        never zero.

        Internal infrastructure identifiers are never included here: no
        runner base URL, no sidecar endpoint, and a sidecar instance's id is
        reduced to its distinguishing suffix (the host prefix is stripped).
        The internal/admin plane (/internal/topology/*) carries the full
        shape for operator tooling.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: One entry per runner in the pool.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerTopology'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/stats/summary:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getStatsSummary
      x-fern-sdk-method-name: summary
      summary: Aggregate stats summary
      description: Aggregate run/usage statistics for the active tenant.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Aggregate statistics summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/stats/agents:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getStatsAgents
      x-fern-sdk-method-name: agents
      summary: Per-agent stats
      description: Per-agent statistics breakdown.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Per-agent statistics breakdown.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/stats/timeseries:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getStatsTimeseries
      x-fern-sdk-method-name: timeseries
      summary: Time-series stats
      description: Time-series statistics bucketed over a configurable window.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Time-series statistics buckets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/stats/hotspots:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getStatsHotspots
      x-fern-sdk-method-name: hotspots
      summary: Hotspot stats
      description: Top contributors (profiles, sessions) by activity over a window.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Hotspot statistics.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'

  /api/usage:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getUsage
      x-fern-sdk-method-name: usage
      summary: Usage meter totals
      description: >-
        Token, tool-call, sandbox-compute, and published-agent ingress meter totals for
        the active tenant over a window: in aggregate (`totals`), per profile
        (`byProfile`), and per published agent (`byPublishedAgent`, top by
        request count). Tool-call/sandbox meters are recorded on every run
        finish; `totals.ingressRequests` counts accepted public requests the
        chat-gateway dispatched to published agents. These are not carried by the
        token/run stats endpoints, so this is their read surface.
        `available=false` distinguishes an unavailable usage store/read from a
        verified zero. `window=all` returns all durable history; other windows
        preserve the existing look-back behavior.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: window
          in: query
          required: false
          description: 'Look-back window, e.g. 1h, 24h, 7d, 30d, or all. Defaults to 1h.'
          schema:
            type: string
        - name: profiles
          in: query
          required: false
          description: Comma-separated profile names to aggregate (up to 500).
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Maximum profile rows when `profiles` is omitted.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        '200':
          description: Aggregate meters and per-profile finalized token totals.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageResponse'

  /api/usage/tokens:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getUsageTokens
      x-fern-sdk-method-name: usageTokens
      summary: Get batched all-time profile token totals
      description: >-
        Polling-friendly token-only aggregation for an explicit bounded profile
        list supplied as repeated `profiles` query parameters. Executes only
        the tenant-scoped durable kind=run token query and
        returns one row per distinct exact profile in first-occurrence request
        order, including explicit zero rows. Duplicate values are coalesced
        byte-for-byte without trimming or splitting. It does not read tool-call, sandbox, ingress, or
        published-agent meters. `available=false` distinguishes an unavailable
        store/read from verified zero usage.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: profiles
          in: query
          required: true
          description: Profile names to aggregate, repeated 1 to 500 times. Values are exact and may contain commas or whitespace; duplicate values are coalesced in first-occurrence order.
          style: form
          explode: true
          schema:
            type: array
            minItems: 1
            maxItems: 500
            items:
              type: string
              minLength: 1
      responses:
        '200':
          description: Batched finalized all-time token totals.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageTokensResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/usage/sessions:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getUsageSessions
      x-fern-sdk-method-name: usageSessions
      summary: Get batched all-time session token totals
      description: >-
        Polling-friendly token-only aggregation for explicit profile/session
        pairs. Repeated `profiles` and `sessions` parameters are matched by
        position and executed as one tenant-scoped durable kind=run query.
        Exact duplicate pairs are coalesced in first-occurrence order and
        missing pairs return explicit zero rows. `available=false`
        distinguishes an unavailable store/read from verified zero usage.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: profiles
          in: query
          required: true
          description: Profile side of each positional pair, repeated 1 to 500 times.
          style: form
          explode: true
          schema:
            type: array
            minItems: 1
            maxItems: 500
            items:
              type: string
              minLength: 1
        - name: sessions
          in: query
          required: true
          description: Session side of each positional pair, repeated exactly as many times as profiles.
          style: form
          explode: true
          schema:
            type: array
            minItems: 1
            maxItems: 500
            items:
              type: string
              minLength: 1
      responses:
        '200':
          description: Batched finalized all-time token totals for the requested profile/session pairs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionUsageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/profiles/{name}/skills/{skill}:
    post:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: attachSkillToProfile
      x-fern-sdk-method-name: attachSkill
      summary: Attach a skill to a profile (idempotent)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: skill
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/Error'
    delete:
      tags: [Profiles]
      x-fern-sdk-group-name: profiles
      operationId: detachSkillFromProfile
      x-fern-sdk-method-name: detachSkill
      summary: Detach a skill from a profile (idempotent)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: skill
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/Error'

  /api/published:
    get:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: listPublishedAgents
      x-fern-sdk-method-name: list
      summary: List active published agents for the tenant
      description: |
        Returns every published_agents row that is currently active
        (not soft-deleted). Each row carries the derived `publicUrl`
        when `CHAT_GATEWAY_PUBLIC_HOST` is wired; the field is omitted
        otherwise.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: All active published rows for the tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishedAgentList'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/metrics:
    get:
      tags: [Stats]
      x-fern-sdk-group-name: stats
      operationId: getPublishedMetrics
      x-fern-sdk-method-name: publishedMetrics
      summary: Per-published-agent metrics timeseries
      description: >-
        Metrics for the published agent behind a profile over a window: totals
        (accepted ingress requests, finished runs, conversations started, cost
        in USD cents, token lanes) plus StatsBucket-shaped timeseries buckets so
        the dashboard chart renders them directly. 404 when the profile is not
        published. sandboxSeconds and failedRuns are 0 (not attributable to a
        single published agent).
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: window
          in: query
          required: false
          description: 'Look-back window, e.g. 1h, 24h, 7d, 30d. Defaults to 1h.'
          schema:
            type: string
      responses:
        '200':
          description: Totals and per-bucket metrics for the published agent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/published:
    get:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: getPublishedAgent
      x-fern-sdk-method-name: get
      summary: Fetch the active published row for a profile
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The active published row.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishedAgentWithURL'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    patch:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: updatePublishedAgent
      x-fern-sdk-method-name: update
      summary: Partial update over the active published row
      description: |
        Slug is intentionally not patchable; slug rotation requires
        unpublish + republish. All other fields are optional and only
        the supplied ones are persisted.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublishUpdateRequest'
      responses:
        '200':
          description: The updated row.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishedAgentWithURL'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: unpublishAgent
      x-fern-sdk-method-name: unpublish
      summary: Soft-delete the active published row
      description: |
        Sets `unpublished_at = now()` and `enabled = false`. The slug
        is released for re-use immediately; republishing under the
        same slug creates a fresh row with a new id.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/publish:
    post:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: publishAgent
      x-fern-sdk-method-name: publish
      summary: Publish a profile (create the published_agents row)
      description: |
        Every body field is optional. When `slug` is omitted, the slug
        is derived from the profile name by lowercasing and replacing
        runs of non-`[a-z0-9]` with `-`. When the derived value fails
        slug validation, returns 400 `profile_name_not_slug_safe` and
        the caller must supply `slug` explicitly.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublishRequest'
      responses:
        '201':
          description: Created (row + derived publicUrl).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishedAgentWithURL'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Slug already in use for this tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/keys:
    get:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: listAgentKeys
      x-fern-sdk-method-name: listKeys
      summary: List API keys for the published agent
      description: |
        Returns every key (active and revoked) belonging to the
        active published row. The hash is never included in the
        response.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Key metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyList'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: issueAgentKey
      x-fern-sdk-method-name: issueKey
      summary: Mint a new API key (plaintext token returned once)
      description: |
        The plaintext token (`ao_<env>_<random>`) is returned in the
        `token` field. This is the only call that ever returns it; the
        server stores only HMAC-SHA256(token, pepper). Subsequent GETs
        list metadata without the token.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueKeyRequest'
      responses:
        '201':
          description: Key issued — plaintext token shown once.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyIssued'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/profiles/{name}/keys/{id}:
    delete:
      tags: [Publishing]
      x-fern-sdk-group-name: publishing
      operationId: revokeAgentKey
      x-fern-sdk-method-name: revokeKey
      summary: Revoke an API key (idempotent)
      description: |
        Sets `revoked_at = now()` on the row. Returns 404 when the key
        does not belong to the named profile (or does not exist) so
        cross-profile probes cannot enumerate ids.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/api-keys:
    get:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: listApiKeys
      x-fern-sdk-method-name: list
      summary: List the caller's tenant API keys
      description: |
        Returns the caller's control-plane API keys. Admins and owners
        see every key in the tenant; members see only their own. Tokens
        and hashes are never included.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Key metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantAPIKeyList'
        '403':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: issueApiKey
      x-fern-sdk-method-name: issue
      summary: Mint a tenant API key (plaintext token returned once)
      description: |
        Mints a durable, tenant-scoped control-plane API key
        (`ao_<env>_<random>`) for tenant-to-server access (SDKs, CI). The
        key inherits the caller's RBAC role. The plaintext token is
        returned once in the `token` field; the server stores only
        HMAC-SHA256(token, pepper).
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueTenantKeyRequest'
      responses:
        '201':
          description: Key issued — plaintext token shown once.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantAPIKeyIssued'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/api-keys/{id}:
    delete:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: revokeApiKey
      x-fern-sdk-method-name: revoke
      summary: Revoke a tenant API key
      description: |
        Revokes one key. Members can revoke only their own keys;
        admins and owners can revoke any key in the tenant.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/device/code:
    post:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: startDeviceAuthorization
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: cli-login-plumbing
      x-fern-sdk-method-name: startDeviceAuthorization
      security: []
      summary: Start an RFC 8628 device-code login (unauthenticated)
      description: |
        Starts a headless CLI login (`orca login` from a terminal or coding
        agent with no browser). Unauthenticated BY DESIGN: the caller has no
        credential yet. Returns a high-entropy `device_code` (the CLI's poll
        secret, stored server-side only as a SHA-256 digest), a human-typable
        `user_code`, and the dashboard verification URI where a signed-in
        user approves or denies the request. Metered by a dedicated per-IP
        rate tier. Not part of the public SDK surface.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceCodeRequest'
      responses:
        '200':
          description: Device authorization started (RFC 8628 section 3.2).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceCodeResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/device/token:
    post:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: pollDeviceToken
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: cli-login-plumbing
      x-fern-sdk-method-name: pollDeviceToken
      security: []
      summary: Poll a device authorization for its token (unauthenticated)
      description: |
        The CLI polls this with its `device_code` until the user decides.
        Answers follow RFC 8628 section 3.5: HTTP 400 with a bare
        `{"error": "<code>"}` body where code is `authorization_pending`,
        `slow_down` (poll interval violated), `expired_token` (unknown,
        expired, or already-consumed handshake -- deliberately
        indistinguishable), or `access_denied`. On approval the response is
        200 with a freshly minted tenant API key; the mint and the
        handshake consumption commit in one transaction, so exactly one
        poll ever receives a token. Not part of the public SDK surface.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceTokenRequest'
      responses:
        '200':
          description: Approved -- tenant API key issued (shown once).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceTokenResponse'
        '400':
          description: RFC 8628 flow answer (pending, slow_down, expired, denied) or malformed request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceTokenError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/device/authorization:
    get:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: getDeviceAuthorization
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: cli-login-plumbing
      x-fern-sdk-method-name: getDeviceAuthorization
      summary: Look up a pending device authorization by user code
      description: |
        Powers the dashboard confirm screen. Resolves a user code
        (case/hyphen tolerant) to its pending, unexpired authorization so
        the approver can see what they are granting. 404 for unknown,
        expired, or already-decided codes. Requires a signed-in member+.
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: user_code
          in: query
          required: true
          schema:
            type: string
          description: The human-typable code shown by the CLI (XXXX-XXXX).
      responses:
        '200':
          description: Pending authorization details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceAuthorizationView'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/device/approve:
    post:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: approveDeviceAuthorization
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: cli-login-plumbing
      x-fern-sdk-method-name: approveDeviceAuthorization
      summary: Approve a pending device authorization
      description: |
        Approves a pending device login as the signed-in principal. The key
        minted at the CLI's next poll inherits the APPROVER's role and
        tenant, mirroring POST /api/api-keys. Same member+ gate as minting
        a key directly, because approval is exactly that. 404 for unknown,
        expired, or already-decided codes; approval is single-shot.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceDecisionRequest'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '403':
          $ref: '#/components/responses/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/device/deny:
    post:
      tags: [ApiKeys]
      x-fern-sdk-group-name: apiKeys
      operationId: denyDeviceAuthorization
      x-agent-orc-sdk: false
      x-agent-orc-internal-reason: cli-login-plumbing
      x-fern-sdk-method-name: denyDeviceAuthorization
      summary: Deny a pending device authorization
      description: |
        Rejects a pending device login. The CLI's next poll receives
        `access_denied` (once; the handshake is then spent). 404 for
        unknown, expired, or already-decided codes.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceDecisionRequest'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/whoami:
    get:
      tags: [Misc]
      x-fern-sdk-group-name: misc
      operationId: whoami
      x-fern-sdk-method-name: whoami
      summary: Identify the authenticated caller
      description: |
        Returns who the current credential acts as: the tenant (id and,
        when available, display name), the caller's RBAC role, the kind of
        credential that authenticated the request (`api_key`, `session`,
        or `run_token`), and the key id for API-key callers (the handle
        for later revocation). This is the only route that answers "which
        tenant am I?" for a bearer key, so CLIs, SDKs, and agents use it
        as their first call after login.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: Caller identity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WhoamiResponse'
        '403':
          $ref: '#/components/responses/Error'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Tenant-scoped API key as a bearer token (format `ao_<env>_<random>`),
        minted in the dashboard under Settings -> API Keys. The key inherits
        the minter's role. Send as `Authorization: Bearer <token>`.
      x-fern-bearer:
        name: token
        env: AGENT_ORC_API_KEY
  parameters:
    TenantId:
      name: X-Tenant-ID
      in: header
      required: false
      schema:
        type: string
        default: default
      description: Tenant slug. Omit for the `default` tenant.
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
    Object:
      type: object
      additionalProperties: true

    CapabilityDiscoverRequest:
      type: object
      additionalProperties: false
      required: [intent]
      properties:
        intent:
          type: string
          minLength: 1
          maxLength: 1000
        limit:
          type: integer
          minimum: 1
          maximum: 5

    CapabilityDiscoverResponse:
      type: object
      required: [queryId, matches]
      properties:
        queryId:
          type: string
        matches:
          type: array
          maxItems: 5
          items:
            $ref: '#/components/schemas/CapabilityActionCard'

    CapabilityActionCard:
      type: object
      required: [actionId, action, description, provider, risk, confidence, expiresAt]
      properties:
        actionId:
          type: string
          pattern: '^act_[A-Za-z0-9_-]{32,}$'
        action:
          type: string
        description:
          type: string
        provider:
          type: string
        risk:
          $ref: '#/components/schemas/CapabilityRisk'
        confidence:
          type: number
          format: double
          minimum: 0
          maximum: 1
        params:
          type: array
          items:
            $ref: '#/components/schemas/CapabilityParamCard'
        expiresAt:
          type: string
          format: date-time

    CapabilityParamCard:
      type: object
      required: [name, type, required, description]
      properties:
        name:
          type: string
        type:
          type: string
          enum: [string, integer, number, boolean, object, array]
        required:
          type: boolean
        description:
          type: string
        enum:
          type: array
          maxItems: 20
          items:
            type: string
        default: {}

    CapabilityRunRequest:
      type: object
      additionalProperties: false
      properties:
        actionId:
          type: string
          description: Optional compatibility echo; when present it must match the route actionId.
          pattern: '^act_[A-Za-z0-9_-]{32,}$'
        params:
          type: object
          additionalProperties: true

    CapabilityDecisionRequest:
      type: object
      additionalProperties: false
      required: [decision]
      properties:
        decision:
          type: string
          enum: [approve, deny]

    CapabilityRunResponse:
      type: object
      required: [status, isError]
      properties:
        operationId:
          type: string
          description: Conductor-issued operation identifier used to correlate spans, confirmations, and durable decisions.
        status:
          type: string
          enum: [succeeded, needs_input, needs_confirmation, not_found, denied, provider_error, temporarily_unavailable]
        action:
          type: string
        provider:
          type: string
        content:
          type: string
        structuredContent: {}
        missing:
          type: array
          items:
            $ref: '#/components/schemas/CapabilityParamCard'
        confirmation:
          $ref: '#/components/schemas/CapabilityConfirmCard'
        isError:
          type: boolean

    CapabilityConfirmCard:
      type: object
      required: [challengeId, action, provider, risk, expiresAt]
      properties:
        challengeId:
          type: string
        action:
          type: string
        provider:
          type: string
        risk:
          $ref: '#/components/schemas/CapabilityRisk'
        paramSummary:
          type: array
          items:
            $ref: '#/components/schemas/CapabilityParamSummary'
        expiresAt:
          type: string
          format: date-time

    CapabilityParamSummary:
      type: object
      required: [name, value]
      properties:
        name:
          type: string
        value:
          type: string

    CapabilityRisk:
      type: string
      enum: [read, reversible_write, destructive, financial, publish, unknown]

    BillingPack:
      type: object
      description: One purchasable credit top-up.
      required: [cents, usd]
      properties:
        cents:
          type: integer
          description: Pack price in USD cents.
        usd:
          type: number
          format: double

    BillingWallet:
      type: object
      description: The tenant's credit wallet as the dashboard sees it.
      required: [configured, balanceMicroUSD, balanceUSD, packs]
      properties:
        configured:
          type: boolean
          description: False when the tenant has no wallet yet (no credits granted).
        balanceMicroUSD:
          type: number
          format: double
        balanceUSD:
          type: number
          format: double
        packs:
          type: array
          items:
            $ref: '#/components/schemas/BillingPack'

    BillingCheckoutRequest:
      type: object
      required: [cents]
      properties:
        cents:
          type: integer
          description: A known pack amount in USD cents.

    BillingCheckoutResponse:
      type: object
      required: [url]
      properties:
        url:
          type: string
          description: Polar checkout URL to redirect the browser to.

    RunnerTopology:
      type: object
      description: |
        One runner's live state, as returned to a tenant. This is a stripped
        view of the conductor's internal pool state: the runner's base URL is
        an internal routing detail and is never included here. The
        internal/admin plane (/internal/topology/*) carries the full shape
        with the URL for operator tooling.
      required: [hash, healthy, activeSessions]
      properties:
        hash:
          type: string
          description: Stable identifier for the runner in the pool.
        healthy:
          type: boolean
        latencyMs:
          type: integer
          format: int64
          description: Last observed health-probe latency in milliseconds.
        activeSessions:
          type: integer
        error:
          type: string
          description: |
            Last health-probe error, when unhealthy. The runner's address is
            redacted from this message (both the full URL and the bare
            host:port a transport error embeds); only the failure reason
            survives.
        capabilities:
          type: array
          items:
            type: string
          description: |
            Runtimes this runner advertises. Absent means it advertised none,
            which by back-compat contract means it serves all runtimes. Read
            together with capabilitiesKnown: absent-and-known is "serves all",
            absent-and-unknown is "never probed".
        state:
          type: string
          enum: [live, draining, unreachable]
          description: |
            Membership lifecycle state. Distinct from `healthy`, which is a
            point-in-time probe: a draining runner is usually healthy but is
            deliberately excluded from new sessions while continuing to serve
            the sessions it already owns.
        capabilitiesKnown:
          type: boolean
          description: |
            Whether capabilities were ever successfully read. False means the
            runner has never answered a probe, which is why it is excluded
            from new sessions. Not the same as an empty capability list.
        lastSeenAt:
          type: string
          format: date-time
          nullable: true
          description: Last successful probe, null if never probed.
        consecutiveFailures:
          type: integer
        process:
          $ref: '#/components/schemas/RunnerProcess'
        sessionsByRuntime:
          type: object
          nullable: true
          additionalProperties:
            type: integer
          description: |
            Live sessions on this runner per runtime, across tenants. Null
            when the runner did not answer GET /runner/topology.
        sidecars:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/SidecarTopology'
          description: |
            The agent-worker layer beneath this runner. Null when the runner
            did not answer; an empty array is a real answer meaning no slots.

    RunnerProcess:
      type: object
      nullable: true
      description: |
        The runner's own Go process footprint. Null when the runner did not
        answer GET /runner/topology; consumers must render that as unknown
        rather than zero.
      properties:
        allocBytes:
          type: integer
          format: int64
        sysBytes:
          type: integer
          format: int64
        heapInuseBytes:
          type: integer
          format: int64
        numGoroutine:
          type: integer
        numCPU:
          type: integer
        uptimeSeconds:
          type: integer
          format: int64

    SidecarTopology:
      type: object
      description: One runtime slot on a runner and the workers observed behind it.
      required: [runtime, mode, state, activeSessions, observedInstances]
      properties:
        runtime:
          type: string
          description: pi, claude, codex, or vercel.
        mode:
          type: string
          enum: [sidecar, stub]
          description: |
            `sidecar` means the slot is bound to an agent-worker base URL.
            `stub` means an in-process adapter serves it and there is no
            worker process to reach. The base URL itself (an internal routing
            detail) is not included in this tenant-facing view.
        state:
          type: string
          enum: [stub, live, cold, degraded, unreachable]
          description: |
            `cold` means configured but with no recent traffic and no
            successful probe. It is NOT a fault: it is what a scaled-to-zero
            sidecar looks like, and must not be treated as `unreachable`.
        activeSessions:
          type: integer
          description: Live sessions on this runner bound to this runtime.
        lastError:
          type: string
          description: |
            Last probe or transport failure for this slot. The sidecar's
            endpoint is redacted from this message (both the full URL and the
            bare host:port form); only the failure reason survives.
        observedInstances:
          type: array
          items:
            $ref: '#/components/schemas/SidecarInstance'
          description: |
            A LOWER BOUND on the agent-worker replicas behind this slot, not
            a replica count. The runner pools connections and a load balancer
            balances per connection, so an idle replica can stay invisible for
            a long time. Slots sharing a sidecar process report the same
            instances because they are served by the same process, so
            summing this across slots multiply-counts a poly sidecar.

    SidecarInstance:
      type: object
      description: One observed agent-worker process, as returned to a tenant.
      required: [instanceId, firstSeenAt, lastSeenAt, runsServed]
      properties:
        instanceId:
          type: string
          description: |
            The distinguishing suffix of the process's per-process identity
            (the part after the last "-"). The full id has the shape
            "<hostname>-<random>"; the hostname prefix names the underlying
            infrastructure and is stripped before this reaches a tenant, so
            only the random suffix that actually distinguishes one worker
            from another on the same host survives. Deliberately not stable
            across restarts: a restarted process is a new instance.
        firstSeenAt:
          type: string
          format: date-time
        lastSeenAt:
          type: string
          format: date-time
        runsServed:
          type: integer
          format: int64
        consecutiveFailures:
          type: integer
        lastError:
          type: string
          description: |
            Last run error reported by this instance. The sidecar's endpoint
            is redacted from this message the same way as the parent slot's
            `lastError`.
        lastProbedAt:
          type: string
          format: date-time
          nullable: true
          description: |
            Null when this instance was discovered through run traffic but has
            never answered a health probe. The stats fields below are null in
            that case and must be rendered as unknown, never as zero.
        inflight:
          type: integer
          nullable: true
        uptimeSeconds:
          type: integer
          format: int64
          nullable: true
        memory:
          type: object
          nullable: true
          properties:
            rssBytes:
              type: integer
              format: int64
            heapUsedBytes:
              type: integer
              format: int64
            heapTotalBytes:
              type: integer
              format: int64
            externalBytes:
              type: integer
              format: int64
        cpu:
          type: object
          nullable: true
          properties:
            userMicros:
              type: integer
              format: int64
            systemMicros:
              type: integer
              format: int64

    Profile:
      type: object
      required: [name, runtime]
      description: |
        Reusable agent definition. Binds a runtime (pi, claude, codex, ...) to a
        system prompt, skills, tools, and an optional sandbox/FS policy.
      properties:
        id:
          type: string
          description: Auto-assigned on registration ("agent-<hex>") when empty.
        name:
          type: string
        runtime:
          type: string
          description: Runtime label (e.g. pi, claude, codex).
        systemPrompt:
          type: string
        skills:
          type: array
          items:
            type: string
        tools:
          type: array
          items:
            type: string
          description: |
            Tool selectors. Names, globs ("web.*"), or @-sentinels
            (@default, @fs, @pool, @orchestration, @artifacts).
        mcpServers:
          type: array
          items:
            $ref: '#/components/schemas/MCPServerSpec'
        model:
          type: string
        fs:
          $ref: '#/components/schemas/FSPolicy'
        sandbox:
          $ref: '#/components/schemas/SandboxSpec'
        workerMode:
          type: string
          enum: ['', static, sandbox]
          description: |
            "" or "static" (default) dials one of the runner's fixed
            agent-worker sidecars, unchanged from before this field
            existed. "sandbox" boots a worker per session in a sandbox
            that calls back into the runner instead of being dialed.
        workerSubstrate:
          type: string
          enum: ['', e2b, daytona, docker, process]
          description: |
            Where a workerMode="sandbox" worker boots. Omitted inherits
            sandbox.provider when the runner serves it, and otherwise falls
            back to the runner's own default.

            A superset of the sandbox providers, because a worker can also
            run as a plain container or a local process. NOT validated at
            save time: which substrates exist is a property of the runner,
            so a profile naming one the runner does not serve fails the
            session's first run with an error listing what it does serve.

            An INHERITED value behaves differently from an explicit one: it
            is only a hint, so a runner that does not serve it uses its
            default instead of failing a profile nobody edited.

            Ignored entirely when workerMode is static.
        workerImage:
          type: string
          description: |
            Overrides the runner's worker image for this profile: a template
            id on e2b, a snapshot name on daytona, a container reference on
            docker.

            Substrate-scoped by nature, so setting it while letting
            workerSubstrate fall back to the runner default is how an image
            built for one provider gets handed to another. Set both or
            neither.
        workerPlacement:
          $ref: '#/components/schemas/WorkerPlacement'

    WorkerPlacement:
      type: object
      required: [source, servable]
      description: |
        COMPUTED on read, IGNORED on write: where this agent's worker is
        actually expected to run, resolved against the live fleet.

        It exists because the answer is not readable off the profile. An agent
        with a shell on e2b and no workerSubstrate looks like an e2b agent and
        runs its worker on whatever the fleet defaults to.

        Never persisted. Storing it would freeze a fleet-dependent value into
        the profile: add an e2b runner later and a stored "daytona" would keep
        asserting daytona forever.

        Absent entirely for a static profile, which boots no worker.
      properties:
        substrate:
          type: string
          description: |
            Where the worker is expected to run. Absent when the fleet cannot
            answer, or when live runners default to different substrates.
        source:
          type: string
          enum: [explicit, shell, default, unknown]
          description: |
            Why: `explicit` (the profile named it), `shell` (inherited from
            sandbox.provider), `default` (the runner default applies), or
            `unknown` (no runner has reported its substrates).
        servable:
          type: boolean
          description: |
            Whether any live runner can actually launch it. False always
            carries a detail explaining what happens instead.
        requested:
          type: string
          description: |
            What the agent asked for, when that differs from substrate. This
            is what turns "runs on daytona" into "asked for e2b, got daytona".
        detail:
          type: string
          description: One sentence for a human, present whenever the answer is not a plainly honoured request.

    ProfileList:
      type: array
      items:
        $ref: '#/components/schemas/Profile'

    MCPServerSpec:
      type: object
      required: [name]
      properties:
        name:
          type: string
        transport:
          type: string
          description: '"http" or "sse".'
        url:
          type: string
        ref:
          type: string
          description: |
            Safe catalog reference in the form "catalog://<name>". Reference
            entries omit transport, URL, and headers and execute through the
            connected-app bridge instead of the worker's native MCP transport.
        headers:
          type: object
          additionalProperties:
            type: string
          description: |
            Header values support literal strings, "${VAR}" env expansion,
            or "secret://name" references resolved via the secrets manager.

    FSPolicy:
      type: object
      description: |
        Per-profile filesystem access policy. Glob patterns may use
        `*`, `**`, and the tokens `{self}`, `{pool}`, `{role}`. The
        agent's home (`/agents/{self}/**`) is always granted implicitly.
      properties:
        read:
          type: array
          items:
            type: string
        write:
          type: array
          items:
            type: string
        delete:
          type: array
          items:
            type: string
        deny:
          type: array
          items:
            type: string

    SandboxSpec:
      type: object
      required: [provider]
      properties:
        provider:
          type: string
        template:
          type: string
        resources:
          $ref: '#/components/schemas/SandboxResources'
        env:
          type: object
          additionalProperties:
            type: string
        idleTimeout:
          type: integer
          description: Idle TTL in nanoseconds (Go time.Duration).

    SandboxResources:
      type: object
      properties:
        cpu:
          type: integer
          description: Millicores.
        memoryMB:
          type: integer
        diskMB:
          type: integer
        timeout:
          type: integer
          description: Wall-clock cap in nanoseconds (Go time.Duration).

    ProfileChange:
      type: object
      required: [id, profile, action, at]
      properties:
        id:
          type: string
        profile:
          type: string
        action:
          type: string
          description: '"created", "updated", or "deleted".'
        at:
          type: string
          format: date-time
        fields:
          type: array
          items:
            type: string
        before:
          $ref: '#/components/schemas/Profile'
        after:
          $ref: '#/components/schemas/Profile'

    ProfileChangeList:
      type: array
      items:
        $ref: '#/components/schemas/ProfileChange'

    Pool:
      type: object
      required: [name, members]
      description: |
        Named group of profiles that share an FS workspace under
        `/pools/{name}/**`. Layered on top of each member's profile
        sandbox; never replaces or shrinks per-profile access.
      properties:
        id:
          type: string
          description: Auto-assigned on registration ("pool-<hex>") when empty.
        name:
          type: string
        description:
          type: string
        members:
          type: array
          items:
            $ref: '#/components/schemas/PoolMember'
        fs:
          $ref: '#/components/schemas/PoolFSPolicy'

    PoolMember:
      type: object
      required: [profile]
      properties:
        profile:
          type: string
        role:
          type: string
          description: 'lead | member | observer (defaults to member).'

    PoolFSPolicy:
      type: object
      description: |
        Extra FS glob lists contributed by the pool to every member's
        effective policy. Read/Write/Delete are additive; Deny subtracts.
      properties:
        read:
          type: array
          items:
            type: string
        write:
          type: array
          items:
            type: string
        delete:
          type: array
          items:
            type: string
        deny:
          type: array
          items:
            type: string

    PoolList:
      type: array
      items:
        $ref: '#/components/schemas/Pool'

    Session:
      type: object
      required: [id, profile, runtime, status, createdAt, lastUsedAt, runCount]
      description: |
        Live agent instance bound to a profile. The lastRun* / lastPrompt
        fields are populated from the run registry so callers (especially
        orchestrators) can decide whether an existing session matches the
        current episode before spawning a new one.
      properties:
        id:
          type: string
        profile:
          type: string
        runtime:
          type: string
        status:
          type: string
          description: 'idle | running | errored | shutdown.'
        createdAt:
          type: string
          format: date-time
        lastUsedAt:
          type: string
          format: date-time
        lastPrompt:
          type: string
        lastRunStartedAt:
          type: string
          format: date-time
        lastRunFinishedAt:
          type: string
          format: date-time
        lastRunStatus:
          type: string
        runCount:
          type: integer

    SessionList:
      type: array
      items:
        $ref: '#/components/schemas/Session'

    SubTask:
      type: object
      required: [id, profile, sessionId, title]
      description: Unit of work the planner emits and an AgentRuntime consumes.
      properties:
        id:
          type: string
        parentId:
          type: string
        profile:
          type: string
        sessionId:
          type: string
        title:
          type: string
        prompt:
          type: string
        files:
          type: array
          items:
            type: string

    RunSummary:
      type: object
      required: [id, subTask, status, startedAt]
      properties:
        id:
          type: string
        subTask:
          $ref: '#/components/schemas/SubTask'
        status:
          type: string
          description: 'running | ok | error | cancelled.'
        startedAt:
          type: string
          format: date-time
        finishedAt:
          type: string
          format: date-time
        usage:
          description: >-
            Observed token-and-cache accounting. Omitted when no usage event
            was observed; an observed all-zero event is a present non-null
            object.
          $ref: '#/components/schemas/Usage'

    RunSummaryList:
      type: array
      items:
        $ref: '#/components/schemas/RunSummary'

    RunCreateRequest:
      allOf:
        - $ref: '#/components/schemas/SubTask'
      description: |
        SubTask body for POST /api/runs. `profile` is required; `sessionId`
        and `id` are minted server-side when omitted.

    Usage:
      type: object
      description: Token-and-cache accounting reported by the underlying SDK.
      properties:
        inputTokens:
          type: integer
        outputTokens:
          type: integer
        cacheReadTokens:
          type: integer
        cacheCreateTokens:
          type: integer

    UsageTokenTotals:
      type: object
      required: [inputTokens, outputTokens, cacheReadTokens, cacheCreateTokens, runCount]
      description: Finalized token totals from durable finished-run usage.
      properties:
        inputTokens:
          type: integer
          format: int64
          minimum: 0
        outputTokens:
          type: integer
          format: int64
          minimum: 0
        cacheReadTokens:
          type: integer
          format: int64
          minimum: 0
        cacheCreateTokens:
          type: integer
          format: int64
          minimum: 0
        runCount:
          type: integer
          format: int64
          minimum: 0

    ProfileSessionUsage:
      allOf:
        - $ref: '#/components/schemas/UsageTokenTotals'
        - type: object
          required: [sessionId]
          properties:
            sessionId:
              type: string

    ProfileUsageResponse:
      type: object
      required: [available, totals, sessions]
      properties:
        available:
          type: boolean
          description: False when durable usage could not be read; true makes zero totals authoritative.
        totals:
          $ref: '#/components/schemas/UsageTokenTotals'
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/ProfileSessionUsage'

    SessionUsageRow:
      allOf:
        - $ref: '#/components/schemas/UsageTokenTotals'
        - type: object
          required: [profile, sessionId]
          properties:
            profile:
              type: string
            sessionId:
              type: string

    SessionUsageResponse:
      type: object
      required: [available, sessions]
      properties:
        available:
          type: boolean
          description: False when durable usage could not be read; true makes zero totals authoritative.
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/SessionUsageRow'

    UsageMeterTotals:
      type: object
      required: [toolCalls, sandboxSeconds, ingressRequests]
      properties:
        toolCalls:
          type: integer
          format: int64
          minimum: 0
        sandboxSeconds:
          type: number
          format: double
          minimum: 0
        ingressRequests:
          type: integer
          format: int64
          minimum: 0

    UsageProfileTotals:
      type: object
      required: [profile, toolCalls, sandboxSeconds, inputTokens, outputTokens, cacheReadTokens, cacheCreateTokens]
      properties:
        profile:
          type: string
        toolCalls:
          type: integer
          format: int64
          minimum: 0
        sandboxSeconds:
          type: number
          format: double
          minimum: 0
        inputTokens:
          type: integer
          format: int64
          minimum: 0
        outputTokens:
          type: integer
          format: int64
          minimum: 0
        cacheReadTokens:
          type: integer
          format: int64
          minimum: 0
        cacheCreateTokens:
          type: integer
          format: int64
          minimum: 0

    UsageProfileTokenTotals:
      type: object
      required: [profile, inputTokens, outputTokens, cacheReadTokens, cacheCreateTokens]
      properties:
        profile:
          type: string
        inputTokens:
          type: integer
          format: int64
          minimum: 0
        outputTokens:
          type: integer
          format: int64
          minimum: 0
        cacheReadTokens:
          type: integer
          format: int64
          minimum: 0
        cacheCreateTokens:
          type: integer
          format: int64
          minimum: 0

    UsageTokensResponse:
      type: object
      required: [available, byProfile]
      properties:
        available:
          type: boolean
          description: False when durable usage could not be read; true makes zero totals authoritative.
        byProfile:
          type: array
          items:
            $ref: '#/components/schemas/UsageProfileTokenTotals'

    UsagePublishedAgent:
      type: object
      required: [publishedId, slug, requests]
      properties:
        publishedId:
          type: string
        slug:
          type: string
        requests:
          type: integer
          format: int64
          minimum: 0

    UsageResponse:
      type: object
      required: [available, window, totals, byProfile, byPublishedAgent]
      properties:
        available:
          type: boolean
          description: False when durable usage could not be read; true makes zero totals authoritative.
        window:
          type: string
        totals:
          $ref: '#/components/schemas/UsageMeterTotals'
        byProfile:
          type: array
          items:
            $ref: '#/components/schemas/UsageProfileTotals'
        byPublishedAgent:
          type: array
          items:
            $ref: '#/components/schemas/UsagePublishedAgent'

    Event:
      type: object
      required: [type]
      description: |
        Single event in a run's stream. `type` selects which fields apply:
          - progress / result / error / assistant: `message`.
          - tool_call: `toolCallId`, `toolName`, `input`.
          - tool_result: `toolCallId`, `output`, `isError`.
          - usage: `usage`.
      properties:
        type:
          type: string
          description: 'progress | result | error | assistant | tool_call | tool_result | usage.'
        message:
          type: string
        ts:
          type: string
          format: date-time
        toolCallId:
          type: string
        toolName:
          type: string
        input:
          $ref: '#/components/schemas/Object'
        output:
          $ref: '#/components/schemas/Object'
        isError:
          type: boolean
        usage:
          $ref: '#/components/schemas/Usage'

    TerminateRunResult:
      type: object
      required: [runId, cancelled, sessionReleased, claimStolen, statusWritten]
      properties:
        runId:
          type: string
        cancelled:
          type: boolean
          description: |
            This conductor held the run's cancel handle and invoked it. False
            means the run was not in its registry (restarted since, or another
            replica owns it); the session and status steps still ran.
        sessionReleased:
          type: boolean
          description: |
            The session's run claim is no longer held, so the session accepts
            runs again — whether the run dropped it itself or it was stolen.
            See claimStolen to tell those apart.
        claimStolen:
          type: boolean
          description: |
            The claim had to be forcibly taken because the run would not drop
            it in time. A healthy cancelled run releases on its own and
            reports false; true means something was genuinely wedged.
        statusWritten:
          type: boolean
          description: |
            This call flipped the durable run row to 'cancelled'. False when
            the row was already terminal or no database is wired.
        sessionId:
          type: string
        warnings:
          type: array
          description: |
            Per-step failures. Terminate is best-effort by design: one failed
            step never blocks the others.
          items:
            type: string

    RunDetail:
      allOf:
        - $ref: '#/components/schemas/RunSummary'
        - type: object
          required: [events]
          properties:
            events:
              type: array
              items:
                $ref: '#/components/schemas/Event'

    PlanNode:
      type: object
      required: [id, title, profile, promptTemplate, status, attempt]
      properties:
        id:
          type: string
        title:
          type: string
        profile:
          type: string
        sessionId:
          type: string
        promptTemplate:
          type: string
        dependsOn:
          type: array
          items:
            type: string
        outputSchema:
          $ref: '#/components/schemas/Object'
          description: Optional JSON Schema used to parse structured output.
        status:
          type: integer
          description: |
            NodeStatus enum value (pending=0, ready=1, running=2, ok=3,
            error=4, skipped=5, cancelled=6).
        runId:
          type: string
        output:
          type: string
        fields:
          $ref: '#/components/schemas/Object'
          description: Structured fields parsed via outputSchema, if any.
        error:
          type: string
        attempt:
          type: integer
        startedAt:
          type: string
          format: date-time
        finishedAt:
          type: string
          format: date-time

    Plan:
      type: object
      required: [id, userPrompt, status, autoStart, nodes, createdAt, repairCount]
      properties:
        id:
          type: string
        userPrompt:
          type: string
        orchestratorRunId:
          type: string
        orchestratorSessionId:
          type: string
        status:
          type: integer
          description: |
            PlanStatus enum value (pending=0, running=1, paused=2,
            completed=3, failed=4, cancelled=5).
        autoStart:
          type: boolean
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/PlanNode'
        createdAt:
          type: string
          format: date-time
        startedAt:
          type: string
          format: date-time
        finishedAt:
          type: string
          format: date-time
        repairCount:
          type: integer
        deadline:
          type: string
          format: date-time
          description: Absolute wall-clock cutoff. Zero (omitted) means no overall timeout.
        delegationDepth:
          type: integer

    PlanList:
      type: array
      items:
        $ref: '#/components/schemas/Plan'

    PlanCreateRequest:
      type: object
      required: [userPrompt, nodes]
      properties:
        userPrompt:
          type: string
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/PlanNode'
          description: Inbound `status` fields are ignored — the engine drives transitions.
        autoStart:
          type: boolean
          description: Defaults to true.
        orchestratorRunId:
          type: string
        orchestratorSessionId:
          type: string
        deadline:
          type: string
          format: date-time
        delegationDepth:
          type: integer

    NodeStatusUpdate:
      type: object
      required: [status]
      properties:
        status:
          type: string
          description: 'ok | error | skipped | cancelled.'
        output:
          type: string
        error:
          type: string

    Skill:
      type: object
      required: [name, body]
      description: |
        Named, versioned instruction body managed by the skill catalog.
        `body` is the SKILL.md content; `resources` carries supporting
        files (scripts/, references/, assets/) per the Agent Skills spec.
      properties:
        name:
          type: string
        description:
          type: string
        license:
          type: string
        compatibility:
          type: string
        allowedTools:
          type: string
          description: Space-separated Open Agent Skills allowed-tools scalar.
        metadata:
          type: object
          additionalProperties:
            type: string
        body:
          type: string
        tags:
          type: array
          items:
            type: string
        source:
          type: string
          description: '"user" | "imported" | "platform" (platform-seeded, refreshed on drift, deletable per tenant).'
        resources:
          type: array
          items:
            $ref: '#/components/schemas/SkillResource'
        requiresSandbox:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    SkillResource:
      type: object
      required: [path]
      properties:
        path:
          type: string
          description: Path relative to the skill root, e.g. `scripts/run.sh`.
        content:
          type: string
          description: Raw bytes, base64-encoded on the wire.
          format: byte
        size:
          type: integer
        contentType:
          type: string
        sha256:
          type: string
        executable:
          type: boolean
        description:
          type: string

    SkillList:
      type: array
      items:
        $ref: '#/components/schemas/Skill'

    MCPServer:
      type: object
      required: [name, transport, url]
      description: |
        MCPServerCatalogEntry — flattened MCPServerSpec plus an optional
        human-readable description. Profiles copy entries from this
        catalog into AgentProfile.MCPServers at authoring time.
      properties:
        name:
          type: string
        transport:
          type: string
          description: '"http" or "sse".'
        url:
          type: string
        headers:
          type: object
          additionalProperties:
            type: string
          description: |
            Header values support literal strings, "${VAR}" env expansion,
            or "secret://name" references resolved via the secrets manager.
        description:
          type: string

    MCPServerList:
      type: array
      items:
        $ref: '#/components/schemas/MCPServer'

    MCPTestResult:
      type: object
      required: [ok, latencyMs]
      properties:
        ok:
          type: boolean
        error:
          type: string
        latencyMs:
          type: integer
        toolCount:
          type: integer
        tools:
          type: array
          items:
            type: string

    StorageInfo:
      type: object
      required: [configured, usedBytes, objectCount]
      properties:
        configured:
          type: boolean
        bucket:
          type: string
        usedBytes:
          type: integer
        objectCount:
          type: integer
        capacityBytes:
          type: integer
          description: Optional; sourced from S3_CAPACITY_BYTES.
        breakdown:
          type: array
          items:
            type: object
            required: [prefix, bytes, count]
            properties:
              prefix:
                type: string
              bytes:
                type: integer
              count:
                type: integer

    StorageObjectEntry:
      type: object
      required: [key, size, lastModified]
      properties:
        key:
          type: string
        size:
          type: integer
        lastModified:
          type: string
          format: date-time
        etag:
          type: string

    StorageObjectList:
      type: object
      required: [prefix, bucket, entries, count]
      properties:
        prefix:
          type: string
        bucket:
          type: string
        entries:
          type: array
          items:
            $ref: '#/components/schemas/StorageObjectEntry'
        count:
          type: integer

    StorageObject:
      type: object
      required: [key, size, encoding, content]
      properties:
        key:
          type: string
        contentType:
          type: string
        size:
          type: integer
        etag:
          type: string
        encoding:
          type: string
          description: '"text" (UTF-8 inline) or "base64" (binary).'
        content:
          type: string

    StoragePutResult:
      type: object
      required: [key, size]
      properties:
        key:
          type: string
        contentType:
          type: string
        size:
          type: integer
        etag:
          type: string

    StorageDeleteResult:
      type: object
      required: [key, deleted]
      properties:
        key:
          type: string
        deleted:
          type: integer
          description: Object count removed (1 for a leaf, N for a prefix).

    Memory:
      type: object
      required:
        - id
        - profileName
        - rawInput
        - processedContent
        - summary
        - category
        - source
        - confidence
        - createdAt
        - lastAccessedAt
        - accessCount
        - stalenessScore
        - isActive
        - version
      properties:
        id:
          type: string
        profileName:
          type: string
        rawInput:
          type: string
        processedContent:
          type: string
        summary:
          type: string
        category:
          type: string
          description: 'preference | fact | behavior | context | general.'
        source:
          type: string
          description: '"explicit" (agent-saved) or "inferred" (post-run extraction).'
        confidence:
          type: number
        createdAt:
          type: string
          format: date-time
        lastAccessedAt:
          type: string
          format: date-time
        accessCount:
          type: integer
        stalenessScore:
          type: number
        isActive:
          type: boolean
        version:
          type: integer

    MemoryCreateRequest:
      type: object
      required: [rawInput]
      properties:
        rawInput:
          type: string
        processedContent:
          type: string
        summary:
          type: string
        category:
          type: string
        source:
          type: string
        confidence:
          type: number

    MemoryRelevance:
      type: object
      required: [score, recency, usage, topic]
      properties:
        score:
          type: number
        recency:
          type: number
        usage:
          type: number
        topic:
          type: number

    ScoredMemory:
      type: object
      required: [memory, relevance]
      properties:
        memory:
          $ref: '#/components/schemas/Memory'
        relevance:
          $ref: '#/components/schemas/MemoryRelevance'

    MemoryListResponse:
      type: object
      required: [total, memories]
      properties:
        profile:
          type: string
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
        memories:
          type: array
          items:
            $ref: '#/components/schemas/Memory'

    MemorySearchResponse:
      type: object
      required: [profile, query, count, results]
      properties:
        profile:
          type: string
        query:
          type: string
        count:
          type: integer
        results:
          type: array
          items:
            $ref: '#/components/schemas/ScoredMemory'

    MemoryBankSnapshot:
      type: object
      required: [total, profilesWithMemory, memories]
      properties:
        total:
          type: integer
        profilesWithMemory:
          type: integer
        memories:
          type: object
          additionalProperties:
            type: array
            items:
              $ref: '#/components/schemas/Memory'

    MemoryBankStats:
      type: object
      required: [totalMemories, profilesWithMemory, perProfile]
      properties:
        totalMemories:
          type: integer
        profilesWithMemory:
          type: integer
        perProfile:
          type: object
          additionalProperties:
            type: integer

    Tenant:
      type: object
      required: [id, displayName, status, capabilityRouterEnabled, createdAt, updatedAt]
      properties:
        id:
          type: string
          description: Tenant slug (`default`, `acme`, `zenith`, ...).
        displayName:
          type: string
        status:
          type: string
        metadata:
          type: object
          additionalProperties: true
        capabilityRouterEnabled:
          type: boolean
          description: Whether capability-router execution is enabled for this tenant when the global rollout flag is also enabled.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    TenantList:
      type: object
      required: [total, tenants]
      properties:
        total:
          type: integer
        tenants:
          type: array
          items:
            $ref: '#/components/schemas/Tenant'

    TenantCreateRequest:
      type: object
      required: [id]
      properties:
        id:
          type: string
        displayName:
          type: string
          description: Defaults to `id` when empty.
        metadata:
          type: object
          additionalProperties: true
    TenantUpdateRequest:
      type: object
      description: |
        Empty strings mean "no change"; nil metadata means "no change".
        Sending an explicit empty object for metadata replaces the value
        with `{}`.
      properties:
        displayName:
          type: string
        status:
          type: string
        metadata:
          type: object
          additionalProperties: true
        capabilityRouterEnabled:
          type: boolean
          description: Enables or disables capability-router execution for this tenant.

    Secret:
      type: object
      required: [name, algorithm, createdAt, updatedAt]
      description: |
        Metadata-only projection of a secrets row. Plaintext and
        ciphertext are never present on the wire — list/read endpoints
        never return them. Plaintext is only valid on the request body of
        POST/PUT.
      properties:
        name:
          type: string
        key:
          type: string
          description: |
            Canonical variable name the secret's value is meant to
            populate (e.g. `AWS_ACCESS_KEY_ID`, `ANTHROPIC_API_KEY`).
            Free-text; empty for legacy/untyped rows.
        description:
          type: string
        algorithm:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    SecretList:
      type: object
      required: [total, secrets]
      properties:
        total:
          type: integer
        secrets:
          type: array
          items:
            $ref: '#/components/schemas/Secret'

    SecretCreateRequest:
      type: object
      required: [plaintext]
      description: |
        Body shape for POST /api/secrets and PUT /api/secrets/{name}.
        Plaintext is required on writes; it is NEVER returned on reads,
        never logged, and never round-tripped.
      properties:
        name:
          type: string
        key:
          type: string
          description: |
            Optional canonical variable name (e.g. `AWS_ACCESS_KEY_ID`).
            Free-text; empty means untyped.
        plaintext:
          type: string
        description:
          type: string

    ConnectedAppProvider:
      type: object
      required: [name, configured]
      properties:
        name:
          type: string
        configured:
          type: boolean

    ConnectedAppProviderList:
      type: object
      required: [providers]
      properties:
        providers:
          type: array
          items:
            $ref: '#/components/schemas/ConnectedAppProvider'

    ConnectAppRequest:
      type: object
      required: [appSlug]
      properties:
        appSlug:
          type: string
        scope:
          type: string
          description: '"tenant" (default) or "end_user".'
        endUserId:
          type: string
          description: Required when `scope` is `end_user`.
        callbackUrl:
          type: string

    InitiateConnectResult:
      type: object
      required: [connectionId, status]
      properties:
        connectionId:
          type: string
        externalId:
          type: string
        redirectUrl:
          type: string
        status:
          type: string
          description: 'pending | active | revoked | error.'

    AppConnection:
      type: object
      required:
        - id
        - tenantId
        - provider
        - appSlug
        - scope
        - externalId
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        tenantId:
          type: string
        provider:
          type: string
        appSlug:
          type: string
        scope:
          type: string
        endUserId:
          type: string
        externalId:
          type: string
        status:
          type: string
        mcpServerName:
          type: string
        redirectUrl:
          type: string
        metadata:
          type: object
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    AppConnectionList:
      type: object
      required: [connections]
      properties:
        connections:
          type: array
          items:
            $ref: '#/components/schemas/AppConnection'

    RepairConnectionResult:
      type: object
      required: [id]
      properties:
        id:
          type: string
        mcpServerUrl:
          type: string
        mcpServerName:
          type: string

    SkillImportResult:
      type: object
      required: [imported, skipped]
      properties:
        imported:
          type: array
          items:
            $ref: '#/components/schemas/Skill'
        skipped:
          type: array
          items:
            type: object
            required: [name, reason]
            properties:
              name:
                type: string
              reason:
                type: string

    SkillPackageValidation:
      type: object
      required: [ok, errors, warnings]
      properties:
        ok:
          type: boolean
        errors:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string

    SkillPackagePreview:
      type: object
      required: [skill, resources, validation, requiresSandbox, totalBytes, stagingId]
      properties:
        skill:
          $ref: '#/components/schemas/Skill'
        resources:
          type: array
          items:
            $ref: '#/components/schemas/SkillResource'
        validation:
          $ref: '#/components/schemas/SkillPackageValidation'
        requiresSandbox:
          type: boolean
        totalBytes:
          type: integer
        stagingId:
          type: string

    SkillPackageCommitRequest:
      type: object
      required: [stagingId]
      properties:
        stagingId:
          type: string
        force:
          type: boolean

    RepairAction:
      type: object
      required: [type]
      description: |
        Body for POST /api/workflows/runs/{id}/repair. Type drives behaviour:
        retry_node (reset to pending), replace_node (overwrite profile /
        prompt / schema), add_dependency (insert prep node), or abort.
      properties:
        type:
          type: string
          description: 'retry_node | replace_node | add_dependency | abort.'
        nodeId:
          type: string
        replacement:
          $ref: '#/components/schemas/PlanNode'
        dependsOn:
          type: array
          items:
            type: string

    PublishedAgent:
      type: object
      required:
        - id
        - tenantId
        - profileName
        - slug
        - visibility
        - authMode
        - allowedOrigins
        - rateLimitRpm
        - syncMaxDurationSeconds
        - exposeToolEvents
        - enabled
        - createdAt
        - updatedAt
      description: |
        Public wrapper around an internal AgentProfile. Mirrors the
        published_agents row: soft-deleted via `unpublishedAt`, slug
        uniqueness scoped to live rows per tenant.
      properties:
        id:
          type: string
          description: "`pub_<22 base32 chars>`."
        tenantId:
          type: string
        profileName:
          type: string
        slug:
          type: string
        visibility:
          type: string
          description: '`private` | `org` | `public`.'
        authMode:
          type: string
          description: '`api_key` | `jwt`.'
        allowedOrigins:
          type: array
          items:
            type: string
        rateLimitRpm:
          type: integer
        syncMaxDurationSeconds:
          type: integer
          description: 1-270 inclusive.
        conversationTtlDays:
          type: [integer, "null"]
          description: Null means keep history forever.
        exposeToolEvents:
          type: boolean
        enabled:
          type: boolean
        unpublishedAt:
          type: [string, "null"]
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    PublishedAgentWithURL:
      allOf:
        - $ref: '#/components/schemas/PublishedAgent'
        - type: object
          properties:
            publicUrl:
              type: string
              description: |
                Externally-visible chat endpoint URL. Empty / omitted
                when `CHAT_GATEWAY_PUBLIC_HOST` is unset.

    PublishedAgentList:
      type: object
      required: [total, publishedAgents]
      properties:
        total:
          type: integer
        publishedAgents:
          type: array
          items:
            $ref: '#/components/schemas/PublishedAgentWithURL'

    PublishRequest:
      type: object
      description: |
        Body for POST /api/profiles/{name}/publish. Every field is
        optional; defaults are filled in server-side. When `slug` is
        omitted it is derived from the profile name (lowercase, runs
        of non-`[a-z0-9]` collapse to `-`, leading/trailing dashes
        trimmed). When the derived value fails slug validation, the
        endpoint returns 400 `profile_name_not_slug_safe`.
      properties:
        slug:
          type: string
        visibility:
          type: string
          description: '`private` (default) | `org` | `public`.'
        authMode:
          type: string
          description: '`api_key` (default) | `jwt`.'
        allowedOrigins:
          type: array
          items:
            type: string
        rateLimitRpm:
          type: integer
        syncMaxDurationSeconds:
          type: integer
          description: 1-270 inclusive. Defaults to 60.
        conversationTtlDays:
          type: [integer, "null"]
          description: Null means keep forever (default).
        exposeToolEvents:
          type: boolean

    PublishUpdateRequest:
      type: object
      description: |
        Body for PATCH /api/profiles/{name}/published. Same shape as
        PublishRequest minus the (immutable) `slug`; adds `enabled` so
        the dashboard can pause without unpublishing. Only fields that
        are present in the body are persisted.
      properties:
        visibility:
          type: string
        authMode:
          type: string
        allowedOrigins:
          type: array
          items:
            type: string
        rateLimitRpm:
          type: integer
        syncMaxDurationSeconds:
          type: integer
        conversationTtlDays:
          type: [integer, "null"]
        exposeToolEvents:
          type: boolean
        enabled:
          type: boolean

    APIKeyMetadata:
      type: object
      required:
        - id
        - tenantId
        - publishedId
        - label
        - createdAt
      description: |
        API key metadata. The plaintext token is NEVER present in this
        shape — that field is only returned by POST /keys.
      properties:
        id:
          type: string
          description: "`key_<22 base32 chars>`."
        tenantId:
          type: string
        publishedId:
          type: string
        label:
          type: string
        lastUsedAt:
          type: [string, "null"]
          format: date-time
        revokedAt:
          type: [string, "null"]
          format: date-time
        expiresAt:
          type: [string, "null"]
          format: date-time
        createdAt:
          type: string
          format: date-time

    APIKeyList:
      type: object
      required: [total, keys]
      properties:
        total:
          type: integer
        keys:
          type: array
          items:
            $ref: '#/components/schemas/APIKeyMetadata'

    APIKeyIssued:
      allOf:
        - $ref: '#/components/schemas/APIKeyMetadata'
        - type: object
          required: [token]
          properties:
            token:
              type: string
              description: |
                Plaintext API key (`ao_<env>_<22 base32 chars>`).
                Returned exactly once; subsequent GETs omit it.

    IssueKeyRequest:
      type: object
      description: |
        Body for POST /api/profiles/{name}/keys. Both fields are
        optional. Empty `label` is allowed; nil `expiresAt` means the
        key never expires.
      properties:
        label:
          type: string
        expiresAt:
          type: [string, "null"]
          format: date-time

    TenantAPIKeyMetadata:
      type: object
      description: |
        A tenant-scoped control-plane API key. The plaintext token and
        the stored hash are never included.
      required: [id, tenantId, name, role, createdBy, createdAt]
      properties:
        id:
          type: string
          description: '`key_<22 base32 chars>` identifier.'
        tenantId:
          type: string
        name:
          type: string
        role:
          type: string
          enum: [owner, admin, member, viewer]
          description: RBAC role the key acts with (inherited from the minter).
        createdBy:
          type: string
          description: Minter id — a Clerk user id, or `key:<id>` if minted via a key.
        createdAt:
          type: string
          format: date-time
        lastUsedAt:
          type: [string, "null"]
          format: date-time
        revokedAt:
          type: [string, "null"]
          format: date-time
        expiresAt:
          type: [string, "null"]
          format: date-time

    TenantAPIKeyList:
      type: object
      required: [keys]
      properties:
        keys:
          type: array
          items:
            $ref: '#/components/schemas/TenantAPIKeyMetadata'

    TenantAPIKeyIssued:
      allOf:
        - $ref: '#/components/schemas/TenantAPIKeyMetadata'
        - type: object
          required: [token]
          properties:
            token:
              type: string
              description: |
                Plaintext API key (`ao_<env>_<22 base32 chars>`).
                Returned exactly once; subsequent GETs omit it.

    IssueTenantKeyRequest:
      type: object
      description: |
        Body for POST /api/api-keys. Both fields are optional. Empty
        `name` is allowed; nil `expiresAt` means the key never expires.
      properties:
        name:
          type: string
        expiresAt:
          type: [string, "null"]
          format: date-time

    DeviceCodeRequest:
      type: object
      description: |
        Optional body for POST /api/device/code. The label is shown on the
        dashboard confirm screen and becomes the minted key's name
        (truncated to 100 chars), e.g. "claude-code-ada@laptop".
      properties:
        client_label:
          type: string

    DeviceCodeResponse:
      type: object
      description: RFC 8628 section 3.2 device authorization response.
      required: [device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval]
      properties:
        device_code:
          type: string
          description: |
            256-bit poll secret (64 hex chars). Held by the CLI only;
            stored server-side as a SHA-256 digest. Never shown to humans.
        user_code:
          type: string
          description: Human-typable approval code, displayed as XXXX-XXXX.
        verification_uri:
          type: string
          description: Dashboard page where a signed-in user enters the code.
        verification_uri_complete:
          type: string
          description: Same page with the code prefilled as ?code=XXXX-XXXX.
        expires_in:
          type: integer
          description: Seconds until the handshake expires (900).
        interval:
          type: integer
          description: Minimum seconds between token polls (5).

    DeviceTokenRequest:
      type: object
      description: |
        Body for POST /api/device/token. `grant_type`, when present, must
        be `urn:ietf:params:oauth:grant-type:device_code`; its absence is
        tolerated for CLI simplicity.
      required: [device_code]
      properties:
        grant_type:
          type: string
        device_code:
          type: string

    DeviceTokenResponse:
      type: object
      description: |
        Success answer for an approved device login: the standard OAuth
        access_token/token_type pair plus the metadata the legacy loopback
        flow's callback carried, so the CLI persists the same context shape.
      required: [access_token, token_type, key_id, role, tenant_id]
      properties:
        access_token:
          type: string
          description: |
            Plaintext tenant API key (`ao_<env>_<22 base32 chars>`).
            Returned exactly once, minted at this poll.
        token_type:
          type: string
          enum: [bearer]
        key_id:
          type: string
          description: '`key_<22 base32 chars>` id for later revocation.'
        role:
          type: string
          enum: [owner, admin, member, viewer]
          description: RBAC role the key acts with (inherited from the approver).
        org_slug:
          type: string
          description: Display slug of the approving organization, when provided.
        tenant_id:
          type: string

    DeviceTokenError:
      type: object
      description: |
        RFC 8628 section 3.5 flow answer, returned with HTTP 400. Unknown,
        expired, and consumed handshakes all answer `expired_token` so the
        endpoint is not an existence oracle.
      required: [error]
      properties:
        error:
          type: string
          enum: [authorization_pending, slow_down, expired_token, access_denied, invalid_request, unsupported_grant_type]

    DeviceAuthorizationView:
      type: object
      description: Pending-authorization details for the dashboard confirm screen.
      required: [userCode, clientLabel, createdAt, expiresAt]
      properties:
        userCode:
          type: string
        clientLabel:
          type: string
        createdAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time

    DeviceDecisionRequest:
      type: object
      description: |
        Body for POST /api/device/{approve,deny}. The user code is
        case/hyphen tolerant. `org_slug` is optional display metadata the
        dashboard passes on approval so the CLI can show which org the key
        belongs to.
      required: [user_code]
      properties:
        user_code:
          type: string
        org_slug:
          type: string
    WhoamiResponse:
      type: object
      description: |
        Identity of the authenticated caller (GET /api/whoami). Exactly
        one of `keyId` / `userId` is set depending on `authKind`; neither
        for `run_token`. `tenantName` is best-effort (absent when the
        server runs without a database).
      required: [tenantId, authKind]
      properties:
        tenantId:
          type: string
        tenantName:
          type: string
        role:
          type: string
          enum: [owner, admin, member, viewer]
          description: RBAC role the credential acts with. Absent for run tokens.
        authKind:
          type: string
          enum: [api_key, session, run_token]
          description: What kind of credential authenticated this request.
        keyId:
          type: string
          description: '`key_<22 base32 chars>` id of the API key, when authKind is api_key.'
        userId:
          type: string
          description: Clerk user id, when authKind is session.
  responses:
    Error:
      description: Error response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NoContent:
      description: Success, no body
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Authenticated caller is not allowed to perform this operation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ServiceUnavailable:
      description: Dependency not wired (e.g. DB, S3, secrets)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
