openapi: 3.1.0
info:
  title: OneCLI API
  version: "1.0"
  description: |
    The OneCLI API manages everything behind the gateway: workspaces, agents and what each agent may use (grants), secrets, app connections, organization policy, hosted-agent conversations, schedules, memory, skills, Slack channels, and the organization's directory.

    **Base URL:** `https://api.onecli.sh/v1` (OneCLI Cloud) or `http://localhost:10254/v1` (self-hosted).

    ## Authentication

    Every endpoint takes an `Authorization: Bearer <token>` header unless its description says otherwise.

    - **Workspace API key** (`oc_…`) — acts inside one workspace. Read it from the dashboard or `GET /user/api-key`.
    - **Organization API key** (`oc_org_…`) — acts for the whole organization. Workspace-scoped endpoints additionally need an `X-Workspace-Id` header naming the workspace; without it they answer `401`.
    - **Dashboard session** — the browser's own session (Cloud: a bearer JWT; self-hosted: the session cookie).

    `X-Workspace-Id` replaced `X-Project-Id`. The old header, the `?_project` query bridge, and the `/v1/projects` path alias still work for a deprecation window and answer with a `Deprecation: true` header; migrate to the new names.

    ## Editions

    Each endpoint states where it is available. Unless marked otherwise an endpoint exists on **OneCLI Cloud**, the **Community** self-hosted edition, and the **Enterprise** self-hosted edition. Endpoints tagged **Enterprise** answer `403` with `error.type: "enterprise_license_required"` on a self-hosted deployment that has not set `ENTERPRISE_ENABLED=true`; on Cloud they are always present, though some features are additionally gated by the organization's plan. Endpoints tagged **Cloud** do not exist on self-hosted deployments at all.

    ## Roles

    Organization-level endpoints (`/org/…`) require the **admin** or **owner** role wherever roles are enforced (Cloud and Enterprise). The Community edition runs a flat team: every active member passes the role check.
servers:
  - url: https://api.onecli.sh/v1
    description: OneCLI Cloud
  - url: http://localhost:10254/v1
    description: Self-hosted

security:
  - bearerAuth: []

# ──────────────────────────────────────────────────────
# Availability blocks (YAML anchors referenced from each operation's x-mint).
# `content` renders at the top of the generated page; `metadata.tag` is the
# sidebar pill. Only endpoints that are NOT available everywhere get a pill.
# ──────────────────────────────────────────────────────
x-availability:
  all: &avail_all
    content: |
      <Info>**Availability:** Cloud · Community (self-hosted) · Enterprise (self-hosted)</Info>
  enterprise: &avail_enterprise
    metadata:
      tag: "Enterprise"
    content: |
      <Info>**Availability:** Cloud · Enterprise (self-hosted). Not available on the Community edition: a self-hosted deployment needs `ENTERPRISE_ENABLED=true`, otherwise the endpoint answers `403` with `error.type: "enterprise_license_required"`.</Info>
  cloud: &avail_cloud
    metadata:
      tag: "Cloud"
    content: |
      <Info>**Availability:** Cloud only. Self-hosted deployments do not serve this endpoint.</Info>
  gateway_all: &avail_gateway_all
    content: |
      <Info>**Availability:** Cloud · Community (self-hosted) · Enterprise (self-hosted)</Info>
      <Note>Served by the **gateway**, not the API server. The base URL is your gateway's HTTP origin (`https://gw.onecli.sh` on Cloud; `http://localhost:10255` self-hosted, or `<app-url>/gw` behind a reverse proxy). Read it from `GET /gateway-url`.</Note>
  gateway_enterprise: &avail_gateway_enterprise
    metadata:
      tag: "Enterprise"
    content: |
      <Info>**Availability:** Cloud · Enterprise (self-hosted). On an unlicensed self-hosted deployment the gateway answers `403` with `{ "error": "enterprise_license_required" }`.</Info>
      <Note>Served by the **gateway**, not the API server. The base URL is your gateway's HTTP origin (`https://gw.onecli.sh` on Cloud; `http://localhost:10255` self-hosted, or `<app-url>/gw` behind a reverse proxy). Read it from `GET /gateway-url`.</Note>
  scim: &avail_scim
    metadata:
      tag: "Enterprise"
    content: |
      <Info>**Availability:** Cloud · Enterprise (self-hosted). The SCIM endpoint answers `401` on an unlicensed self-hosted deployment.</Info>
      <Note>SCIM lives on its own base URL: `https://api.onecli.sh/scim/v2` on Cloud, `http://localhost:10254/scim/v2` self-hosted. It authenticates with a **SCIM token** (`oc_scim_…`, minted at `POST /org/scim/tokens`), never a OneCLI API key, and speaks `application/scim+json` per RFC 7644. Each token is rate-limited to 600 requests per minute.</Note>

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "A workspace API key (`oc_…`) or organization API key (`oc_org_…`). Workspace-scoped endpoints called with an organization key also need `X-Workspace-Id`."

  parameters:
    agentId:
      name: agentId
      in: path
      required: true
      schema:
        type: string
    secretId:
      name: secretId
      in: path
      required: true
      schema:
        type: string
    ruleId:
      name: ruleId
      in: path
      required: true
      schema:
        type: string
    provider:
      name: provider
      in: path
      required: true
      schema:
        type: string
      description: App provider identifier (for example `gmail`, `github`, `jira`).
    workspaceId:
      name: workspaceId
      in: path
      required: true
      schema:
        type: string
    connectionId:
      name: connectionId
      in: path
      required: true
      schema:
        type: string
    conversationId:
      name: conversationId
      in: path
      required: true
      schema:
        type: string
    attachmentId:
      name: attachmentId
      in: path
      required: true
      schema:
        type: string
    cronId:
      name: cronId
      in: path
      required: true
      schema:
        type: string
    memoryId:
      name: memoryId
      in: path
      required: true
      schema:
        type: string
    revisionId:
      name: revisionId
      in: path
      required: true
      schema:
        type: string
    skillId:
      name: skillId
      in: path
      required: true
      schema:
        type: string
    channelProvider:
      name: provider
      in: path
      required: true
      schema:
        type: string
        enum: [slack]
      description: Channel provider. Slack is the only provider today.
    externalRef:
      name: externalRef
      in: path
      required: true
      schema:
        type: string
        maxLength: 200
      description: The provider's own identifier for the space (a Slack channel id) or the person (a Slack user id).
    approvalId:
      name: approvalId
      in: path
      required: true
      schema:
        type: string
    contactId:
      name: contactId
      in: path
      required: true
      schema:
        type: string
    peerAgentId:
      name: peerAgentId
      in: path
      required: true
      schema:
        type: string
      description: The other agent. It may live in another workspace of the same organization.
    userId:
      name: userId
      in: path
      required: true
      schema:
        type: string
    groupId:
      name: groupId
      in: path
      required: true
      schema:
        type: string
    domainId:
      name: domainId
      in: path
      required: true
      schema:
        type: string
    tokenId:
      name: tokenId
      in: path
      required: true
      schema:
        type: string
    keyId:
      name: keyId
      in: path
      required: true
      schema:
        type: string
    invitationId:
      name: id
      in: path
      required: true
      schema:
        type: string
    providerQuery:
      name: provider
      in: query
      required: false
      schema:
        type: string
      description: Filter results to a single provider (for example `gmail`, `github`).
    blocklistRuleId:
      name: ruleId
      in: path
      required: true
      schema:
        type: string
      description: Blocklist rule ID (the `ruleId` field of a blocklist state entry).
    policyStatus:
      name: status
      in: query
      required: false
      schema:
        type: string
        enum: [draft, published]
        default: draft
      description: Which copy to read. Every write publishes immediately, so the two are identical unless a write is in flight.
    directoryLimit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
      description: Page size.
    directoryCursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
      description: Opaque keyset cursor from the previous page's `nextCursor`.
    directorySearch:
      name: q
      in: query
      required: false
      schema:
        type: string
        maxLength: 200
      description: Free-text filter (name or email).

  schemas:
    # ── Errors ──────────────────────────────────────────
    Error:
      description: |
        Error responses take one of two shapes depending on the failing layer: route-level validation returns the flat shape (`{ "error": "..." }`), while authentication failures and service errors return the envelope (`{ "error": { "message": "...", "type": "..." } }`).
      oneOf:
        - $ref: "#/components/schemas/ErrorFlat"
        - $ref: "#/components/schemas/ErrorEnvelope"
    ErrorFlat:
      type: object
      description: Flat error shape used by route-level validation.
      required: [error]
      properties:
        error:
          type: string
    ErrorEnvelope:
      type: object
      description: Envelope error shape used for authentication failures and service errors.
      properties:
        error:
          type: object
          properties:
            message:
              type: string
            type:
              type: string
              description: "Error category: `authentication_error`, `invalid_request_error`, `not_found_error`, `validation_error`, `rate_limit_error`, `enterprise_license_required`, or `api_error`."
            code:
              type: string
              description: Present on some `401`s (for example when the organization requires SSO).

    # ── Workspaces / organization ───────────────────────
    Workspace:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        slug: { type: string }
        createdAt:
          type: string
          format: date-time
    WorkspaceCreated:
      allOf:
        - $ref: "#/components/schemas/Workspace"
        - type: object
          properties:
            apiKey:
              type: string
              nullable: true
              description: The creator's workspace API key (`oc_…`), minted with the workspace.
    Organization:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        slug: { type: string }
        byoLegacy:
          type: boolean
          description: Cloud creation posture. `true` = the organization creates bring-your-own agents only; `false` = hosted agents are the default. Ignored on self-hosted deployments.
        byoEnabled:
          type: boolean
          description: When `byoLegacy` is `false`, whether bring-your-own agents may be created alongside hosted ones. Ignored on self-hosted deployments.
    PendingInvitation:
      type: object
      properties:
        id: { type: string }
        email: { type: string }
        role:
          type: string
          enum: [admin, member]
        invitedByEmail: { type: string }
        expiresAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
    WorkspaceAccessBindings:
      type: object
      properties:
        users:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                description: The access-binding id.
              userId: { type: string }
              name:
                type: string
                nullable: true
              email: { type: string }
              role:
                type: string
                enum: [owner, member]
                description: "`owner` may manage the workspace (rename, delete, share)."
              isOwner:
                type: boolean
                description: The workspace's creator. A display hint only; the binding is removable like any other.
              createdAt:
                type: string
                format: date-time
        groups:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              groupId: { type: string }
              name: { type: string }
              memberCount: { type: integer }
              createdAt:
                type: string
                format: date-time

    # ── Agents ──────────────────────────────────────────
    AgentChannelSummary:
      type: object
      properties:
        provider:
          type: string
          example: slack
        identityName:
          type: string
          nullable: true
          description: "The app's handle in the workspace (Slack: `@donna`)."
        externalId:
          type: string
          description: The provider-side app id.
        status:
          type: string
          description: Presence lifecycle (`pending_setup` until the install completes, `connected` after, `disabled` when removed on the provider side).
        settingsUrl:
          type: string
          nullable: true
          description: Deep link to the provider's app settings page.
    Agent:
      type: object
      description: One row of `GET /agents`.
      properties:
        id: { type: string }
        name: { type: string }
        identifier:
          type: string
          description: Stable, URL-safe handle (lowercase letters, digits, hyphens).
        accessToken:
          type: string
          description: The agent's gateway access token (`aoc_…`). Returned by the list; not by `POST /agents`.
        kind:
          type: string
          enum: [byo, hosted]
          description: "`byo` runs on your own machine through the gateway; `hosted` runs in a OneCLI sandbox on a runner."
        imageUrl:
          type: string
          nullable: true
          description: Public avatar URL, or null for the default mark.
        channels:
          type: array
          items:
            $ref: "#/components/schemas/AgentChannelSummary"
        lastSeenAt:
          type: string
          format: date-time
          nullable: true
          description: Newest gateway request from this agent inside the recent window; null when none.
        workingInBackground:
          type: boolean
          description: Hosted agents only. True while live background work (a schedule, a watch, a peer task) keeps the sandbox awake.
        createdAt:
          type: string
          format: date-time
    AgentDetail:
      type: object
      description: "`GET /agents/{agentId}`. Carries the hosted-agent configuration the list omits; never the access token."
      properties:
        id: { type: string }
        name: { type: string }
        identifier: { type: string }
        kind:
          type: string
          enum: [byo, hosted]
        harness:
          type: string
          nullable: true
          description: Hosted agents only. The agent runtime inside the sandbox.
        model:
          type: string
          nullable: true
          description: Hosted agents only. The model override, or null for the provider's default.
        instructions:
          type: string
          nullable: true
          description: Hosted agents only. The agent's standing brief.
        imageUrl:
          type: string
          nullable: true
        channels:
          type: array
          items:
            $ref: "#/components/schemas/AgentChannelSummary"
        recentRequestAt:
          type: string
          format: date-time
          nullable: true
        workingInBackground:
          type: boolean
        createdAt:
          type: string
          format: date-time
    AgentCreated:
      type: object
      description: "`POST /agents` response. The access token is not included; read it from `GET /agents` or mint a new one with `POST /agents/{agentId}/regenerate-token`."
      properties:
        id: { type: string }
        name: { type: string }
        identifier: { type: string }
        kind:
          type: string
          enum: [byo, hosted]
        harness:
          type: string
          nullable: true
        model:
          type: string
          nullable: true
        instructions:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        llmKeys:
          type: array
          items: { type: string }
          description: Secret ids of the LLM keys attached to the new agent automatically. Empty means the workspace has no LLM key yet.
    AgentGrantsSummary:
      type: object
      description: A compact per-agent summary of what's granted — enough to render an attach list without the full tool lists.
      properties:
        mode:
          type: string
          enum: [all, grants]
          description: Always `grants` on current servers. The `all` value is wire compatibility with older self-hosted releases; never branch behavior on it.
        entries:
          type: array
          items:
            oneOf:
              - type: object
                title: App connection
                properties:
                  kind:
                    type: string
                    enum: [app]
                  provider: { type: string }
                  connectionId: { type: string }
                  label:
                    type: string
                    nullable: true
              - type: object
                title: Secret or LLM key
                properties:
                  kind:
                    type: string
                    enum: [secret, llm]
                  id: { type: string }
                  name: { type: string }
        total: { type: integer }
    AgentWithGrantsSummary:
      type: object
      description: An agent row from `GET /agents?include=grants-summary`. Same fields as the plain list row, plus `grantsSummary`.
      required: [grantsSummary]
      properties:
        id: { type: string }
        name: { type: string }
        identifier: { type: string }
        accessToken: { type: string }
        kind:
          type: string
          enum: [byo, hosted]
        createdAt:
          type: string
          format: date-time
        channels:
          type: array
          items:
            $ref: "#/components/schemas/AgentChannelSummary"
        lastSeenAt:
          type: string
          format: date-time
          nullable: true
        grantsSummary:
          $ref: "#/components/schemas/AgentGrantsSummary"
    AgentModels:
      type: object
      description: What a hosted agent can run and what it runs now, derived from the LLM key granted to it.
      properties:
        provider:
          type: string
          nullable: true
          description: The LLM provider of the granted key (`anthropic`, `openai`), or null when no key is granted.
        providerLabel:
          type: string
          nullable: true
        providerScope:
          type: string
          nullable: true
          description: Where the granted key lives (`workspace` or `organization`).
        models:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              label: { type: string }
        efforts:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              label: { type: string }
        defaults:
          type: object
          properties:
            model: { type: string }
            effort:
              type: string
              nullable: true
        selected:
          type: object
          properties:
            model: { type: string }
            effort:
              type: string
              nullable: true
            overridden:
              type: boolean
              description: True when the agent carries an explicit override (set with `PATCH /agents/{agentId}`).
        degraded:
          type: boolean
          description: True when the live model list could not be fetched and a static fallback is shown.
    SshCertificate:
      type: object
      properties:
        certificate:
          type: string
          description: The signed OpenSSH user certificate line (`ssh-ed25519-cert-v01@openssh.com …`). Save it next to your private key as `<key>-cert.pub`.
        host: { type: string }
        port: { type: integer }
        user:
          type: string
          description: The SSH username to connect as (the agent id).
        serial: { type: string }
        expiresAt:
          type: string
          format: date-time

    # ── Grants ──────────────────────────────────────────
    SessionPolicy:
      description: |
        Resource scoping for a connection's credential ("Resources"): an object keyed by the provider's resource axis. Exactly one key: `repositories` (GitHub, `owner/repo`), `folders` (Dropbox, absolute paths), or `driveFolders` (Google Drive, folder-id chains). Lists must be non-empty.
      oneOf:
        - type: object
          title: GitHub repositories
          required: [repositories]
          properties:
            repositories:
              type: array
              minItems: 1
              maxItems: 1000
              items: { type: string }
          additionalProperties: false
        - type: object
          title: Dropbox folders
          required: [folders]
          properties:
            folders:
              type: array
              minItems: 1
              maxItems: 100
              items: { type: string }
          additionalProperties: false
        - type: object
          title: Google Drive folders
          required: [driveFolders]
          properties:
            driveFolders:
              type: array
              minItems: 1
              maxItems: 100
              items: { type: string }
          additionalProperties: false
    AgentGrantConnection:
      type: object
      description: One app connection granted to an agent.
      properties:
        connectionId: { type: string }
        provider:
          type: string
          description: App provider, for example `gmail`.
        label:
          type: string
          nullable: true
          description: The connection's label — distinguishes multiple accounts of one app.
        scope:
          type: string
          enum: [workspace, organization]
          description: Where the connection lives. Organization connections are shared into every workspace.
        access:
          type: string
          enum: [full, custom]
          description: "`full` = every tool the app supports; `custom` = only the `allow`/`ask` lists below."
        allow:
          type: array
          items: { type: string }
          description: Tool ids the agent may call freely. Empty when `access` is `full`.
        ask:
          type: array
          items: { type: string }
          description: Tool ids that pause for human approval on every call. Empty when `access` is `full`.
        resources:
          allOf:
            - $ref: "#/components/schemas/SessionPolicy"
          nullable: true
          description: The grant's resource scoping, or null for unrestricted.
    AgentGrantSecret:
      type: object
      description: One secret granted to an agent. Secrets are all-or-nothing — no per-tool lists.
      properties:
        secretId: { type: string }
        name: { type: string }
        type:
          type: string
          description: "`generic` is a plain secret; `anthropic`/`openai` are LLM keys."
        scope:
          type: string
          enum: [workspace, organization]
    AgentGrants:
      type: object
      description: An agent's full grant set — returned by grant reads and by every grant write.
      properties:
        agentId: { type: string }
        mode:
          type: string
          enum: [all, grants]
          description: Always `grants` on current servers. The `all` value is wire compatibility with older self-hosted releases; never branch behavior on it.
        connections:
          type: array
          items: { $ref: "#/components/schemas/AgentGrantConnection" }
        secrets:
          type: array
          items: { $ref: "#/components/schemas/AgentGrantSecret" }
    ConnectionGrantInput:
      description: |
        The grant to write. Two forms:

        - `{"access": "full"}` — every tool the app supports.
        - `{"access": "custom", "allow": [...], "ask": [...]}` — named tools only. `allow` runs freely, `ask` pauses for human approval, everything unnamed is blocked.

        Validation laws on `custom`: the two lists together must name at least one tool (an all-blocked grant is a detach — use DELETE), and a tool can't appear in both lists. Tool ids come from `GET /apps/{provider}/permission-definition`.

        `resources` is tri-state on both forms: omit it to keep the current scoping, send `null` to clear it, or send a session-policy object to set it. Setting resources is an Enterprise feature (resource-level access).
      oneOf:
        - type: object
          title: Full access
          required: [access]
          properties:
            access:
              type: string
              enum: [full]
            resources:
              allOf:
                - $ref: "#/components/schemas/SessionPolicy"
              nullable: true
          additionalProperties: false
        - type: object
          title: Custom access
          required: [access, allow, ask]
          properties:
            access:
              type: string
              enum: [custom]
            allow:
              type: array
              maxItems: 200
              items: { type: string }
              description: Tool ids the agent may call freely.
            ask:
              type: array
              maxItems: 200
              items: { type: string }
              description: Tool ids that require human approval on every call.
            resources:
              allOf:
                - $ref: "#/components/schemas/SessionPolicy"
              nullable: true
          additionalProperties: false
    ConnectionGrants:
      type: object
      description: Which agents hold a grant for one connection (the reverse orientation of `AgentGrants`).
      properties:
        connectionId: { type: string }
        agents:
          type: array
          items:
            type: object
            properties:
              agentId: { type: string }
              access:
                type: string
                enum: [full, custom]
              allow:
                type: array
                items: { type: string }
              ask:
                type: array
                items: { type: string }

    # ── Effective policy (read-only reflections) ────────
    EffectiveProvenance:
      type: object
      description: Which rule decided a verdict. An organization rule's name is visible only to organization admins; other viewers get `redacted` set instead, with the name withheld.
      properties:
        kind:
          type: string
          enum: [rule]
        scope:
          type: string
          enum: [organization, workspace]
        redacted:
          type: boolean
        rule:
          type: object
          properties:
            logicalId: { type: string }
            name: { type: string }
    EffectiveToolVerdict:
      type: string
      description: |
        `mixed` means the tool's variants — or a group's tools — disagree. `unmanaged` means no rule applies and the traffic passes through under the enforce-deny carve (no credential attached, or an LLM host). A rate limit is not a verdict: it rides `rateLimit`/`rateLimitWindow` on an `allow`.
      enum: [allow, approval, block, mixed, unmanaged]
    EffectiveAppPermissions:
      type: object
      properties:
        provider: { type: string }
        basis:
          type: object
          properties:
            agentId:
              type: string
              nullable: true
              description: Null = the all-agents baseline.
            credentialAttached: { type: boolean }
            scope:
              type: string
              enum: [organization, workspace]
        variesByIdentity:
          type: integer
          description: Identity-scoped rules the baseline view cannot show.
        orgResources:
          allOf:
            - $ref: "#/components/schemas/SessionPolicy"
          nullable: true
          description: The organization's resource boundary for this agent + connection. Null when the organization does not restrict, or when no `agentId` + `connectionId` basis was given.
        effectiveResources:
          allOf:
            - $ref: "#/components/schemas/SessionPolicy"
          nullable: true
          description: What the credential actually reaches — the organization boundary composed with the workspace grant's selection. An empty list means the two do not overlap.
        groups:
          type: array
          items:
            type: object
            properties:
              category:
                type: string
                enum: [read, write]
              verdict: { $ref: "#/components/schemas/EffectiveToolVerdict" }
              tools:
                type: array
                items:
                  type: object
                  properties:
                    toolId: { type: string }
                    verdict: { $ref: "#/components/schemas/EffectiveToolVerdict" }
                    rateLimit: { type: integer, nullable: true }
                    rateLimitWindow: { type: string, nullable: true }
                    decidedBy:
                      allOf: [{ $ref: "#/components/schemas/EffectiveProvenance" }]
                      nullable: true
                    orgCeiling:
                      type: string
                      enum: [allow, approval, block]
                      nullable: true
                      description: What the organization level alone decides for this tool — the ceiling a workspace grant cannot exceed. Null when no organization rule touches it.
    EffectiveCredential:
      type: object
      description: One credential the agent can use. `kind` selects which fields are present.
      properties:
        kind:
          type: string
          enum: [secret, connection]
        id: { type: string }
        name: { type: string, description: "Secrets only." }
        host: { type: string, description: "Secrets only." }
        label: { type: string, nullable: true, description: "Connections only." }
        provider: { type: string, description: "Connections only." }
        status:
          type: string
          enum: [usable, limited, blocked, unknown]
        provenance:
          type: array
          items: { $ref: "#/components/schemas/EffectiveProvenance" }
    EffectiveCredentials:
      type: object
      properties:
        agentId: { type: string }
        mode:
          type: string
          enum: [all, selective]
          description: "`selective` on current servers (an agent uses only what its grants provide)."
        secrets:
          type: array
          items: { $ref: "#/components/schemas/EffectiveCredential" }
        connections:
          type: array
          items: { $ref: "#/components/schemas/EffectiveCredential" }
    ConnectionEffectiveAgents:
      type: object
      properties:
        connectionId: { type: string }
        provider: { type: string }
        catalog:
          type: boolean
          description: False = no tool catalog, so `decisions` is omitted rather than guessed.
        agents:
          type: array
          items:
            type: object
            properties:
              agentId: { type: string }
              name: { type: string }
              access:
                type: string
                enum: [usable, limited, blocked, none, unknown]
              credential:
                type: object
                properties:
                  status:
                    type: string
                    enum: [full, viaRule, none]
                  provenance:
                    type: array
                    items: { $ref: "#/components/schemas/EffectiveProvenance" }
              decisions:
                type: object
                nullable: true
                properties:
                  allowedTools: { type: integer }
                  totalTools: { type: integer }
                  anyApproval: { type: boolean }
                  anyRateLimit: { type: boolean }

    # ── Secrets ─────────────────────────────────────────
    InjectionConfig:
      nullable: true
      description: |
        How the gateway injects this secret into matching outbound requests (generic secrets only). Exactly one variant:

        - **Header** — `{ "headerName": "Authorization", "valueFormat": "Bearer {value}" }`
        - **Query parameter** — `{ "paramName": "key", "paramFormat": "{value}" }`
        - **URL path template** — `{ "pathTemplate": "/bot{value}" }` (must start with `/` and contain `{value}` exactly once)
        - **URL path regex** — `{ "pathRegex": "^/bot([^/]+)", "pathReplacement": "/bot{value}" }` (replacement must contain `{value}`)

        Unknown or mixed keys are rejected.
      oneOf:
        - type: object
          title: Header injection
          required: [headerName]
          properties:
            headerName:
              type: string
              minLength: 1
            valueFormat:
              type: string
              description: "Value template; `{value}` is replaced with the secret (default: `{value}`)."
          additionalProperties: false
        - type: object
          title: Query parameter injection
          required: [paramName]
          properties:
            paramName:
              type: string
              minLength: 1
            paramFormat:
              type: string
              description: "Value template; `{value}` is replaced with the secret (default: `{value}`)."
          additionalProperties: false
        - type: object
          title: URL path template injection
          required: [pathTemplate]
          properties:
            pathTemplate:
              type: string
              description: Path with a `{value}` hole, for example `/bot{value}`. Must start with `/` and contain `{value}` exactly once.
          additionalProperties: false
        - type: object
          title: URL path regex injection
          required: [pathRegex, pathReplacement]
          properties:
            pathRegex:
              type: string
              description: Regular expression matched against the request path.
            pathReplacement:
              type: string
              description: Replacement with `$N` capture references and a `{value}` token for the secret.
          additionalProperties: false
    Secret:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        type:
          type: string
          enum: [anthropic, openai, generic]
        valueSource:
          type: string
          enum: [inline, onepassword]
          description: Where the secret value lives — stored encrypted (`inline`) or resolved from 1Password at request time (`onepassword`).
        opRef:
          type: string
          nullable: true
          description: 1Password secret reference (`op://vault/item/field`). Only set when `valueSource` is `onepassword`.
        hostPattern: { type: string }
        pathPattern:
          type: string
          nullable: true
        injectionConfig:
          $ref: "#/components/schemas/InjectionConfig"
        metadata:
          type: object
          nullable: true
          description: Type-specific metadata (for example `authMode` for `anthropic`/`openai` secrets, 1Password display labels).
        scope:
          type: string
          enum: [workspace, organization]
          description: Workspace lists include inherited organization secrets; use this to tell them apart.
        typeLabel: { type: string }
        lastError:
          type: object
          nullable: true
          description: LLM keys only. The most recent upstream response that indicts the key itself (401, 402, 403, 429) inside the recent window, or null when the last injected call succeeded.
          properties:
            status: { type: integer }
            at:
              type: string
              format: date-time
        createdAt:
          type: string
          format: date-time
    SecretCreated:
      type: object
      description: The narrowed secret representation returned by create endpoints.
      properties:
        id: { type: string }
        name: { type: string }
        type: { type: string }
        valueSource:
          type: string
          enum: [inline, onepassword]
        opRef:
          type: string
          nullable: true
        hostPattern: { type: string }
        pathPattern:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        preview:
          type: string
          description: Masked preview of the value (inline secrets only).
        attachedAgents:
          type: array
          items: { type: string }
          description: Agent ids the new secret was granted to automatically (workspace secrets only; empty for organization secrets).
    CreateSecretInput:
      type: object
      required: [name, type, hostPattern]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
          example: "Anthropic Production"
        type:
          type: string
          enum: [anthropic, openai, generic]
        valueSource:
          type: string
          enum: [inline, onepassword]
          default: inline
        value:
          type: string
          minLength: 1
          maxLength: 10000
          description: The secret value (API key, token, and so on). Required unless `valueSource` is `onepassword`.
        opRef:
          type: string
          description: 1Password secret reference (`op://vault/item/field`). Required when `valueSource` is `onepassword`.
        opDisplay:
          type: object
          description: Human-readable labels of the picked 1Password vault/item/field, for display only.
          properties:
            vault: { type: string }
            item: { type: string }
            field: { type: string }
        hostPattern:
          type: string
          description: Hostname pattern to match (no `://`, `/`, or spaces). A `*` wildcard must stand for whole labels and the pattern must end in a literal domain that is not a public suffix (`*.example.com` is fine, `*.com` is not).
          example: "api.anthropic.com"
        pathPattern:
          type: string
          maxLength: 1000
          description: Optional path pattern to match.
        injectionConfig:
          $ref: "#/components/schemas/InjectionConfig"
    UpdateSecretInput:
      type: object
      description: At least one field must be provided. Set `valueSource` to `onepassword` (with `opRef`) to switch to a 1Password-resolved value, or provide `value` to store an inline value.
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        valueSource:
          type: string
          enum: [inline, onepassword]
        value:
          type: string
          minLength: 1
          maxLength: 10000
        opRef:
          type: string
        opDisplay:
          type: object
          properties:
            vault: { type: string }
            item: { type: string }
            field: { type: string }
        hostPattern:
          type: string
        pathPattern:
          type: string
          nullable: true
          maxLength: 1000
        injectionConfig:
          $ref: "#/components/schemas/InjectionConfig"

    # ── Apps & connections ──────────────────────────────
    AppConfigState:
      type: object
      nullable: true
      properties:
        hasCredentials: { type: boolean }
        enabled: { type: boolean }
        source:
          type: string
          enum: [organization]
          description: Present when the workspace has no configuration of its own and an organization-level one applies.
    App:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        available:
          type: boolean
          deprecated: true
          description: Always `true`. Kept for older clients that read a missing field as `false`.
        connectionType:
          type: string
          enum: [oauth, credentials_import, api_key, cloud_only]
        configurable:
          type: boolean
          description: Whether the app accepts your own OAuth client credentials (`POST /apps/{provider}/config`).
        config:
          $ref: "#/components/schemas/AppConfigState"
        connection:
          type: object
          nullable: true
          deprecated: true
          description: The first connection only. Misleading for multi-account providers; read `connections` instead.
          properties:
            status: { type: string }
            scopes:
              type: array
              items: { type: string }
            connectedAt:
              type: string
              format: date-time
        connections:
          type: array
          description: All of the provider's connections in the current workspace.
          items:
            type: object
            properties:
              id: { type: string }
              label:
                type: string
                nullable: true
              status: { type: string }
              scopes:
                type: array
                items: { type: string }
              connectedAt:
                type: string
                format: date-time
        credentialStubs:
          type: array
          description: Local credential stub files some MCP servers need before they can start (secrets replaced with `onecli-managed` placeholders).
          items:
            type: object
            properties:
              path:
                type: string
                description: Full destination path (for example `~/.config/gcloud/application_default_credentials.json`).
              content:
                type: object
                description: Stub file content with `onecli-managed` sentinel values.
    AppDetail:
      allOf:
        - $ref: "#/components/schemas/App"
        - type: object
          properties:
            hint:
              type: string
              description: A human-readable next step — how to create credential stubs when the app is configured, or where to configure it when it is not.
    AppConnection:
      type: object
      properties:
        id: { type: string }
        provider: { type: string }
        label:
          type: string
          nullable: true
          description: Display label extracted from metadata (for example the account email).
        status: { type: string }
        scopes:
          type: array
          items: { type: string }
        scope:
          type: string
          enum: [workspace, organization]
          description: Workspace lists include inherited organization connections; use this to tell them apart.
        metadata:
          type: object
          nullable: true
          description: Provider metadata captured at connect time (for example account email or username).
        connectedAt:
          type: string
          format: date-time
        reauthRequiredAt:
          type: string
          format: date-time
          nullable: true
          description: Set when the provider refused the refresh token. The account needs to be reconnected.
    ConnectionRenamed:
      type: object
      properties:
        id: { type: string }
        provider: { type: string }
        status: { type: string }
        label: { type: string }
    ConnectInput:
      type: object
      description: Direct-credential connect (API key and credentials-import apps). The required `fields` keys are the app's own and are listed on its integration page.
      properties:
        fields:
          type: object
          additionalProperties: { type: string }
          description: "The app's credential fields (for example `{ \"apiKey\": \"…\" }`)."
        connectionId:
          type: string
          description: Re-authenticate this existing connection instead of creating a new one.
        label:
          type: string
          description: Display label for the new connection. Defaults to an identity extracted from the credentials.
        method:
          type: string
          description: For apps that offer more than one direct method, which one `fields` belongs to.
    ConnectResult:
      type: object
      properties:
        success: { type: boolean }
        connection:
          type: object
          description: Present only when a brand-new connection was created (not on reconnects).
          properties:
            id: { type: string }
            label:
              type: string
              nullable: true
    AppConfigInput:
      type: object
      description: The app's own OAuth client credentials ("bring your own client"). Field names are per app; most take `clientId` and `clientSecret`.
      additionalProperties: { type: string }
      example:
        clientId: "1234567890.apps.googleusercontent.com"
        clientSecret: "GOCSPX-…"
    AppAvailabilityView:
      type: object
      description: The app-availability allowlist as it applies to the current workspace.
      properties:
        restricted:
          type: boolean
          description: "`false` = every app is available (no allowlist, or the organization is in `open` mode); the `providers` list is then empty and meaningless."
        providers:
          type: array
          items: { type: string }
          description: When `restricted` is true, the exact providers this workspace may connect.
    AppTool:
      type: object
      description: One operation in an app's permission catalog, by identity. The endpoint mapping behind a tool is internal; permission changes reference the tool ID and the server resolves the rest.
      properties:
        id:
          type: string
          description: The tool ID that grants and rules reference.
        name: { type: string }
        description: { type: string }
    AppToolGroup:
      type: object
      properties:
        category:
          type: string
          enum: [read, write]
        tools:
          type: array
          items:
            $ref: "#/components/schemas/AppTool"
        wildcard:
          allOf:
            - $ref: "#/components/schemas/AppTool"
          description: Optional wildcard tool covering the whole group ("all read/write operations").
        wildcardComplete:
          type: boolean
          description: Present with `wildcard`. True when the wildcard covers every tool in the group.
    PermissionDefinition:
      type: object
      description: An app's static tool catalog — the tool IDs that grants and policy rules operate on.
      properties:
        provider: { type: string }
        groups:
          type: array
          items:
            $ref: "#/components/schemas/AppToolGroup"
        unlisted:
          type: string
          enum: [block]
          description: Present when requests outside the catalog are blocked under a customized grant.
    BlocklistHostState:
      type: object
      description: State of one of the app's predefined blockable hosts.
      properties:
        hostId: { type: string }
        ruleId:
          type: string
          nullable: true
          description: The backing rule ID, or null when the host has never been activated.
        enabled: { type: boolean }
        custom:
          type: boolean
          description: Always `false`. Blocking an arbitrary host is a policy rule now; this surface only toggles the hosts the app itself declares.
        name: { type: string }
        hostPattern: { type: string }
        scope:
          type: string
          nullable: true
          enum: [workspace, organization, null]
          description: Where the backing rule lives. An organization rule overrides the workspace's view of that host.
    OrgAppConfigState:
      type: object
      properties:
        hasCredentials: { type: boolean }
        enabled: { type: boolean }
        dependents:
          type: integer
          description: How many connections across the organization were minted from this configuration.
    VaultConnection:
      type: object
      properties:
        id: { type: string }
        provider:
          type: string
          description: External vault provider (for example `onepassword`).
        status: { type: string }
        name:
          type: string
          nullable: true
        lastConnectedAt:
          type: string
          format: date-time
          nullable: true

    # ── Policy rules (organization) ─────────────────────
    PolicyRule:
      type: object
      description: "A policy-engine rule. Every write is enforced immediately; the draft/published split survives only for older clients. Published row `id`s regenerate on every publish; `logicalId` is the identity stable across statuses and generations."
      properties:
        id: { type: string }
        scope:
          type: string
          enum: [organization, workspace]
        status:
          type: string
          enum: [draft, published]
        generation:
          type: integer
          description: 0 for the draft working copy; the snapshot number for published rows.
        priority:
          type: integer
          description: First-match order (lower evaluates first).
        enabled: { type: boolean }
        isDefault:
          type: boolean
          description: True on the scope's terminal Default Rule.
        logicalId:
          type: string
          description: Generation-stable identity; compare rules across draft/published by this, never by `id`.
        source:
          type: string
          description: "`custom` (user-owned, editable), `default` (the Default Rule), or system-managed rows: `blocklist` (compiled from app blocklists) and, at workspace scope, `grant` (compiled from agent grants; managed through the Grants endpoints, never edited as rules)."
        name: { type: string }
        description:
          type: string
          nullable: true
        action:
          type: string
          enum: [allow, block]
        rateLimit:
          type: integer
          nullable: true
        rateLimitWindow:
          type: string
          nullable: true
          enum: [minute, hour, day, null]
        requireApproval: { type: boolean }
        conditions:
          description: "A conditions array, a session-policy object (`{repositories}` / `{folders}` / `{driveFolders}`), or null."
          nullable: true
        identities:
          type: array
          items:
            $ref: "#/components/schemas/PolicyRuleIdentity"
        targets:
          type: array
          items:
            $ref: "#/components/schemas/PolicyRuleTarget"
        createdAt:
          type: string
          format: date-time
    PolicyRuleIdentity:
      type: object
      description: A principal the rule applies to. Organization rules take `user`/`group` identities (`agent` exists at workspace scope only). An empty identity list means the rule applies to everyone. Targeting a `group` is an Enterprise feature.
      required: [type, id]
      properties:
        type:
          type: string
          enum: [agent, user, group]
        id: { type: string }
    PolicyRuleTarget:
      type: object
      description: "One destination the rule matches, a discriminated union on `kind`: an `app` target (provider, optionally narrowed by `tools`), a `connection` target (`connectionId`, optional `tools`), a `secret` target (a specific `secretId` XOR a `secretScope` level), or a `network` target (host/path/method patterns). A rule needs at least one target."
      required: [kind]
      properties:
        kind:
          type: string
          enum: [app, connection, secret, network]
        provider:
          type: string
          description: "`app` targets: the provider id (for example `gmail`)."
        tools:
          type: array
          maxItems: 100
          items: { type: string }
          description: "`app`/`connection` targets: narrow matching to these catalog tool ids; empty = the whole app."
        connectionScope:
          type: string
          enum: [organization, workspace]
          nullable: true
          description: "`app` targets: set = inject all the agent's connections of the provider at that level; `null` (or omitted on write) = a block/allow layer with no injection."
        connectionId:
          type: string
          description: "`connection` targets: the connection id."
        secretId:
          type: string
          description: "`secret` targets: a specific secret (mutually exclusive with `secretScope`)."
        secretScope:
          type: string
          enum: [organization, workspace]
          description: "`secret` targets: all the agent's secrets at that level."
        hostPattern:
          type: string
          maxLength: 1000
          description: "`network` targets."
        pathPattern:
          type: string
          nullable: true
          maxLength: 1000
        method:
          type: string
          nullable: true
          enum: [GET, POST, PUT, PATCH, DELETE, null]
    CreatePolicyRuleInput:
      type: object
      description: "A rule must name at least one target (an empty or missing `targets` list is refused with `422`). `rateLimit`/`rateLimitWindow` are paired and, like `requireApproval`, valid only with `action: allow`. On Cloud, `rateLimit` needs the Pro plan or above and `group` identities need the Enterprise plan; manual approval is available on every plan."
      required: [name, action, targets]
      properties:
        name:
          type: string
          maxLength: 255
        description:
          type: string
          maxLength: 1000
        enabled:
          type: boolean
          default: true
        action:
          type: string
          enum: [allow, block]
        rateLimit:
          type: integer
          minimum: 1
          maximum: 1000000
        rateLimitWindow:
          type: string
          enum: [minute, hour, day]
        requireApproval:
          type: boolean
        conditions:
          description: "A conditions array (max 10) or a session-policy object (`{repositories: [...]}` / `{folders: [...]}` / `{driveFolders: [...]}`; allow rules with a connection target only)."
        identities:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/PolicyRuleIdentity"
        targets:
          type: array
          maxItems: 100
          minItems: 1
          items:
            $ref: "#/components/schemas/PolicyRuleTarget"
    UpdatePolicyRuleInput:
      type: object
      description: Every field optional (at least one required); nullable fields accept an explicit `null` to clear.
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        enabled:
          type: boolean
        action:
          type: string
          enum: [allow, block]
        rateLimit:
          type: integer
          nullable: true
        rateLimitWindow:
          type: string
          nullable: true
        requireApproval:
          type: boolean
        conditions:
          nullable: true
        identities:
          type: array
          items:
            $ref: "#/components/schemas/PolicyRuleIdentity"
        targets:
          type: array
          items:
            $ref: "#/components/schemas/PolicyRuleTarget"
    PolicyPublishResult:
      type: object
      properties:
        generation: { type: integer }
        ruleCount: { type: integer }
    PolicyLastPublish:
      type: object
      nullable: true
      description: The most recent publish; `null` when the scope was never published.
      properties:
        generation: { type: integer }
        ruleCount: { type: integer }
        appliedAt:
          type: string
          format: date-time
        appliedBy:
          type: object
          nullable: true
          description: Null for system publishes.
          properties:
            name:
              type: string
              nullable: true
            email: { type: string }

    # ── Conversations ───────────────────────────────────
    Conversation:
      type: object
      properties:
        id: { type: string }
        agentId: { type: string }
        source:
          type: string
          description: "Where the conversation was born: `web`, a channel provider (`slack`), `cron`, `watch`, or `agent` (an agent-to-agent pair)."
        externalRef:
          type: string
          nullable: true
          description: The provider-side thread reference for channel conversations.
        direct:
          type: boolean
          description: True for a person's private direct thread with the agent. Direct threads are visible only to their owner.
        userId:
          type: string
          nullable: true
          description: The owner of a direct thread.
        title:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    AttachmentMeta:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        mimeType: { type: string }
        sizeBytes: { type: integer }
        status:
          type: string
          description: "`pending` until a message binds the attachment; bound attachments carry the message's lifecycle."
        direction:
          type: string
          enum: [inbound, outbound]
          description: "`inbound` = the person's message; `outbound` = the agent's reply."
        caption:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
    Turn:
      type: object
      properties:
        id: { type: string }
        conversationId: { type: string }
        status:
          type: string
          enum: [queued, dispatched, running, done, failed, aborted, joining, joined]
          description: "`joining` is a follow-up waiting to steer into the active turn; `joined` means it did."
        source:
          type: string
          description: "`web` for API and dashboard turns; a channel provider id for channel turns; `cron`, `watch`, `agent`, `greeting`, or `peer_task` for platform-authored turns."
        userId:
          type: string
          nullable: true
        message: { type: string }
        error:
          type: string
          nullable: true
          description: Human-readable failure text for `failed` turns.
        errorCode:
          type: string
          nullable: true
        usage:
          type: object
          nullable: true
          description: Token usage reported by the harness, when available.
        followUpOfTurnId:
          type: string
          nullable: true
        startedAt:
          type: string
          format: date-time
          nullable: true
        finishedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/AttachmentMeta"
    TurnInput:
      type: object
      required: [message]
      properties:
        message:
          type: string
          maxLength: 100000
        attachmentIds:
          type: array
          maxItems: 5
          items: { type: string }
          description: Ids of attachments staged with `POST /conversations/{conversationId}/attachments`.
    TranscriptEvent:
      type: object
      properties:
        seq:
          type: integer
          description: Monotonic position in the conversation's transcript. Resume a read or a stream from the last `seq` you saw.
        turnId: { type: string }
        type:
          type: string
          description: "Event kind, for example `text` (assistant output), `notice` (a platform message), or `turn.done`."
        payload:
          type: object
          description: Event-specific body.
    Transcript:
      type: object
      properties:
        events:
          type: array
          items:
            $ref: "#/components/schemas/TranscriptEvent"
        nextSince:
          type: integer
          description: Pass as `since` to continue reading.
        hasMore: { type: boolean }

    # ── Schedules, memories, skills ─────────────────────
    AgentCron:
      type: object
      properties:
        id: { type: string }
        agentId: { type: string }
        name: { type: string }
        prompt: { type: string }
        schedule:
          type: string
          description: A cron expression (five fields, croner syntax).
        timezone:
          type: string
          description: IANA time zone the schedule is evaluated in.
        enabled: { type: boolean }
        disabledReason:
          type: string
          nullable: true
          enum: [authorization, failures, completed, null]
          description: Why the platform paused it, when it did. `completed` marks a one-shot schedule that fired its last occurrence.
        nextFireAt:
          type: string
          format: date-time
          nullable: true
        lastFiredAt:
          type: string
          format: date-time
          nullable: true
        lastOutcome:
          type: string
          nullable: true
          enum: [ok, failed, skipped_busy, null]
        consecutiveFailures: { type: integer }
        createdAt:
          type: string
          format: date-time
    CreateCronInput:
      type: object
      required: [name, prompt, schedule, timezone]
      additionalProperties: false
      properties:
        name:
          type: string
          maxLength: 100
        prompt:
          type: string
          maxLength: 10000
          description: What the agent is asked to do each time the schedule fires.
        schedule:
          type: string
          maxLength: 100
          example: "0 9 * * 1-5"
        timezone:
          type: string
          maxLength: 100
          example: "Europe/London"
    UpdateCronInput:
      type: object
      description: "Partial. `enabled: false` pauses the schedule; re-enabling clears a platform auto-disable."
      additionalProperties: false
      properties:
        name:
          type: string
          maxLength: 100
        prompt:
          type: string
          maxLength: 10000
        schedule:
          type: string
          maxLength: 100
        timezone:
          type: string
          maxLength: 100
        enabled:
          type: boolean
    AgentMemoryIndex:
      type: object
      description: One memory without its content (the index projection).
      properties:
        id: { type: string }
        agentId: { type: string }
        key:
          type: string
          description: The agent's own handle for the memory and its file name (`memory/<key>.md`). Immutable.
        title:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        lastRevisionSeq: { type: integer }
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    AgentMemory:
      allOf:
        - $ref: "#/components/schemas/AgentMemoryIndex"
        - type: object
          properties:
            content: { type: string }
    MemorySearchHit:
      type: object
      properties:
        id: { type: string }
        key: { type: string }
        title:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        snippet: { type: string }
        rank: { type: number }
        updatedAt:
          type: string
          format: date-time
    MemoryRevision:
      type: object
      properties:
        id: { type: string }
        seq: { type: integer }
        op:
          type: string
          enum: [save, restore]
        restoredFromSeq:
          type: integer
          nullable: true
        title:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        content:
          type: string
          description: Full content on the single-revision read; a preview on the list (see `contentTruncated`).
        contentTruncated:
          type: boolean
          description: List reads only. True when `content` was clipped.
        authorKind:
          type: string
          enum: [user, agent]
        authorUserId:
          type: string
          nullable: true
        authorEmail:
          type: string
          nullable: true
        conversationId:
          type: string
          nullable: true
        turnId:
          type: string
          nullable: true
        redactedAt:
          type: string
          format: date-time
          nullable: true
        redactedByUserId:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
    SkillFile:
      type: object
      required: [path, content]
      properties:
        path:
          type: string
          maxLength: 128
          description: Relative, at most two lowercase segments (for example `references/api.md`). `SKILL.md` is reserved.
        content:
          type: string
          maxLength: 24000
    SkillListItem:
      type: object
      properties:
        id: { type: string }
        scope:
          type: string
          enum: [agent, workspace, organization]
        agentId:
          type: string
          nullable: true
        workspaceId:
          type: string
          nullable: true
        organizationId:
          type: string
          nullable: true
        name:
          type: string
          description: The directory name the agent sees. Immutable.
        description: { type: string }
        enabled: { type: boolean }
        createdByEmail:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        fileCount: { type: integer }
    Skill:
      type: object
      properties:
        id: { type: string }
        scope:
          type: string
          enum: [agent, workspace, organization]
        agentId:
          type: string
          nullable: true
        workspaceId:
          type: string
          nullable: true
        organizationId:
          type: string
          nullable: true
        name: { type: string }
        description: { type: string }
        content:
          type: string
          description: The `SKILL.md` body.
        enabled: { type: boolean }
        createdByEmail:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        files:
          type: array
          items:
            $ref: "#/components/schemas/SkillFile"
    CreateSkillInput:
      type: object
      required: [name, description, content]
      additionalProperties: false
      description: The body and every file together are limited to 32,000 characters. A workspace holds at most 20 skills, an agent 10, an organization 20.
      properties:
        name:
          type: string
          maxLength: 64
          pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          example: release-checklist
        description:
          type: string
          maxLength: 500
          description: One line. The agent reads it to decide when to load the skill.
        content:
          type: string
          maxLength: 24000
        enabled:
          type: boolean
          default: true
        agentId:
          type: string
          description: Create the skill at the agent tier instead of the workspace tier. Hosted agents only.
        files:
          type: array
          maxItems: 5
          items:
            $ref: "#/components/schemas/SkillFile"
    CreateOrgSkillInput:
      type: object
      required: [name, description, content]
      additionalProperties: false
      properties:
        name:
          type: string
          maxLength: 64
          pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
        description:
          type: string
          maxLength: 500
        content:
          type: string
          maxLength: 24000
        enabled:
          type: boolean
          default: true
        files:
          type: array
          maxItems: 5
          items:
            $ref: "#/components/schemas/SkillFile"
    UpdateSkillInput:
      type: object
      description: Partial; at least one field. `name` is immutable.
      additionalProperties: false
      properties:
        description:
          type: string
          maxLength: 500
        content:
          type: string
          maxLength: 24000
        enabled:
          type: boolean
        files:
          type: array
          maxItems: 5
          items:
            $ref: "#/components/schemas/SkillFile"

    # ── Channels ────────────────────────────────────────
    AgentChannelPresence:
      type: object
      properties:
        provider:
          type: string
          example: slack
        status: { type: string }
        transport:
          type: string
          enum: [events, socket]
          description: "`events` = Slack calls OneCLI over HTTPS (needs a public API origin); `socket` = OneCLI's channel adapter holds a Socket Mode connection."
        externalId:
          type: string
          description: The provider-side app id.
        identityRef:
          type: string
          nullable: true
        identityName:
          type: string
          nullable: true
        tenant:
          type: object
          properties:
            externalId: { type: string }
            name:
              type: string
              nullable: true
        managedBy:
          type: object
          nullable: true
          properties:
            name:
              type: string
              nullable: true
            email: { type: string }
        groupThreads:
          type: array
          items:
            type: object
            properties:
              externalThreadId: { type: string }
              createdAt:
                type: string
                format: date-time
        spaces:
          type: array
          description: Every channel the presence is in or was asked about, with its reach state.
          items:
            type: object
            properties:
              externalRef: { type: string }
              label:
                type: string
                nullable: true
              state:
                type: string
                enum: [pending, approved, members_only, blocked]
              decidedAt:
                type: string
                format: date-time
                nullable: true
        people:
          type: array
          description: Everyone who reached the agent by direct message, with their standing.
          items:
            type: object
            properties:
              externalRef: { type: string }
              label:
                type: string
                nullable: true
              state:
                type: string
                enum: [pending, approved, blocked]
              decidedAt:
                type: string
                format: date-time
                nullable: true
    AgentChannels:
      type: object
      properties:
        presences:
          type: array
          items:
            $ref: "#/components/schemas/AgentChannelPresence"
        posture:
          type: object
          description: The transport an attach would use right now, and the alternatives this deployment can offer.
          properties:
            transport:
              type: string
              enum: [events, socket]
            available:
              type: array
              items:
                type: string
                enum: [events, socket]
        organizationId: { type: string }
        viewerIsOrgAdmin: { type: boolean }
        orgIntegrations:
          type: array
          items:
            type: object
            properties:
              provider: { type: string }
              connected: { type: boolean }
              hasCredentials: { type: boolean }
        adapter:
          type: object
          properties:
            online: { type: boolean }
            lastSeenAt:
              type: string
              format: date-time
              nullable: true
    PresenceCreated:
      type: object
      properties:
        presenceId: { type: string }
        transport:
          type: string
          enum: [events, socket]
        installUrl:
          type: string
          nullable: true
          description: Events transport — the one-click "Add to Slack" consent URL.
        settingsUrl:
          type: string
          description: Socket transport — where the app-level token is generated and the app installed.
    ActionApproval:
      type: object
      description: A one-shot approval an agent asked a human for before taking an action (sending a message, for example).
      properties:
        id: { type: string }
        action: { type: string }
        summary: { type: string }
        status:
          type: string
          enum: [pending, approved, executed, failed, rejected, expired]
        reason:
          type: string
          nullable: true
        decidedAt:
          type: string
          format: date-time
          nullable: true
        expiresAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
    Contact:
      type: object
      description: A standing decision about who the agent may message on its own initiative.
      properties:
        id: { type: string }
        kind: { type: string }
        externalRef: { type: string }
        displayName:
          type: string
          nullable: true
        policy:
          type: string
          enum: [ask, allow, blocked]
        updatedAt:
          type: string
          format: date-time
    AgentPeer:
      type: object
      properties:
        agentId: { type: string }
        name: { type: string }
        workspaceName:
          type: string
          nullable: true
          description: Set when the peer lives in another workspace.
        myPolicy:
          type: string
          enum: [ask, allow, blocked]
          description: This agent's side of the relationship.
        theirPolicy:
          type: string
          enum: [ask, allow, blocked]
        conversationId:
          type: string
          nullable: true
          description: The pair conversation on this agent's side, once they have talked.
        paused:
          type: boolean
          description: True while the pair conversation sits at its turn cap waiting for a person to resume it.
        taskOpen:
          type: boolean
          description: True while a peer task is open on this pair (one agent is working for a person through the other).
    PendingChannelApproval:
      oneOf:
        - type: object
          title: Action approval
          properties:
            kind:
              type: string
              enum: [action]
            id: { type: string }
            agentId: { type: string }
            agentName: { type: string }
            summary: { type: string }
            offersAlwaysAllow:
              type: boolean
              description: Whether `approve_always` is a valid decision for this action.
            createdAt:
              type: string
              format: date-time
            expiresAt:
              type: string
              format: date-time
        - type: object
          title: Reach ask
          properties:
            kind:
              type: string
              enum: [reach]
            id: { type: string }
            agentId: { type: string }
            agentName: { type: string }
            provider: { type: string }
            subjectKind:
              type: string
              enum: [space, external_user]
            subjectLabel:
              type: string
              nullable: true
            externalRef:
              type: string
              description: Decide it with `PUT /agents/{agentId}/channels/{provider}/reach/{externalRef}` (spaces) or `…/reach/people/{externalRef}` (people).
            createdAt:
              type: string
              format: date-time
    ChannelUserLink:
      type: object
      description: An explicit link between a provider identity (a Slack user) and a OneCLI member.
      properties:
        id: { type: string }
        externalUserId: { type: string }
        linkedVia:
          type: string
          description: How the link was made (`manual` for admin-made links).
        createdAt:
          type: string
          format: date-time
        user:
          type: object
          properties:
            id: { type: string }
            email: { type: string }
            name:
              type: string
              nullable: true
        integration:
          type: object
          description: List reads only.
          properties:
            provider: { type: string }
    OrgChannels:
      type: object
      properties:
        integrations:
          type: array
          items:
            type: object
            properties:
              provider: { type: string }
              externalId:
                type: string
                description: The provider tenant (Slack team) id.
              name:
                type: string
                nullable: true
              hasCredentials:
                type: boolean
                description: A usable automation credential is stored.
              needsCredentials:
                type: boolean
                description: The credential was refused on rotation and needs a re-paste.
              credentialsRotatedAt:
                type: string
                format: date-time
                nullable: true
              presenceCount:
                type: integer
                description: How many agent presences ride this integration.
        userLinks:
          type: array
          items:
            $ref: "#/components/schemas/ChannelUserLink"
        adapter:
          type: object
          properties:
            online: { type: boolean }
            lastSeenAt:
              type: string
              format: date-time
              nullable: true
        sharedApp:
          type: object
          nullable: true
          description: The deployment's shared Slack app posture and this organization's install, when the deployment has one.

    # ── Approvals (gateway) ─────────────────────────────
    PendingApproval:
      type: object
      description: A manual-approval request awaiting a decision, tagged with the workspace it belongs to.
      properties:
        id:
          type: string
          description: Unique approval ID. Pass to `POST /approvals/{id}/decision` to decide it.
        workspaceId:
          type: string
          description: The workspace this request belongs to. Send it as `X-Workspace-Id` when submitting the decision with an organization key.
        projectId:
          type: string
          deprecated: true
          description: Legacy twin of `workspaceId`, kept for older SDKs.
        method:
          type: string
          description: HTTP method of the held request (for example `POST`).
        url:
          type: string
          description: Full URL of the held request.
        host: { type: string }
        path: { type: string }
        headers:
          type: object
          additionalProperties: { type: string }
          description: Sanitized request headers (auth headers removed).
        bodyPreview:
          type: string
          nullable: true
          description: Human-readable, length-bounded rendering of the request body — safe to display directly.
        summary:
          type: object
          nullable: true
          description: Structured form of `bodyPreview` for richer rendering. `null` on records written before summaries existed.
          required: [action, details]
          properties:
            action:
              type: string
              description: A short verb phrase for the card title.
            kind:
              type: string
              enum: [email, event]
              description: Present when the request is recognised as a specific record type.
            details:
              type: array
              items:
                type: object
                required: [label, value]
                properties:
                  label: { type: string }
                  value: { type: string }
                  url: { type: string }
            subject:
              type: object
              description: The record the request acts on, when one was recognised.
              properties:
                verb: { type: string }
                lead: { type: string }
                record: { type: string }
                row: { type: integer }
        rawBody:
          type: object
          nullable: true
          description: The request body as the agent sent it, for a "raw request" view. Omitted when there was no body.
          required: [text, truncated, binary]
          properties:
            text:
              type: string
              description: UTF-8 text, at most 8 KiB, with secret-named values masked. Empty when `binary`.
            truncated:
              type: boolean
              description: The body is longer than `text`.
            binary:
              type: boolean
              description: The body is not UTF-8 text, so none of it is shown.
            redacted:
              type: boolean
              description: At least one secret-named value was masked as `***`.
        app:
          type: string
          nullable: true
          description: The catalog app id the request's host belongs to (for example `salesforce`), or `null` for an unknown host.
        batch:
          type: object
          nullable: true
          description: Present when the agent tagged the request as part of a task batch so UIs can group approvals. Agent-supplied claims only.
          required: [id]
          properties:
            id:
              type: string
              description: Opaque id shared by the task's requests.
            label:
              type: string
              maxLength: 120
            total:
              type: integer
              minimum: 1
              maximum: 500
              description: How many requests the agent says the task will send.
        agent:
          type: object
          description: The agent that made the request.
          properties:
            id: { type: string }
            name: { type: string }
            externalId:
              type: string
              nullable: true
              description: The agent's `identifier`.
        createdAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time

    # ── User ────────────────────────────────────────────
    User:
      type: object
      properties:
        id: { type: string }
        email: { type: string }
        name:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
    SshKey:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        fingerprint:
          type: string
          description: Server-computed `SHA256:…` fingerprint.
        createdAt:
          type: string
          format: date-time
        lastUsedAt:
          type: string
          format: date-time
          nullable: true

    # ── Instance & utility ──────────────────────────────
    Instance:
      type: object
      properties:
        edition:
          type: string
          enum: [cloud, onprem]
        entitled:
          type: boolean
          description: Whether Enterprise features are enabled on this deployment (always true on Cloud).
        version: { type: string }
        runners:
          type: object
          description: Hosted-agent availability.
          properties:
            registered: { type: boolean }
            online: { type: boolean }
            homeDurability:
              type: string
              description: Present when a runner is online — how agent files survive sleep.
        origins:
          type: object
          properties:
            external: { type: string }
            api: { type: string }
            gateway: { type: string }
            mode: { type: string }
        ssh:
          type: object
          description: Present when the SSH front door is configured.
          properties:
            host: { type: string }
            port: { type: integer }
    EgressFeed:
      type: object
      description: The addresses this deployment calls out from, in the shape of AWS's `ip-ranges.json`.
      properties:
        syncToken:
          type: string
          description: Changes exactly when the list does.
        createDate:
          type: string
          format: date-time
        prefixes:
          type: array
          items:
            type: object
            properties:
              ip_prefix: { type: string }
              region:
                type: string
                nullable: true
              service:
                type: string
                enum: [ONECLI_EGRESS]
              effective:
                type: string
                nullable: true
                description: ISO date the address starts carrying traffic; null = already does.
              expires:
                type: string
                nullable: true
                description: ISO date the address stops carrying traffic; null = no plan to.
    ResourceCounts:
      type: object
      properties:
        agents: { type: integer }
        apps:
          type: integer
          description: Connected app connections (including inherited organization connections).
        llms:
          type: integer
          description: Non-generic secrets (LLM keys).
        secrets:
          type: integer
          description: Generic secrets.
        agentsWorkingInBackground:
          type: integer
          description: Hosted agents currently held awake by live background work.
    Runner:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        online: { type: boolean }
        lastSeenAt:
          type: string
          format: date-time
          nullable: true
        sandboxCount: { type: integer }
        capabilities:
          type: object
          nullable: true
        homeDurability:
          type: string
          nullable: true
        heldAwakeCount: { type: integer }
        heldAwakeCeiling: { type: integer }

    # ── Enterprise: directory ──��────────────────────────
    Member:
      type: object
      properties:
        userId: { type: string }
        email: { type: string }
        name:
          type: string
          nullable: true
        role:
          type: string
          enum: [owner, admin, member]
        status:
          type: string
          enum: [active, suspended]
        ssoExempt:
          type: boolean
          description: Break-glass exemption from the organization's require-SSO policy.
        joinedAt:
          type: string
          format: date-time
    MemberPage:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Member"
        nextCursor:
          type: string
          nullable: true
    Group:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        source:
          type: string
          enum: [manual, scim]
        externalId:
          type: string
          nullable: true
          description: The identity provider's id for SCIM-synced groups.
        memberCount: { type: integer }
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    GroupPage:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Group"
        nextCursor:
          type: string
          nullable: true
    GroupMember:
      type: object
      properties:
        userId: { type: string }
        email: { type: string }
        name:
          type: string
          nullable: true
        addedAt:
          type: string
          format: date-time
    GroupMemberPage:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/GroupMember"
        nextCursor:
          type: string
          nullable: true
    RoleMapping:
      type: object
      properties:
        id: { type: string }
        groupId: { type: string }
        groupName: { type: string }
        role:
          type: string
          enum: [admin, member]
        priority:
          type: integer
          description: Lower wins when a member belongs to several mapped groups.
        memberCount: { type: integer }
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    OrgDomain:
      type: object
      properties:
        id: { type: string }
        domain: { type: string }
        verificationToken:
          type: string
          description: Publish this as a DNS TXT record on the domain, then call verify.
        verifiedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
    SsoConnection:
      type: object
      description: Credentials never leave the server; `config` carries only the non-secret parts.
      properties:
        id: { type: string }
        type:
          type: string
          enum: [saml, oidc]
        status:
          type: string
          description: "`pending` until the first successful sign-in, then `active`; `disabled` when turned off."
        displayName: { type: string }
        cognitoProviderName:
          type: string
          description: The identity-provider name the login page sends users to.
        config:
          type: object
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    SsoEnforcement:
      type: object
      properties:
        ssoRequired: { type: boolean }
        hasActiveConnection: { type: boolean }
        hasVerifiedDomain: { type: boolean }
        canRequire:
          type: boolean
          description: Both preconditions hold, so enforcement may be switched on.
        exemptMemberCount:
          type: integer
          description: Break-glass members. Keep it above zero.
    ScimToken:
      type: object
      properties:
        id: { type: string }
        label: { type: string }
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
    AppAvailability:
      type: object
      properties:
        mode:
          type: string
          enum: [open, restricted]
        rules:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              name:
                type: string
                nullable: true
              userIds:
                type: array
                items: { type: string }
              groupIds:
                type: array
                items: { type: string }
              providers:
                type: array
                items: { type: string }
    ProvisionResult:
      type: object
      description: Returned once when a user is provisioned. The API key is shown only here.
      properties:
        id:
          type: string
          description: Provision record ID.
        userId:
          type: string
          description: Placeholder user ID (becomes the real user after the claim).
        projectId:
          type: string
          description: The pre-created workspace ID. The field keeps its historical name on the wire.
        apiKey:
          type: string
          description: Workspace API key (`oc_…`), usable immediately.
        claimUrl:
          type: string
          description: Link the user opens to claim the account.
        expiresAt:
          type: string
          format: date-time
          description: When the unclaimed provision expires (7 days from creation).
    DriveFolder:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
    ScimListResponse:
      type: object
      description: RFC 7644 list response.
      properties:
        schemas:
          type: array
          items: { type: string }
          example: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"]
        totalResults: { type: integer }
        startIndex: { type: integer }
        itemsPerPage: { type: integer }
        Resources:
          type: array
          items:
            type: object
    ScimUser:
      type: object
      description: RFC 7643 User. OneCLI maps `userName` and `emails[0]` to the member's email, `displayName` (or `name`) to the display name, and `active` to the membership status (`false` = suspended). Group membership is managed through `/Groups`.
      properties:
        schemas:
          type: array
          items: { type: string }
          example: ["urn:ietf:params:scim:schemas:core:2.0:User"]
        id: { type: string }
        userName: { type: string }
        displayName: { type: string }
        name:
          type: object
          description: The name components as supplied by the identity provider.
          properties:
            givenName: { type: string }
            familyName: { type: string }
            formatted: { type: string }
        emails:
          type: array
          items:
            type: object
            properties:
              value: { type: string }
              primary: { type: boolean }
        active: { type: boolean }
        meta:
          type: object
          properties:
            resourceType: { type: string }
            created: { type: string }
            lastModified: { type: string }
            location: { type: string }
    ScimGroup:
      type: object
      description: RFC 7643 Group.
      properties:
        schemas:
          type: array
          items: { type: string }
          example: ["urn:ietf:params:scim:schemas:core:2.0:Group"]
        id: { type: string }
        externalId: { type: string }
        displayName: { type: string }
        members:
          type: array
          items:
            type: object
            properties:
              value: { type: string }
              display: { type: string }
        meta:
          type: object
          properties:
            resourceType: { type: string }
            created: { type: string }
            lastModified: { type: string }
            location: { type: string }
    ScimPatch:
      type: object
      description: RFC 7644 PatchOp.
      required: [schemas, Operations]
      properties:
        schemas:
          type: array
          items: { type: string }
          example: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]
        Operations:
          type: array
          items:
            type: object
            properties:
              op:
                type: string
                enum: [add, remove, replace]
              path: { type: string }
              value: {}
    ScimError:
      type: object
      properties:
        schemas:
          type: array
          items: { type: string }
          example: ["urn:ietf:params:scim:api:messages:2.0:Error"]
        status: { type: string }
        scimType: { type: string }
        detail: { type: string }

tags:
  - name: Workspaces
    description: Workspaces partition an organization's agents, secrets, and connections. Every workspace-scoped endpoint resolves its workspace from the API key (or `X-Workspace-Id` with an organization key).
  - name: Agents
    description: Manage agents, their gateway access tokens, and hosted-agent configuration.
  - name: Grants
    description: Which credentials each agent may use — attach app connections (with optional per-tool allow/approval lists and resource scoping) and secrets. Writes take effect immediately.
  - name: Secrets
    description: Credentials the gateway injects into outbound requests that match a host pattern.
  - name: Apps
    description: The app catalog, OAuth and direct-credential connects, your own OAuth client configuration, permission catalogs, and blocklists.
  - name: Connections
    description: Connected app accounts as a top-level resource.
  - name: Effective policy
    description: Read-only reflections of what the enforced policy means for an agent, a connection, or an app — workspace grants composed with organization rules.
  - name: Conversations
    description: Talk to a hosted agent. A conversation owns its turns, transcript, and attachments.
  - name: Schedules
    description: Cron-style schedules on a hosted agent.
  - name: Memories
    description: A hosted agent's memory files and their revision history.
  - name: Skills
    description: Skills reachable from a workspace — the organization's, the workspace's own, and per-agent ones.
  - name: Channels
    description: Where a hosted agent is reachable (Slack), who may reach it, who it may message, and which other agents it may talk to.
  - name: Approvals
    description: Long-poll for the gateway's manual-approval requests and submit decisions.
  - name: User
    description: Your profile, API keys, SSH keys, and account deletion.
  - name: Instance
    description: Unauthenticated deployment facts and bootstrap endpoints.
  - name: Utility
    description: Counts, vaults, and agent bootstrap helpers.
  - name: Organization
    description: The current organization and its invitations. Free on every edition.
  - name: Organization secrets
    description: Secrets shared into every workspace of the organization.
  - name: Organization policy
    description: The organization's first-match rule set — blocks, allows, rate limits, approvals, and app permissions that bind every workspace.
  - name: Organization connections
    description: App connections shared into every workspace of the organization.
  - name: Organization apps
    description: Connect apps and configure your own OAuth clients at the organization level.
  - name: Organization skills
    description: Skills every workspace in the organization receives.
  - name: Organization channels
    description: The organization's Slack integration and identity links.
  - name: Organization approvals
    description: Long-poll for manual-approval requests across every workspace.
  - name: Members
    description: The organization directory — list, provision, suspend, and remove members.
  - name: Groups
    description: Directory groups and group-to-role mappings.
  - name: Workspace access
    description: Share a workspace with people and groups.
  - name: Domains
    description: Verified email domains, the anchor for SSO and home-realm discovery.
  - name: SSO
    description: SAML and OIDC sign-in connections and organization-wide enforcement.
  - name: SCIM tokens
    description: Bearer tokens for the organization's SCIM endpoint.
  - name: App availability
    description: Restrict which apps each workspace may connect.
  - name: Provisioning
    description: Pre-create members with a ready workspace and API key, then let them claim the account.
  - name: Resource browsing
    description: Browse a connected account's folders to pick resource scopes for a grant.
  - name: SCIM
    description: The SCIM 2.0 provisioning endpoint for identity providers (Okta, Entra ID, and others).

paths:
  # ──────────────────────────────────────────────────────
  # Workspaces
  # ──────────────────────────────────────────────────────
  /workspaces:
    get:
      operationId: listWorkspaces
      summary: List workspaces
      description: |
        The workspaces in the organization the caller can see. Admins and owners see every workspace; other members see the workspaces shared with them.

        No `X-Workspace-Id` is needed. `/v1/projects` is a deprecated alias of this path.
      tags: [Workspaces]
      x-mint: *avail_all
      responses:
        "200":
          description: Workspaces, oldest first
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Workspace"
    post:
      operationId: createWorkspace
      summary: Create a workspace
      description: Requires the admin or owner role where roles are enforced. The response includes the caller's API key for the new workspace.
      tags: [Workspaces]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 100
                  description: Display name; at least one letter or digit. The slug is derived from it.
                  example: "Growth team"
      responses:
        "201":
          description: Workspace created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceCreated"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient permissions, or the organization's workspace quota is reached (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: A workspace with the same slug already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /workspaces/{workspaceId}:
    get:
      operationId: getWorkspace
      summary: Get a workspace
      description: Visibility-fenced like the list — a workspace the caller cannot see answers `404`.
      tags: [Workspaces]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      responses:
        "200":
          description: The workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workspace"
        "404":
          description: Workspace not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: updateWorkspace
      summary: Rename a workspace
      description: A management action — organization admins, or the workspace's `owner`-role members. At least one field must be provided.
      tags: [Workspaces]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 100
      responses:
        "200":
          description: The updated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workspace"
        "400":
          description: Invalid body (for example no field provided)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not allowed to manage this workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Workspace not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteWorkspace
      summary: Delete a workspace
      description: Deletes the workspace and everything in it (agents, secrets, connections, conversations). The organization's last workspace cannot be deleted. A management action, like rename.
      tags: [Workspaces]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      responses:
        "204":
          description: Workspace deleted
        "400":
          description: Cannot delete the only workspace in the organization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not allowed to manage this workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Workspace not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /workspaces/{workspaceId}/channel-approvals:
    get:
      operationId: listWorkspaceChannelApprovals
      summary: List pending channel approvals
      description: Every pending action approval and reach ask across the workspace's hosted agents, oldest first — the dashboard's approvals bell. Decide them through the agent's channel endpoints.
      tags: [Workspaces]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      responses:
        "200":
          description: Pending items
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PendingChannelApproval"
        "404":
          description: Workspace not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /workspaces/{workspaceId}/access:
    get:
      operationId: getWorkspaceAccess
      summary: List who a workspace is shared with
      description: The workspace's human access bindings — people (with their management role) and groups. A management action.
      tags: [Workspace access]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      responses:
        "200":
          description: The bindings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceAccessBindings"
        "403":
          description: Not entitled, or not allowed to manage this workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    put:
      operationId: setWorkspaceAccess
      summary: Replace who a workspace is shared with
      description: |
        Replaces the full set of shares. People are added, removed, or re-roled to match `users`; groups to match `groupIds`. On Cloud, sharing with people needs the Team plan and sharing with groups needs the Enterprise plan; the check applies to additions only, so an existing binding never blocks an edit.
      tags: [Workspace access]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [users, groupIds]
              properties:
                users:
                  type: array
                  maxItems: 1000
                  items:
                    type: object
                    required: [userId, role]
                    properties:
                      userId: { type: string }
                      role:
                        type: string
                        enum: [owner, member]
                groupIds:
                  type: array
                  maxItems: 1000
                  items: { type: string }
      responses:
        "200":
          description: The diff that was applied
          content:
            application/json:
              schema:
                type: object
                properties:
                  added: { type: integer }
                  removed: { type: integer }
                  roleChanged: { type: integer }
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled, plan too low for the addition, or not allowed to manage this workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Agents
  # ──────────────────────────────────────────────────────
  /agents:
    get:
      operationId: listAgents
      summary: List agents
      description: |
        Returns all agents in the current workspace, newest first, each with its gateway access token.

        Pass `include=grants-summary` to also get each agent's granted credentials (which app connections and secrets are attached) in one request. Any other `include` value is rejected with `422`.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - name: include
          in: query
          required: false
          schema:
            type: string
            enum: [grants-summary]
          description: "`grants-summary` adds a `grantsSummary` object to each agent."
      responses:
        "200":
          description: List of agents — plain, or with `grantsSummary` when `include=grants-summary` was sent.
          content:
            application/json:
              schema:
                type: array
                items:
                  anyOf:
                    - $ref: "#/components/schemas/Agent"
                    - $ref: "#/components/schemas/AgentWithGrantsSummary"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Unknown `include` value
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createAgent
      summary: Create an agent
      description: |
        Creates an agent in the current workspace. The identifier must be lowercase alphanumeric with hyphens, starting with a letter or digit (max 50 characters), and unique in the workspace.

        `kind` picks the agent's home: `byo` (default) runs on your own machine and routes through the gateway; `hosted` runs in a OneCLI sandbox and needs a runner to be online. `harness` and `instructions` apply to hosted agents only.

        A new agent is attached to every workspace connection, custom secret, and LLM key automatically, so it can work right away; adjust with the Grants endpoints. The response's `llmKeys` lists the LLM keys it received — empty means the workspace has none yet.

        The response omits the access token — read it from `GET /agents` or regenerate it with `POST /agents/{agentId}/regenerate-token`. Re-creating an existing identifier answers `409`.
      tags: [Agents]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, identifier]
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 255
                  example: "Claude Assistant"
                identifier:
                  type: string
                  pattern: "^[a-z0-9][a-z0-9-]{0,49}$"
                  example: "claude-assistant"
                kind:
                  type: string
                  enum: [byo, hosted]
                  default: byo
                harness:
                  type: string
                  description: Hosted agents only. The agent runtime; omit for the default.
                instructions:
                  type: string
                  maxLength: 20000
                  description: Hosted agents only. The agent's standing brief.
                parentIdentifier:
                  type: string
                  pattern: "^[a-z0-9][a-z0-9-]{0,49}$"
                  deprecated: true
                  description: Accepted for older CLIs that send it on sub-agent creation; ignored by the server.
      responses:
        "201":
          description: Agent created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentCreated"
        "400":
          description: Validation error (including `harness`/`instructions` on a `byo` agent)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: The organization's agent quota is reached, or this kind of agent cannot be created for the organization (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: An agent with this identifier already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: No runner is available to host the agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}:
    get:
      operationId: getAgent
      summary: Get an agent
      description: The agent with its hosted configuration (`harness`, `model`, `instructions`). Never returns the access token.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: The agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentDetail"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: updateAgent
      summary: Update an agent
      description: |
        Rename the agent, or (hosted agents) change its brief and model override. At least one field is required. `null` clears `instructions`, `model`, or `effort`. `kind` and `harness` are immutable.

        Changing the model restarts the hosted agent's sandbox.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 255
                instructions:
                  type: string
                  nullable: true
                  maxLength: 20000
                model:
                  type: string
                  nullable: true
                  maxLength: 200
                  description: A model id from `GET /agents/{agentId}/models`.
                effort:
                  type: string
                  nullable: true
                  description: A reasoning-effort id from `GET /agents/{agentId}/models`.
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteAgent
      summary: Delete an agent
      description: Deletes the agent, its grants, conversations, schedules, memories, and channel presences. The gateway stops honoring its token immediately.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "204":
          description: Agent deleted
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/regenerate-token:
    post:
      operationId: regenerateAgentToken
      summary: Regenerate an agent's access token
      description: Mints a new gateway access token and invalidates the old one. A hosted agent's sandbox is restarted with the new token.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: The new token
          content:
            application/json:
              schema:
                type: object
                properties:
                  accessToken:
                    type: string
                    example: "aoc_…"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/restart:
    post:
      operationId: restartAgent
      summary: Restart a hosted agent
      description: Every conversation of the agent starts fresh and its sandbox stops; the next message cold-starts it. Work in flight is lost.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: Restart requested
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Not a hosted agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/models:
    get:
      operationId: getAgentModels
      summary: List an agent's available models
      description: The models the agent's granted LLM key can run, the provider's defaults, and the agent's current selection. Always `200` for an existing agent — no granted key yields an empty list.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: The model view
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentModels"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/image:
    put:
      operationId: setAgentImage
      summary: Upload an agent's avatar
      description: Raw image bytes in the body (PNG, JPEG, GIF, or WebP; SVG is refused). The format is detected from the bytes, not the header. Capped at 1 MB. Each upload rotates the public URL.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      requestBody:
        required: true
        content:
          image/*:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: The new public avatar URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  imageUrl: { type: string }
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          description: The image exceeds the size cap
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Empty, unreadable, or unsupported image
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteAgentImage
      summary: Remove an agent's avatar
      description: Back to the default mark. Shared caches may keep serving the old URL for up to an hour.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "204":
          description: Avatar removed
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/conversations/direct:
    put:
      operationId: ensureDirectConversation
      summary: Open your direct conversation with an agent
      description: |
        Returns the caller's private direct thread with the agent, creating it on first call. Idempotent: every call returns the same conversation. Each workspace member has their own direct thread.

        When a dashboard session opens a brand-new thread, the agent greets first; API-key callers get the conversation with no greeting, so a `POST …/turns` right after cannot collide with one.
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: The direct conversation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Not a hosted agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/ssh-certificate:
    post:
      operationId: mintAgentSshCertificate
      summary: Mint an SSH certificate for an agent
      description: |
        Signs a short-lived OpenSSH user certificate that lets you SSH into the hosted agent's sandbox. Name exactly one key source: an ed25519 public key pasted inline, or the id of a key registered at `POST /user/ssh-keys`.

        Answers `404` when the deployment has no SSH front door (see `ssh` in `GET /instance`).
      tags: [Agents]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  title: Inline public key
                  required: [publicKey]
                  properties:
                    publicKey:
                      type: string
                      maxLength: 1024
                      description: An `ssh-ed25519 …` public key line.
                  additionalProperties: false
                - type: object
                  title: Registered key
                  required: [sshKeyId]
                  properties:
                    sshKeyId:
                      type: string
                  additionalProperties: false
      responses:
        "200":
          description: The certificate and where to connect
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SshCertificate"
        "400":
          description: Body names both sources, or neither
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Agent not found, SSH not configured, or unknown key id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Not a hosted agent, or the public key is not ed25519
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Grants — which credentials an agent may use
  # ──────────────────────────────────────────────────────
  /agents/{agentId}/grants:
    get:
      operationId: getAgentGrants
      summary: Get an agent's grants
      description: |
        Everything granted to one agent: the app connections it may use (with the per-tool `allow`/`ask` lists when access is `custom`, and any resource scoping) and the secrets attached to it.

        Grants are the writable **intent**. To see the **effect** — what requests actually get through once organization policy is applied on top — use `GET /agents/{agentId}/effective-credentials` and `GET /policy/effective-app-permissions`.
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: The agent's grant set
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentGrants"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/grants/connections/{connectionId}:
    put:
      operationId: setAgentConnectionGrant
      summary: Attach a connection to an agent
      description: |
        Attach an app connection to an agent, or change what the agent may do with it. Takes effect immediately — there is no draft/publish step.

        Send `{"access": "full"}` for every tool the app supports, or `{"access": "custom", "allow": [...], "ask": [...]}` to name tools: `allow` runs freely, `ask` pauses each call for human approval, and everything unnamed is blocked. Add `resources` to scope the credential to specific repositories or folders (Enterprise).
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/connectionId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectionGrantInput"
      responses:
        "200":
          description: The agent's updated grant set
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentGrants"
        "403":
          description: Resource scoping without the Enterprise entitlement (self-hosted) or a plan that includes it (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Agent or connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            Invalid grant. The two validation laws: a custom grant must name at least one tool across `allow` and `ask` (`Custom access needs at least one allowed or approval tool. To block everything, detach instead.`), and a tool can't appear in both lists (`A tool can't be both always-allowed and require approval.`). Unknown tool ids are also rejected — valid ids come from `GET /apps/{provider}/permission-definition`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: removeAgentConnectionGrant
      summary: Detach a connection from an agent
      description: Removes the agent's grant for this connection. The gateway stops serving the connection's credentials to the agent immediately.
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/connectionId"
      responses:
        "204":
          description: Grant removed
        "404":
          description: Agent or connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/grants/secrets/{secretId}:
    put:
      operationId: setAgentSecretGrant
      summary: Attach a secret to an agent
      description: Attach a secret (an API key or LLM key) to an agent. Secrets are all-or-nothing — there are no per-tool lists, so the request takes **no body**.
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/secretId"
      responses:
        "200":
          description: The agent's updated grant set
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentGrants"
        "404":
          description: Agent or secret not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: removeAgentSecretGrant
      summary: Detach a secret from an agent
      description: Removes the agent's grant for this secret. The gateway stops injecting it for the agent immediately.
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/secretId"
      responses:
        "204":
          description: Grant removed
        "404":
          description: Agent or secret not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /connections/{connectionId}/grants:
    get:
      operationId: getConnectionGrants
      summary: Get a connection's grants
      description: Which agents hold a grant for this connection, with each agent's access level — the reverse orientation of `GET /agents/{agentId}/grants`.
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
      responses:
        "200":
          description: Per-agent grants for the connection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionGrants"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /connections/{connectionId}/grants/agents/{agentId}:
    put:
      operationId: setConnectionAgentGrant
      summary: Attach an agent to a connection
      description: The same write as `PUT /agents/{agentId}/grants/connections/{connectionId}`, addressed from the connection's side. Takes the same body and returns the agent's updated grant set.
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
        - $ref: "#/components/parameters/agentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectionGrantInput"
      responses:
        "200":
          description: The agent's updated grant set
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentGrants"
        "404":
          description: Agent or connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Invalid grant (see the agent-side endpoint)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: removeConnectionAgentGrant
      summary: Detach an agent from a connection
      description: The same write as `DELETE /agents/{agentId}/grants/connections/{connectionId}`, addressed from the connection's side.
      tags: [Grants]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
        - $ref: "#/components/parameters/agentId"
      responses:
        "204":
          description: Grant removed
        "404":
          description: Agent or connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Secrets
  # ──────────────────────────────────────────────────────
  /secrets:
    get:
      operationId: listSecrets
      summary: List secrets
      description: Returns the workspace's secrets plus the organization secrets shared into it (`scope` tells them apart), newest first. Secret values are never returned. LLM keys carry a `lastError` health marker.
      tags: [Secrets]
      x-mint: *avail_all
      responses:
        "200":
          description: List of secrets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Secret"
    post:
      operationId: createSecret
      summary: Create a secret
      description: |
        Creates a workspace secret. The gateway uses secrets to inject credentials into outbound requests matching the host and path patterns.

        - **anthropic** and **openai** types are auto-configured (an `openai` value that is a full connection string also sets the host).
        - **generic** type requires an `injectionConfig`.
        - Instead of an inline `value`, set `valueSource` to `onepassword` and provide `opRef`; the gateway resolves the value at request time through the workspace's 1Password vault connection.

        A new secret is attached to every agent in the workspace automatically (`attachedAgents`); adjust with the Grants endpoints.
      tags: [Secrets]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSecretInput"
      responses:
        "201":
          description: Secret created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecretCreated"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: The organization's secret quota is reached (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /secrets/{secretId}:
    patch:
      operationId: updateSecret
      summary: Update a secret
      description: Updates one or more fields on a workspace secret. At least one field must be provided. Organization secrets are updated through `PATCH /org/secrets/{secretId}`.
      tags: [Secrets]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/secretId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSecretInput"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Secret not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteSecret
      summary: Delete a secret
      description: Deletes a workspace secret and every grant that referenced it.
      tags: [Secrets]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/secretId"
      responses:
        "204":
          description: Secret deleted
        "404":
          description: Secret not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Apps
  # ──────────────────────────────────────────────────────
  /apps:
    get:
      operationId: listApps
      summary: List apps
      description: Every app in the catalog with its connection method, whether your own OAuth client is configured (workspace or organization level), and the workspace's connections of it.
      tags: [Apps]
      x-mint: *avail_all
      responses:
        "200":
          description: The app catalog
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/App"

  /apps/configured:
    get:
      operationId: listConfiguredApps
      summary: List configured apps
      description: Provider ids that have an enabled OAuth client configuration, at the workspace level or inherited from the organization.
      tags: [Apps]
      x-mint: *avail_all
      responses:
        "200":
          description: Provider ids
          content:
            application/json:
              schema:
                type: array
                items: { type: string }

  /apps/available:
    get:
      operationId: getAppAvailabilityForWorkspace
      summary: Get the app allowlist for this workspace
      description: "Which apps this workspace may connect under the organization's app-availability rules. Always `restricted: false` on deployments without the feature."
      tags: [Apps]
      x-mint: *avail_all
      responses:
        "200":
          description: The allowlist
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppAvailabilityView"

  /apps/env-defaults:
    get:
      operationId: listEnvDefaultApps
      summary: List apps with platform-provided OAuth clients
      description: Provider ids whose OAuth client credentials are supplied by the deployment's environment, so they connect without `POST /apps/{provider}/config`. On Cloud that is every OAuth app; on a self-hosted deployment it reflects what the operator configured. No workspace context needed.
      tags: [Apps]
      x-mint: *avail_all
      responses:
        "200":
          description: Provider ids
          content:
            application/json:
              schema:
                type: array
                items: { type: string }

  /apps/permission-definitions:
    get:
      operationId: listPermissionDefinitions
      summary: List every app's tool catalog
      description: The static tool catalogs (tool ids, names, descriptions by read/write group) for every app that has one. No workspace context needed.
      tags: [Apps]
      x-mint: *avail_all
      responses:
        "200":
          description: Permission definitions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PermissionDefinition"

  /apps/{provider}:
    get:
      operationId: getApp
      summary: Get an app
      description: One app with its connections in the current workspace and a human-readable `hint` on what to do next.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "200":
          description: The app
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppDetail"
        "404":
          description: Unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/{provider}/permission-definition:
    get:
      operationId: getPermissionDefinition
      summary: Get an app's tool catalog
      description: The tool ids a grant's `allow`/`ask` lists and a policy rule's `tools` may reference. No workspace context needed.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "200":
          description: The catalog
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PermissionDefinition"
        "404":
          description: Unknown provider, or the app has no catalog
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/{provider}/authorize:
    get:
      operationId: authorizeApp
      summary: Start an OAuth connect
      description: |
        Redirects the browser to the provider's consent screen. A browser navigation cannot set headers, so the scope rides the query string instead: `_token` (the bearer), `_workspace` (the workspace id), and optionally `_org` to connect at the organization level. After consent the provider calls back to `/v1/apps/{provider}/callback` and the browser lands on the dashboard's `/app-connect/{provider}` page with `status=success` or `status=error`.

        Pass `connectionId` to re-authenticate an existing connection instead of creating a new one.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
        - name: _token
          in: query
          required: false
          schema: { type: string }
          description: The bearer token, for browser navigations that cannot send `Authorization`.
        - name: _workspace
          in: query
          required: false
          schema: { type: string }
          description: The workspace id, for browser navigations that cannot send `X-Workspace-Id`.
        - name: _org
          in: query
          required: false
          schema: { type: string }
          description: Connect at the organization level (admin only). Prefer `GET /org/apps/{provider}/authorize`.
        - name: connectionId
          in: query
          required: false
          schema: { type: string }
          description: Re-authenticate this connection.
        - name: agent_name
          in: query
          required: false
          schema: { type: string }
          description: Carried through to the success redirect for the CLI's post-connect flow.
      responses:
        "302":
          description: Redirect to the provider's consent screen
        "400":
          description: Not an OAuth app, or its OAuth client is not configured
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/{provider}/connect:
    post:
      operationId: connectApp
      summary: Connect an app with credentials
      description: |
        Creates a connection for an API-key or credentials-import app from the credentials in `fields`. Re-sending credentials whose extracted identity (an email, a username) matches an existing connection re-authenticates that connection instead of creating a duplicate; pass `connectionId` to target one explicitly.

        A brand-new connection is attached to every agent in the workspace automatically; the response carries it under `connection` so you can adjust grants right away. Send `X-Organization-Id` to connect at the organization level (admin only) — `POST /org/apps/{provider}/connect` is the canonical form.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectInput"
      responses:
        "200":
          description: Connected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectResult"
        "400":
          description: Unknown or OAuth-only provider, missing `fields`, or a required field is empty
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: The organization's connection quota is reached (Cloud), or not a member of the `X-Organization-Id` organization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/{provider}/config:
    get:
      operationId: getAppConfig
      summary: Get an app's OAuth client configuration
      description: "Whether the workspace has its own OAuth client for the app. When it does not and an organization-level one applies, the answer carries `source: \"organization\"`. Credential values are never returned."
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "200":
          description: Configuration state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppConfigState"
    post:
      operationId: setAppConfig
      summary: Set an app's OAuth client configuration
      description: Store your own OAuth client credentials for the app at the workspace level ("bring your own client"). For an app that shares a vendor client (Google, Atlassian, Microsoft) the credentials are saved once for the vendor and serve every app of that vendor in the workspace. Replacing an existing configuration disconnects the connections minted from it. Requires the admin or owner role wherever roles are enforced (see [Roles](/api-reference/editions#roles)).
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AppConfigInput"
      responses:
        "201":
          description: Saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: The app is not configurable, or the body is missing its required fields
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteAppConfig
      summary: Remove an app's OAuth client configuration
      description: Removes the workspace's own OAuth client for the app and disconnects the connections minted from it. Requires the admin or owner role wherever roles are enforced.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "204":
          description: Removed
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/{provider}/config/toggle:
    patch:
      operationId: toggleAppConfig
      summary: Enable or disable an app's OAuth client configuration
      description: Requires the admin or owner role wherever roles are enforced.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled: { type: boolean }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/{provider}/blocklist:
    get:
      operationId: getAppBlocklist
      summary: Get an app's blocklist state
      description: The app's predefined blockable hosts and whether each is blocked at the workspace or organization level. An organization block overrides the workspace's view. To block an arbitrary host, write an organization policy rule instead.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "200":
          description: Blocklist state
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/BlocklistHostState"
        "404":
          description: Unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: activateAppBlocklistHost
      summary: Block one of an app's hosts
      description: Activates (or re-enables) the block for one of the app's predefined hosts.
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [hostId]
              properties:
                hostId:
                  type: string
                  description: A `hostId` from the blocklist state.
      responses:
        "201":
          description: The host's new state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlocklistHostState"
        "400":
          description: Missing `hostId`, or not one of the app's hosts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/{provider}/blocklist/{ruleId}:
    patch:
      operationId: toggleAppBlocklistRule
      summary: Enable or disable a blocklist entry
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
        - $ref: "#/components/parameters/blocklistRuleId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled: { type: boolean }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Missing `enabled`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Rule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: removeAppBlocklistRule
      summary: Remove a blocklist entry
      tags: [Apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
        - $ref: "#/components/parameters/blocklistRuleId"
      responses:
        "204":
          description: Removed
        "404":
          description: Rule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/dropbox/folders:
    get:
      operationId: listDropboxFolders
      summary: Browse a Dropbox connection's folders
      description: The subfolders of `path` on the connected Dropbox account, for picking `folders` in a grant's resource scope.
      tags: [Resource browsing]
      x-mint: *avail_enterprise
      parameters:
        - name: connectionId
          in: query
          required: true
          schema: { type: string }
        - name: path
          in: query
          required: false
          schema: { type: string }
          description: Absolute folder path; empty for the root.
      responses:
        "200":
          description: Subfolders
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id: { type: string }
                    name: { type: string }
                    pathLower: { type: string }
                    pathDisplay:
                      type: string
                      description: Use this as the `folders` entry in a grant's resource scope.
        "400":
          description: Missing `connectionId`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/google-drive/folders:
    get:
      operationId: listGoogleDriveFolders
      summary: Browse a Google Drive connection's folders
      description: The subfolders of `parentId` on the connected Drive (no `parentId` = My Drive's top level plus shared drives), for picking `driveFolders` in a grant's resource scope.
      tags: [Resource browsing]
      x-mint: *avail_enterprise
      parameters:
        - name: connectionId
          in: query
          required: true
          schema: { type: string }
        - name: parentId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Subfolders
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/DriveFolder"
        "400":
          description: Missing `connectionId`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /apps/google-drive/folder-names:
    get:
      operationId: resolveGoogleDriveFolderNames
      summary: Resolve Google Drive folder names
      description: Labels for saved folder ids. Unknown ids resolve to `null`.
      tags: [Resource browsing]
      x-mint: *avail_enterprise
      parameters:
        - name: connectionId
          in: query
          required: true
          schema: { type: string }
        - name: ids
          in: query
          required: true
          schema: { type: string }
          description: Comma-separated folder ids.
      responses:
        "200":
          description: Folder id to name
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: string
                  nullable: true
        "403":
          description: Not entitled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Connections
  # ──────────────────────────────────────────────────────
  /connections:
    get:
      operationId: listConnections
      summary: List connections
      description: Every connected account the workspace can use, newest first — its own plus the organization's (`scope` tells them apart). Filter with `?provider=`.
      tags: [Connections]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/providerQuery"
      responses:
        "200":
          description: Connections
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AppConnection"

  /connections/{connectionId}:
    patch:
      operationId: renameConnection
      summary: Rename a connection
      description: Sets the label that distinguishes multiple accounts of one app. Works on the workspace's own connections and on organization connections.
      tags: [Connections]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label]
              properties:
                label:
                  type: string
                  example: "support@acme.com"
      responses:
        "200":
          description: Renamed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionRenamed"
        "400":
          description: Label is required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteConnection
      summary: Disconnect
      description: Removes the connection and every grant that referenced it. When it was the provider's last connection, the app's blocklist entries are removed too.
      tags: [Connections]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
      responses:
        "204":
          description: Disconnected
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Effective policy (read-only reflections)
  # ──────────────────────────────────────────────────────
  /policy/effective-app-permissions:
    get:
      operationId: getEffectiveAppPermissions
      summary: Effective app permissions (workspace)
      description: |
        What the enforced policy decides for every tool of one app, in this workspace: the agent's grant composed with organization rules. Without `agentId` the answer is the all-agents baseline; `variesByIdentity` counts the identity-scoped rules it cannot show. Add `connectionId` to reflect one specific account as the injected connection and to get the resource boundary (`orgResources`, `effectiveResources`).

        Organization rule names are visible to organization admins only; other viewers see `redacted: true` on the provenance.
      tags: [Effective policy]
      x-mint: *avail_all
      parameters:
        - name: provider
          in: query
          required: true
          schema: { type: string }
        - name: agentId
          in: query
          required: false
          schema: { type: string }
        - name: connectionId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: The per-tool verdicts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EffectiveAppPermissions"
        "422":
          description: Missing or invalid `provider`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/effective-credentials:
    get:
      operationId: getAgentEffectiveCredentials
      summary: Effective credentials of an agent
      description: Every secret and connection the agent can actually use once organization rules are applied, with a status per credential and the rules that decided it.
      tags: [Effective policy]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: The agent's usable credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EffectiveCredentials"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /connections/{connectionId}/effective-agents:
    get:
      operationId: getConnectionEffectiveAgents
      summary: Effective agent access to a connection
      description: For one connection, which agents can use it and how far (usable, limited, blocked, none), with a summary of their tool decisions when the app has a catalog.
      tags: [Effective policy]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
      responses:
        "200":
          description: Per-agent access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionEffectiveAgents"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Conversations
  # ──────────────────────────────────────────────────────
  /conversations:
    get:
      operationId: listConversations
      summary: List conversations
      description: The workspace's conversations, newest first, optionally one agent's. Other people's direct threads are never listed.
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - name: agentId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Conversations
          content:
            application/json:
              schema:
                type: object
                properties:
                  conversations:
                    type: array
                    items:
                      $ref: "#/components/schemas/Conversation"
    post:
      operationId: createConversation
      summary: Start a conversation
      description: Opens a new, non-direct conversation with a hosted agent. For a private thread with the agent use `PUT /agents/{agentId}/conversations/direct` instead.
      tags: [Conversations]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [agentId]
              properties:
                agentId: { type: string }
                source:
                  type: string
                  enum: [web, slack, cron, watch, agent]
                  default: web
                  description: Where the conversation is born. Defaults to `web`.
                title:
                  type: string
                  maxLength: 200
                externalRef:
                  type: string
                  maxLength: 500
                  description: Your own reference for the thread.
      responses:
        "201":
          description: The conversation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, or not a hosted agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}:
    get:
      operationId: getConversation
      summary: Get a conversation
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
      responses:
        "200":
          description: The conversation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/turns:
    get:
      operationId: listTurns
      summary: List turns
      description: A newest-first window of the conversation's turns, answered in chronological order. Page backwards with `before` (a turn id), never an offset.
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: before
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Return turns older than this turn.
      responses:
        "200":
          description: The window
          content:
            application/json:
              schema:
                type: object
                properties:
                  turns:
                    type: array
                    items:
                      $ref: "#/components/schemas/Turn"
                  hasMore: { type: boolean }
                  oldestSeq:
                    type: integer
                    nullable: true
                    description: The transcript `seq` of the window's first event, for fetching the matching transcript page.
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createTurn
      summary: Send a message (strict)
      description: Says something to the agent. Exactly one turn may be active per conversation; a message sent while one is in flight answers `409`. Use `POST …/messages` to follow up on an active turn instead.
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TurnInput"
      responses:
        "201":
          description: The queued turn
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Turn"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: A turn is already in flight
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/messages:
    post:
      operationId: sendMessage
      summary: Send a message
      description: Says something whatever the agent is doing. A free conversation gets an ordinary turn; a turn in flight gets a follow-up that steers into it. The response says which. At most 10 follow-ups may wait on one conversation.
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TurnInput"
      responses:
        "201":
          description: What the message became
          content:
            application/json:
              schema:
                type: object
                properties:
                  kind:
                    type: string
                    enum: [turn, followUp]
                  turn:
                    $ref: "#/components/schemas/Turn"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Too many follow-ups are waiting
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/events:
    get:
      operationId: readTranscript
      summary: Read the transcript
      description: The conversation's event log — the source of truth every live stream reconciles against. Read `since < seq <= until`, at most `limit` events, and continue from `nextSince`.
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
        - name: since
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
        - name: until
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 200
      responses:
        "200":
          description: A transcript page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transcript"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/stream:
    get:
      operationId: streamTranscript
      summary: Stream the transcript
      description: |
        A live Server-Sent Events tail of the transcript. Authenticated like every other endpoint (a bearer token, nothing in the URL), so read it with `fetch`, not `EventSource`. Each event's SSE `id` is its `seq`; resume with `?since=` or a `Last-Event-ID` header.
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
        - name: since
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
          description: Replay events after this `seq` before tailing.
      responses:
        "200":
          description: An SSE stream of transcript events
          content:
            text/event-stream:
              schema:
                type: string
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/attachments:
    post:
      operationId: uploadAttachment
      summary: Upload an attachment
      description: Stages one file for a message. Raw bytes in the body with the file's own `Content-Type`; name it with `?name=`. Capped at 10 MB. The attachment stays `pending` until a message references it in `attachmentIds`; unbound attachments are swept after 24 hours.
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
        - name: name
          in: query
          required: true
          schema: { type: string }
          description: The file name.
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "201":
          description: The staged attachment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentMeta"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          description: The file exceeds the size cap
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Missing name, empty body, or unreadable upload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/attachments/{attachmentId}:
    get:
      operationId: downloadAttachment
      summary: Download an attachment
      description: "The bytes, always served as `Content-Disposition: attachment` with `nosniff`."
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
        - $ref: "#/components/parameters/attachmentId"
      responses:
        "200":
          description: The file
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "404":
          description: Conversation or attachment not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/attachments/{attachmentId}/meta:
    get:
      operationId: getAttachmentMeta
      summary: Get an attachment's metadata
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
        - $ref: "#/components/parameters/attachmentId"
      responses:
        "200":
          description: Metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentMeta"
        "404":
          description: Conversation or attachment not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /conversations/{conversationId}/attachments/{attachmentId}/download-url:
    get:
      operationId: getAttachmentDownloadUrl
      summary: Get a short-lived download URL
      description: Where a browser should fetch the bytes from. Answers a presigned URL when the deployment stores attachments in object storage, or `204` when the bytes are served inline (use the download endpoint).
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/conversationId"
        - $ref: "#/components/parameters/attachmentId"
      responses:
        "200":
          description: A presigned URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  url: { type: string }
                  expiresAt:
                    type: string
                    format: date-time
        "204":
          description: Bytes are inline; use `GET …/attachments/{attachmentId}`
        "404":
          description: Conversation or attachment not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /turns/{turnId}/abort:
    post:
      operationId: abortTurn
      summary: Abort a turn
      description: "Asks the agent to stop. A turn that never reached a sandbox is abandoned outright (`aborted: true`); a running one is flagged and the sandbox confirms (`delivered: true`)."
      tags: [Conversations]
      x-mint: *avail_all
      parameters:
        - name: turnId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The outcome
          content:
            application/json:
              schema:
                type: object
                properties:
                  aborted: { type: boolean }
                  delivered: { type: boolean }
        "404":
          description: Turn not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: The turn has already finished
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Schedules
  # ──────────────────────────────────────────────────────
  /agents/{agentId}/crons:
    get:
      operationId: listCrons
      summary: List an agent's schedules
      description: Hosted agents only.
      tags: [Schedules]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: Schedules
          content:
            application/json:
              schema:
                type: object
                properties:
                  crons:
                    type: array
                    items:
                      $ref: "#/components/schemas/AgentCron"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Not a hosted agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createCron
      summary: Create a schedule
      description: Each firing runs `prompt` as a turn in the creator's direct thread with the agent, so reports land where the schedule was born. The cron expression is validated by computing its next occurrence; an expression that can never fire is rejected. An agent holds at most 20 schedules.
      tags: [Schedules]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCronInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentCron"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, invalid schedule, not a hosted agent, or the per-agent limit is reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/crons/{cronId}:
    patch:
      operationId: updateCron
      summary: Update, pause, or resume a schedule
      tags: [Schedules]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/cronId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCronInput"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentCron"
        "404":
          description: Agent or schedule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteCron
      summary: Delete a schedule
      tags: [Schedules]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/cronId"
      responses:
        "204":
          description: Deleted
        "404":
          description: Agent or schedule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/crons/{cronId}/run:
    post:
      operationId: runCronNow
      summary: Run a schedule now
      description: Pulls the next firing to now. The normal scheduler does the rest, so a forced run goes through exactly the machinery a scheduled one does.
      tags: [Schedules]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/cronId"
      responses:
        "200":
          description: The schedule, with `nextFireAt` pulled forward
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentCron"
        "404":
          description: Agent or schedule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Memories
  # ──────────────────────────────────────────────────────
        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/memories:
    get:
      operationId: listMemories
      summary: List or search an agent's memories
      description: Without `q`, the index of every memory (no content). With `q`, ranked full-text hits with a snippet each.
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - name: q
          in: query
          required: false
          schema:
            type: string
            maxLength: 500
      responses:
        "200":
          description: The index, or search hits
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: Index
                    required: [memories]
                    properties:
                      memories:
                        type: array
                        items:
                          $ref: "#/components/schemas/AgentMemoryIndex"
                  - type: object
                    title: Search hits
                    required: [hits]
                    properties:
                      hits:
                        type: array
                        items:
                          $ref: "#/components/schemas/MemorySearchHit"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createMemory
      summary: Create a memory
      description: Keys are lowercase words separated by single hyphens and are unique per agent. Content is capped at 100,000 characters.
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key, content]
              additionalProperties: false
              properties:
                key:
                  type: string
                  maxLength: 64
                  example: deploy-notes
                content:
                  type: string
                  maxLength: 100000
                title:
                  type: string
                  description: One line.
                description:
                  type: string
                  description: One line.
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentMemory"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: A memory with this key already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/memories/{memoryId}:
    get:
      operationId: getMemory
      summary: Get a memory
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/memoryId"
      responses:
        "200":
          description: The memory with its content
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentMemory"
        "404":
          description: Agent or memory not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: updateMemory
      summary: Update a memory
      description: Partial; `key` is immutable. `null` clears `title` or `description`. Every update appends a revision.
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/memoryId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                title:
                  type: string
                  nullable: true
                description:
                  type: string
                  nullable: true
                content:
                  type: string
                  maxLength: 100000
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentMemory"
        "404":
          description: Agent or memory not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteMemory
      summary: Delete a memory
      description: Hard delete; the revisions go with it.
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/memoryId"
      responses:
        "204":
          description: Deleted
        "404":
          description: Agent or memory not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/memories/{memoryId}/revisions:
    get:
      operationId: listMemoryRevisions
      summary: List a memory's revisions
      description: Newest first, bounded by retention. Each row's `content` is a preview (`contentTruncated` flags a clip); fetch one revision for the full text.
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/memoryId"
      responses:
        "200":
          description: Revisions
          content:
            application/json:
              schema:
                type: object
                properties:
                  revisions:
                    type: array
                    items:
                      $ref: "#/components/schemas/MemoryRevision"
        "404":
          description: Agent or memory not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/memories/{memoryId}/revisions/{revisionId}:
    get:
      operationId: getMemoryRevision
      summary: Get a revision
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/memoryId"
        - $ref: "#/components/parameters/revisionId"
      responses:
        "200":
          description: The revision with its full content
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryRevision"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/memories/{memoryId}/revisions/{revisionId}/restore:
    post:
      operationId: restoreMemoryRevision
      summary: Restore a revision
      description: Appends a new revision that copies the old snapshot, so the history shows the restore happened.
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/memoryId"
        - $ref: "#/components/parameters/revisionId"
      responses:
        "200":
          description: The memory as restored
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentMemory"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/memories/{memoryId}/revisions/{revisionId}/redact:
    post:
      operationId: redactMemoryRevision
      summary: Redact a revision
      description: Scrubs an old snapshot's content in place (for example after a secret was pasted). The revision row stays, marked with `redactedAt`.
      tags: [Memories]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/memoryId"
        - $ref: "#/components/parameters/revisionId"
      responses:
        "200":
          description: The redacted revision
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryRevision"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Skills
  # ──────────────────────────────────────────────────────
        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /skills:
    get:
      operationId: listSkills
      summary: List skills
      description: Every skill that reaches this workspace — the organization's, the workspace's own, and its agents' — without bodies.
      tags: [Skills]
      x-mint: *avail_all
      responses:
        "200":
          description: Skills
          content:
            application/json:
              schema:
                type: object
                properties:
                  skills:
                    type: array
                    items:
                      $ref: "#/components/schemas/SkillListItem"
    post:
      operationId: createSkill
      summary: Create a skill
      description: Creates a workspace-tier skill, or an agent-tier one when `agentId` is set (hosted agents only). `name` becomes the directory the agent sees and cannot change afterwards.
      tags: [Skills]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSkillInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Skill"
        "409":
          description: A skill with this name already exists at that tier
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, or the tier's limit is reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /skills/{skillId}:
    get:
      operationId: getSkill
      summary: Get a skill
      description: The full skill with its body and files. Organization skills are readable here but edited through `/org/skills`.
      tags: [Skills]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/skillId"
      responses:
        "200":
          description: The skill
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Skill"
        "404":
          description: Skill not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: updateSkill
      summary: Update a skill
      description: Partial. `files` replaces the whole file set. Organization skills answer `403` with a pointer to `/org/skills`.
      tags: [Skills]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/skillId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSkillInput"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Skill"
        "403":
          description: An organization skill — edit it through `/org/skills`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Skill not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, or the total size budget is exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteSkill
      summary: Delete a skill
      tags: [Skills]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/skillId"
      responses:
        "204":
          description: Deleted
        "403":
          description: An organization skill — delete it through `/org/skills`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Skill not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Channels
  # ──────────────────────────────────────────────────────
  /agents/{agentId}/channels:
    get:
      operationId: getAgentChannels
      summary: Get an agent's channels
      description: The agent's channel presences (where it is reachable, who may reach it there), the transport posture, the organization's channel credentials, and the channel adapter's liveness — everything the Channels section renders.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: The view
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentChannels"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/channels/{provider}/manifest:
    get:
      operationId: getChannelManifest
      summary: Get the setup material for a channel
      description: "What you need to create the provider app by hand (Slack: the app manifest for the chosen transport). The guided path (`POST …/channels/{provider}`) builds the app for you from the organization's credential."
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
        - name: transport
          in: query
          required: false
          schema:
            type: string
            enum: [events, socket]
      responses:
        "200":
          description: Setup material
          content:
            application/json:
              schema:
                type: object
                properties:
                  transport:
                    type: string
                    enum: [events, socket]
                  material:
                    type: object
                    description: "Provider-specific (Slack: the app manifest and the steps)."
        "404":
          description: Agent not found, or unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Not a hosted agent, or unknown transport
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/channels/{provider}:
    post:
      operationId: attachChannel
      summary: Attach a channel (guided)
      description: Creates the provider app from the organization's channel credential and returns what to drive next — the install URL (events transport) or the settings page where the app-level token is generated (socket transport). Finish a socket attach with `POST …/complete`.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                transport:
                  type: string
                  enum: [events, socket]
                  description: Omit to let the deployment posture decide.
      responses:
        "201":
          description: The presence and what to drive next
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PresenceCreated"
        "404":
          description: Agent not found, or unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Not a hosted agent, no organization credential, or the provider refused the request (its own code is quoted)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: detachChannel
      summary: Detach a channel
      description: Removes the presence, its reach decisions, and its tokens. Conversations stay. Set `deleteRemote` to also delete the app on the provider side.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                deleteRemote:
                  type: boolean
                  default: false
      responses:
        "204":
          description: Detached
        "404":
          description: Agent or presence not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/channels/{provider}/complete:
    post:
      operationId: completeChannel
      summary: Complete a channel attach with pasted tokens
      description: Finishes a socket-transport attach, or an attach done entirely by hand from the manifest, by pasting the provider app's tokens.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [botToken]
              additionalProperties: false
              properties:
                botToken:
                  type: string
                  maxLength: 500
                appToken:
                  type: string
                  maxLength: 500
                  description: Socket transport.
                signingSecret:
                  type: string
                  maxLength: 500
                  description: Events transport.
                appId:
                  type: string
                  maxLength: 100
                transport:
                  type: string
                  enum: [events, socket]
      responses:
        "200":
          description: The completed presence
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentChannelPresence"
        "404":
          description: Agent not found, or unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, or the provider rejected the tokens
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/channels/{provider}/reach/{externalRef}:
    put:
      operationId: setSpaceReach
      summary: Decide who may reach the agent in a space
      description: Settles a channel's reach — `approved` opens it to everyone in it, `members_only` limits it to OneCLI members, `blocked` silences it there. Idempotent.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
        - $ref: "#/components/parameters/externalRef"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [state]
              additionalProperties: false
              properties:
                state:
                  type: string
                  enum: [approved, members_only, blocked]
      responses:
        "200":
          description: The outcome
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: Decided
                    properties:
                      kind:
                        type: string
                        enum: [decided]
                      decidedByName: { type: string }
                      state:
                        type: string
                        enum: [approved, members_only, blocked]
                  - type: object
                    title: Already settled
                    properties:
                      kind:
                        type: string
                        enum: [already_settled]
        "404":
          description: Agent or presence not found, or an unknown decider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: dismissSpaceReach
      summary: Forget a space
      description: Removes the reach decision and the thread links for the space. The next message from a stranger there asks again.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
        - $ref: "#/components/parameters/externalRef"
      responses:
        "204":
          description: Forgotten
        "404":
          description: Agent or presence not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/channels/{provider}/reach/people/{externalRef}:
    put:
      operationId: setPersonReach
      summary: Decide whether a person may reach the agent
      description: Settles one person's standing for direct messages — `approved` or `blocked`.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
        - $ref: "#/components/parameters/externalRef"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [state]
              additionalProperties: false
              properties:
                state:
                  type: string
                  enum: [approved, blocked]
      responses:
        "200":
          description: The outcome
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: Decided
                    properties:
                      kind:
                        type: string
                        enum: [decided]
                      decidedByName: { type: string }
                      state:
                        type: string
                        enum: [approved, blocked]
                  - type: object
                    title: Already settled
                    properties:
                      kind:
                        type: string
                        enum: [already_settled]
        "404":
          description: Agent or presence not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: dismissPersonReach
      summary: Forget a person
      description: Removes the person's reach decision. Their next message asks again.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/channelProvider"
        - $ref: "#/components/parameters/externalRef"
      responses:
        "200":
          description: What was removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  removedGrant: { type: boolean }
                  removedLinks: { type: integer }
        "404":
          description: Agent or presence not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "422":
          description: The agent is not a hosted agent (`validation_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /agents/{agentId}/approvals:
    get:
      operationId: listActionApprovals
      summary: List an agent's action approvals
      description: The one-shot approvals the agent asked for before taking an action (sending a message, for instance), newest first, at most 50. Filter with `?status=`.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [pending, approved, executed, failed, rejected, expired]
      responses:
        "200":
          description: Approvals
          content:
            application/json:
              schema:
                type: object
                properties:
                  approvals:
                    type: array
                    items:
                      $ref: "#/components/schemas/ActionApproval"

  /agents/{agentId}/approvals/{approvalId}/decision:
    post:
      operationId: decideActionApproval
      summary: Decide an action approval
      description: "`approve` runs the action once; `approve_always` also records a standing permission (where the action offers one); `reject` refuses it, optionally with a reason the agent sees. Approvals expire after 24 hours."
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/approvalId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [decision]
              additionalProperties: false
              properties:
                decision:
                  type: string
                  enum: [approve, approve_always, reject]
                reason:
                  type: string
                  maxLength: 500
      responses:
        "200":
          description: The outcome
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: Decided
                    properties:
                      kind:
                        type: string
                        enum: [decided]
                      status:
                        type: string
                        enum: [approved, executed, failed, rejected]
                  - type: object
                    title: Already settled
                    properties:
                      kind:
                        type: string
                        enum: [already_settled]
        "404":
          description: Approval not found, no longer pending, or expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/contacts:
    get:
      operationId: listContacts
      summary: List an agent's contacts
      description: The agent's outbound address book — standing decisions about who it may message on its own initiative.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: Contacts
          content:
            application/json:
              schema:
                type: object
                properties:
                  contacts:
                    type: array
                    items:
                      $ref: "#/components/schemas/Contact"

  /agents/{agentId}/contacts/{contactId}:
    put:
      operationId: setContactPolicy
      summary: Set a contact's policy
      description: "`allow` lets the agent message them without asking; `blocked` refuses; `ask` revokes a standing permission (the row survives)."
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/contactId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [policy]
              additionalProperties: false
              properties:
                policy:
                  type: string
                  enum: [ask, allow, blocked]
      responses:
        "200":
          description: The contact
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"
        "404":
          description: Contact not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteContact
      summary: Remove a contact
      description: Back to the ask-by-default state.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/contactId"
      responses:
        "204":
          description: Removed
        "404":
          description: Contact not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/links:
    get:
      operationId: listAgentPeers
      summary: List an agent's peers
      description: Every other hosted agent this one may message, with both sides' standing decision and the pair conversation once they have talked.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
      responses:
        "200":
          description: Peers
          content:
            application/json:
              schema:
                type: object
                properties:
                  peers:
                    type: array
                    items:
                      $ref: "#/components/schemas/AgentPeer"

  /agents/{agentId}/links/{peerAgentId}:
    put:
      operationId: setAgentLinkPolicy
      summary: Set this agent's side of a peer link
      description: The same three words as a contact. `ask` revokes a standing permission.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/peerAgentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [policy]
              additionalProperties: false
              properties:
                policy:
                  type: string
                  enum: [ask, allow, blocked]
      responses:
        "200":
          description: The link row
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  policy:
                    type: string
                    enum: [ask, allow, blocked]
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: forgetAgentLink
      summary: Forget a peer
      description: Removes the relationship from this agent's side — the pair row, this side's pair conversation, and its pending cards. The peer keeps its own.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/peerAgentId"
      responses:
        "204":
          description: Forgotten
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /agents/{agentId}/links/{peerAgentId}/resume:
    post:
      operationId: resumeAgentLink
      summary: Resume a paused pair conversation
      description: Continues this agent's conversation with the peer past its turn cap. Idempotent on a pair that is not paused.
      tags: [Channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/agentId"
        - $ref: "#/components/parameters/peerAgentId"
      responses:
        "200":
          description: Resumed
          content:
            application/json:
              schema:
                type: object
                properties:
                  paused:
                    type: boolean
                    enum: [false]
        "404":
          description: Agent not found, or the pair has never talked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Approvals (gateway)
  # ──────────────────────────────────────────────────────
  /approvals/pending:
    get:
      operationId: listPendingApprovals
      summary: List pending approvals
      description: |
        Long-polls for manual-approval requests awaiting a decision in the current workspace. Returns immediately when any are pending, otherwise holds the connection open (up to `timeoutSeconds`) until one arrives or the poll times out.

        Authenticate with a workspace API key, or an organization key plus an `X-Workspace-Id` header. Submit each decision to `POST /approvals/{id}/decision`.
      tags: [Approvals]
      x-mint: *avail_gateway_all
      servers:
        - url: https://gw.onecli.sh
          description: OneCLI Cloud gateway
        - url: http://localhost:10255
          description: Self-hosted gateway
      parameters:
        - name: exclude
          in: query
          required: false
          schema:
            type: string
          description: Comma-separated approval IDs to skip (already being handled) so the poll can hold open for genuinely new requests.
      responses:
        "200":
          description: Pending approval requests in the workspace
          content:
            application/json:
              schema:
                type: object
                properties:
                  requests:
                    type: array
                    items:
                      $ref: "#/components/schemas/PendingApproval"
                  timeoutSeconds:
                    type: integer
                    description: How long the server holds the long-poll open, in seconds.
        "401":
          description: No workspace context (send an `X-Workspace-Id` header with an organization key, or use a workspace key)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /approvals/{id}/decision:
    post:
      operationId: submitApprovalDecision
      summary: Submit an approval decision
      description: Approve or deny a pending manual-approval request. Approving forwards the held request to its upstream service; denying blocks it. Scope the call to the request's workspace with a workspace API key or an `X-Workspace-Id` header.
      tags: [Approvals]
      x-mint: *avail_gateway_all
      servers:
        - url: https://gw.onecli.sh
          description: OneCLI Cloud gateway
        - url: http://localhost:10255
          description: Self-hosted gateway
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The approval ID from `GET /approvals/pending`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [decision]
              properties:
                decision:
                  type: string
                  enum: [approve, deny]
                  description: Whether to forward the held request (`approve`) or block it (`deny`).
      responses:
        "200":
          description: Decision recorded and the held request released
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
        "404":
          description: No pending approval with that ID in this workspace
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "410":
          description: The approval expired before the decision was submitted (it was auto-denied)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    enum: [approval_expired]

  /org/approvals/pending:
    get:
      operationId: listOrgPendingApprovals
      summary: List pending approvals across the organization
      description: |
        Long-polls for manual-approval requests awaiting a decision across **every** workspace in the organization. Returns immediately when any are pending, otherwise holds the connection open (up to `timeoutSeconds`) until one arrives or the poll times out.

        Each request carries its own `workspaceId`; submit the decision to `POST /approvals/{id}/decision` with that workspace's `X-Workspace-Id`. Authenticate with an organization API key (`oc_org_…`) — a workspace key answers `403`.
      tags: [Organization approvals]
      x-mint: *avail_gateway_enterprise
      servers:
        - url: https://gw.onecli.sh
          description: OneCLI Cloud gateway
        - url: http://localhost:10255
          description: Self-hosted gateway
      parameters:
        - name: exclude
          in: query
          required: false
          schema:
            type: string
          description: Comma-separated approval IDs to skip (already being handled) so the poll can hold open for genuinely new requests.
      responses:
        "200":
          description: Pending approval requests across the organization
          content:
            application/json:
              schema:
                type: object
                properties:
                  requests:
                    type: array
                    items:
                      $ref: "#/components/schemas/PendingApproval"
                  timeoutSeconds:
                    type: integer
        "403":
          description: Not an organization API key (`organization_scope_required`), or the deployment is not entitled (`enterprise_license_required`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    enum: [organization_scope_required, enterprise_license_required]

  # ──────────────────────────────────────────────────────
  # User
  # ──────────────────────────────────────────────────────
  /user:
    get:
      operationId: getUser
      summary: Get your profile
      description: Works with a workspace key, an organization key, or a session. The CLI uses it to verify a key.
      tags: [User]
      x-mint: *avail_all
      responses:
        "200":
          description: The profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
    patch:
      operationId: updateUser
      summary: Update your profile
      tags: [User]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 255
      responses:
        "200":
          description: The profile
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  email: { type: string }
                  name: { type: string }
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteUser
      summary: Delete your account
      description: |
        Deletes the account. Organizations you alone belong to are deleted with it; shared ones are left. Refused while you own an organization that has other members — transfer ownership first. Preview the consequences with `GET /user/deletion-impact`.

        Session only: API keys cannot delete the account that owns them.
      tags: [User]
      x-mint: *avail_all
      responses:
        "204":
          description: Account deleted
        "401":
          description: Not a session (API keys are refused)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: You own an organization with other members
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/deletion-impact:
    get:
      operationId: getUserDeletionImpact
      summary: Preview account deletion
      description: What `DELETE /user` would do to each of your organizations. Session only.
      tags: [User]
      x-mint: *avail_all
      responses:
        "200":
          description: Per-organization outcomes
          content:
            application/json:
              schema:
                type: object
                properties:
                  organizations:
                    type: array
                    items:
                      type: object
                      properties:
                        organizationId: { type: string }
                        name: { type: string }
                        outcome:
                          type: string
                          enum: [delete, leave, blocked]
                        otherMemberCount: { type: integer }
                        workspaces:
                          type: array
                          description: Workspaces permanently deleted by this outcome.
                          items:
                            type: object
                            properties:
                              id: { type: string }
                              name:
                                type: string
                                nullable: true
        "401":
          description: Not a session
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/api-key:
    get:
      operationId: getApiKey
      summary: Get your workspace API key
      description: Your personal key for the current workspace, created on first read. Needs a workspace context.
      tags: [User]
      x-mint: *avail_all
      responses:
        "200":
          description: The key
          content:
            application/json:
              schema:
                type: object
                properties:
                  apiKey:
                    type: string
                    example: "oc_…"
        "400":
          description: No workspace context
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/api-key/regenerate:
    post:
      operationId: regenerateApiKey
      summary: Regenerate your workspace API key
      description: Replaces your personal key for the current workspace. The old key stops working immediately.
      tags: [User]
      x-mint: *avail_all
      responses:
        "200":
          description: The new key
          content:
            application/json:
              schema:
                type: object
                properties:
                  apiKey:
                    type: string
        "400":
          description: No workspace context
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/ssh-keys:
    get:
      operationId: listSshKeys
      summary: List your SSH keys
      description: The public keys you can mint agent SSH certificates for. Account-level — no workspace context needed.
      tags: [User]
      x-mint: *avail_all
      responses:
        "200":
          description: Keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  sshKeys:
                    type: array
                    items:
                      $ref: "#/components/schemas/SshKey"
    post:
      operationId: createSshKey
      summary: Register an SSH key
      description: ed25519 only. The fingerprint is computed server-side; the same key cannot be registered twice. At most 25 keys per account.
      tags: [User]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, publicKey]
              properties:
                name:
                  type: string
                  maxLength: 100
                  example: "work laptop"
                publicKey:
                  type: string
                  maxLength: 1024
                  example: "ssh-ed25519 AAAAC3… user@host"
      responses:
        "201":
          description: Registered
          content:
            application/json:
              schema:
                type: object
                properties:
                  sshKey:
                    $ref: "#/components/schemas/SshKey"
        "409":
          description: Already registered, or the per-account limit is reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, or not an ed25519 key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/ssh-keys/{keyId}:
    delete:
      operationId: deleteSshKey
      summary: Remove an SSH key
      tags: [User]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/keyId"
      responses:
        "204":
          description: Removed
        "404":
          description: Key not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Instance & utility
  # ──────────────────────────────────────────────────────
  /health:
    get:
      operationId: getHealth
      summary: Health check
      description: Liveness. No authentication.
      tags: [Instance]
      x-mint: *avail_all
      security: []
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [ok]
                  version: { type: string }
                  timestamp:
                    type: string
                    format: date-time

  /instance:
    get:
      operationId: getInstance
      summary: Get deployment facts
      description: The edition, whether Enterprise features are enabled, the public origins, hosted-agent availability, and the SSH front door. Posture only, never data. No authentication.
      tags: [Instance]
      x-mint: *avail_all
      security: []
      responses:
        "200":
          description: Instance facts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Instance"

  /instance/egress:
    get:
      operationId: getEgressAddresses
      summary: Get the deployment's egress addresses
      description: The source addresses this deployment calls out from, for allowlisting on your side. Published by OneCLI Cloud; a self-hosted deployment answers `404` unless its operator configured a list. No authentication; cacheable for an hour.
      tags: [Instance]
      x-mint: *avail_all
      security: []
      responses:
        "200":
          description: The feed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EgressFeed"
        "404":
          description: This deployment does not publish egress addresses
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /gateway-url:
    get:
      operationId: getGatewayUrl
      summary: Get the gateway URL
      description: The HTTP origin of this deployment's gateway (for the approvals endpoints and client bootstrap). No authentication.
      tags: [Instance]
      x-mint: *avail_all
      security: []
      responses:
        "200":
          description: The URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    example: "https://gw.onecli.sh"

  /gateway/ca:
    get:
      operationId: getGatewayCa
      summary: Download the gateway CA certificate
      description: The PEM certificate agents trust so the gateway can inspect and inject into HTTPS traffic. No authentication.
      tags: [Instance]
      x-mint: *avail_all
      security: []
      responses:
        "200":
          description: The certificate
          content:
            application/x-pem-file:
              schema:
                type: string
        "503":
          description: The gateway has not generated its CA yet
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /counts:
    get:
      operationId: getCounts
      summary: Get resource counts
      description: Headline counts for the current workspace.
      tags: [Utility]
      x-mint: *avail_all
      responses:
        "200":
          description: Counts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceCounts"

  /vaults:
    get:
      operationId: listVaults
      summary: List vault connections
      description: The workspace's external vault connections (1Password, Bitwarden). Pairing happens on the gateway.
      tags: [Utility]
      x-mint: *avail_all
      responses:
        "200":
          description: Vault connections
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/VaultConnection"

  /runners:
    get:
      operationId: listRunners
      summary: List runners
      description: The deployment's hosted-agent runners and whether each is alive. Deployment-wide, so admin-only where roles are enforced.
      tags: [Utility]
      x-mint: *avail_all
      responses:
        "200":
          description: Runners
          content:
            application/json:
              schema:
                type: object
                properties:
                  runners:
                    type: array
                    items:
                      $ref: "#/components/schemas/Runner"
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /container-config:
    get:
      operationId: getContainerConfig
      summary: Get an agent's container configuration
      description: Everything an orchestrator needs to point a container at the gateway as a given agent — environment variables, the CA certificate, and credential stub files. Name the agent with `agent=`; omitting it only works for workspaces that still carry a pre-v2 default agent.
      tags: [Utility]
      x-mint: *avail_all
      parameters:
        - name: agent
          in: query
          required: false
          schema: { type: string }
          description: The agent's `identifier`.
      responses:
        "200":
          description: The configuration
          content:
            application/json:
              schema:
                type: object
                description: Server-controlled environment variables, file contents, and paths.
        "404":
          description: Unknown identifier (`AGENT_NOT_FOUND`), no agent named and no legacy default (`AGENT_REQUIRED`), or the workspace has no agents (`NO_AGENTS`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string }
                  code:
                    type: string
                    enum: [AGENT_NOT_FOUND, AGENT_REQUIRED, NO_AGENTS]
                  agentIdentifier: { type: string }
        "503":
          description: The gateway has not generated its CA yet
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /credential-stubs:
    get:
      operationId: listCredentialStubs
      summary: List agents with credential stubs
      tags: [Utility]
      x-mint: *avail_all
      responses:
        "200":
          description: Agent frameworks that need a stub file
          content:
            application/json:
              schema:
                type: object
                properties:
                  agents:
                    type: array
                    items: { type: string }
                    example: [codex]

  /credential-stubs/{agent}:
    get:
      operationId: getCredentialStub
      summary: Get a credential stub
      description: The placeholder credentials file a coding agent needs locally so it starts and routes through the gateway (the gateway injects the real credential). Today only `codex`, whose stub follows the workspace's OpenAI secrets (API-key or OAuth mode).
      tags: [Utility]
      x-mint: *avail_all
      parameters:
        - name: agent
          in: path
          required: true
          schema:
            type: string
            enum: [codex]
      responses:
        "200":
          description: The stub
          content:
            application/json:
              schema:
                type: object
                properties:
                  agent: { type: string }
                  filePath:
                    type: string
                    example: "~/.codex/auth.json"
                  content: { type: string }
                  authMode:
                    type: string
                    enum: [api-key, oauth]
                  permissions:
                    type: string
                    example: "0600"
        "404":
          description: No stub for that agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /skill/gateway:
    get:
      operationId: getGatewaySkill
      summary: Get the gateway skill
      description: The Markdown skill that teaches a coding agent how to use the gateway, optionally tailored to a framework.
      tags: [Utility]
      x-mint: *avail_all
      parameters:
        - name: agent_framework
          in: query
          required: false
          schema: { type: string }
          example: claude-code
      responses:
        "200":
          description: The skill
          content:
            text/markdown:
              schema:
                type: string

  # ──────────────────────────────────────────────────────
  # Organization
  # ──────────────────────────────────────────────────────
  /org:
    get:
      operationId: getOrganization
      summary: Get the current organization
      description: The organization the key or session belongs to. Any active member.
      tags: [Organization]
      x-mint: *avail_all
      responses:
        "200":
          description: The organization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Organization"

  /org/invitations:
    get:
      operationId: listInvitations
      summary: List pending invitations
      description: Admin or owner where roles are enforced.
      tags: [Organization]
      x-mint: *avail_all
      responses:
        "200":
          description: Pending invitations, newest first
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PendingInvitation"
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createInvitation
      summary: Invite someone
      description: Mints an invitation link, and emails it when the deployment has an email provider. The link is always returned so you can deliver it yourself.
      tags: [Organization]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, role]
              additionalProperties: false
              properties:
                email:
                  type: string
                  format: email
                role:
                  type: string
                  enum: [admin, member]
      responses:
        "201":
          description: Invitation created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  joinUrl: { type: string }
                  emailed:
                    type: boolean
                    description: Whether an email went out.
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient permissions, or the organization's seat quota is reached (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/invitations/{id}:
    delete:
      operationId: cancelInvitation
      summary: Cancel an invitation
      tags: [Organization]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/invitationId"
      responses:
        "204":
          description: Cancelled
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Invitation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /invitations/accept:
    post:
      operationId: acceptInvitation
      summary: Accept an invitation
      description: Redeems an invitation token for the signed-in person. Session only — the caller is not a member of the organization yet, so no organization context applies. The invitation must be addressed to the caller's email.
      tags: [Organization]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token: { type: string }
      responses:
        "200":
          description: Joined
          content:
            application/json:
              schema:
                type: object
                properties:
                  organizationId: { type: string }
                  organizationName: { type: string }
        "400":
          description: Missing token, or the invitation is used, expired, or addressed to someone else
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Not signed in
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Organization secrets
  # ──────────────────────────────────────────────────────
  /org/secrets:
    get:
      operationId: listOrgSecrets
      summary: List organization secrets
      description: Admin or owner.
      tags: [Organization secrets]
      x-mint: *avail_all
      responses:
        "200":
          description: Secrets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Secret"
    post:
      operationId: createOrgSecret
      summary: Create an organization secret
      description: An organization secret is visible in every workspace and can be granted to any agent. 1Password-resolved values are workspace-only, so `valueSource` must be `inline` here.
      tags: [Organization secrets]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSecretInput"
      responses:
        "201":
          description: Secret created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecretCreated"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient permissions, or the organization's secret quota is reached (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/secrets/{secretId}:
    patch:
      operationId: updateOrgSecret
      summary: Update an organization secret
      tags: [Organization secrets]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/secretId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSecretInput"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Secret not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteOrgSecret
      summary: Delete an organization secret
      tags: [Organization secrets]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/secretId"
      responses:
        "204":
          description: Deleted
        "404":
          description: Secret not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Organization policy
  # ──────────────────────────────────────────────────────
  /org/policy/rules:
    get:
      operationId: listOrgPolicyRules
      summary: List organization rules
      description: The organization's rules in first-match order, the Default Rule last. Admin or owner.
      tags: [Organization policy]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/policyStatus"
      responses:
        "200":
          description: Rules
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PolicyRule"
    post:
      operationId: createOrgPolicyRule
      summary: Create an organization rule
      description: Appended just above the Default Rule and enforced immediately.
      tags: [Organization policy]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePolicyRuleInput"
      responses:
        "201":
          description: The rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyRule"
        "403":
          description: A plan-gated modifier (rate limit, group identity) on a plan without it (Cloud), or a group identity without the Enterprise entitlement (self-hosted)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error (unpaired `rateLimit`/`rateLimitWindow`, a modifier on a `block` rule, no targets, resource scoping without a connection target)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/policy/rules/order:
    put:
      operationId: reorderOrgPolicyRules
      summary: Reorder organization rules
      description: Sets first-match order. `orderedIds` must name every non-default rule exactly once; a stale list answers `409`.
      tags: [Organization policy]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [orderedIds]
              properties:
                orderedIds:
                  type: array
                  minItems: 1
                  items: { type: string }
      responses:
        "200":
          description: The reordered rules
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PolicyRule"
        "409":
          description: The rule set changed — refresh and resend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/policy/rules/{ruleId}:
    get:
      operationId: getOrgPolicyRule
      summary: Get an organization rule
      tags: [Organization policy]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/ruleId"
      responses:
        "200":
          description: The rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyRule"
        "404":
          description: Rule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: updateOrgPolicyRule
      summary: Update an organization rule
      description: Partial. The action-versus-modifier laws are re-checked against the merged rule.
      tags: [Organization policy]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/ruleId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePolicyRuleInput"
      responses:
        "200":
          description: The rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyRule"
        "403":
          description: Plan or entitlement gate (see create)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Rule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteOrgPolicyRule
      summary: Delete an organization rule
      description: System-managed rules (`source` other than `custom`) and the Default Rule cannot be deleted.
      tags: [Organization policy]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/ruleId"
      responses:
        "204":
          description: Deleted
        "404":
          description: Rule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/policy/default:
    get:
      operationId: getOrgPolicyDefault
      summary: Get the organization's Default Rule
      description: The terminal rule that decides requests no other rule matched.
      tags: [Organization policy]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/policyStatus"
      responses:
        "200":
          description: The Default Rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyRule"
    patch:
      operationId: setOrgPolicyDefault
      summary: Set the organization's default action
      description: "`block` turns the organization into deny-by-default: an agent's request passes only when a rule allows it. On Cloud this needs the Team plan or above."
      tags: [Organization policy]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: string
                  enum: [allow, block]
      responses:
        "200":
          description: The Default Rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyRule"
        "403":
          description: Deny-by-default needs a higher plan (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/policy/publish:
    post:
      operationId: publishOrgPolicy
      summary: Publish the organization's rules
      description: Compatibility for older clients. Every write already publishes; this re-snapshots an identical rule set and answers the current generation.
      deprecated: true
      tags: [Organization policy]
      x-mint: *avail_all
      responses:
        "200":
          description: The generation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyPublishResult"

  /org/policy/last-publish:
    get:
      operationId: getOrgPolicyLastPublish
      summary: Get the most recent publish
      description: Compatibility for older clients.
      deprecated: true
      tags: [Organization policy]
      x-mint: *avail_all
      responses:
        "200":
          description: The last publish, or `null`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyLastPublish"

  /org/policy/effective-app-permissions:
    get:
      operationId: getOrgEffectiveAppPermissions
      summary: Effective app permissions (organization)
      description: What organization rules alone decide for every tool of one app — the ceiling every workspace grant sits under. Admin or owner; rule names are never redacted here.
      tags: [Organization policy]
      x-mint: *avail_all
      parameters:
        - name: provider
          in: query
          required: true
          schema: { type: string }
        - name: connectionId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: The per-tool verdicts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EffectiveAppPermissions"
        "422":
          description: Missing or invalid `provider`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Organization connections
  # ──────────────────────────────────────────────────────
  /org/connections:
    get:
      operationId: listOrgConnections
      summary: List organization connections
      description: Connections shared into every workspace, newest first. Filter with `?provider=`. Admin or owner.
      tags: [Organization connections]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/providerQuery"
      responses:
        "200":
          description: Connections
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AppConnection"

  /org/connections/{connectionId}:
    patch:
      operationId: renameOrgConnection
      summary: Rename an organization connection
      tags: [Organization connections]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label]
              properties:
                label: { type: string }
      responses:
        "200":
          description: Renamed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionRenamed"
        "400":
          description: Label is required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteOrgConnection
      summary: Disconnect an organization connection
      description: Removes the connection from every workspace and every grant that referenced it.
      tags: [Organization connections]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/connectionId"
      responses:
        "204":
          description: Disconnected
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Organization apps
  # ──────────────────────────────────────────────────────
  /org/apps/{provider}/authorize:
    get:
      operationId: authorizeOrgApp
      summary: Start an organization-level OAuth connect
      description: Like `GET /apps/{provider}/authorize`, but the resulting connection is shared into every workspace. Browser navigations pass the scope as `_token` and `_org`. Admin or owner.
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
        - name: _token
          in: query
          required: false
          schema: { type: string }
        - name: org
          in: query
          required: false
          schema: { type: string }
          description: Target another organization you belong to.
        - name: connectionId
          in: query
          required: false
          schema: { type: string }
          description: Re-authenticate this connection.
      responses:
        "302":
          description: Redirect to the provider's consent screen
        "400":
          description: Not an OAuth app, or its OAuth client is not configured
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/apps/{provider}/connect:
    post:
      operationId: connectOrgApp
      summary: Connect an app at the organization level
      description: Like `POST /apps/{provider}/connect`, but the connection is shared into every workspace. Admin or owner.
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectInput"
      responses:
        "200":
          description: Connected
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Unknown or OAuth-only provider, or missing credential fields
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient permissions, or the organization's connection quota is reached (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/apps/aws-external-id:
    get:
      operationId: getOrgAwsExternalId
      summary: Get the organization's AWS external ID
      description: The `sts:ExternalId` to put in the trust policy of the IAM role an AWS connection assumes. Minted on first read and stable after.
      tags: [Organization apps]
      x-mint: *avail_all
      responses:
        "200":
          description: The external ID
          content:
            application/json:
              schema:
                type: object
                properties:
                  externalId: { type: string }

  /org/apps/configured:
    get:
      operationId: listOrgConfiguredApps
      summary: List organization-configured apps
      description: Provider ids with an enabled organization-level OAuth client configuration.
      tags: [Organization apps]
      x-mint: *avail_all
      responses:
        "200":
          description: Provider ids
          content:
            application/json:
              schema:
                type: array
                items: { type: string }

  /org/apps/{provider}/config:
    get:
      operationId: getOrgAppConfig
      summary: Get an organization app configuration
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "200":
          description: Configuration state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgAppConfigState"
    post:
      operationId: setOrgAppConfig
      summary: Set an organization app configuration
      description: Your own OAuth client for the app, used by every workspace that has none of its own.
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AppConfigInput"
      responses:
        "201":
          description: Saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: The app is not configurable, or the body is missing its required fields
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteOrgAppConfig
      summary: Remove an organization app configuration
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "204":
          description: Removed

  /org/apps/{provider}/config/toggle:
    patch:
      operationId: toggleOrgAppConfig
      summary: Enable or disable an organization app configuration
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled: { type: boolean }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/apps/{provider}/blocklist:
    get:
      operationId: getOrgAppBlocklist
      summary: Get an app's organization blocklist state
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      responses:
        "200":
          description: Blocklist state
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/BlocklistHostState"
        "404":
          description: Unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: activateOrgAppBlocklistHost
      summary: Block one of an app's hosts organization-wide
      description: An organization block applies to every workspace and cannot be lifted from a workspace.
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [hostId]
              properties:
                hostId: { type: string }
      responses:
        "201":
          description: The host's new state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlocklistHostState"
        "400":
          description: Missing `hostId`, or not one of the app's hosts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/apps/{provider}/blocklist/{ruleId}:
    patch:
      operationId: toggleOrgAppBlocklistRule
      summary: Enable or disable an organization blocklist entry
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
        - $ref: "#/components/parameters/blocklistRuleId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled: { type: boolean }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "400":
          description: Missing `enabled`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Rule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: removeOrgAppBlocklistRule
      summary: Remove an organization blocklist entry
      tags: [Organization apps]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/provider"
        - $ref: "#/components/parameters/blocklistRuleId"
      responses:
        "204":
          description: Removed
        "404":
          description: Rule not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Organization skills
  # ──────────────────────────────────────────────────────
  /org/skills:
    get:
      operationId: listOrgSkills
      summary: List organization skills
      description: Any member can read; writes need admin or owner where roles are enforced.
      tags: [Organization skills]
      x-mint: *avail_all
      responses:
        "200":
          description: Skills
          content:
            application/json:
              schema:
                type: object
                properties:
                  skills:
                    type: array
                    items:
                      $ref: "#/components/schemas/SkillListItem"
    post:
      operationId: createOrgSkill
      summary: Create an organization skill
      description: Delivered to every hosted agent in the organization. At most 20 per organization.
      tags: [Organization skills]
      x-mint: *avail_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrgSkillInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Skill"
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: A skill with this name already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, or the limit is reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/skills/{skillId}:
    get:
      operationId: getOrgSkill
      summary: Get an organization skill
      tags: [Organization skills]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/skillId"
      responses:
        "200":
          description: The skill
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Skill"
        "404":
          description: Skill not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: updateOrgSkill
      summary: Update an organization skill
      tags: [Organization skills]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/skillId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSkillInput"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Skill"
        "404":
          description: Skill not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteOrgSkill
      summary: Delete an organization skill
      tags: [Organization skills]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/skillId"
      responses:
        "204":
          description: Deleted
        "404":
          description: Skill not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Organization channels
  # ──────────────────────────────────────────────────────
  /org/channels:
    get:
      operationId: getOrgChannels
      summary: Get the organization's channel integrations
      description: Integrations, identity links, adapter liveness, and the shared-app posture in one call. Credentials never leave the server. Admin or owner where roles are enforced.
      tags: [Organization channels]
      x-mint: *avail_all
      responses:
        "200":
          description: The view
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgChannels"

  /org/channels/{provider}/credentials:
    put:
      operationId: setOrgChannelCredentials
      summary: Connect or refresh the organization's channel credential
      description: Slack — the app configuration token that lets OneCLI create per-agent apps on your workspace's behalf.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [credential]
              additionalProperties: false
              properties:
                credential:
                  type: string
                  maxLength: 4000
      responses:
        "200":
          description: Connected
          content:
            application/json:
              schema:
                type: object
                properties:
                  provider: { type: string }
                  tenant:
                    type: object
                    properties:
                      externalId: { type: string }
                      name:
                        type: string
                        nullable: true
        "404":
          description: Unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error, or the provider rejected the credential
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/channels/{provider}:
    delete:
      operationId: deleteOrgChannelIntegration
      summary: Disconnect the organization's channel credential
      description: Drops the credential, or the whole integration when nothing references it.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      responses:
        "204":
          description: Disconnected
        "404":
          description: Unknown provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/channels/{provider}/shared-manifest:
    get:
      operationId: getOrgChannelSharedManifest
      summary: Get the shared-app setup manifest
      description: The document a self-hosted operator creates their deployment-wide Slack app from. Needs a public HTTPS API origin.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      responses:
        "200":
          description: The manifest
          content:
            application/json:
              schema:
                type: object
                properties:
                  manifest:
                    type: object
        "422":
          description: The provider has no shared app, or the API origin is not public HTTPS
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/channels/{provider}/shared-install:
    post:
      operationId: startOrgChannelSharedInstall
      summary: Start a shared-app install
      description: Mints the "Add to Slack" consent URL for the deployment's shared app. Side-effect free; the install lands on the OAuth callback.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      responses:
        "200":
          description: The consent URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  installUrl: { type: string }
        "422":
          description: The provider has no shared app
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteOrgChannelSharedInstall
      summary: Disconnect the shared-app install
      description: Uninstalls from the provider best-effort and deletes the local install regardless.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      responses:
        "204":
          description: Disconnected
        "404":
          description: No shared-app install
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/channels/{provider}/finish-install/inspect:
    post:
      operationId: inspectOrgChannelInstallCode
      summary: Inspect a marketplace install code
      description: Exchanges the code parked by an install begun in the provider's directory and names the workspace it belongs to, binding nothing. Confirm with `POST …/finish-install`.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              additionalProperties: false
              properties:
                code:
                  type: string
                  maxLength: 2000
      responses:
        "200":
          description: What the code would connect, plus a sealed `claim` for the confirm step
          content:
            application/json:
              schema:
                type: object
        "422":
          description: The install link is not valid
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/channels/{provider}/finish-install:
    post:
      operationId: finishOrgChannelInstall
      summary: Finish a marketplace install
      description: Binds the inspected claim to this organization.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [claim]
              additionalProperties: false
              properties:
                claim:
                  type: string
                  maxLength: 16000
      responses:
        "200":
          description: Installed
          content:
            application/json:
              schema:
                type: object
                properties:
                  teamId: { type: string }
        "422":
          description: The install link is not valid
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/channels/{provider}/user-links:
    post:
      operationId: createOrgChannelUserLink
      summary: Link a provider identity to a member
      description: Tells OneCLI which member a Slack user is, so their messages run under that member's access. The member must be active.
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [externalUserId, userId]
              additionalProperties: false
              properties:
                externalUserId:
                  type: string
                  maxLength: 200
                  description: The Slack user id.
                userId:
                  type: string
                  maxLength: 200
                  description: The OneCLI member's user id.
      responses:
        "201":
          description: The link
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelUserLink"
        "404":
          description: Unknown provider, no integration, or the member is not active
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/channels/{provider}/user-links/{linkId}:
    delete:
      operationId: deleteOrgChannelUserLink
      summary: Remove an identity link
      tags: [Organization channels]
      x-mint: *avail_all
      parameters:
        - $ref: "#/components/parameters/channelProvider"
        - name: linkId
          in: path
          required: true
          schema: { type: string }
      responses:
        "204":
          description: Removed
        "404":
          description: Link not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Enterprise: members
  # ──────────────────────────────────────────────────────
  /org/members:
    get:
      operationId: listMembers
      summary: List members
      description: The organization directory, cursor-paginated. Admin or owner.
      tags: [Members]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/directoryLimit"
        - $ref: "#/components/parameters/directoryCursor"
        - $ref: "#/components/parameters/directorySearch"
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [active, suspended]
      responses:
        "200":
          description: A page of members
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemberPage"
        "400":
          description: Invalid query or cursor
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled, or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createMember
      summary: Provision a member
      description: The first-party twin of SCIM `POST /Users` — creates the member (and the user, when new) by email without an invitation. On Cloud this needs the Scale plan or above.
      tags: [Members]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              additionalProperties: false
              properties:
                email:
                  type: string
                  format: email
                name:
                  type: string
                  maxLength: 200
      responses:
        "201":
          description: The member
          content:
            application/json:
              schema:
                type: object
                properties:
                  userId: { type: string }
                  email: { type: string }
                  name:
                    type: string
                    nullable: true
                  role: { type: string }
                  status: { type: string }
                  joinedAt:
                    type: string
                    format: date-time
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled, plan too low, or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Already a member
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/members/{userId}:
    patch:
      operationId: updateMember
      summary: Suspend, reinstate, or SSO-exempt a member
      description: Exactly one change per request — either a `status` transition or the break-glass `ssoExempt` flag. Suspending revokes the member's sessions.
      tags: [Members]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/userId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  title: Status
                  required: [status]
                  properties:
                    status:
                      type: string
                      enum: [active, suspended]
                  additionalProperties: false
                - type: object
                  title: SSO exemption
                  required: [ssoExempt]
                  properties:
                    ssoExempt: { type: boolean }
                  additionalProperties: false
      responses:
        "200":
          description: The member's new state
          content:
            application/json:
              schema:
                type: object
                properties:
                  userId: { type: string }
                  status: { type: string }
                  ssoExempt: { type: boolean }
                  revocation:
                    type: string
                    description: Status changes only — what happened to the member's identity-provider sessions (`revoked`, `skipped`, `failed`).
        "400":
          description: Not exactly one change
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled, or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Not a member
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: removeMember
      summary: Remove a member
      description: Hard offboarding — the membership row is deleted and the person's truly-personal workspaces go with it (unlike SCIM `DELETE /Users/{id}`, which only suspends). The owner cannot be removed.
      tags: [Members]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/userId"
      responses:
        "204":
          description: Removed
        "400":
          description: The organization owner cannot be removed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled, or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Not a member
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/members/{userId}/groups:
    get:
      operationId: listMemberGroups
      summary: List a member's groups
      tags: [Members]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/userId"
        - $ref: "#/components/parameters/directoryLimit"
        - $ref: "#/components/parameters/directoryCursor"
        - $ref: "#/components/parameters/directorySearch"
      responses:
        "200":
          description: A page of groups
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupPage"
        "403":
          description: Not entitled, or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Enterprise: groups + role mappings
  # ──────────────────────────────────────────────────────
  /org/groups:
    get:
      operationId: listGroups
      summary: List groups
      description: Cursor-paginated. Admin or owner.
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/directoryLimit"
        - $ref: "#/components/parameters/directoryCursor"
        - $ref: "#/components/parameters/directorySearch"
        - name: source
          in: query
          required: false
          schema:
            type: string
            enum: [manual, scim]
      responses:
        "200":
          description: A page of groups
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupPage"
        "403":
          description: Not entitled, or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createGroup
      summary: Create a group
      description: On Cloud, groups need the Enterprise plan.
      tags: [Groups]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  maxLength: 100
      responses:
        "201":
          description: The group
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "403":
          description: Not entitled, plan too low, or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/groups/{groupId}:
    get:
      operationId: getGroup
      summary: Get a group
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/groupId"
      responses:
        "200":
          description: The group
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: renameGroup
      summary: Rename a group
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/groupId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              additionalProperties: false
              properties:
                name:
                  type: string
                  maxLength: 100
      responses:
        "200":
          description: The group
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteGroup
      summary: Delete a group
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/groupId"
      responses:
        "204":
          description: Deleted
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/groups/{groupId}/members:
    get:
      operationId: listGroupMembers
      summary: List a group's members
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/groupId"
        - $ref: "#/components/parameters/directoryLimit"
        - $ref: "#/components/parameters/directoryCursor"
        - $ref: "#/components/parameters/directorySearch"
      responses:
        "200":
          description: A page of members
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupMemberPage"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    put:
      operationId: setGroupMembers
      summary: Replace a group's members
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/groupId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [userIds]
              properties:
                userIds:
                  type: array
                  maxItems: 1000
                  items: { type: string }
      responses:
        "200":
          description: The diff that was applied
          content:
            application/json:
              schema:
                type: object
                properties:
                  added: { type: integer }
                  removed: { type: integer }
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/groups/{groupId}/members/{userId}:
    put:
      operationId: addGroupMember
      summary: Add a member to a group
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/groupId"
        - $ref: "#/components/parameters/userId"
      responses:
        "204":
          description: Added (or already a member)
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Group or member not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: removeGroupMember
      summary: Remove a member from a group
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/groupId"
        - $ref: "#/components/parameters/userId"
      responses:
        "204":
          description: Removed
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Group or member not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/role-mappings:
    get:
      operationId: listRoleMappings
      summary: List group-to-role mappings
      description: Highest priority first. A member in several mapped groups gets the role of the highest-priority one; unmapped members keep their manual role.
      tags: [Groups]
      x-mint: *avail_enterprise
      responses:
        "200":
          description: Mappings
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/RoleMapping"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createRoleMapping
      summary: Create a role mapping
      description: Applies immediately — the group's members are re-resolved.
      tags: [Groups]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [groupId, role]
              properties:
                groupId: { type: string }
                role:
                  type: string
                  enum: [admin, member]
                priority:
                  type: integer
                  description: Omit to append at the lowest priority.
      responses:
        "201":
          description: The mapping
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RoleMapping"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: The group already has a mapping
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/role-mappings/order:
    put:
      operationId: reorderRoleMappings
      summary: Reorder role mappings
      description: Highest priority first; must name every mapping exactly once. Members of several mapped groups are re-resolved.
      tags: [Groups]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [orderedIds]
              properties:
                orderedIds:
                  type: array
                  minItems: 1
                  items: { type: string }
      responses:
        "200":
          description: The reordered mappings
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/RoleMapping"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /org/role-mappings/preview:
    post:
      operationId: previewRoleMapping
      summary: Preview a role mapping's impact
      description: How many members would change role if the mapping were saved.
      tags: [Groups]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [groupId, role]
              properties:
                groupId: { type: string }
                role:
                  type: string
                  enum: [admin, member]
      responses:
        "200":
          description: The impact
          content:
            application/json:
              schema:
                type: object
                properties:
                  affectedCount: { type: integer }

        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /org/role-mappings/{id}:
    patch:
      operationId: updateRoleMapping
      summary: Update a role mapping
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [role]
              properties:
                role:
                  type: string
                  enum: [admin, member]
                priority:
                  type: integer
      responses:
        "200":
          description: The mapping
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RoleMapping"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Mapping not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteRoleMapping
      summary: Delete a role mapping
      description: The group's members re-resolve among their remaining mapped groups, or keep their current role when none remains.
      tags: [Groups]
      x-mint: *avail_enterprise
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204":
          description: Deleted
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Mapping not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Enterprise: domains, SSO, SCIM tokens
  # ──────────────────────────────────────────────────────
  /org/domains:
    get:
      operationId: listDomains
      summary: List verified domains
      tags: [Domains]
      x-mint: *avail_enterprise
      responses:
        "200":
          description: Domains
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/OrgDomain"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createDomain
      summary: Add a domain
      description: Returns the TXT record token to publish. A domain is globally unique — one organization may own it. On Cloud this needs the Scale plan or above.
      tags: [Domains]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain:
                  type: string
                  example: acme.com
      responses:
        "201":
          description: The domain, unverified
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgDomain"
        "400":
          description: Not a valid domain
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Already claimed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/domains/{domainId}/verify:
    post:
      operationId: verifyDomain
      summary: Verify a domain
      description: Looks up the DNS TXT record and marks the domain verified when it matches.
      tags: [Domains]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/domainId"
      responses:
        "200":
          description: The verified domain
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgDomain"
        "400":
          description: The TXT record was not found or does not match
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/domains/{domainId}:
    delete:
      operationId: deleteDomain
      summary: Remove a domain
      description: Never plan-gated — removing a domain must always be possible.
      tags: [Domains]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/domainId"
      responses:
        "204":
          description: Removed
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/sso/connections:
    get:
      operationId: listSsoConnections
      summary: List SSO connections
      tags: [SSO]
      x-mint: *avail_enterprise
      responses:
        "200":
          description: Connections (credentials redacted)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SsoConnection"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createSsoConnection
      summary: Create an SSO connection
      description: One connection per organization. SAML takes exactly one of `metadataUrl` or `metadataXml`; OIDC takes the issuer and client credentials. On Cloud this needs the Scale plan or above.
      tags: [SSO]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  title: SAML
                  required: [type, displayName]
                  properties:
                    type:
                      type: string
                      enum: [saml]
                    displayName:
                      type: string
                      maxLength: 100
                    metadataUrl:
                      type: string
                      format: uri
                    metadataXml:
                      type: string
                - type: object
                  title: OIDC
                  required: [type, displayName, issuer, clientId, clientSecret]
                  properties:
                    type:
                      type: string
                      enum: [oidc]
                    displayName:
                      type: string
                      maxLength: 100
                    issuer:
                      type: string
                      format: uri
                    clientId:
                      type: string
                    clientSecret:
                      type: string
      responses:
        "201":
          description: The connection, `pending` until the first sign-in
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SsoConnection"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: The organization already has a connection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/sso/connections/{connectionId}:
    patch:
      operationId: updateSsoConnection
      summary: Update an SSO connection
      description: Rename, rotate credentials or metadata, or enable/disable. Enabling is plan-gated on Cloud; disabling never is. The type cannot change.
      tags: [SSO]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/connectionId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                displayName:
                  type: string
                  maxLength: 100
                enabled: { type: boolean }
                metadataUrl:
                  type: string
                  format: uri
                metadataXml:
                  type: string
                issuer:
                  type: string
                  format: uri
                clientId:
                  type: string
                clientSecret:
                  type: string
      responses:
        "200":
          description: The connection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SsoConnection"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteSsoConnection
      summary: Delete an SSO connection
      description: Never plan-gated — teardown must always be possible.
      tags: [SSO]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/connectionId"
      responses:
        "204":
          description: Deleted
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/sso/connections/{connectionId}/test:
    post:
      operationId: testSsoConnection
      summary: Test an SSO connection
      description: Checks reachability and shape of the configured metadata or issuer. Changes nothing. Throttled to 10 per minute per organization.
      tags: [SSO]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/connectionId"
      responses:
        "200":
          description: The checks
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  checks:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string }
                        ok: { type: boolean }
                        detail:
                          type: string
                          nullable: true
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Too many tests
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/sso/enforcement:
    get:
      operationId: getSsoEnforcement
      summary: Get SSO enforcement
      description: Whether members must sign in through SSO, and whether the preconditions (an active connection, a verified domain) hold.
      tags: [SSO]
      x-mint: *avail_enterprise
      responses:
        "200":
          description: The state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SsoEnforcement"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      operationId: setSsoEnforcement
      summary: Require or stop requiring SSO
      description: Turning enforcement on needs an active connection and a verified domain (and, on Cloud, the Scale plan). Turning it off is never gated. Members flagged `ssoExempt` keep signing in with a password.
      tags: [SSO]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ssoRequired]
              properties:
                ssoRequired: { type: boolean }
      responses:
        "200":
          description: The state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SsoEnforcement"
        "400":
          description: Preconditions not met
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /auth/sso/lookup:
    post:
      operationId: lookupSso
      summary: Look up SSO for an email
      description: Home-realm discovery for the login page — whether the email's domain is verified and has an SSO connection, and whether SSO is enforced. Reveals only the domain mapping, never whether a user exists. No authentication; throttled to 20 per minute per IP.
      tags: [SSO]
      x-mint: *avail_enterprise
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
      responses:
        "200":
          description: The answer
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: No SSO
                    properties:
                      sso:
                        type: boolean
                        enum: [false]
                  - type: object
                    title: SSO
                    properties:
                      sso:
                        type: boolean
                        enum: [true]
                      provider:
                        type: string
                        description: The identity-provider name to sign in through.
                      enforced: { type: boolean }
        "400":
          description: Not an email
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Too many lookups
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /org/scim/tokens:
    get:
      operationId: listScimTokens
      summary: List SCIM tokens
      description: Active tokens, without their values.
      tags: [SCIM tokens]
      x-mint: *avail_enterprise
      responses:
        "200":
          description: Tokens
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ScimToken"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createScimToken
      summary: Create a SCIM token
      description: The plaintext token (`oc_scim_…`) appears exactly once, in this response. Paste it into your identity provider's SCIM configuration with the base URL `<api-origin>/scim/v2`. On Cloud this needs the Scale plan or above.
      tags: [SCIM tokens]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label]
              additionalProperties: false
              properties:
                label:
                  type: string
                  maxLength: 64
                  example: Okta
      responses:
        "201":
          description: The token
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/ScimToken"
                  - type: object
                    properties:
                      token:
                        type: string
                        example: "oc_scim_…"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /org/scim/tokens/{tokenId}:
    delete:
      operationId: revokeScimToken
      summary: Revoke a SCIM token
      description: Never plan-gated — cutting off an integration must always be possible.
      tags: [SCIM tokens]
      x-mint: *avail_enterprise
      parameters:
        - $ref: "#/components/parameters/tokenId"
      responses:
        "204":
          description: Revoked
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Token not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # Enterprise: app availability, provisioning
  # ──────────────────────────────────────────────────────
  /org/app-availability:
    get:
      operationId: getOrgAppAvailability
      summary: Get the app-availability policy
      tags: [App availability]
      x-mint: *avail_enterprise
      responses:
        "200":
          description: The policy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppAvailability"
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    put:
      operationId: setOrgAppAvailability
      summary: Replace the app-availability policy
      description: "`open` lets every workspace connect every app. `restricted` allows only what the rules grant — each rule names people and groups and the providers they may connect. Resetting to `open` with no rules is never gated. On Cloud, restriction needs the Scale plan and group-targeted rules the Enterprise plan."
      tags: [App availability]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [mode, rules]
              properties:
                mode:
                  type: string
                  enum: [open, restricted]
                rules:
                  type: array
                  maxItems: 1000
                  items:
                    type: object
                    required: [userIds, groupIds, providers]
                    properties:
                      id:
                        type: string
                        description: Present for an existing rule; omit for a new one.
                      name:
                        type: string
                        nullable: true
                        maxLength: 200
                      userIds:
                        type: array
                        maxItems: 1000
                        items: { type: string }
                      groupIds:
                        type: array
                        maxItems: 1000
                        items: { type: string }
                      providers:
                        type: array
                        maxItems: 200
                        items: { type: string }
      responses:
        "200":
          description: The policy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppAvailability"
        "403":
          description: Plan too low (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /team/provisions:
    post:
      operationId: provisionUser
      summary: Provision a user
      description: |
        Pre-creates a member with a ready workspace and API key before they ever sign in. The key works immediately; the person claims the account by opening `claimUrl` and signing in, which transfers everything to their real identity. Unclaimed provisions expire after 7 days. Admin or owner.

        The response's `projectId` field keeps its historical name on the wire; it is the workspace id.
      tags: [Provisioning]
      x-mint: *avail_enterprise
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                role:
                  type: string
                  enum: [admin, member]
                  default: member
                skipOnboarding:
                  type: boolean
                  default: true
      responses:
        "201":
          description: The provision
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProvisionResult"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Not entitled, insufficient permissions, or the organization's seat quota is reached (Cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /provisions/claim:
    post:
      operationId: claimProvision
      summary: Claim a provisioned account
      description: Redeems a provision token for the signed-in person. Session only — the caller belongs to no organization yet.
      tags: [Provisioning]
      x-mint: *avail_enterprise
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              additionalProperties: false
              properties:
                token: { type: string }
      responses:
        "200":
          description: Claimed
          content:
            application/json:
              schema:
                type: object
                properties:
                  organizationId: { type: string }
                  organizationName: { type: string }
        "400":
          description: Missing token, or the provision is used or expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Not signed in
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────────────
  # SCIM 2.0 (own base URL: /scim/v2)
  # ──────────────────────────────────────────────────────
        "403":
          description: Not entitled (`enterprise_license_required` on a Community deployment), or insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /Users:
    get:
      operationId: scimListUsers
      summary: List users
      description: RFC 7644 list with `filter` (`userName eq "…"` only), `startIndex`, and `count` (default 100, max 500). An unknown `userName` answers an empty list, never `404`.
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: filter
          in: query
          required: false
          schema: { type: string }
        - name: startIndex
          in: query
          required: false
          schema:
            type: integer
            default: 1
        - name: count
          in: query
          required: false
          schema:
            type: integer
            default: 100
      responses:
        "200":
          description: A list response of Users
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimListResponse"
        "401":
          description: Authentication failed
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    post:
      operationId: scimCreateUser
      summary: Create a user
      description: Creates the member (and the user, when new). `userName` is the email.
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: "#/components/schemas/ScimUser"
      responses:
        "201":
          description: The User
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimUser"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "409":
          description: Already a member (`uniqueness`)
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"

  /Users/{id}:
    get:
      operationId: scimGetUser
      summary: Get a user
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The User
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimUser"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: Not found
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    put:
      operationId: scimReplaceUser
      summary: Replace a user
      description: "`active: false` suspends the member; `true` reinstates."
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: "#/components/schemas/ScimUser"
      responses:
        "200":
          description: The User
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimUser"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: Not found
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    patch:
      operationId: scimPatchUser
      summary: Patch a user
      description: RFC 7644 PatchOp. Supports `active`, `displayName`, `userName`, and the `name` sub-attributes.
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: "#/components/schemas/ScimPatch"
      responses:
        "200":
          description: The User
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimUser"
        "400":
          description: Invalid patch (`invalidPath`, `invalidValue`)
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: Not found
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    delete:
      operationId: scimDeleteUser
      summary: Delete a user
      description: "Reversible offboarding, never destructive — the membership is suspended exactly as `active: false` would, so the person's data survives and a later `active: true` restores them. A repeat delete of an already-suspended user answers `404` (what IdP validators expect for an already-gone resource). The owner cannot be removed."
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204":
          description: Removed
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: No such user, or the user is already suspended
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"

  /Groups:
    get:
      operationId: scimListGroups
      summary: List groups
      description: RFC 7644 list with `filter` (`displayName eq "…"` or `externalId eq "…"`), `startIndex`, and `count` (default 100, max 500).
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: filter
          in: query
          required: false
          schema: { type: string }
        - name: startIndex
          in: query
          required: false
          schema:
            type: integer
            default: 1
        - name: count
          in: query
          required: false
          schema:
            type: integer
            default: 100
      responses:
        "200":
          description: A list response of Groups
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimListResponse"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    post:
      operationId: scimCreateGroup
      summary: Create a group
      description: Creates a `scim`-sourced directory group.
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: "#/components/schemas/ScimGroup"
      responses:
        "201":
          description: The Group
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimGroup"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "409":
          description: A group with this name exists (`uniqueness`)
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"

  /Groups/{id}:
    get:
      operationId: scimGetGroup
      summary: Get a group
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The Group
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimGroup"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: Not found
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    put:
      operationId: scimReplaceGroup
      summary: Replace a group
      description: Replaces the name and the full member set.
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: "#/components/schemas/ScimGroup"
      responses:
        "200":
          description: The Group
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimGroup"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: Not found
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    patch:
      operationId: scimPatchGroup
      summary: Patch a group
      description: RFC 7644 PatchOp. Supports `displayName`, `externalId`, and `members` add/remove/replace.
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: "#/components/schemas/ScimPatch"
      responses:
        "200":
          description: The Group
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimGroup"
        "400":
          description: Invalid patch
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: Not found
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
    delete:
      operationId: scimDeleteGroup
      summary: Delete a group
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204":
          description: Deleted
        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"
        "404":
          description: Not found
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"

  /ServiceProviderConfig:
    get:
      operationId: scimServiceProviderConfig
      summary: Service provider configuration
      description: RFC 7643 discovery — which SCIM features this endpoint supports (patch, filter, bulk, and so on). `/ResourceTypes` and `/Schemas` are served alongside. `/Me` answers `501`.
      tags: [SCIM]
      x-mint: *avail_scim
      servers:
        - url: https://api.onecli.sh/scim/v2
          description: OneCLI Cloud
        - url: http://localhost:10254/scim/v2
          description: Self-hosted
      responses:
        "200":
          description: The configuration
          content:
            application/scim+json:
              schema:
                type: object

        "403":
          description: Not entitled — the SCIM surface is dark on a Community deployment
          content:
            application/scim+json:
              schema:
                $ref: "#/components/schemas/ScimError"