openapi: 3.0.0
info:
  title: Foxguide Public API
  description: Public API for external integrations. Authenticate with API tokens (Bearer fgd_live_xxx).
  version: 1.0.0
servers:
  - url: https://api.foxguide.io
    description: Production
  - url: https://api.stage.foxguide.io
    description: Staging
paths:
  /.well-known/jwks.json:
    get:
      x-public:
        auth: none
      operationId: get_.well-known_jwks.json
      summary: "GET /.well-known/jwks.json — the JSON Web Key Set for verifying Foxguide-issued access tokens. Public and
        unauthenticated by design: a verifier needs the key before it can hold a token. Publishes the RS256 public key
        under its `kid`, served from a 5-minute in-process cache"
      tags:
        - users-management
      responses:
        "200":
          description: Success
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsersErrorEnvelope"
      security: []
  /public/knowledge/{shareToken}:
    get:
      x-public:
        auth: none
      operationId: get_public_knowledge_shareToken
      summary: Public knowledge entry access via share token (no JWT)
      description: |
        Unauthenticated endpoint — the share token IS the authentication
        primitive. Controller verifies HMAC signature + checks revocation row.
      tags:
        - knowledge-management
        - share-tokens
      parameters:
        - in: path
          name: shareToken
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Knowledge entry content
        "404":
          description: Share token invalid / revoked / expired
      security: []
  /public/launch-unsubscribe:
    post:
      x-public:
        auth: none
      operationId: post_public_launch_one_click_unsubscribe
      summary: RFC-8058 one-click launch unsubscribe — honours immediately, no login
      description: "Unauthenticated. The signed fgd_lunsub_* token IS the auth and the subject is an EMAIL ADDRESS, not a user
        — the launch recipients are beta-allowlist addresses that mostly have no account. Writes a permanent
        address-keyed suppression row; idempotent, so a replay returns the same success. POST only: a GET that mutated
        would let a link scanner unsubscribe someone who never clicked."
      tags:
        - notifications-management
        - Public
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
      security: []
      responses:
        "200":
          description: "The address is suppressed. Idempotent — a replay returns the same success. The ADDRESS IS NOT ECHOED: a
            caller holding a valid token already knows it, and echoing it would turn this endpoint into an
            address-confirmation oracle."
          content:
            application/json:
              schema:
                type: object
                description: Mirrors `LaunchUnsubscribeResponse` (`@foxguide/api-contracts/public/notifications`).
                required:
                  - suppressed
                properties:
                  suppressed:
                    type: boolean
                    enum:
                      - true
        "400":
          description: "`LAUNCH_UNSUBSCRIBE_TOKEN_INVALID` (wrong prefix, incomplete claims, HMAC mismatch) or
            `LAUNCH_UNSUBSCRIBE_TOKEN_MALFORMED` (undecodable)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "410":
          description: "`LAUNCH_UNSUBSCRIBE_TOKEN_EXPIRED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "429":
          description: "`RATE_LIMIT_EXCEEDED` — too many requests for this one link."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "503":
          description: "`LAUNCH_UNSUBSCRIBE_KEY_UNAVAILABLE` — the signing key could not be read. Our fault, not a claim about the
            token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
  /public/media/{token}:
    get:
      x-public:
        auth: none
      operationId: get_public_media_token
      summary: GET /public/media/{token} — UNAUTHENTICATED public-share resolve (the token IS the auth; HMAC-verified +
        revocation-checked in-handler). STREAMS the raw file bytes inline (MP322-fileviewers SUB-G, DEC-MP322-7, bug
        013). NOTE the pre-MP330 declaration here claimed a 302 to a presigned URL — that path was replaced by the byte
        stream at the MP322 bug-C rewrite and the 302 was stale doc drift, retired at MP330-filesapp SUB-14 (bug 016)
      tags:
        - media-management
      responses:
        "200":
          description: The raw file bytes (Content-Type = the file's mime; Content-Disposition inline)
        "401":
          description: Invalid / malformed share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Share token revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Share token not found or file no longer available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "429":
          description: Rate limit exceeded for this share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security: []
    parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
  /public/media/{token}/archive/list:
    post:
      x-public:
        auth: none
      operationId: post_public_media_token_archive_list
      summary: POST /public/media/{token}/archive/list — UNAUTHENTICATED zip entry tree. Shares the resolveArchiveListing core
        with the authed sibling (no fork). READ-ONLY — extraction has no public sibling (MP330-filesapp SUB-14, bug 016)
      tags:
        - media-management
      responses:
        "200":
          description: The archive entry tree
        "401":
          description: Invalid / malformed share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Share token revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Share token not found or file no longer available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "413":
          description: Archive source exceeds the listing size cap
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "415":
          description: Unsupported archive format (server listing is ZIP-only; RAR / 7z are client-side)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "429":
          description: Rate limit exceeded for this share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security: []
    parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
  /public/media/{token}/content:
    get:
      x-public:
        auth: none
      operationId: get_public_media_token_content
      summary: GET /public/media/{token}/content — UNAUTHENTICATED editable-text / backend-rendered-HTML content. Backs the
        public Monaco + markdown viewers. READ-ONLY (there is deliberately no PUT sibling) (MP330-filesapp SUB-14, bug
        016)
      tags:
        - media-management
      responses:
        "200":
          description: The file content envelope
        "401":
          description: Invalid / malformed share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Share token revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Share token not found or file no longer available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "429":
          description: Rate limit exceeded for this share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security: []
    parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
  /public/media/{token}/download:
    get:
      x-public:
        auth: none
      operationId: get_public_media_token_download
      summary: GET /public/media/{token}/download — UNAUTHENTICATED SERVER-SIDE download gate (MP330-fileviewers3 SUB-1, bug
        007). Streams the raw bytes with Content-Disposition attachment ONLY when the resolved share's accessLevel is
        'download'; a 'view'-access share is REJECTED 403 SHARE_DOWNLOAD_FORBIDDEN so a view-only token cannot be
        coerced into a download by any client. The token IS the auth (HMAC-verified + revocation-checked + rate-limited
        in-handler)
      tags:
        - media-management
      responses:
        "200":
          description: The raw file bytes (Content-Disposition attachment)
        "401":
          description: Invalid / malformed share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Share token revoked OR the share is view-only (SHARE_DOWNLOAD_FORBIDDEN)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Share token not found or file no longer available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "429":
          description: Rate limit exceeded for this share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security: []
    parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
  /public/media/{token}/meta:
    get:
      x-public:
        auth: none
      operationId: get_public_media_token_meta
      summary: GET /public/media/{token}/meta — UNAUTHENTICATED render descriptor for the public viewer. Returns {fileName,
        mimeType, fileKind, fileSize, accessLevel}; accessLevel ('view'|'download') is a public-safe access-MODE enum
        (MP330-fileviewers3 SUB-1, bug 007), NOT tenant identity — teamId / mediaId / driveId are NEVER exposed and the
        response is gated by the strict MediaPublicShareMetaSchema (MP330-filesapp SUB-14, bug 016)
      tags:
        - media-management
      responses:
        "200":
          description: The render descriptor (fileName / mimeType / fileKind / fileSize / accessLevel)
        "401":
          description: Invalid / malformed share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Share token revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Share token not found or file no longer available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "429":
          description: Rate limit exceeded for this share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security: []
    parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
  /public/media/{token}/preview:
    post:
      x-public:
        auth: none
      operationId: post_public_media_token_preview
      summary: POST /public/media/{token}/preview — UNAUTHENTICATED Gotenberg→PDF office preview. Mirrors the authed sibling's
        status contract byte-for-byte (503 when the sidecar is unset/unreachable, 200 otherwise). Performs no mutation
        (MP330-filesapp SUB-14, bug 016)
      tags:
        - media-management
      responses:
        "200":
          description: The preview descriptor (available:true, or a stable not_convertible / convert_failed reason)
        "401":
          description: Invalid / malformed share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Share token revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Share token not found or file no longer available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "429":
          description: Rate limit exceeded for this share token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "503":
          description: Preview sidecar unset / unreachable — retryable degrade
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security: []
    parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
  /public/notifications/{id}/open:
    get:
      x-public:
        auth: none
      operationId: get_public_notification_open
      summary: Tracked redirect — verify token, mark read, 302 to the resource
      tags:
        - notifications-management
        - Public
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: token
          in: query
          required: true
          schema:
            type: string
      security: []
  /public/notifications/{id}/pixel:
    get:
      x-public:
        auth: none
      operationId: get_public_notification_pixel
      summary: Email-open tracking pixel — verify token, mark read, return a 1x1 GIF
      tags:
        - notifications-management
        - Public
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: token
          in: query
          required: true
          schema:
            type: string
      security: []
  /public/unsubscribe:
    post:
      x-public:
        auth: none
      operationId: post_public_one_click_unsubscribe
      summary: RFC-8058 one-click unsubscribe — honours immediately, no login
      description: Unauthenticated. The signed fgd_unsub_* token IS the auth and tenancy comes only from its claims. Accepts
        an empty body (scope defaults to 'category'), the mail-client form body List-Unsubscribe=One-Click, or a JSON
        {"scope":"all"} escalation from the landing page.
      tags:
        - notifications-management
        - Public
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
      security: []
      responses:
        "200":
          description: The suppression is in effect. A REPLAY is also a 200 — the write is `enabled = false`, so re-clicking is
            inherently a no-op — and `alreadySuppressed` is what separates the two for the landing page and for an
            operator debugging a delivery complaint.
          content:
            application/json:
              schema:
                type: object
                description: Mirrors `OneClickUnsubscribeResponse` (`@foxguide/api-contracts/public/notifications`).
                required:
                  - ok
                  - scope
                  - category
                  - suppressedCategories
                  - alreadySuppressed
                properties:
                  ok:
                    type: boolean
                    enum:
                      - true
                  scope:
                    type: string
                    enum:
                      - category
                      - all
                  category:
                    type: string
                    description: The category the token was minted for.
                  suppressedCategories:
                    type: array
                    items:
                      type: string
                    description: Every category this request left suppressed. DERIVED FROM THE WRITE RESULT, never from the request, so the
                      response cannot claim a suppression the row does not carry.
                  alreadySuppressed:
                    type: boolean
        "400":
          description: "`UNSUBSCRIBE_TOKEN_INVALID` — wrong prefix, malformed, undecodable, incomplete claims, or the HMAC does
            not verify. 400 and NOT 401: there is no session to authenticate, so a challenge would be a lie about what
            the caller could do differently."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "410":
          description: "`UNSUBSCRIBE_TOKEN_INVALID` (expired) or `UNSUBSCRIBE_TOKEN_REVOKED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "429":
          description: "`RATE_LIMIT_EXCEEDED` — too many requests for this one link."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "500":
          description: "`INTERNAL_ERROR` — the suppression write failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "503":
          description: "`SERVICE_UNAVAILABLE` — the signing key could not be read. An operational fault on OUR side, NOT a claim
            about the token, so it must not be reported as an invalid link the recipient can do nothing about."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
  /public/unsubscribe/manage:
    get:
      x-public:
        auth: none
      operationId: get_public_unsubscribe_state
      summary: Footer-link target — 302 to the landing page for a browser, state JSON otherwise
      tags:
        - notifications-management
        - Public
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
      security: []
      responses:
        "200":
          description: "The state a non-browser caller reads. A real browser navigation gets the 302 instead — the branch is on
            `Accept: text/html`, explicitly, never sniffed from a user agent."
          content:
            application/json:
              schema:
                type: object
                description: Mirrors `UnsubscribeStateResponse` (`@foxguide/api-contracts/public/notifications`).
                required:
                  - category
                  - categoryEnabled
                  - allSuppressed
                  - preferencesUrl
                properties:
                  category:
                    type: string
                  categoryEnabled:
                    type: boolean
                    description: False once the category is suppressed.
                  allSuppressed:
                    type: boolean
                    description: True when the `all` escalation has already been applied.
                  preferencesUrl:
                    type: string
                    description: Deep link to the AUTHENTICATED preferences screen. An ONWARD link, never the one-click target — a footer
                      link that hits a login wall is half-compliance in a different place.
        "302":
          description: "A browser (`Accept: text/html`) is bridged to the landing page. The redirect happens BEFORE verification
            on purpose: an invalid token must still render the page's refusal state rather than a bare error body in a
            browser tab."
        "400":
          description: "`UNSUBSCRIBE_TOKEN_INVALID` — see the sibling POST."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "410":
          description: Token expired or revoked.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "429":
          description: "`RATE_LIMIT_EXCEEDED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "500":
          description: "`INTERNAL_ERROR` — the state read failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
        "503":
          description: "`SERVICE_UNAVAILABLE` — the signing key could not be read."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsErrorEnvelope"
  /public/visual-pages/{teamId}/{id}:
    get:
      x-public:
        auth: none
      summary: Public render of published version (no auth, noindex) — canonical /public prefix
      tags:
        - visuals-management
        - Public
      security: []
  /v1/agents:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents
      summary: List installed agents for current team
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agents retrieved successfully
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/agents/bulk:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_bulk
      summary: "Bulk model switch (MP397-modelrouting SUB-9, DEC-MP397-27) — move the selected agents onto the `target`
        runtime tuple (AgentModelSwitchTarget = the canonical TeamAgentDefaults block). Per agent: decide, snapshot a
        restorable config version (DEC-MP397-31), gate the runtime tuple, then write. Partial-success: every agent that
        did not move is reported in blocked[] with ACTION_PRECONDITION_UNMET and the specific reason in detail.reason.
        Declared BEFORE /v1/agents/{id} so `bulk` is not swallowed as an agent id."
      tags:
        - ai-agents-management
      security:
        - apiToken: []
      responses:
        "200":
          description: Bulk switch partial-success result (affected + blocked[])
        "400":
          description: Unsupported bulk action, missing/invalid target, or unsupported selection field/condition
        "401":
          description: UNAUTHORIZED
        "409":
          description: Bulk selection changed since the confirmation count
  /v1/agents/bulk/count:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_bulk_count
      summary: Bulk model-switch preflight (MP397-modelrouting SUB-9, DEC-MP397-27) — resolve the id-list or
        select-all-matching filter, apply BOTH authorization axes (the `installedBy` ownership scope and the per-agent
        visibility ACL the list applies), count, and mint the confirm token. Select-all-matching ALWAYS requires
        confirmation. Declared BEFORE /v1/agents/{id} so `bulk` is not swallowed as an agent id.
      tags:
        - ai-agents-management
      security:
        - apiToken: []
      responses:
        "200":
          description: Preflight count + blocked[] + confirm token
        "400":
          description: Unsupported bulk action or unsupported selection field/condition
        "401":
          description: UNAUTHORIZED
  /v1/agents/create:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_create
      summary: Create custom agent
      tags:
        - ai-agents-management
      responses:
        "201":
          description: Custom agent created
      security:
        - apiToken: []
  /v1/agents/graph:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_graph
      summary: Whole-team agent DAG graph (MP375-subagents SUB-4) — unpaginated agent nodes
        {agentId,name,avatar*,isSystemAgent,parentAgentIds,childAgentIds,source} feeding the members-tree + child-agent
        picker + parent-awareness. `childAgentIds` is server-computed (inverted from parentAgentIds). Declared BEFORE
        /v1/agents/{id} so `graph` is not swallowed as an agent id.
      tags:
        - ai-agents-management
      security:
        - apiToken: []
      responses:
        "200":
          description: ok (data[] agent graph nodes)
        "401":
          description: UNAUTHORIZED
  /v1/agents/models:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_models
      summary: "Canonical model catalog (MP330-sdkmodels-SUB-4, DEC-MP330-8) — the server-side projection of MODEL_REGISTRY.
        Clients (web + iOS) MUST read the selectable models, their per-provider defaults and their capabilities from
        here instead of enumerating models locally. JWT-gated: the catalog carries the per-model cost structure and is
        not public information. Declared BEFORE /v1/agents/{id} so `models` is not swallowed as an agent id."
      tags:
        - ai-agents-management
      security:
        - apiToken: []
      responses:
        "200":
          description: ok (models[] + per-provider defaults + platform default)
        "401":
          description: UNAUTHORIZED
  /v1/agents/{id}:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id
      summary: Get installed agent by ID
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent retrieved successfully
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: patch_v1_agents_id
      summary: Update installed agent configuration. An UNDECLARED body field is refused with 400 VALIDATION_ERROR
        (MP6294-bugs23 SUB-9 W6) — it is no longer silently dropped behind a 200.
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent updated successfully
        "400":
          description: "Validation error — including an UNDECLARED body field, which this route refuses rather than deleting. The
            key is named in `details[].message` with `code: unrecognized_keys`; `details[].field` is empty for that
            issue class."
        "401":
          description: Unauthorized
        "403":
          description: Insufficient permissions
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: ai_agents
          action: delete
      operationId: delete_v1_agents_id
      summary: Uninstall agent
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent uninstalled successfully
        "401":
          description: Unauthorized
        "403":
          description: Insufficient permissions
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/access:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_access
      summary: Get agent user access settings
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Access settings retrieved
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: patch_v1_agents_id_access
      summary: Update agent user access settings
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Access settings updated
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      security:
        - apiToken: []
  /v1/agents/{id}/archive:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_archive
      summary: Archive agent
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Agent archived successfully
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      security:
        - apiToken: []
  /v1/agents/{id}/children:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_children
      summary: List an agent's direct sub-agent children (DAG down-traversal)
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Direct children retrieved
        "401":
          description: Unauthorized
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_children
      summary: Link a DAG sub-agent edge (parent -> childAgentId)
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Edge linked (idempotent)
        "400":
          description: AGENT_DAG_SELF_EDGE / AGENT_DAG_CYCLE / AGENT_DAG_DEPTH_EXCEEDED / VALIDATION_ERROR
        "401":
          description: Unauthorized
        "404":
          description: AGENT_DAG_PARENT_NOT_FOUND
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/children/{childId}:
    delete:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: delete_v1_agents_id_children_childId
      summary: Unlink a DAG sub-agent edge (parent -> childId)
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Edge unlinked (idempotent)
        "401":
          description: Unauthorized
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: path
          name: childId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/clone:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_clone
      summary: Clone agent
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "201":
          description: Agent cloned successfully
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
        "500":
          description: Clone failed
      security:
        - apiToken: []
  /v1/agents/{id}/context-budget:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_context_budget
      summary: Get context budget for agent
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Context budget retrieved
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/favorites:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_favorites
      summary: Get agent favorites status
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Favorites status retrieved
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_favorites
      summary: Add agent to favorites
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent added to favorites
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: delete_v1_agents_id_favorites
      summary: Remove agent from favorites
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent removed from favorites
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/onboarding-progress:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_onboarding_progress
      summary: Get calling user's per-(agent, team) onboarding progress (MP49 DEC-OB-2-v2 per-user split)
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Per-user onboarding progress retrieved (null when not yet initialized)
        "400":
          description: Missing selectedTeamId
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/agents/{id}/onboardings:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_agent_onboardings
      summary: Get agent onboarding stages from KB manifest (MP100-SUB-5)
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Onboarding stages manifest from agent KB
        "400":
          description: Validation error
        "404":
          description: Agent not found
      security:
        - apiToken: []
  /v1/agents/{id}/restore:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_restore
      summary: Restore archived agent
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Agent restored successfully
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      security:
        - apiToken: []
  /v1/agents/{id}/skills:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_skills
      summary: List agent's skills manifest (proxy to knowledge-management)
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Skills manifest envelope
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/summary:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_summary
      summary: Get per-agent summary counters (skills/subAgents/onboarding/commands/tools/sessions/secrets/hierarchy)
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent summary envelope
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/tool-access-overrides:
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_tool_access_overrides
      summary: Read agent-scope per-TOOL override map (MP223-SUB-5)
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent tool-access overrides
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: patch_v1_agents_id_tool_access_overrides
      summary: Update agent-scope per-TOOL override map (MP223-SUB-5)
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Agent tool-access overrides updated
        "400":
          description: Validation error (TOOL_ACCESS_OVERRIDE_INVALID)
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/tree-access:
    patch:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: patch_v1_agents_id_tree_access
      summary: "Update the three installed tree-node access axes — nodeEnabled / childAccess / commandAccess (MP404-agentdocs
        SUB-D1 W5). INSTALLER-ONLY: the author / listing team is refused. Deliberately a separate path from
        /v1/agents/{id}/access, which writes the USER-visibility axis under a different authorization model — merging
        them would make authorization depend on which body fields a request happens to name."
      tags:
        - ai-agents-management
      responses:
        "200":
          description: Tree access updated
          content:
            application/json:
              schema:
                type: object
                description: The three axes AFTER the write, so a caller never re-reads to learn what they now are.
                required:
                  - message
                  - agentId
                  - nodeEnabled
                  - childAccess
                  - commandAccess
                properties:
                  message:
                    type: string
                  agentId:
                    type: string
                  nodeEnabled:
                    type: boolean
                  childAccess:
                    type: object
                    additionalProperties:
                      type: boolean
                  commandAccess:
                    type: object
                    additionalProperties:
                      type: boolean
        "400":
          description: Validation error (the AgentTreeAccessSchema strict IL-56 gate refused)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Author or listing team attempted an installer-only write
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/agents/{id}/versions:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_versions
      summary: Create new config version for agent
      tags:
        - ai-agents-management
      responses:
        "201":
          description: Config version created
        "400":
          description: Validation error or version limit exceeded
        "401":
          description: Unauthorized
        "404":
          description: Agent not found
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_versions
      summary: List a agent's version history, newest first
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: limit
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: Version page. Body is `{data, pagination}` where pagination carries `limit` + `hasMore`. There is
            deliberately NO `total` — the store answers a page and a cursor, and a synthesised total beside `hasMore`
            would be a self-contradicting pair.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceVersionPage"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: FORBIDDEN (missing ai_agents:read permission)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: AGENT_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "500":
          description: INTERNAL_ERROR
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
      security:
        - apiToken: []
  /v1/agents/{id}/versions/{versionNumber}:
    patch:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: patch_v1_agents_id_versions_versionnumber
      summary: Edit the OPEN DRAFT's config — a sealed version refuses (VERSION_SEALED)
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: path
          name: versionNumber
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: The updated draft
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceVersion"
        "409":
          description: VERSION_SEALED — a published version cannot be edited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: delete_v1_agents_id_versions_versionnumber
      summary: Delete one version — the last remaining one refuses (VERSION_LAST_REMAINING)
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: path
          name: versionNumber
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "{ deleted, remaining }"
          content:
            application/json:
              schema:
                type: object
                required:
                  - deleted
                  - remaining
                properties:
                  deleted:
                    type: boolean
                  remaining:
                    type: integer
                    description: Live versions left after the delete.
        "409":
          description: VERSION_LAST_REMAINING — a versioned record always keeps one
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
      security:
        - apiToken: []
    get:
      x-public:
        scope:
          entity: ai_agents
          action: read
      operationId: get_v1_agents_id_versions_versionnumber
      summary: Fetch one agent version by number
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: path
          name: versionNumber
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Version retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceVersion"
        "400":
          description: VALIDATION_ERROR — versionNumber must be a positive integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: FORBIDDEN (missing ai_agents:read permission)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: AGENT_NOT_FOUND or VERSION_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "500":
          description: INTERNAL_ERROR
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
      security:
        - apiToken: []
  /v1/agents/{id}/versions/{versionNumber}/publish:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_versions_versionnumber_publish
      summary: Publish the open draft — applies its config to the agent, then seals it
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: path
          name: versionNumber
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: The now-sealed version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceVersion"
        "409":
          description: VERSION_NOT_DRAFT — already published
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
      security:
        - apiToken: []
  /v1/agents/{id}/versions/{versionNumber}/restore:
    post:
      x-public:
        scope:
          entity: ai_agents
          action: write
      operationId: post_v1_agents_id_versions_versionnumber_restore
      summary: Take a version as the base — APPEND-ONLY, never a pointer move
      tags:
        - ai-agents-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: path
          name: versionNumber
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Re-materialized from the version; a new `changeType:'restored'` version was appended (or deduped when the
            content already matched the newest)
          content:
            application/json:
              schema:
                type: object
                description: "The UPDATED installed agent, as `GET /v1/agents/{id}` returns it — not the version row. Canonical shape:
                  `@foxguide/api-contracts/domain/agents::Agent`; it is not restated field-by-field here because this
                  api-config declares no Agent schema to keep honest, and a hand-copied duplicate would drift from the
                  canonical one silently."
        "400":
          description: VALIDATION_ERROR — versionNumber must be a positive integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: FORBIDDEN (missing ai_agents:write permission)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: AGENT_NOT_FOUND or VERSION_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "500":
          description: INTERNAL_ERROR
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
      security:
        - apiToken: []
  /v1/billing/earnings:
    get:
      x-public:
        scope:
          entity: billing
          action: read
      summary: Author earnings summary (totals per currency + per-listing rows)
      tags:
        - billing-management
        - Monetization
      security:
        - apiToken: []
  /v1/billing/earnings/{listingId}:
    get:
      x-public:
        scope:
          entity: billing
          action: read
      summary: Author earnings detail for one listing
      tags:
        - billing-management
        - Monetization
      security:
        - apiToken: []
      parameters:
        - name: listingId
          in: path
          required: true
          schema:
            type: string
  /v1/billing/payout-requests:
    get:
      x-public:
        scope:
          entity: billing
          action: read
      summary: List the caller's own payout requests
      tags:
        - billing-management
        - Monetization
      security:
        - apiToken: []
  /v1/calendar-events:
    get:
      x-public:
        scope:
          entity: calendar_events
          action: read
      summary: List calendar events
      tags:
        - tasks-management
        - Calendar Events
      parameters:
        - name: calendarIds
          in: query
          schema:
            type: string
        - name: startDate
          in: query
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
        - name: status
          in: query
          schema:
            type: string
            enum:
              - confirmed
              - tentative
              - cancelled
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: calendar_events
          action: write
      summary: Create calendar event
      tags:
        - tasks-management
        - Calendar Events
      security:
        - apiToken: []
  /v1/calendar-events/{id}:
    get:
      x-public:
        scope:
          entity: calendar_events
          action: read
      summary: Get calendar event
      tags:
        - tasks-management
        - Calendar Events
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: calendar_events
          action: write
      summary: Update calendar event
      tags:
        - tasks-management
        - Calendar Events
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: calendar_events
          action: delete
      summary: Delete calendar event
      tags:
        - tasks-management
        - Calendar Events
      security:
        - apiToken: []
  /v1/calendar-events/{id}/guests:
    post:
      x-public:
        scope:
          entity: calendar_events
          action: write
      summary: Invite additional guests to a calendar event
      tags:
        - tasks-management
        - Calendar Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GuestInvitePayload"
      responses:
        "201":
          description: Guests appended; returns the updated event.
        "400":
          description: Validation error or all guests already invited.
        "404":
          description: Calendar event not found.
      security:
        - apiToken: []
  /v1/calendar-events/{id}/rsvp:
    post:
      x-public:
        scope:
          entity: calendar_events
          action: write
      summary: RSVP to calendar event
      tags:
        - tasks-management
        - Calendar Events
      security:
        - apiToken: []
  /v1/calendars:
    get:
      x-public:
        scope:
          entity: calendars
          action: read
      summary: List calendars
      tags:
        - tasks-management
        - Calendars
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
        - name: type
          in: query
          schema:
            type: string
            enum:
              - local
              - google
              - caldav
              - calcom
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: calendars
          action: write
      summary: Create local calendar
      tags:
        - tasks-management
        - Calendars
      security:
        - apiToken: []
  /v1/calendars/{id}:
    get:
      x-public:
        scope:
          entity: calendars
          action: read
      summary: Get calendar
      tags:
        - tasks-management
        - Calendars
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: calendars
          action: write
      summary: Update calendar
      tags:
        - tasks-management
        - Calendars
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: calendars
          action: delete
      summary: Soft delete calendar
      tags:
        - tasks-management
        - Calendars
      security:
        - apiToken: []
  /v1/categories:
    get:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: get_v1_categories
      summary: List categories (filter by knowledgeBaseId)
      tags:
        - knowledge-management
      responses:
        "200":
          description: Categories retrieved successfully
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/categories/{id}:
    get:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: get_v1_categories_id
      summary: Get category by ID
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Category retrieved successfully
        "401":
          description: Unauthorized
        "404":
          description: Category not found
      security:
        - apiToken: []
  /v1/contacts:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: List contacts (paginated; filter by status/stage/leadSource/platform/tags/query/dateRange)
      tags:
        - contacts-management
        - Contacts
      security:
        - apiToken: []
      parameters:
        - in: query
          name: query
          schema:
            type: string
        - in: query
          name: status
          schema:
            type: string
            enum:
              - active
              - inactive
              - blocked
              - deleted
        - in: query
          name: stage
          schema:
            type: string
            enum:
              - lead
              - prospect
              - opportunity
              - customer
              - churned
        - in: query
          name: leadSource
          schema:
            type: string
        - in: query
          name: platform
          schema:
            type: string
        - in: query
          name: tags
          schema:
            type: string
        - in: query
          name: dateFrom
          schema:
            type: string
            format: date-time
        - in: query
          name: dateTo
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
        - in: query
          name: offset
          schema:
            type: integer
      responses:
        "200":
          description: Paginated contact list
    post:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Create a contact (IL-56 gated canonical-shape write; dedups IFF a contactBookId is named)
      tags:
        - contacts-management
        - Contacts
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                contactBookId:
                  type: string
                  nullable: true
                  description: MP6431-chats-SUB-7. PRESENT — the create is routed through the ratified per-book acceptance engine, which
                    applies THIS book's declared identityRule (an absent rule inherits the platform default) and answers
                    409 CONTACT_DUPLICATE_FIELD on a declared-unique repeat. ABSENT or null — a BOOKLESS create, which
                    does NOT deduplicate and MAY produce a second row for the same address. That is the 2026-08-28
                    operator ruling working as designed, not a defect.
      responses:
        "201":
          description: Contact created
        "400":
          $ref: "#/components/responses/ValidationError"
        "409":
          description: "CONTACT_DUPLICATE_FIELD — a contact in the NAMED book already carries a field that book declares unique.
            Reachable only when the request named a contactBookId: a bookless create does not deduplicate and cannot
            answer 409 (2026-08-28 operator ruling)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsErrorEnvelope"
  /v1/contacts/contact-books:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: List the team's contact books (paginated), optionally filtered by origin
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
        - in: query
          name: offset
          schema:
            type: integer
        - in: query
          name: origin
          schema:
            type: string
            enum:
              - manual
              - channel
              - guest
      responses:
        "200":
          description: Paginated contact book list
        "400":
          description: 'VALIDATION_ERROR — an unrecognised `origin` value. Fails CLOSED rather than silently narrowing to
            `manual`: a typo must not return a filtered list the caller reads as the filter they asked for. An ABSENT
            `origin` is a different state ("no filter") and is not an error.'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsErrorEnvelope"
    post:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Create an contact book (IL-56 gated)
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                origin:
                  type: string
                  enum:
                    - manual
                    - channel
                    - guest
      responses:
        "201":
          description: Contact book created
        "409":
          description: Contact book name already taken
  /v1/contacts/contact-books/channel-bindings:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: List the team's channel -> contact-book bindings (paginated)
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
        - in: query
          name: offset
          schema:
            type: integer
      responses:
        "200":
          description: The page of channel bindings for the selected team.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsChannelBindingPage"
    post:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Bind a channel to a contact book — the book is MANDATORY and the choice is EXPLICIT (IL-56 gated)
      description: "MP5483-realimport SUB-7 (`DEC-MP5483-82`). Exactly one of `contactBookId` (an existing book) or
        `createBook: true` (a dedicated `origin:'channel'` book) MUST be present; a body naming neither is a `400
        CHANNEL_BOOK_REQUIRED`. Silence used to select the auto-create default — that implicit choice is what the
        operator's ruling replaced with a question. When a re-point would strand the profiles the channel's current book
        already holds, the request answers `409 CHANNEL_REBIND_REQUIRES_PROFILE_DECISION` carrying the available
        options; resend with `profileDisposition` to proceed."
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - channelId
              properties:
                channelId:
                  type: string
                contactBookId:
                  type: string
                  description: Bind to this EXISTING book. Mutually exclusive with createBook.
                createBook:
                  type: boolean
                  enum:
                    - true
                  description: Create a dedicated origin:'channel' book. Mutually exclusive with contactBookId.
                profileDisposition:
                  type: string
                  enum:
                    - leave
                    - move
                    - copy
                  description: What happens to the profiles the CURRENT book holds for this channel when it is re-pointed. Required only
                    after a `409 CHANNEL_REBIND_REQUIRES_PROFILE_DECISION`. `move` re-parents them into the target book;
                    `copy` duplicates them there and leaves the originals in place; `leave` does nothing. The server
                    emits the accepted list in that 409's `details.options`, so a client never hardcodes it.
      responses:
        "201":
          description: The binding that now exists. `binding` is the NARROW pair, not the stored row — the create path returns
            only what it decided.
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                  - binding
                properties:
                  message:
                    type: string
                    example: Channel bound to contact book
                  binding:
                    $ref: "#/components/schemas/ContactsChannelBindingRef"
        "404":
          description: NOT_FOUND — an explicit contactBookId that resolves to no existing book. Reachable only on the explicit
            re-bind arm; the auto-ensure arm creates the book instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsErrorEnvelope"
  /v1/contacts/contact-books/channel-bindings/{channelId}:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: Read ONE channel's contact-book binding
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: channelId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The binding for this channel. Exists BESIDE the team-wide collection GET because that list caps at 100 — a
            settings page filtering the returned page client-side would answer "no book bound" for a channel that IS
            bound on any team past the cap, a wrong answer that looks exactly like a right one.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsChannelBindingDto"
        "404":
          description: 'NOT_FOUND — the channel is UNBOUND. That is a legitimate, expected state (a channel lazily auto-creates
            its book on first inbound), so the service returns null and the wire encoding of "absent resource" is
            decided here: 404, never a 200 with an empty body.'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsErrorEnvelope"
    delete:
      x-public:
        scope:
          entity: contact
          action: delete
      summary: Unbind a channel from its contact book
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: channelId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Acknowledgement. The unbind is IDEMPOTENT — unbinding a channel that was never bound is a 200, not a 404,
            because the post-condition the caller asked for already holds.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsAcknowledgement"
  /v1/contacts/contact-books/{id}:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: Get one contact book
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Contact book
        "404":
          description: Not found
    patch:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Rename an contact book (IL-56 gated)
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
      responses:
        "200":
          description: Contact book renamed
        "409":
          description: Contact book name already taken
    delete:
      x-public:
        scope:
          entity: contact
          action: delete
      summary: Delete an contact book (members un-parented — contacts survive)
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Contact book deleted
  /v1/contacts/contact-books/{id}/records:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: List the member records of a book, for ANY Contacts-app resource type
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: resourceType
          required: true
          schema:
            type: string
            enum:
              - contact
              - profile
              - visitor
        - in: query
          name: limit
          schema:
            type: integer
        - in: query
          name: offset
          schema:
            type: integer
      responses:
        "200":
          description: The page of member records, projected per `resourceType`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsBookRecordsPage"
        "404":
          description: NOT_FOUND — no such contact book in this team.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsErrorEnvelope"
    post:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Move existing records of ANY Contacts-app resource type into a book (re-parent)
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - resourceType
                - recordIds
              properties:
                resourceType:
                  type: string
                  enum:
                    - contact
                    - profile
                    - visitor
                recordIds:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          description: How many records were re-parented. `moved` can be lower than `recordIds.length` — an id that names no live
            record of that resourceType in this team is skipped, not an error.
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                  - moved
                properties:
                  message:
                    type: string
                    example: Records moved
                  moved:
                    type: integer
                    description: Count of records re-parented.
        "400":
          description: "VALIDATION_ERROR — `recordIds` must be an array of strings and `resourceType` is REQUIRED with no default.
            A mixed array is REFUSED rather than partially applied: the response reports only a count, so a silent drop
            would be invisible to the caller."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsErrorEnvelope"
        "404":
          description: NOT_FOUND — no such contact book in this team.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsErrorEnvelope"
  /v1/contacts/contact-books/{id}/resolve:
    post:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Resolve an identity into a book (dedup-preserving — one person, one contact)
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  nullable: true
                phone:
                  type: string
                  nullable: true
                displayName:
                  type: string
                firstName:
                  type: string
                lastName:
                  type: string
      responses:
        "200":
          description: Existing contact re-parented into the book
        "201":
          description: New book-scoped contact created
  /v1/contacts/contact-books/{id}/restore:
    post:
      x-public:
        scope:
          entity: contact
          action: delete
      summary: Restore a soft-deleted contact book from the trash (members return with it)
      tags:
        - contacts-management
        - Contact Books
      security:
        - apiToken: []
  /v1/contacts/schema/fields:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: Read the custom-field schema for a resource type (?resourceType=)
      tags:
        - contacts-management
        - Contacts App
      security:
        - apiToken: []
      parameters:
        - in: query
          name: resourceType
          required: true
          schema:
            type: string
            enum:
              - contact
              - profile
              - visitor
      responses:
        "200":
          description: Field schema (fields[] TableField)
  /v1/contacts/{id}:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: Get a contact + its rolled-up profiles + visitors
      tags:
        - contacts-management
        - Contacts
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Contact + rollup
        "404":
          description: Not found
    patch:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Update a contact (IL-56 gated on full post-update doc)
      tags:
        - contacts-management
        - Contacts
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Contact updated
        "404":
          description: Not found
    delete:
      x-public:
        scope:
          entity: contact
          action: delete
      summary: Soft-delete a contact (status -> deleted)
      tags:
        - contacts-management
        - Contacts
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
        "404":
          description: Not found
  /v1/contacts/{id}/custom-fields:
    patch:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Set (PATCH-merge) custom-field values on a contact (governed vs schema)
      tags:
        - contacts-management
        - Contacts App
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                values:
                  type: object
      responses:
        "200":
          description: Updated customFields map
        "400":
          $ref: "#/components/responses/ValidationError"
  /v1/contacts/{id}/links:
    get:
      x-public:
        scope:
          entity: contact
          action: read
      summary: List a contact's linked resources
      tags:
        - contacts-management
        - Contacts App
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: links array (far-end openable pointers)
    post:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Link an openable pointer to a contact (idempotent)
      tags:
        - contacts-management
        - Contacts App
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "201":
          description: Updated links array (far-end openable pointers)
        "400":
          $ref: "#/components/responses/ValidationError"
    delete:
      x-public:
        scope:
          entity: contact
          action: write
      summary: Unlink a pointer from a contact (by pointerType + pointerId query)
      tags:
        - contacts-management
        - Contacts App
      security:
        - apiToken: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Updated links array (far-end openable pointers)
  /v1/contacts/{id}/restore:
    post:
      x-public:
        scope:
          entity: contact
          action: delete
      summary: Restore a soft-deleted contact from the trash
      tags:
        - contacts-management
        - Contacts
      security:
        - apiToken: []
  /v1/databases:
    get:
      x-public:
        scope:
          entity: databases
          action: read
      operationId: get_v1_databases
      summary: GET /v1/databases - Get all databases (system + user merged)
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: databases
          action: write
      operationId: post_v1_databases
      summary: POST /v1/databases - Create user database
      tags:
        - databases-management
      responses:
        "201":
          description: Created
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
  /v1/databases/{id}:
    get:
      x-public:
        scope:
          entity: databases
          action: read
      operationId: get_v1_databases_id
      summary: GET /v1/databases/{id} - Get database by ID
      tags:
        - databases-management
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: databases
          action: write
      operationId: patch_v1_databases_id
      summary: PATCH /v1/databases/{id} - Update database
      tags:
        - databases-management
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives:
    get:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: get_v1_drives
      summary: List all media drives
      tags:
        - media-management
      responses:
        "200":
          description: Drives list retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: drives
          action: write
      operationId: post_v1_drives
      summary: Create new media drive
      tags:
        - media-management
      responses:
        "201":
          description: Drive created successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/external/browse:
    post:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: post_v1_drives_external_browse
      summary: Browse integration drive contents before connecting
      tags:
        - media-management
      responses:
        "200":
          description: Drive contents
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}:
    get:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: get_v1_drives_driveId
      summary: Get drive by ID
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Drive retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Drive not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: drives
          action: write
      operationId: patch_v1_drives_driveId
      summary: Update drive
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Drive updated successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Drive not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/browse:
    get:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: get_v1_drives_driveId_browse
      summary: Browse external drive contents
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Drive contents
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/capabilities:
    get:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: get_v1_drives_driveId_capabilities
      summary: Get drive capabilities
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Drive capabilities
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/files/{externalFileId}:
    patch:
      x-public:
        scope:
          entity: drives
          action: write
      operationId: patch_v1_drives_driveId_files_externalFileId
      summary: Rename or move external file
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
        - name: externalFileId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: File updated
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/files/{externalFileId}/download:
    get:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: get_v1_drives_driveId_files_externalFileId_download
      summary: Download file from external drive
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
        - name: externalFileId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: File stream
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/folders:
    post:
      x-public:
        scope:
          entity: drives
          action: write
      operationId: post_v1_drives_driveId_folders
      summary: Create folder in external drive
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "201":
          description: Folder created
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/folders/ensure-tree:
    post:
      x-public:
        scope:
          entity: drives
          action: write
      operationId: post_v1_drives_driveId_folders_ensure_tree
      summary: Idempotently materialize a nested folder tree (whole-folder upload)
      tags:
        - media-management
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Folder tree materialized (pathMap + created/reused counts)
        "400":
          description: Validation error / invalid folder path
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Write access disabled for this drive
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "413":
          description: Folder-tree caps or storage quota exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/pause:
    post:
      x-public:
        scope:
          entity: drives
          action: write
      operationId: post_v1_drives_driveId_pause
      summary: Pause drive sync
      tags:
        - media-management
        - Drives
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Drive paused
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/resume:
    post:
      x-public:
        scope:
          entity: drives
          action: write
      operationId: post_v1_drives_driveId_resume
      summary: Resume drive sync
      tags:
        - media-management
        - Drives
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Drive resumed
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/sync-journal:
    get:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: get_v1_drives_driveId_sync_journal
      summary: Paginated drive sync journal
      tags:
        - media-management
        - Drives
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
        - name: cursor
          in: query
          schema:
            type: string
        - name: direction
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Sync journal entries
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/drives/{driveId}/sync-status:
    get:
      x-public:
        scope:
          entity: drives
          action: read
      operationId: get_v1_drives_driveId_sync_status
      summary: Get current drive sync state, stats and progress
      tags:
        - media-management
        - Drives
      parameters:
        - name: driveId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Sync status
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/events/configs:
    get:
      x-public:
        scope:
          entity: events
          action: read
      summary: List event configs for team
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
      parameters:
        - name: category
          in: query
          schema:
            type: string
        - name: isEnabled
          in: query
          schema:
            type: string
    post:
      x-public:
        scope:
          entity: events
          action: write
      summary: Create new event config
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
  /v1/events/configs/by-id/{configId}:
    patch:
      x-public:
        scope:
          entity: events
          action: write
      summary: Update event config by ID
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
      parameters:
        - name: configId
          in: path
          required: true
          schema:
            type: string
  /v1/events/configs/by-id/{configId}/toggle:
    patch:
      x-public:
        scope:
          entity: events
          action: write
      summary: Toggle event config by ID
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
      parameters:
        - name: configId
          in: path
          required: true
          schema:
            type: string
  /v1/events/configs/{eventType}:
    get:
      x-public:
        scope:
          entity: events
          action: read
      summary: Get event config by type
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
      parameters:
        - name: eventType
          in: path
          required: true
          schema:
            type: string
    put:
      x-public:
        scope:
          entity: events
          action: write
      summary: Upsert event config
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
      parameters:
        - name: eventType
          in: path
          required: true
          schema:
            type: string
  /v1/events/configs/{eventType}/toggle:
    patch:
      x-public:
        scope:
          entity: events
          action: write
      summary: Toggle event config enabled/disabled
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
      parameters:
        - name: eventType
          in: path
          required: true
          schema:
            type: string
  /v1/events/log:
    get:
      x-public:
        scope:
          entity: events
          action: read
      summary: List event log entries
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
        - name: offset
          in: query
          schema:
            type: integer
        - name: category
          in: query
          schema:
            type: string
        - name: eventType
          in: query
          schema:
            type: string
  /v1/events/types:
    get:
      x-public:
        scope:
          entity: events
          action: read
      summary: List all available event types
      tags:
        - events-management
        - Events
      security:
        - apiToken: []
  /v1/exports:
    get:
      x-public:
        scope:
          entity: export
          action: read
      summary: List export jobs for the current user
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        "200":
          description: OK
      tags:
        - exports-management
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: export
          action: write
      summary: Request a new export job
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - type
                - format
              properties:
                type:
                  type: string
                  enum:
                    - table
                    - knowledge
                    - media
                    - session
                    - visuals
                    - database
                    - calendar
                    - task
                    - flow
                    - repository
                    - roadmap
                    - broadcast
                    - code_function
                    - edge_node
                    - checklist
                    - conversation
                    - perplexity
                    - firecrawl
                format:
                  type: string
                  enum:
                    - csv
                    - json
                    - xlsx
                    - txt
                    - md
                    - docx
                    - archive
                destination:
                  type: string
                  enum:
                    - download
                    - files
                    - knowledge
                  default: download
                destinationRef:
                  type: object
                  properties:
                    kind:
                      type: string
                      enum:
                        - files
                        - knowledge
                    driveId:
                      type: string
                    folderId:
                      type: string
                    knowledgeBaseId:
                      type: string
                    categoryId:
                      type: string
                name:
                  type: string
                  minLength: 1
                  maxLength: 200
                source:
                  type: object
      responses:
        "202":
          description: Accepted
        "400":
          description: VALIDATION_ERROR — type x format x destination coherence
        "429":
          description: EXPORT_LIMIT_EXCEEDED
      tags:
        - exports-management
      security:
        - apiToken: []
  /v1/exports/direct:
    post:
      x-public:
        scope:
          entity: export
          action: write
      summary: Synchronous single-record direct export (streams bytes, no job)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - type
                - format
              properties:
                type:
                  type: string
                  enum:
                    - knowledge
                    - media
                format:
                  type: string
                  enum:
                    - md
                    - txt
                source:
                  type: object
      responses:
        "200":
          description: Rendered export bytes (Content-Disposition attachment)
        "400":
          description: EXPORT_SYNC_NOT_ELIGIBLE / EMPTY_EXPORT
        "413":
          description: EXPORT_SYNC_TOO_LARGE
      tags:
        - exports-management
      security:
        - apiToken: []
  /v1/exports/{id}:
    get:
      x-public:
        scope:
          entity: export
          action: read
      summary: Get export job by id
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
        "404":
          description: EXPORT_NOT_FOUND
      tags:
        - exports-management
      security:
        - apiToken: []
  /v1/exports/{id}/cancel:
    post:
      x-public:
        scope:
          entity: export
          action: write
      summary: Cancel a pending or processing export
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Cancelled
        "409":
          description: EXPORT_NOT_CANCELLABLE
      tags:
        - exports-management
      security:
        - apiToken: []
  /v1/exports/{id}/download:
    get:
      x-public:
        scope:
          entity: export
          action: read
      summary: Get signed download URL for completed export
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
        "400":
          description: EXPORT_NOT_READY
        "410":
          description: EXPORT_EXPIRED
      tags:
        - exports-management
      security:
        - apiToken: []
  /v1/flows:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows
      summary: Get all flows
      tags:
        - flows-management
      responses:
        "200":
          description: Flows retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: flows
          action: write
      operationId: post_v1_flows
      summary: Create new flow
      tags:
        - flows-management
      responses:
        "201":
          description: Flow created successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - flowGroupId
              properties:
                name:
                  type: string
                description:
                  type: string
                flowGroupId:
                  type: string
  /v1/flows/bulk:
    post:
      x-public:
        scope:
          entity: flows
          action: delete
      operationId: post_v1_flows_bulk
      summary: Bulk delete flows (id-list or select-all-matching filter)
      tags:
        - flows-management
      responses:
        "200":
          description: Bulk delete completed (affected + blocked)
        "400":
          description: Validation error or unsupported bulk action
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "409":
          description: Bulk selection changed since confirmation — re-count required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - action
                - selection
              properties:
                action:
                  type: string
                  enum:
                    - delete
                selection:
                  type: object
                recursive:
                  type: boolean
                confirmToken:
                  type: string
  /v1/flows/bulk/count:
    post:
      x-public:
        scope:
          entity: flows
          action: delete
      operationId: post_v1_flows_bulk_count
      summary: Bulk-operation preflight — resolve + authorize + count + mint confirm token
      tags:
        - flows-management
      responses:
        "200":
          description: Preflight resolved (targetCount + blocked + requiresConfirm + confirmToken)
        "400":
          description: Validation error or unsupported bulk action
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - action
                - selection
              properties:
                action:
                  type: string
                  enum:
                    - delete
                selection:
                  type: object
                recursive:
                  type: boolean
  /v1/flows/runs/{runId}:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_runs_runId
      summary: "GET /v1/flows/runs/{runId} — one flow run in full, including its status and per-step execution detail.
        Addressed by run id ALONE: no flow id is needed, and `runs` is a distinct first segment rather than a flow
        identifier"
      description: Run detail + step journal. `runId` is the canonical `run_<uuid-v4>` string (it IS `flow_runs._id`) — a
        non-canonical id is rejected with 400.
      tags:
        - flows-management
      responses:
        "200":
          description: Flow run detail (with step journal)
        "400":
          description: VALIDATION_ERROR — runId is not a canonical `run_<uuid>` string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: FLOW_RUN_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: runId
        in: path
        required: true
        schema:
          type: string
  /v1/flows/{flowId}:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_flowId
      summary: Get flow by ID
      tags:
        - flows-management
      responses:
        "200":
          description: Flow retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
    put:
      x-public:
        scope:
          entity: flows
          action: write
      operationId: put_v1_flows_flowId
      summary: Update flow
      tags:
        - flows-management
      responses:
        "200":
          description: Flow updated successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                flowGroupId:
                  type: string
    delete:
      x-public:
        scope:
          entity: flows
          action: delete
      operationId: delete_v1_flows_flowId
      summary: Delete flow
      tags:
        - flows-management
      responses:
        "200":
          description: Flow deleted successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/flows/{flowId}/runs:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_flowId_runs
      summary: GET /v1/flows/{flowId}/runs — the runs of ONE flow, newest first. Offset-paginated (default limit 50) and
        filterable by `status`
      description: Offset-paginated run list for a flow (newest first, optional `status` filter).
      tags:
        - flows-management
      responses:
        "200":
          description: Paginated flow runs
        "400":
          description: Validation error (flowId)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
  /v1/flows/{flowId}/versions:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_flowId_versions
      summary: Get all versions of a flow
      tags:
        - flows-management
      responses:
        "200":
          description: Flow versions retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
    post:
      x-public:
        scope:
          entity: flows
          action: write
      operationId: post_v1_flows_flowId_versions
      summary: Create new flow version
      tags:
        - flows-management
      responses:
        "201":
          description: Flow version created successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - flowData
              properties:
                name:
                  type: string
                description:
                  type: string
                flowData:
                  type: object
  /v1/flows/{flowId}/versions/{versionId}:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_flowId_versions_versionId
      summary: Get flow version by ID
      tags:
        - flows-management
      responses:
        "200":
          description: Flow version retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow or version not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
        - in: path
          name: versionId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
      - name: versionId
        in: path
        required: true
        schema:
          type: string
    put:
      x-public:
        scope:
          entity: flows
          action: write
      operationId: put_v1_flows_flowId_versions_versionId
      summary: Update flow version
      tags:
        - flows-management
      responses:
        "200":
          description: Flow version updated successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow or version not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
        - in: path
          name: versionId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                flowData:
                  type: object
  /v1/flows/{flowId}/versions/{versionId}/history:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_flowId_versions_versionId_history
      summary: "GET /v1/flows/{flowId}/versions/{versionId}/history — the edit history of ONE flow version: the list of
        snapshots taken as the version's map was saved. Scoped to the version, not the flow"
      tags:
        - flows-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
      - name: versionId
        in: path
        required: true
        schema:
          type: string
  /v1/flows/{flowId}/versions/{versionId}/history/{historyId}:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_flowId_versions_versionId_history_historyId
      summary: GET /v1/flows/{flowId}/versions/{versionId}/history/{historyId} — the flow MAP as it stood at one history
        entry. Returns the snapshot itself, for previewing or diffing an earlier state of the version; reading it
        changes nothing
      tags:
        - flows-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
      - name: versionId
        in: path
        required: true
        schema:
          type: string
      - name: historyId
        in: path
        required: true
        schema:
          type: string
  /v1/flows/{flowId}/versions/{versionId}/map:
    get:
      x-public:
        scope:
          entity: flows
          action: read
      operationId: get_v1_flows_flowId_versions_versionId_map
      summary: Get flow version map for editor
      tags:
        - flows-management
      responses:
        "200":
          description: Flow version map retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow or version not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
        - in: path
          name: versionId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
      - name: versionId
        in: path
        required: true
        schema:
          type: string
    put:
      x-public:
        scope:
          entity: flows
          action: write
      operationId: put_v1_flows_flowId_versions_versionId_map
      summary: Save flow version map
      tags:
        - flows-management
      responses:
        "200":
          description: Flow version map saved successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow or version not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "409":
          description: Cannot update published version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      parameters:
        - in: path
          name: flowId
          required: true
          schema:
            type: string
        - in: path
          name: versionId
          required: true
          schema:
            type: string
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                nodes:
                  type: array
                  description: Flow nodes with entities
  /v1/flows/{flowId}/versions/{versionId}/publish:
    post:
      x-public:
        scope:
          entity: flows
          action: write
      operationId: post_v1_flows_flowId_versions_versionId_publish
      summary: POST /v1/flows/{flowId}/versions/{versionId}/publish — make this version the flow's ACTIVE version. Returns the
        published version, the updated flow, and `previousActiveVersionId` — the version it displaced, which is what a
        caller needs to roll the publish back
      description: Publish a flow version. Runs the SUB-6 publish gate (`validateFlowDefinition` over the to-be-published
        snapshot) BEFORE flipping `activeVersionId`, then reconciles the flow's managed trigger rows (event_config +
        scheduler jobs).
      tags:
        - flows-management
      responses:
        "200":
          description: Version published (publish gate passed, managed triggers reconciled)
        "400":
          description: FLOW_PUBLISH_VALIDATION_FAILED — the publish gate rejected the snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "403":
          description: Email not verified or team not selected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "404":
          description: Flow or version not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "409":
          description: FLOW_VERSION_ALREADY_PUBLISHED
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlowsErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: flowId
        in: path
        required: true
        schema:
          type: string
      - name: versionId
        in: path
        required: true
        schema:
          type: string
  /v1/folders:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_folders
      summary: List folders
      tags:
        - media-management
      responses:
        "200":
          description: Folders list retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_folders
      summary: Create new folder
      tags:
        - media-management
      responses:
        "201":
          description: Folder created successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/folders/{folderId}:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_folders_folderId
      summary: Get folder by ID
      tags:
        - media-management
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Folder retrieved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Folder not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: media
          action: write
      operationId: patch_v1_folders_folderId
      summary: Update folder
      tags:
        - media-management
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Folder updated successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Folder not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: media
          action: delete
      operationId: delete_v1_folders_folderId
      summary: Delete folder
      tags:
        - media-management
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Folder deleted successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Folder not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/folders/{folderId}/breadcrumb-chain:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_folders_folderId_breadcrumb_chain
      summary: Get folder ancestor breadcrumb chain (drive + root-to-leaf folders)
      tags:
        - media-management
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Breadcrumb chain resolved successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Folder or drive not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/imports:
    post:
      x-public:
        scope:
          entity: import
          action: write
      summary: >-
        Accept a bundle-import job. Body { sourceMediaId, idempotencyKey?, dryRun?, sync?, record? } — sourceMediaId
        points at an already-uploaded bundle. The same idempotencyKey submitted twice returns the FIRST job rather than
        creating a second (DEC-MP2389-8). Publishes ONE import:job:created event on a genuine create.

        sync=true asks the door to run the job inside the request. It is a REQUEST, not a guarantee: at or below
        IMPORT_SYNC_ROW_LIMIT parsed rows the response carries a TERMINAL job with its report inline; above it, or when
        the bundle declares a dir-backed entity whose row count is unknown (D-SUB4-E), or when the door could not parse
        the bundle, the job runs asynchronously exactly as it does without the flag. The response discriminates from the
        LOCKED contract alone — status:'pending' is never terminal — so no wire member was added for the mode.

        record={table, number} is the D-9 human record address, resolved to a canonical table_record pointer at the
        BOUNDARY, before the job is created. An address that does not resolve refuses the request under the resolver's
        own code and creates no job.
      tags:
        - imports-management
        - Imports
      responses:
        "201":
          description: The created ImportJobDto. status:pending phase:parse for an asynchronous run; a TERMINAL status carrying
            report/error for a synchronous one.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportJob"
        "400":
          description: TEAM_ID_REQUIRED (no selected team), VALIDATION_ERROR (malformed record address) or
            IMPORT_JOB_WRITE_VALIDATION_FAILED (IL-56 canonical-shape rejection).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportsErrorEnvelope"
        "404":
          description: IMPORT_RECORD_ADDRESS_NOT_FOUND — the record address matched no row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportsErrorEnvelope"
        "422":
          description: IMPORT_RECORD_ADDRESS_AMBIGUOUS (the table carries more than one system autonumber field; ambiguity is
            never guessed) or IMPORT_RECORD_ADDRESS_UNAVAILABLE (the table carries no human address at all).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportsErrorEnvelope"
      security:
        - apiToken: []
    get:
      x-public:
        scope:
          entity: import
          action: read
      summary: List the team's import jobs. Canonical {data, pagination} envelope, newest first. Manager-scope callers see
        only jobs they created (req.scopeFilter via requireScopeFilter('import')).
      tags:
        - imports-management
        - Imports
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
        - name: offset
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: "{ data: ImportJobDto[], pagination: {total, limit, offset, hasMore} }"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportJobPage"
        "400":
          description: TEAM_ID_REQUIRED
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportsErrorEnvelope"
      security:
        - apiToken: []
  /v1/imports/{jobId}:
    get:
      x-public:
        scope:
          entity: import
          action: read
      summary: Read one import job by its stable application id.
      tags:
        - imports-management
        - Imports
      parameters:
        - name: jobId
          in: path
          required: true
          description: The imp_* application identifier (not the Mongo _id).
          schema:
            type: string
      responses:
        "200":
          description: The ImportJobDto.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportJob"
        "404":
          description: IMPORT_JOB_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportsErrorEnvelope"
      security:
        - apiToken: []
  /v1/imports/{jobId}/issues:
    get:
      x-public:
        scope:
          entity: import
          action: read
      summary: The persisted import_row_issues rows for one job — the paginated read that makes the dry-run's persistence
        OBSERVABLE. ImportJobReport carries issueRows as a COUNT the driver self-reports in the SAME document as its
        outcomes, so it cannot witness persistence; this route returns the rows themselves. Canonical {data, pagination}
        envelope, never a bare array. ?outcome= narrows to one of the three D-4 outcomes (resolved is a legal filter
        that always yields an empty list — resolved references are counted, never persisted). ?kind= narrows to one arm
        of the row union.
      tags:
        - imports-management
        - Imports
      parameters:
        - name: jobId
          in: path
          required: true
          description: The imp_* application identifier (not the Mongo _id).
          schema:
            type: string
        - name: outcome
          in: query
          required: false
          description: One of resolved | unresolved | absent. Selects REFERENCE rows only — the defect arm declares no outcome, so
            this filter can never return one, and a defect row is therefore not a 400 here.
          schema:
            type: string
            enum:
              - resolved
              - unresolved
              - absent
        - name: kind
          in: query
          required: false
          description: One arm of the row union (MP5483-realimport-SUB-1). `reference` also admits rows persisted before that
            wave, which carry no discriminant on disk and are read back as reference rows.
          schema:
            type: string
            enum:
              - reference
              - defect
        - name: limit
          in: query
          schema:
            type: integer
        - name: offset
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: "{ data: ImportRowIssue[], pagination: {total, limit, offset, hasMore} }"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportRowIssuePage"
        "400":
          description: TEAM_ID_REQUIRED (no selected team) or VALIDATION_ERROR (outcome outside the three-member vocabulary, or
            kind outside the two-member one).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportsErrorEnvelope"
        "404":
          description: IMPORT_JOB_NOT_FOUND — distinct from an empty issue list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportsErrorEnvelope"
      security:
        - apiToken: []
  /v1/knowledge:
    get:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: get_v1_knowledge
      summary: Get all knowledge entries
      tags:
        - knowledge-management
      parameters:
        - in: query
          name: tags
          required: false
          schema:
            type: string
          description: Comma-separated tag names for AND filtering
      responses:
        "200":
          description: Knowledge entries retrieved successfully
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/knowledge-bases:
    get:
      x-public:
        scope:
          entity: knowledge_bases
          action: read
      operationId: get_v1_knowledge_bases
      summary: List knowledge bases
      tags:
        - knowledge-management
      responses:
        "200":
          description: Knowledge bases retrieved successfully
        "401":
          description: Unauthorized
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: knowledge_bases
          action: write
      operationId: post_v1_knowledge_bases
      summary: Create knowledge base (supports language, visibility, publishStatus for system KBs)
      tags:
        - knowledge-management
      responses:
        "201":
          description: Knowledge base created successfully
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: System knowledge read-only (SYSTEM_KNOWLEDGE_READ_ONLY)
      security:
        - apiToken: []
  /v1/knowledge-bases/bulk/count:
    post:
      x-public:
        scope:
          entity: knowledge_bases
          action: read
      operationId: post_v1_knowledge_bases_bulk_count
      summary: KB bulk preflight — recursive KB → category → entry count + confirm token (MP292-SUB-5)
      tags:
        - knowledge-management
      responses:
        "200":
          description: Bulk preflight response
        "400":
          description: Validation error
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/knowledge-bases/{id}:
    get:
      x-public:
        scope:
          entity: knowledge_bases
          action: read
      operationId: get_v1_knowledge_bases_id
      summary: Get knowledge base by ID
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Knowledge base retrieved successfully
        "401":
          description: Unauthorized
        "404":
          description: Knowledge base not found
      security:
        - apiToken: []
    put:
      x-public:
        scope:
          entity: knowledge_bases
          action: write
      operationId: put_v1_knowledge_bases_id
      summary: Full update of knowledge base
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Knowledge base updated successfully
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: System knowledge read-only (SYSTEM_KNOWLEDGE_READ_ONLY)
        "404":
          description: Knowledge base not found
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: knowledge_bases
          action: write
      operationId: patch_v1_knowledge_bases_id
      summary: Partial update of knowledge base
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Knowledge base updated successfully
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: System knowledge read-only (SYSTEM_KNOWLEDGE_READ_ONLY)
        "404":
          description: Knowledge base not found
      security:
        - apiToken: []
  /v1/knowledge-bases/{id}/breadcrumb-chain:
    get:
      x-public:
        scope:
          entity: knowledge_bases
          action: read
      operationId: get_v1_knowledge_bases_id_breadcrumb_chain
      summary: Resolve breadcrumb chain for tabs BreadcrumbBuilder primitive (MP27-SUB-12 W2.A)
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: sectionId
          required: false
          schema:
            type: string
        - in: query
          name: entryId
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Breadcrumb chain (kb + optional section + optional entry)
        "401":
          description: Unauthorized
        "404":
          description: Knowledge base not found
      security:
        - apiToken: []
  /v1/knowledge/bulk:
    post:
      x-public:
        scope:
          entity: ai_knowledge
          action: delete
      operationId: post_v1_knowledge_bulk
      summary: Canonical bulk delete/move over knowledge entries + sections (MP292-SUB-5)
      tags:
        - knowledge-management
      responses:
        "200":
          description: Bulk operation completed (partial-success blocked[])
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "409":
          description: Bulk selection changed since the confirmation count
      security:
        - apiToken: []
  /v1/knowledge/bulk/count:
    post:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: post_v1_knowledge_bulk_count
      summary: Bulk preflight — resolve + authorize + recursive-count + mint confirm token (MP292-SUB-5)
      tags:
        - knowledge-management
      responses:
        "200":
          description: Bulk preflight response
        "400":
          description: Validation error
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/knowledge/{id}:
    get:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: get_v1_knowledge_id
      summary: Get knowledge by ID
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Knowledge retrieved successfully
        "401":
          description: Unauthorized
        "404":
          description: Knowledge not found
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: ai_knowledge
          action: delete
      operationId: delete_v1_knowledge_id
      summary: Delete knowledge (soft or hard with ?force=true)
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: force
          required: false
          schema:
            type: boolean
      responses:
        "200":
          description: Knowledge deleted successfully
        "401":
          description: Unauthorized
        "404":
          description: Knowledge not found
      security:
        - apiToken: []
  /v1/knowledge/{id}/read:
    post:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: post_v1_knowledge_id_read
      summary: Mark knowledge as read
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Knowledge marked as read
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/knowledge/{id}/versions:
    get:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: get_v1_knowledge_id_versions
      summary: Get all versions of knowledge
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Versions retrieved successfully
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/knowledge/{id}/versions/{version}:
    get:
      x-public:
        scope:
          entity: ai_knowledge
          action: read
      operationId: get_v1_knowledge_id_versions_version
      summary: Get specific version of knowledge
      tags:
        - knowledge-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: path
          name: version
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Version retrieved successfully
        "401":
          description: Unauthorized
        "404":
          description: Version not found
      security:
        - apiToken: []
  /v1/media:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media
      summary: Upload media files
      tags:
        - media-management
      responses:
        "201":
          description: Files uploaded successfully
        "400":
          description: Bad request - validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "413":
          description: File too large or quota exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: string
                  description: Base64 encoded file content
                context:
                  type: string
                  description: JSON string with context information
              required:
                - files
                - context
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media
      summary: List media files with filtering
      tags:
        - media-management
      responses:
        "200":
          description: List of media files
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      parameters:
        - name: driveId
          in: query
          description: Filter by drive ID
          schema:
            type: string
        - name: folderId
          in: query
          description: Filter by folder ID
          schema:
            type: string
        - name: fileType
          in: query
          description: Filter by file type
          schema:
            type: string
            enum:
              - image
              - video
              - audio
              - document
        - name: search
          in: query
          description: Search by file name
          schema:
            type: string
        - name: sortBy
          in: query
          description: Sort field (default uploadedAt)
          schema:
            type: string
            enum:
              - uploadedAt
              - originalName
              - fileSize
              - fileType
        - name: sortOrder
          in: query
          description: Sort direction (default desc)
          schema:
            type: string
            enum:
              - asc
              - desc
        - name: limit
          in: query
          description: Number of results to return (default 20)
          schema:
            type: integer
        - name: offset
          in: query
          description: Number of results to skip (default 0)
          schema:
            type: integer
      security:
        - apiToken: []
  /v1/media/bulk:
    post:
      x-public:
        scope:
          entity: media
          action: delete
      operationId: post_v1_media_bulk
      summary: Bulk delete or move media files and folders (recursive cascade)
      tags:
        - media-management
      responses:
        "200":
          description: Bulk operation applied (partial-success blocked[] reported)
        "400":
          description: Bad request - validation error or missing move target
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "409":
          description: Bulk selection changed since the confirmation count
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
        description: Canonical bulk operation request (action + selection + recursive + targetLocation + confirmToken)
  /v1/media/bulk/count:
    post:
      x-public:
        scope:
          entity: media
          action: read
      operationId: post_v1_media_bulk_count
      summary: Preflight count + confirm-token for a bulk media operation (recursive descendant counts)
      tags:
        - media-management
      responses:
        "200":
          description: Preflight counts + confirmToken minted
        "400":
          description: Bad request - validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
        description: Canonical bulk count request (action + selection + recursive)
  /v1/media/playlists:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_playlists
      summary: POST /v1/media/playlists — create an empty .m3u8 playlist FILE (MP316)
      tags:
        - media-management
      responses:
        "201":
          description: Playlist created
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Storage quota exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Drive not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/storage:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_storage
      summary: Get team storage usage statistics
      tags:
        - media-management
      responses:
        "200":
          description: Storage usage data
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/upload-abort/{grantId}:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_upload_abort
      summary: Abandon an upload grant and any in-flight multipart upload
      description: Discards the storage-side multipart upload together with every part it holds, then releases the reserved
        media_files row and the grant row. Answers 204 with no body. A second abort answers 404, because the grant row
        is gone — abort is belt, not the only reaper.
      tags:
        - media-management
      parameters:
        - name: grantId
          in: path
          required: true
          description: The grant to abandon
          schema:
            type: string
      responses:
        "204":
          description: Aborted; nothing remains of the grant
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaUploadGrantEnvelope"
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Grant unknown, or already aborted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/upload-commit:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_upload_commit
      summary: Finish an upload whose bytes have landed in storage
      description: Re-derives the object's size and etag from storage, settles quota and container counters, and runs the
        media ingest pass. With no bucket-event mechanism on the object store this call is the ONLY post-arrival
        trigger. A repeat commit of the SAME object answers 200 `already_committed`.
      tags:
        - media-management
      responses:
        "200":
          description: Committed, or an idempotent replay of a prior commit
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaUploadGrantEnvelope"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Grant unknown, or the object never reached storage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "409":
          description: Size mismatch, or the grant already committed a different object
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "410":
          description: Grant expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/upload-grant:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_upload_grant
      summary: Request permission to upload a file
      description: >-
        Applies the moderation freeze gate, the size gate, the dangerous-type denylist, the storage quota and
        drive/folder resolution, reserves the media_files row in status 'uploading', and returns a uniform transport
        envelope pointing either at the object store (presigned POST) or at this service's proxy endpoint for an
        external drive. The union discriminates on `kind: single | multipart` and on nothing else.


        MP5651-files SUB-4 adds two members. REQUEST: an OPTIONAL `targetMediaId` replaces an existing file's CONTENT
        instead of creating a new one — the grant reuses that file's id and the commit archives a version through the
        shared resource_versions store, so the file keeps its id, links and history. Refused with 400 on an
        external-drive grant, which has no native row to version. RESPONSE: a REQUIRED `identityAssignment` on the grant
        spine states WHEN `mediaId` becomes final — `grant` (it is final) or `settlement` (it is a RESERVATION the proxy
        deletes on success; read the id off the terminal response and do NOT persist this one). It is a THIRD question,
        independent of `completion` and of the diagnostics-only `transportKind`.
      tags:
        - media-management
      responses:
        "200":
          description: Grant issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaUploadGrantEnvelope"
        "400":
          description: Validation error or dangerous file type
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "402":
          description: Storage quota exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Drive or folder not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "409":
          description: Listing under moderation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "413":
          description: Declared size exceeds the configured maximum
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/upload-proxy/{grantId}:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_upload_proxy
      summary: Send an external-drive upload's bytes through this service
      description: The external-drive branch of the same grant, so a client has ONE code path. Pipes the raw request body to
        the provider adapter without buffering. This endpoint re-authorizes NOTHING (DEC-MP7324-3) — the grant already
        carries the authorization decision.
      tags:
        - media-management
      parameters:
        - name: grantId
          in: path
          required: true
          description: The grant this upload belongs to
          schema:
            type: string
      responses:
        "200":
          description: Bytes accepted by the provider
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaUploadGrantEnvelope"
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Grant unknown
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "410":
          description: Grant expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaBulkIpcError"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/{mediaId}:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_mediaId
      summary: Get media file by ID
      tags:
        - media-management
      responses:
        "200":
          description: Media file details
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      parameters:
        - name: mediaId
          in: path
          required: true
          description: Media file ID
          schema:
            type: string
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
    put:
      x-public:
        scope:
          entity: media
          action: write
      operationId: put_v1_media_mediaId
      summary: Update media metadata
      tags:
        - media-management
      responses:
        "200":
          description: Media file updated successfully
        "400":
          description: Bad request - validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      parameters:
        - name: mediaId
          in: path
          required: true
          description: Media file ID
          schema:
            type: string
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                originalName:
                  type: string
                  description: New file name
                context:
                  type: object
                  description: New context information
    delete:
      x-public:
        scope:
          entity: media
          action: delete
      operationId: delete_v1_media_mediaId
      summary: Delete media file
      tags:
        - media-management
      responses:
        "200":
          description: Media file deleted successfully
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      parameters:
        - name: mediaId
          in: path
          required: true
          description: Media file ID
          schema:
            type: string
      security:
        - apiToken: []
  /v1/media/{mediaId}/bytes:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_mediaId_bytes
      summary: GET /v1/media/{mediaId}/bytes — same-origin raw-bytes proxy (provenance-blind native S3 ∥ external adapter).
        Streams the file bytes through the app origin so the Univer spreadsheet + libarchive archive viewers fetch()
        them same-origin instead of the CORS-gated cross-origin signed URL (MP321-fileviewers SUB-A, DEC-MP321-1)
      tags:
        - media-management
      responses:
        "200":
          description: The raw file bytes (Content-Type from the media doc)
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/content:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_mediaId_content
      summary: GET /v1/media/{mediaId}/content — provenance-blind editable-text read
      tags:
        - media-management
      responses:
        "200":
          description: Editable-text content. Mirrors `GetMediaFileContentResponseSchema` (IL-56 gate). Also sets a quoted `ETag`
            header when `etag` is present.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetMediaFileContentResponseSchema"
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    put:
      x-public:
        scope:
          entity: media
          action: write
      operationId: put_v1_media_mediaId_content
      summary: PUT /v1/media/{mediaId}/content — provenance-blind text write-back
      tags:
        - media-management
      responses:
        "200":
          description: Write accepted. Mirrors `UpdateMediaFileContentResponseSchema` (IL-56 gate). Also sets the new quoted
            `ETag` header.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpdateMediaFileContentResponseSchema"
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Drive read-only or needs re-authorization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "413":
          description: Content too large
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "422":
          description: File is not an editable text file
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/playlist:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_mediaId_playlist
      summary: GET /v1/media/{mediaId}/playlist — parsed + server-resolved (MP316)
      tags:
        - media-management
      responses:
        "200":
          description: Parsed playlist
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Playlist not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "422":
          description: File is not a playlist
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: media
          action: write
      operationId: patch_v1_media_mediaId_playlist
      summary: PATCH /v1/media/{mediaId}/playlist — update-meta (rename) (MP316)
      tags:
        - media-management
      responses:
        "200":
          description: Playlist updated
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "409":
          description: Playlist conflict (CAS hash mismatch)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "422":
          description: File is not a playlist
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/playlist/tracks:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_mediaId_playlist_tracks
      summary: POST /v1/media/{mediaId}/playlist/tracks — track_add (MP316)
      tags:
        - media-management
      responses:
        "200":
          description: Track added
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Playlist or track not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "409":
          description: Playlist conflict (CAS hash mismatch)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/playlist/tracks/reorder:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_mediaId_playlist_tracks_reorder
      summary: POST /v1/media/{mediaId}/playlist/tracks/reorder — track_reorder (MP316)
      tags:
        - media-management
      responses:
        "200":
          description: Tracks reordered
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "409":
          description: Playlist conflict (CAS hash mismatch)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "422":
          description: Track set mismatch
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/playlist/tracks/{trackRef}:
    delete:
      x-public:
        scope:
          entity: media
          action: write
      operationId: delete_v1_media_mediaId_playlist_tracks_trackRef
      summary: DELETE /v1/media/{mediaId}/playlist/tracks/{trackRef} — track_remove (MP316)
      tags:
        - media-management
      responses:
        "200":
          description: Track removed
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Track not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "409":
          description: Playlist conflict (CAS hash mismatch)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
      - name: trackRef
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/replace:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_mediaId_replace
      summary: POST /v1/media/{mediaId}/replace — PUBLIC JWT + capabilities-gated BINARY content-replace (image Filerobot
        editor SUB-10 + spreadsheet Univer editor SUB-11 write-back). Multipart `file` blob (image OR xlsx/xls/csv);
        archives the prior version + atomically swaps the stored bytes + mime; returns a fresh signed URL
        (MP316-fileviewers SUB-10/SUB-11)
      tags:
        - media-management
      responses:
        "200":
          description: Replaced — { mediaId, versionId, version, newSize, url }
        "400":
          description: No file provided OR content outside the image/spreadsheet write-back allowlist
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "413":
          description: File exceeds the upload size limit
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Failed to replace content
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/usage:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_mediaId_usage
      summary: "GET /v1/media/{mediaId}/usage — where is this file referenced? Ask BEFORE deleting: returns `canDelete`, a
        total `usageCount`, and the referencing `locations`, so a delete that would break a live flow can be refused
        rather than discovered afterwards"
      tags:
        - media-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: mediaId
        in: path
        required: true
        schema:
          type: string
  /v1/media/{mediaId}/versions:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_mediaId_versions
      summary: GET /v1/media/{mediaId}/versions — PUBLIC JWT. Version history of a file, newest first, off the SHARED
        `resource_versions` store (MP398-devwave2 SUB-28, V-13 / DEC-MP398-100). `media_versions` is retired as a store;
        snapshotMode 'ref' means a row references the S3 object rather than copying bytes.
      tags:
        - media-management
      responses:
        "200":
          description: "{ versions: [{ versionNumber, changeType, createdBy, createdAt, ... }], total }"
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Forbidden — requires `media` read
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/{mediaId}/versions/{versionNumber}:
    get:
      x-public:
        scope:
          entity: media
          action: read
      operationId: get_v1_media_mediaId_versions_versionNumber
      summary: GET /v1/media/{mediaId}/versions/{versionNumber} — PUBLIC JWT. One version's metadata + its signed content
        reference (MP398-devwave2 SUB-28).
      tags:
        - media-management
      responses:
        "200":
          description: "{ versionNumber, changeType, createdBy, createdAt, url }"
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Forbidden — requires `media` read
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file or version not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/media/{mediaId}/versions/{versionNumber}/restore:
    post:
      x-public:
        scope:
          entity: media
          action: write
      operationId: post_v1_media_mediaId_versions_versionNumber_restore
      summary: POST /v1/media/{mediaId}/versions/{versionNumber}/restore — PUBLIC JWT. "Take this version as the base" (V-13).
        Appends a NEW version carrying the restored content rather than rewinding the chain, so the pre-restore state
        stays reachable (the append-only rule DEC-MP398-100 applies platform-wide).
      tags:
        - media-management
      responses:
        "200":
          description: "{ mediaId, versionNumber, url }"
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "403":
          description: Forbidden — requires `media` write
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "404":
          description: Media file or version not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaErrorEnvelope"
      security:
        - apiToken: []
  /v1/repositories:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories
      summary: List repositories with pagination
      tags:
        - repositories-management
      responses:
        "200":
          description: Repositories retrieved successfully
        "401":
          description: Unauthorized
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: repositories
          action: write
      operationId: post_v1_repositories
      summary: Clone a new repository
      tags:
        - repositories-management
      responses:
        "201":
          description: Repository clone initiated
        "400":
          description: Validation error or repo limit exceeded
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/repositories/bulk:
    post:
      x-public:
        scope:
          entity: repositories
          action: write
      operationId: post_v1_repositories_bulk
      summary: Bulk delete repositories (id-list or select-all-matching filter)
      tags:
        - repositories-management
      responses:
        "200":
          description: Bulk delete partial-success result
        "400":
          description: Validation error or unsupported bulk action
        "401":
          description: Unauthorized
        "409":
          description: Bulk selection changed since confirmation count
      security:
        - apiToken: []
  /v1/repositories/bulk/count:
    post:
      x-public:
        scope:
          entity: repositories
          action: write
      operationId: post_v1_repositories_bulk_count
      summary: Bulk-operation preflight — resolve + authorize + count + mint confirm token
      tags:
        - repositories-management
      responses:
        "200":
          description: Preflight count + confirm token
        "401":
          description: Unauthorized
      security:
        - apiToken: []
  /v1/repositories/{id}:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories_id
      summary: Get repository by ID
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Repository retrieved successfully
        "401":
          description: Unauthorized
        "404":
          description: Repository not found
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: repositories
          action: write
      operationId: delete_v1_repositories_id
      summary: Delete repository
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Repository deleted successfully
        "401":
          description: Unauthorized
        "404":
          description: Repository not found
      security:
        - apiToken: []
  /v1/repositories/{id}/file:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories_id_file
      summary: Get file content from repository
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: path
          required: true
          schema:
            type: string
        - in: query
          name: branch
          schema:
            type: string
      responses:
        "200":
          description: File content retrieved
        "400":
          description: Validation error, path traversal, or file too large
        "404":
          description: Repository not found
      security:
        - apiToken: []
  /v1/repositories/{id}/git/branches:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories_id_git_branches
      summary: List branches
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Branches retrieved
        "404":
          description: Repository not found
      security:
        - apiToken: []
  /v1/repositories/{id}/git/diff:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories_id_git_diff
      summary: Get diff output
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: staged
          schema:
            type: boolean
      responses:
        "200":
          description: Diff retrieved
        "404":
          description: Repository not found
      security:
        - apiToken: []
  /v1/repositories/{id}/git/log:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories_id_git_log
      summary: Get commit log
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: branch
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
      responses:
        "200":
          description: Commit log retrieved
        "404":
          description: Repository not found
      security:
        - apiToken: []
  /v1/repositories/{id}/git/status:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories_id_git_status
      summary: Get working tree status
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Status retrieved
        "404":
          description: Repository not found
      security:
        - apiToken: []
  /v1/repositories/{id}/tree:
    get:
      x-public:
        scope:
          entity: repositories
          action: read
      operationId: get_v1_repositories_id_tree
      summary: Get file tree for repository
      tags:
        - repositories-management
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: path
          schema:
            type: string
        - in: query
          name: branch
          schema:
            type: string
      responses:
        "200":
          description: File tree retrieved
        "400":
          description: Repository not ready or path traversal
        "404":
          description: Repository not found
      security:
        - apiToken: []
  /v1/tables:
    get:
      x-public:
        scope:
          entity: tables
          action: read
      operationId: get_v1_tables
      summary: Get team tables
      tags:
        - databases-management
      responses:
        "200":
          description: List of tables
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: Access denied
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      parameters:
        - in: query
          name: type
          schema:
            type: string
            enum:
              - system
              - custom
        - in: query
          name: search
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: tables
          action: write
      operationId: post_v1_tables
      summary: Create new table
      tags:
        - databases-management
      responses:
        "201":
          description: Table created successfully
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "409":
          description: Table with this system name already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - systemName
              properties:
                name:
                  type: string
                  maxLength: 100
                systemName:
                  type: string
                  maxLength: 50
                description:
                  type: string
                  maxLength: 500
                icon:
                  type: string
                  default: table
                fields:
                  type: array
                  items:
                    type: object
  /v1/tables/check-system-name:
    get:
      x-public:
        scope:
          entity: tables
          action: write
      operationId: get_v1_tables_check-system-name
      summary: Check if system name is available
      tags:
        - databases-management
      responses:
        "200":
          description: System name availability result
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      parameters:
        - in: query
          name: systemName
          required: true
          schema:
            type: string
      security:
        - apiToken: []
  /v1/tables/records/references:
    post:
      x-public:
        scope:
          entity: tables
          action: read
      operationId: post_v1_tables_records_references
      summary: PUBLIC, user-ACL'd reverse path of the universal record reference (MP4712-sales SUB-10, DEC-MP4712-12) — the
        per-user-filtered sibling of the internal POST /internal/tables/records/referencing.
      tags:
        - databases-management
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - pointer
              properties:
                pointer:
                  type: object
                  description: The referenced record pointer being asked about. Parsed as the canonical OpenablePointer
                    (PublicReferencesRequestSchema). The team is taken from the request context, never from the body.
                  additionalProperties: true
                limit:
                  type: integer
                  minimum: 1
                  maximum: 200
                  default: 50
                offset:
                  type: integer
                  minimum: 0
                  default: 0
      responses:
        "200":
          description: The page of referencing rows the caller may see. An ENVELOPE, never a bare array. Rows are filtered per
            table by `hasDataPermission` + `resource_access_rules` + `userIsolation`, so two callers can get different
            pages for the same pointer.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesReferencingRecordsPage"
        "400":
          description: VALIDATION_ERROR (the body did not parse; `details` carries the Zod issue list) or TEAM_ID_REQUIRED (no
            team selected on the request).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: FORBIDDEN — the route-level requirePermission('tables', 'read') gate refused. Per-table visibility is NOT
            decided here; it is applied inside the handler and shows up as absent rows in a 200.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: INTERNAL_ERROR — the scan could not complete. Deliberately NOT fail-open, for the same reason as the
            internal sibling.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
  /v1/tables/{tableIdentifier}:
    get:
      x-public:
        scope:
          entity: tables
          action: read
      operationId: get_v1_tables_tableIdentifier
      summary: "GET /v1/tables/{tableIdentifier} — one table's definition: its fields, settings and metadata.
        `tableIdentifier` accepts the table's systemName OR its id"
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
    put:
      x-public:
        scope:
          entity: tables
          action: write
      operationId: put_v1_tables_tableIdentifier
      summary: PUT /v1/tables/{tableIdentifier} — update a table's definition (fields, display settings, metadata). Addressed
        by systemName or id; the table's RECORDS are not touched
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: tables
          action: delete
      operationId: delete_v1_tables_tableIdentifier
      summary: DELETE /v1/tables/{tableIdentifier} — SOFT-delete a table into the trash. Its records are retained, which is
        what makes `POST /v1/tables/{tableIdentifier}/restore` an exact inverse rather than an approximation
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
  /v1/tables/{tableIdentifier}/bulk:
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
    post:
      x-public:
        scope:
          entity: tables
          action: delete
      operationId: post_v1_tables_tableIdentifier_bulk
      summary: POST /v1/tables/{tableIdentifier}/bulk — EXECUTE a bulk record delete. Requires the confirm token minted by
        `.../bulk/count`; deletes only the subset the caller is permitted to delete and reports the remainder in
        `blocked[]` rather than failing the whole call
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
  /v1/tables/{tableIdentifier}/bulk/count:
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
    post:
      x-public:
        scope:
          entity: tables
          action: delete
      operationId: post_v1_tables_tableIdentifier_bulk_count
      summary: "POST /v1/tables/{tableIdentifier}/bulk/count — PRE-FLIGHT a bulk record delete: resolve the selection,
        authorize it per record, count what would actually be deleted, and mint the confirm token that `POST
        /v1/tables/{tableIdentifier}/bulk` requires. Deletes nothing"
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
  /v1/tables/{tableIdentifier}/records:
    get:
      x-public:
        scope:
          entity: tables
          action: read
      operationId: get_v1_tables_tableIdentifier_records
      summary: GET /v1/tables/{tableIdentifier}/records — a table's records, paginated. Per-record ACL and user-isolation
        rules are applied inside the handler, so a page reflects what THIS caller may see rather than the table's full
        contents
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
  /v1/tables/{tableIdentifier}/records/{recordId}:
    get:
      x-public:
        scope:
          entity: tables
          action: read
      operationId: get_v1_tables_tableIdentifier_records_recordId
      summary: GET /v1/tables/{tableIdentifier}/records/{recordId} — one record by id, subject to the same per-record ACL and
        user-isolation rules as the list
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
      - name: recordId
        in: path
        required: true
        schema:
          type: string
    put:
      x-public:
        scope:
          entity: tables
          action: write
      operationId: put_v1_tables_tableIdentifier_records_recordId
      summary: PUT /v1/tables/{tableIdentifier}/records/{recordId} — update one record's field values. When the parent table
        has record versioning ENABLED (it is opt-in per table), the resulting content is appended as a new version
        afterwards; a pure tag change mints no version
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: tables
          action: delete
      operationId: delete_v1_tables_tableIdentifier_records_recordId
      summary: DELETE /v1/tables/{tableIdentifier}/records/{recordId} — SOFT-delete one record into the trash. `POST
        /v1/tables/{tableIdentifier}/records/{recordId}/restore` brings it back
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
  /v1/tables/{tableIdentifier}/records/{recordId}/restore:
    post:
      x-public:
        scope:
          entity: tables
          action: delete
      operationId: post_v1_tables_tableIdentifier_records_recordId_restore
      summary: "POST /v1/tables/{tableIdentifier}/records/{recordId}/restore — bring one record back from the trash. Gated on
        `delete`, not `write`: a member who may not delete has no business undoing someone else's deletion"
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
      - name: recordId
        in: path
        required: true
        schema:
          type: string
  /v1/tables/{tableIdentifier}/records/{recordId}/versions:
    get:
      x-public:
        scope:
          entity: tables
          action: read
      operationId: get_v1_tables_tableIdentifier_records_recordId_versions
      summary: "GET /v1/tables/{tableIdentifier}/records/{recordId}/versions — one record's versions, newest first. Gated on
        `read`: a version is the record's own content at an earlier moment and carries exactly the confidentiality the
        record does"
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
      - name: recordId
        in: path
        required: true
        schema:
          type: string
  /v1/tables/{tableIdentifier}/records/{recordId}/versions/{versionNumber}/restore:
    post:
      x-public:
        scope:
          entity: tables
          action: write
      operationId: post_v1_tables_tableIdentifier_records_recordId_versions_versionNumber_restore
      summary: "POST /v1/tables/{tableIdentifier}/records/{recordId}/versions/{versionNumber}/restore — take an earlier
        version as the new base. APPEND-ONLY: the live record is written first and a NEW version records that it
        happened, so history is never rewritten and the restore is itself restorable"
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
      - name: recordId
        in: path
        required: true
        schema:
          type: string
      - name: versionNumber
        in: path
        required: true
        schema:
          type: string
  /v1/tables/{tableIdentifier}/restore:
    post:
      x-public:
        scope:
          entity: tables
          action: delete
      operationId: post_v1_tables_tableIdentifier_restore
      summary: "POST /v1/tables/{tableIdentifier}/restore — bring a table back from the trash WITH every record it had. Gated
        on `delete`, not `write`: restoring is the inverse of deleting, so it takes the deleting grant"
      tags:
        - databases-management
      responses:
        "200":
          description: Success
        "400":
          description: Malformed request, or no team selected — the flat envelope names the specific code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "401":
          description: Missing or invalid API token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "403":
          description: The token is valid but its scope does not grant this entity + action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "404":
          description: No such resource, or it lies outside the caller's access.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasesErrorEnvelope"
      security:
        - apiToken: []
    parameters:
      - name: tableIdentifier
        in: path
        required: true
        schema:
          type: string
  /v1/tasks:
    get:
      x-public:
        scope:
          entity: tasks
          action: read
      summary: List tasks
      tags:
        - tasks-management
        - Tasks
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
        - name: status
          in: query
          schema:
            type: string
            enum:
              - draft
              - todo
              - in_progress
              - review
              - done
              - failed
              - cancelled
              - blocked
              - queued
        - name: assignedTo
          in: query
          schema:
            type: string
        - name: assigneeType
          in: query
          schema:
            type: string
            enum:
              - user
              - agent
        - name: priority
          in: query
          schema:
            type: string
            enum:
              - critical
              - high
              - normal
              - low
        - name: dueDateFrom
          in: query
          schema:
            type: string
            format: date
        - name: dueDateTo
          in: query
          schema:
            type: string
            format: date
        - name: search
          in: query
          schema:
            type: string
        - name: sortBy
          in: query
          schema:
            type: string
            enum:
              - title
              - status
              - priority
              - dueDate
              - createdAt
              - updatedAt
            default: createdAt
        - name: sortOrder
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
        - name: type
          in: query
          description: Filter by task origin — manual (user-created), recurring (from schedule), template (from template),
            ai_generated (from AI agent).
          schema:
            type: string
            enum:
              - manual
              - recurring
              - template
              - ai_generated
        - name: statusNot
          in: query
          description: Exclude tasks whose status equals this value (MongoDB $ne). e.g. statusNot=completed hides done tasks.
          schema:
            type: string
            enum:
              - draft
              - todo
              - in_progress
              - review
              - done
              - failed
              - cancelled
              - blocked
              - queued
        - name: parentTaskId
          in: query
          description: Filter to the direct subtasks of this parent task (B24-backend). Returns all subtasks of the parent in a
            single request; when omitted the list returns top-level tasks only.
          schema:
            type: string
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Create task
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/bulk:
    post:
      x-public:
        scope:
          entity: tasks
          action: delete
      operationId: post_v1_tasks_bulk
      summary: Bulk delete tasks (id-list or select-all-matching filter) (MP292-SUB-9)
      tags:
        - tasks-management
        - Tasks
      responses:
        "200":
          description: Bulk delete completed (affected + blocked)
        "400":
          description: Validation error or unsupported bulk action
        "401":
          description: Unauthorized
        "403":
          description: Email not verified or team not selected
        "409":
          description: Bulk selection changed since confirmation — re-count required
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - action
                - selection
              properties:
                action:
                  type: string
                  enum:
                    - delete
                selection:
                  type: object
                recursive:
                  type: boolean
                confirmToken:
                  type: string
  /v1/tasks/bulk/count:
    post:
      x-public:
        scope:
          entity: tasks
          action: delete
      operationId: post_v1_tasks_bulk_count
      summary: Bulk-operation preflight — resolve + authorize + count + mint confirm token (MP292-SUB-9)
      tags:
        - tasks-management
        - Tasks
      responses:
        "200":
          description: Preflight resolved (targetCount + blocked + requiresConfirm + confirmToken)
        "400":
          description: Validation error or unsupported bulk action
        "401":
          description: Unauthorized
        "403":
          description: Email not verified or team not selected
      security:
        - apiToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - action
                - selection
              properties:
                action:
                  type: string
                  enum:
                    - delete
                selection:
                  type: object
                recursive:
                  type: boolean
  /v1/tasks/by-active-session:
    get:
      x-public:
        scope:
          entity: tasks
          action: read
      summary: List active tasks bound to given chat sessions (MP51-SUB-8 W2 F-F1 — chat-task binding derivation)
      tags:
        - tasks-management
        - Tasks
      parameters:
        - name: sessionIds
          in: query
          required: true
          schema:
            type: string
          description: Comma-separated session id list
      security:
        - apiToken: []
  /v1/tasks/{id}:
    get:
      x-public:
        scope:
          entity: tasks
          action: read
      summary: Get task detail
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Update task
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: tasks
          action: delete
      summary: Soft delete task
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/assign:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Reassign task
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/execution-state:
    get:
      x-public:
        scope:
          entity: tasks
          action: read
      summary: Read the current execution state of a task
      tags:
        - tasks-management
      responses:
        "200":
          description: Execution state
      security:
        - apiToken: []
  /v1/tasks/{id}/pause:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Pause an in-progress task
      tags:
        - tasks-management
      responses:
        "200":
          description: Task paused
      security:
        - apiToken: []
  /v1/tasks/{id}/release:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Release ownership of a task
      tags:
        - tasks-management
      responses:
        "200":
          description: Task released
      security:
        - apiToken: []
  /v1/tasks/{id}/status:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Change task status
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/subtask-groups:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Create a subtask group ("Group N", sequential default) (MP249-SUB-6 E2)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/subtask-groups/{groupId}:
    patch:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Inline-rename and/or flip a subtask group's launch mode (MP249-SUB-6 E2)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Delete a subtask group (members return to flat ungrouped) (MP249-SUB-6 E2)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/subtask-groups/{groupId}/reorder:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Reorder subtasks within a group (MP249-SUB-6 E2)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/subtask-groups/{groupId}/subtasks:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Move a subtask INTO a group (MP249-SUB-6 E2)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/subtask-groups/{groupId}/subtasks/{subtaskId}:
    delete:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Remove a subtask FROM a group (returns to flat ungrouped) (MP249-SUB-6 E2)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{id}/subtasks/reorder:
    post:
      x-public:
        scope:
          entity: tasks
          action: write
      summary: Reorder subtasks atomically (MP51-SUB-6 F-D8 DnD)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/tasks/{taskId}/timeline:
    get:
      x-public:
        scope:
          entity: tasks
          action: read
      summary: Get DERIVED timeline events for task (MP51-SUB-6 F-D10)
      tags:
        - tasks-management
        - Tasks
      security:
        - apiToken: []
  /v1/team-members:
    get:
      x-public:
        scope:
          entity: users
          action: read
      operationId: get_v1_team_members
      summary: The selected team's combined human + AI-agent roster (teamId from x-selected-team)
      tags:
        - teams-management
      responses:
        "200":
          description: Team members and agents retrieved successfully
        "400":
          description: Team selection is required
        "401":
          description: Unauthorized
        "403":
          description: Access denied — the caller is not a member of the selected team
      security:
        - apiToken: []
  /v1/webhook-endpoints:
    get:
      x-public:
        scope:
          entity: webhook_endpoint
          action: read
      operationId: list_webhook_endpoints
      summary: List webhook endpoints for the current team
      tags:
        - integrations-management
      responses:
        "200":
          description: Paginated webhook endpoint list (sensitive fields stripped)
      security:
        - apiToken: []
    post:
      x-public:
        scope:
          entity: webhook_endpoint
          action: write
      operationId: create_webhook_endpoint
      summary: Create webhook endpoint (kind inbound | outbound)
      tags:
        - integrations-management
      responses:
        "201":
          description: Webhook endpoint created
      security:
        - apiToken: []
  /v1/webhook-endpoints/{id}:
    get:
      x-public:
        scope:
          entity: webhook_endpoint
          action: read
      operationId: get_webhook_endpoint
      summary: Get webhook endpoint summary (sensitive fields stripped)
      tags:
        - integrations-management
      responses:
        "200":
          description: Webhook endpoint summary
      security:
        - apiToken: []
    patch:
      x-public:
        scope:
          entity: webhook_endpoint
          action: write
      operationId: update_webhook_endpoint
      summary: Update a webhook endpoint of EITHER kind (partial merge). Common - name, description, tags, status.
        Inbound-only - config.verifySignature ('required' | 'off'; null CLEARS it back to absent, which is a distinct
        stored state). Outbound-only - targetUrl, httpMethod, authType, accessToken, enableSigning, customHeaders,
        eventTypes, retryPolicy. `kind` is immutable and is not an input.
      tags:
        - integrations-management
      responses:
        "200":
          description: Updated endpoint summary; a NEWLY minted signingSecret returned once
        "400":
          description: Invalid input, a blocked targetUrl, a status outside the declared WebhookEndpointStatus union, or a field
            belonging to the OTHER kind (refused rather than silently dropped)
        "404":
          description: Webhook endpoint not found for this team
      security:
        - apiToken: []
    delete:
      x-public:
        scope:
          entity: webhook_endpoint
          action: delete
      operationId: delete_webhook_endpoint
      summary: Delete webhook endpoint
      tags:
        - integrations-management
      responses:
        "204":
          description: Webhook endpoint deleted
      security:
        - apiToken: []
  /v1/webhook-endpoints/{id}/logs:
    get:
      x-public:
        scope:
          entity: webhook_endpoint
          action: read
      operationId: list_webhook_endpoint_delivery_logs
      summary: List delivery logs for a webhook endpoint (offset-paginated)
      tags:
        - integrations-management
      responses:
        "200":
          description: Paginated webhook delivery-log entries
      security:
        - apiToken: []
  /v1/webhook-endpoints/{id}/rotate-secret:
    post:
      x-public:
        scope:
          entity: webhook_endpoint
          action: write
      operationId: rotate_webhook_endpoint_secret
      summary: Rotate a webhook endpoint credential. Body `rotate` selects the target - 'secret' (default, and the
        pre-MP2921-SUB-4 behaviour), 'token' (the inbound URL token, which invalidates the old URL), or 'both'. 'token'
        and 'both' are refused on an outbound endpoint, which has no URL token.
      tags:
        - integrations-management
      responses:
        "200":
          description: Rotated. A newly minted secret is returned once; when the inbound TOKEN was rotated the rebuilt public URL
            rides alongside it as `inboundUrl`, exactly as the create path returns it.
        "400":
          description: An unrecognised rotate target, or a token rotation on an outbound endpoint
      security:
        - apiToken: []
  /v1/webhook-endpoints/{id}/test:
    post:
      x-public:
        scope:
          entity: webhook_endpoint
          action: write
      operationId: test_webhook_endpoint
      summary: Live test-send for a webhook endpoint (outbound signed POST | inbound self-POST)
      tags:
        - integrations-management
      responses:
        "200":
          description: Test-send result (status, responseCode, latencyMs, responseBody)
      security:
        - apiToken: []
  /webhooks/email/events:
    post:
      x-public:
        auth: none
      summary: SNS webhook for bounce/complaint/delivery events
      description: Receives SNS notifications from SES Configuration Set event destinations. Public route (no JWT),
        authenticated via SNS signature verification.
      responses:
        "200":
          description: Acknowledged
      tags:
        - email-listener
      security: []
  /webhooks/email/inbound:
    post:
      x-public:
        auth: none
      summary: SNS webhook for inbound email notifications
      description: Receives SNS notifications when SES stores inbound email in S3. Public route (no JWT), authenticated via
        SNS signature verification.
      responses:
        "200":
          description: Acknowledged
      tags:
        - email-listener
      security: []
  /webhooks/instagram/{integrationId}:
    get:
      x-public:
        auth: none
      operationId: get_webhooks_instagram_integrationId
      summary: Meta webhook verification (hub.challenge)
      tags:
        - instagram-listener
      parameters:
        - in: path
          name: integrationId
          required: true
          schema:
            type: string
          description: Integration ID
        - in: query
          name: hub.mode
          schema:
            type: string
        - in: query
          name: hub.verify_token
          schema:
            type: string
        - in: query
          name: hub.challenge
          schema:
            type: string
      responses:
        "200":
          description: Verification challenge echoed back
        "403":
          description: Token mismatch
      security: []
    post:
      x-public:
        auth: none
      operationId: post_webhooks_instagram_integrationId
      summary: Instagram webhook endpoint for receiving messaging events
      tags:
        - instagram-listener
      parameters:
        - in: path
          name: integrationId
          required: true
          schema:
            type: string
          description: Integration ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - object
                - entry
              properties:
                object:
                  type: string
                  enum:
                    - instagram
                entry:
                  type: array
                  items:
                    type: object
      responses:
        "200":
          description: Webhook processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
        "403":
          description: Signature verification failed
      security: []
  /webhooks/jivosite/{token}:
    post:
      x-public:
        auth: none
      operationId: post_webhooks_jivosite_token
      summary: Jivosite inbound webhook endpoint (token-in-path auth, no HMAC)
      tags:
        - jivosite-listener
      responses:
        "200":
          description: Webhook acknowledged (fast-200 before async processing)
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
        "401":
          description: Invalid per-channel token (constant-time mismatch)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "404":
          description: No online-chat channel for the inbound site
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "500":
          description: Internal error during resolution (Jivo retries)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "503":
          description: Service initializing (resolvers not ready)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
      parameters:
        - in: path
          name: token
          required: true
          description: Per-channel high-entropy inbound token (the ONLY inbound auth)
          schema:
            type: string
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Jivosite Bot API webhook payload (CLIENT_MESSAGE or lifecycle event)
              required:
                - event
    parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
  /webhooks/livekit:
    post:
      x-public:
        auth: none
      summary: LiveKit webhook receiver (HMAC over the raw body) — store fan-out
      tags:
        - calls-service
        - Webhooks
      responses:
        "200":
          description: "{ received: true }"
        "400":
          description: INVALID_SIGNATURE
        "503":
          description: CALLS_LIVEKIT_UNCONFIGURED (INFRA-GATE)
      security: []
  /webhooks/system-bot:
    post:
      x-public:
        auth: none
      operationId: post_webhooks_system_bot
      summary: System bot webhook for employee Telegram verification
      tags:
        - telegram-listener
      responses:
        "200":
          description: Webhook processed successfully
      security: []
  /webhooks/telegram/listing/{botId}:
    post:
      x-public:
        auth: none
      operationId: post_webhooks_telegram_listing_botId
      summary: Fixed per-bot webhook endpoint for a platform-owned listing bot
      description: MP3955-tgforum SUB-2 W2. ONE hand-created bot per marketplace listing serves every team and every forum, so
        its webhook URL carries the bot id and no channel segment. Authenticated by that bot's OWN registered secret in
        X-Telegram-Bot-Api-Secret-Token, constant-time compared against the credential's encrypted webhook.secret. The
        system bot's shared secret does NOT authenticate here.
      tags:
        - telegram-listener
      responses:
        "200":
          description: Webhook accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
        "400":
          description: Invalid webhook payload
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "403":
          description: Webhook secret token verification failed
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "404":
          description: No listing credential for this bot id
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
      parameters:
        - in: path
          name: botId
          required: true
          description: Telegram bot id (the credential externalId)
          schema:
            type: string
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Telegram webhook payload
              required:
                - update_id
    parameters:
      - name: botId
        in: path
        required: true
        schema:
          type: string
  /webhooks/telegram/{integrationId}/{channelId}:
    post:
      x-public:
        auth: none
      operationId: post_webhooks_telegram_integrationId_channelId
      summary: Telegram webhook endpoint for specific integration and channel
      tags:
        - telegram-listener
      responses:
        "200":
          description: Webhook processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
        "400":
          description: Invalid webhook payload
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "404":
          description: Integration or channel not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
      parameters:
        - in: path
          name: integrationId
          required: true
          description: Integration ID
          schema:
            type: string
        - in: path
          name: channelId
          required: true
          description: Channel ID
          schema:
            type: string
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Telegram webhook payload
              required:
                - update_id
    parameters:
      - name: integrationId
        in: path
        required: true
        schema:
          type: string
      - name: channelId
        in: path
        required: true
        schema:
          type: string
  /webhooks/vk/{integrationId}:
    post:
      x-public:
        auth: none
      operationId: post_webhooks_vk_integrationId
      summary: VK Callback API webhook endpoint for receiving events
      tags:
        - vk-listener
      parameters:
        - in: path
          name: integrationId
          required: true
          schema:
            type: string
          description: Integration ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - type
                - group_id
              properties:
                type:
                  type: string
                group_id:
                  type: integer
                object:
                  type: object
                secret:
                  type: string
                event_id:
                  type: string
      responses:
        "200":
          description: Event acknowledged with plain text response
          content:
            text/plain:
              schema:
                type: string
        "403":
          description: Secret verification failed
      security: []
  /webhooks/whatsapp/{integrationId}:
    get:
      x-public:
        auth: none
      operationId: get_webhooks_whatsapp_integrationId
      summary: Meta webhook verification (hub.challenge)
      tags:
        - whatsapp-listener
      parameters:
        - in: path
          name: integrationId
          required: true
          schema:
            type: string
          description: Integration ID
        - in: query
          name: hub.mode
          schema:
            type: string
        - in: query
          name: hub.verify_token
          schema:
            type: string
        - in: query
          name: hub.challenge
          schema:
            type: string
      responses:
        "200":
          description: Verification challenge echoed back
        "403":
          description: Token mismatch
      security: []
    post:
      x-public:
        auth: none
      operationId: post_webhooks_whatsapp_integrationId
      summary: WhatsApp webhook endpoint for receiving messaging events
      tags:
        - whatsapp-listener
      parameters:
        - in: path
          name: integrationId
          required: true
          schema:
            type: string
          description: Integration ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - object
                - entry
              properties:
                object:
                  type: string
                  enum:
                    - whatsapp_business_account
                entry:
                  type: array
                  items:
                    type: object
      responses:
        "200":
          description: Webhook processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
        "403":
          description: Signature verification failed
      security: []
  /webhooks/{endpointId}/{token}:
    post:
      x-public:
        auth: none
      summary: Inbound webhook endpoint for external event delivery (MP144 DEC-IMU-11 renamed from /:integrationId/:token;
        receiver flipped to installed_app_credentials lookup byte-for-byte URL preserved)
      tags:
        - events-management
        - Webhooks
      security: []
      parameters:
        - name: endpointId
          in: path
          required: true
          schema:
            type: string
        - name: token
          in: path
          required: true
          schema:
            type: string
        - name: X-Webhook-Signature
          in: header
          required: false
          description: sha256=HMAC_SHA256(config.secret, "{X-Webhook-Timestamp}.{raw request body}")
          schema:
            type: string
        - name: X-Webhook-Timestamp
          in: header
          required: false
          description: Unix seconds; the receiver refuses a value more than 300 seconds from its own clock in EITHER direction
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Webhook received
        "401":
          description: Not authenticated — invalid token, failed signature, unknown endpoint, or a disabled endpoint reached
            without a valid token. Deliberately indistinguishable; the reason is in the endpoint's delivery log.
        "410":
          description: Endpoint disabled. Reachable ONLY with a VALID token — to any other caller a disabled endpoint is
            indistinguishable from an unknown one (401 above).
        "413":
          description: Payload too large (>1MB)
        "415":
          description: Unsupported media type — Content-Type must be application/json
components:
  securitySchemes:
    apiToken:
      type: http
      scheme: bearer
      description: "API Token (format: fgd_live_<hex>)"
  responses:
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
              message:
                type: string
  schemas:
    ContactsAcknowledgement:
      type: object
      description: "A mutation that has nothing to report but its own success. NOT a 204: the platform returns a body a
        generic client can read (.claude/rules/backend-details.md section Response Patterns)."
      required:
        - message
      properties:
        message:
          type: string
          example: Channel unbound
    ContactsBookRecordsPage:
      type: object
      description: A collection response whose members are genuinely POLYMORPHIC — the `resourceType` the caller asked for
        selects which of three collections was read and which projection was applied.
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            type: object
            description: A ContactDto, a ProfileDto or a VisitorDto, per the request's `resourceType`. The three share `id`,
              `teamId`, `contactBookId` and the timestamps; the rest differs by type, so the shape is declared OPEN
              rather than as a false closed union.
            required:
              - id
              - teamId
            additionalProperties: true
            properties:
              id:
                type: string
              teamId:
                type: string
              contactBookId:
                type: string
                nullable: true
              createdAt:
                type: string
                format: date-time
              updatedAt:
                type: string
                format: date-time
        pagination:
          $ref: "#/components/schemas/ContactsPaginationWithTotal"
    ContactsChannelBindingDto:
      type: object
      description: The stored channel -> contact-book routing FK, as projected onto the wire. Mirrors canonical
        `ContactBookChannelBindingDto`.
      required:
        - channelId
        - contactBookId
        - teamId
        - createdAt
        - updatedAt
      properties:
        channelId:
          type: string
        contactBookId:
          type: string
        teamId:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ContactsChannelBindingPage:
      type: object
      description: A collection response. Never a bare array.
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ContactsChannelBindingDto"
        pagination:
          $ref: "#/components/schemas/ContactsPaginationWithTotal"
    ContactsChannelBindingRef:
      type: object
      description: "The NARROW binding pair the create path returns — which channel now routes into which book. Deliberately
        not the stored row: the auto-ensure arm knows the book id it created without re-reading it."
      required:
        - channelId
        - contactBookId
      properties:
        channelId:
          type: string
        contactBookId:
          type: string
    ContactsErrorEnvelope:
      type: object
      description: "Flat. Never nested, never a {success: false} wrapper. Built by buildHttpError and rendered by the
        canonical createHttpErrorHandler."
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: "Machine-readable ErrorCode. Emitted on this service's refusal paths: VALIDATION_ERROR, INVALID_ID,
            TEAM_ID_REQUIRED, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT. A sample of what callers meet, not a closed
            union — do not branch on its exhaustiveness."
        message:
          type: string
        details:
          description: Zod issue list on a schema refusal, or free-form service metadata. ABSENT on the hand-built refusals that
            carry no structured detail.
          oneOf:
            - type: object
              properties:
                issues:
                  type: array
                  items:
                    type: object
                    additionalProperties: true
            - type: object
              additionalProperties: true
    ContactsPaginationWithTotal:
      type: object
      description: "`total` IS present here and is a real countDocuments over the same filter as the page — not synthesised
        from the page length. Endpoints whose cardinality is unknown omit it entirely rather than inventing one."
      required:
        - total
        - limit
        - offset
        - hasMore
      properties:
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
        hasMore:
          type: boolean
    DatabasesErrorEnvelope:
      type: object
      description: "Never nested, never a {success: false} wrapper."
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: "Machine-readable ErrorCode. Emitted on this service's refusal paths: VALIDATION_ERROR, TEAM_ID_REQUIRED,
            FORBIDDEN, INTERNAL_ERROR, FILTER_DEFINITION_INVALID, FILTER_DATE_UNSUPPORTED. The list is a sample of what
            callers meet, not a closed union — do not branch on its exhaustiveness."
        message:
          type: string
        details:
          description: Validation issue list, or free-form service metadata.
          oneOf:
            - type: array
              items:
                type: object
            - type: object
    DatabasesReferencingRecordRef:
      type: object
      description: One reference, addressed as the caller can act on it.
      required:
        - tableId
        - tableSystemName
        - tableName
        - recordId
        - fieldSystemName
        - match
      properties:
        tableId:
          type: string
        tableSystemName:
          type: string
        tableName:
          type: string
          description: Falls back to `tableSystemName` when the table has no display name.
        recordId:
          type: string
        fieldSystemName:
          type: string
          description: The lookup column on the referencing row that holds the reference.
        match:
          type: string
          description: Which arm answered — `pointer` for a full pointer cell, `legacy` for a bare id in a column whose
            targetTable names the pointer's table.
          enum:
            - pointer
            - legacy
    DatabasesReferencingRecordsPage:
      type: object
      description: A collection response. Never a bare array and never flat paging tokens (.claude/rules/backend-details.md
        section Response Patterns).
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/DatabasesReferencingRecordRef"
        pagination:
          type: object
          description: "`total` is deliberately ABSENT. The scan stops as soon as a page plus one is collected, so the true
            cardinality is NOT known, and synthesising it from the page length would be a self-contradicting pair beside
            `hasMore`."
          required:
            - limit
            - offset
            - hasMore
          properties:
            limit:
              type: integer
            offset:
              type: integer
            hasMore:
              type: boolean
    ErrorEnvelope:
      type: object
      description: "The canonical flat error envelope (.claude/rules/backend-details.md § Error Handling). Never nested, never
        {success: false}."
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Machine-readable ErrorCode, e.g. VALIDATION_ERROR.
        message:
          type: string
        details:
          description: Validation issue list, or free-form service metadata.
          oneOf:
            - type: array
              items:
                type: object
            - type: object
    FlowsErrorEnvelope:
      type: object
      description: >-
        The canonical flat error envelope (`.claude/rules/backend-details.md` § Error Handling) — never nested, never a
        `{success: false}` wrapper.

        Named for THIS service rather than something generic because `aggregate-api-docs.js` merges every service's
        `components.schemas` into ONE namespace, so a bare `ErrorResponse` here would be a cross-service collision
        waiting to happen.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Machine-readable ErrorCode. Emitted on this service's refusal paths — TEAM_ID_REQUIRED, VALIDATION_ERROR,
            FLOW_NOT_FOUND, FLOW_VERSION_NOT_FOUND, FLOW_GROUP_NOT_FOUND, FLOW_VERSION_ALREADY_PUBLISHED,
            INTERNAL_ERROR. The list is a SAMPLE of what callers meet, NOT a closed union; do not branch on its
            exhaustiveness.
        message:
          type: string
          description: Human-readable explanation. Not stable — do not parse it.
        details:
          description: Validation issue list, or free-form service metadata.
          oneOf:
            - type: array
              items:
                type: object
            - type: object
    GuestInvitePayload:
      type: object
      required:
        - guests
      properties:
        guests:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
            description: Either `userId` or `email` is required.
            properties:
              userId:
                type: string
              email:
                type: string
                format: email
              name:
                type: string
              displayName:
                type: string
              role:
                type: string
              responseNote:
                type: string
    ImportJob:
      type: object
      description: The canonical `ImportJobDto`. Addressed by the stable `imp_*` `jobId`, never by `id`.
      required:
        - id
        - jobId
        - teamId
        - createdBy
        - sourceMediaId
        - status
        - phase
        - dryRun
        - idempotencyKey
        - progress
        - report
        - error
        - createdAt
        - updatedAt
        - startedAt
        - completedAt
      properties:
        id:
          type: string
          description: Mongo `_id` hex.
        jobId:
          type: string
          description: The stable `imp_*` application id every route addresses.
        teamId:
          type: string
        createdBy:
          type: string
        sourceMediaId:
          type: string
          description: The already-uploaded bundle this job reads.
        status:
          type: string
          enum:
            - pending
            - running
            - paused
            - completed
            - failed
            - rolled-back
        phase:
          type: string
        dryRun:
          type: boolean
          description: "`true` runs the plan without a single write."
        idempotencyKey:
          type: string
          nullable: true
          description: "`null` when the caller supplied none. The `import_jobs` unique index is PARTIAL for exactly this reason,
            so keyless jobs do not collide on `null`."
        progress:
          $ref: "#/components/schemas/ImportJobProgress"
        report:
          $ref: "#/components/schemas/ImportJobReport"
        error:
          $ref: "#/components/schemas/ImportJobError"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        startedAt:
          type: string
          format: date-time
          nullable: true
        completedAt:
          type: string
          format: date-time
          nullable: true
    ImportJobError:
      type: object
      nullable: true
      description: "`null` unless `status` is `failed`."
      required:
        - code
        - message
      properties:
        code:
          type: string
        message:
          type: string
        phase:
          type: string
    ImportJobPage:
      type: object
      description: A collection NEVER ships as a bare array — there is no slot on one for paging.
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ImportJob"
        pagination:
          $ref: "#/components/schemas/PaginationMeta"
    ImportJobProgress:
      type: object
      nullable: true
      description: "`null` until the first checkpoint."
      properties:
        rowsDone:
          type: integer
        rowsTotal:
          type: integer
          nullable: true
          description: "`null` when the total is genuinely UNKNOWN (a dir-backed entity), never 0 as a stand-in."
        entity:
          type: string
    ImportJobReport:
      type: object
      nullable: true
      description: "`null` until the report phase completes."
      properties:
        entities:
          type: array
          items:
            type: object
        counts:
          type: object
    ImportReferenceRowIssue:
      type: object
      required:
        - kind
        - id
        - jobId
        - entity
        - outcome
        - column
        - keyValue
        - rowIndex
        - createdAt
      properties:
        kind:
          type: string
          enum:
            - reference
        id:
          type: string
        jobId:
          type: string
        entity:
          type: string
          description: The `ImportEntityDecl.name` whose row produced this outcome.
        outcome:
          type: string
          enum:
            - resolved
            - unresolved
            - absent
          description: "`resolved` is a legal filter that always yields an empty page — resolved references are COUNTED, never
            persisted."
        column:
          type: string
        keyValue:
          type: string
          nullable: true
          description: "`null` ONLY for `absent`, meaning the source carried no key at all. An empty string would be
            indistinguishable from a genuinely empty key."
        targetEntity:
          type: string
          nullable: true
        rowIndex:
          type: integer
          nullable: true
        createdAt:
          type: string
          format: date-time
    ImportRowDefectIssue:
      type: object
      required:
        - kind
        - id
        - jobId
        - entity
        - reason
        - columns
        - keyValue
        - rowIndex
        - createdAt
      properties:
        kind:
          type: string
          enum:
            - defect
        id:
          type: string
        jobId:
          type: string
        entity:
          type: string
          description: The `ImportEntityDecl.name` whose row carried the defect.
        reason:
          type: string
          enum:
            - EMPTY_KEY
            - DUPLICATE_KEY_IN_SOURCE
            - AMBIGUOUS_KEY_IN_TABLE
            - INVALID_CONTENT
            - COLUMN_UNMAPPED
            - IDENTITY_AMBIGUOUS
            - FIELDS_DROPPED
          description: The first four names are ADOPTED VERBATIM from the table door's RecordImportSkipReason and are counted by
            ImportSkipCounts; the last three are counted by ImportRowDefectCounts. Which counter a reason increments is
            ROW_DEFECT_REASON_AXIS's answer, never a hard-coded one.
        columns:
          type: array
          items:
            type: string
          description: The offending source column(s). REQUIRED, with `[]` a first-class claim — "this defect is not
            column-scoped" (IDENTITY_AMBIGUOUS, EMPTY_KEY).
        keyValue:
          type: string
          nullable: true
        rowIndex:
          type: integer
          nullable: true
        createdAt:
          type: string
          format: date-time
    ImportRowIssue:
      description: A DISCRIMINATED UNION on `kind` (MP5483-realimport-SUB-1). The defect arm declares NEITHER `column` NOR
        `targetEntity` — those name a RELATION, and a row refused for a defect of its own has none. That absence is the
        reason this is a union rather than a wider record with nulled members.
      oneOf:
        - $ref: "#/components/schemas/ImportReferenceRowIssue"
        - $ref: "#/components/schemas/ImportRowDefectIssue"
      discriminator:
        propertyName: kind
        mapping:
          reference: "#/components/schemas/ImportReferenceRowIssue"
          defect: "#/components/schemas/ImportRowDefectIssue"
    ImportRowIssuePage:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ImportRowIssue"
        pagination:
          $ref: "#/components/schemas/PaginationMeta"
    ImportsErrorEnvelope:
      type: object
      description: "The canonical FLAT error envelope. Never nested, never a `{success: false}` wrapper — built by
        `buildHttpError` and rendered by the shared `createHttpErrorHandler` (`.claude/rules/backend-details.md § Error
        Handling`)."
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: A declared `ErrorCode` union member. Branch on THIS, never on `message`.
        message:
          type: string
        details:
          description: Zod `issues[]` from a refused parse, or a service-specific record. Absent when the refusal carries no
            structure.
          oneOf:
            - type: array
              items:
                type: object
            - type: object
    MediaBulkIpcError:
      type: object
      description: "The canonical FLAT error envelope (`.claude/rules/backend-details.md § Error Handling`) — never nested and
        never `{success: false}`."
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: "The codes this pair emits: `BULK_WRITE_VALIDATION_FAILED` (400) · `INTERNAL_TOKEN_MISSING` and
            `INVALID_INTERNAL_TOKEN` (401, from `authenticateInternalToken`) · `SERVICE_INITIALIZING` (503)."
          example: BULK_WRITE_VALIDATION_FAILED
        message:
          type: string
        details:
          type: object
          description: "Present ONLY on the 400 Zod path, where it carries `{issues: [...]}` from the failed `safeParse`. Absent
            on every other status."
          properties:
            issues:
              type: array
              items:
                type: object
                additionalProperties: true
    MediaErrorEnvelope:
      type: object
      description: >-
        The canonical flat error envelope (`.claude/rules/backend-details.md` § Error Handling) — never nested, never a
        `{success: false}` wrapper.

        Named for THIS service rather than something generic because `aggregate-api-docs.js` merges every service's
        `components.schemas` into ONE namespace, so a bare `ErrorResponse` here would be a cross-service collision
        waiting to happen.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Machine-readable ErrorCode. Emitted on this service's refusal paths — MEDIA_NOT_FOUND, DRIVE_NOT_FOUND,
            FOLDER_NOT_FOUND, VALIDATION_ERROR, INVALID_SHARE_TOKEN, STORAGE_QUOTA_EXCEEDED, MEDIA_NO_STORAGE_PATH,
            INTERNAL_ERROR. The list is a SAMPLE of what callers meet, NOT a closed union; do not branch on its
            exhaustiveness.
        message:
          type: string
          description: Human-readable explanation. Not stable — do not parse it.
        details:
          description: Validation issue list, or free-form service metadata.
          oneOf:
            - type: array
              items:
                type: object
            - type: object
    MediaUploadGrantEnvelope:
      type: object
      description: "The 2xx body of the canonical upload endpoints — grant, commit, abort and the external-drive proxy.
        Deliberately PERMISSIVE here rather than a per-endpoint mirror: the authoritative shapes are the TypeScript
        contract (`@foxguide/api-contracts` upload-grant) and its Zod sibling, which gate the wire at the persistence
        boundary (IL-56). A hand-maintained YAML copy of a discriminated union is a second source of truth that drifts
        silently, and this file already carries that lesson elsewhere. What this schema DOES assert is the thing the
        coverage gate exists for: a documented operation says what it returns, and an error path uses the flat envelope
        rather than this one. `POST /v1/media/upload-abort/{grantId}` answers 204 with NO body by design — a JSON result
        would need a contract export and every export-liveness ceiling sits at slack 0 — so its 2xx carries this schema
        only as a shape declaration."
      additionalProperties: true
    NotificationsErrorEnvelope:
      type: object
      description: "The canonical flat error envelope (.claude/rules/backend-details.md § Error Handling). Never nested, never
        a {success: false} wrapper."
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Machine-readable ErrorCode, e.g. LAUNCH_CONFIRMATION_TOKEN_EXPIRED.
        message:
          type: string
        details:
          description: Validation issue list, or free-form service metadata.
          oneOf:
            - type: array
              items:
                type: object
            - type: object
    PaginationMeta:
      type: object
      required:
        - limit
        - offset
        - hasMore
      properties:
        limit:
          type: integer
        offset:
          type: integer
        hasMore:
          type: boolean
        total:
          type: integer
          description: "OPTIONAL, and omitted when the cardinality is not genuinely known. It is NEVER synthesised from the page
            length — a `total` equal to `data.length` beside `hasMore: true` is a self-contradiction, and four sites in
            this repository shipped exactly that pair."
    ResourceVersion:
      type: object
      description: "One version of one record, as stored in the shared `resource_versions` store. Canonical declaration:
        `@foxguide/api-contracts/domain/versioning/resource-version.ts`."
      required:
        - id
        - teamId
        - target
        - versionNumber
        - contentHash
        - changeType
        - snapshot
        - createdBy
        - createdAt
      properties:
        id:
          type: string
          description: Branded `ResourceVersionId`.
        teamId:
          type: string
        target:
          $ref: "#/components/schemas/ResourceVersionTarget"
        versionNumber:
          type: integer
          minimum: 1
          description: 1-based, monotonic per target, and DENSE — a gap means a lost write, not a pruned row.
        contentHash:
          type: string
          description: SHA-256 of the captured content, lowercase hex. Present on every row in both snapshot modes; it is what
            makes content dedup possible.
        changeType:
          type: string
          enum:
            - created
            - updated
            - restored
            - published
        changeDescription:
          type: string
          description: Free-text, author-supplied or platform-generated for a restore.
        label:
          type: string
          description: Optional human label.
        snapshot:
          description: The captured state, discriminated on `mode` so a reader cannot mistake a storage reference for a body.
          oneOf:
            - type: object
              required:
                - mode
                - body
              properties:
                mode:
                  type: string
                  enum:
                    - inline
                body:
                  type: object
                  description: The record's own fields. Shape is owner-specific.
            - type: object
              required:
                - mode
              properties:
                mode:
                  type: string
                  enum:
                    - ref
        createdBy:
          type: string
        createdAt:
          type: string
          format: date-time
        isDeleted:
          type: boolean
        deletedAt:
          type: string
          format: date-time
          nullable: true
    ResourceVersionPage:
      type: object
      description: One page of version history, newest first. `pagination.total` is deliberately ABSENT — the store answers a
        page and a `hasMore`, and a synthesised total beside `hasMore` is the self-contradicting pair
        `.claude/rules/backend-details.md § Pagination` names.
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ResourceVersion"
        pagination:
          type: object
          required:
            - limit
            - hasMore
          properties:
            limit:
              type: integer
            hasMore:
              type: boolean
    ResourceVersionTarget:
      type: object
      description: What a version is a version OF — the full `TabResourcePointer` spine, not a flat id pair. A table record is
        not uniquely addressed by its own id; it needs its table, which rides in `parentRefs`.
      required:
        - appId
        - kind
        - type
        - id
        - parentRefs
      properties:
        appId:
          type: string
        kind:
          type: string
        type:
          type: string
          description: The versionable resource type, e.g. `agent` or `code_function`.
        id:
          type: string
        parentRefs:
          type: array
          items:
            type: string
    UsersErrorEnvelope:
      type: object
      description: >-
        The canonical flat error envelope (`.claude/rules/backend-details.md` § Error Handling) — never nested, never a
        `{success: false}` wrapper.

        Named for THIS service rather than something generic because `aggregate-api-docs.js` merges every service's
        `components.schemas` into ONE namespace, so a bare `ErrorResponse` here would be a cross-service collision
        waiting to happen.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Machine-readable ErrorCode. Emitted on this service's refusal paths — VALIDATION_ERROR, USER_NOT_FOUND,
            HANDLE_TAKEN, HANDLE_RESERVED, EMAIL_NOT_VERIFIED, JWKS_GENERATION_FAILED, INTERNAL_ERROR. The list is a
            SAMPLE of what callers meet, NOT a closed union; do not branch on its exhaustiveness.
        message:
          type: string
          description: Human-readable explanation. Not stable — do not parse it.
        details:
          description: Validation issue list, or free-form service metadata.
          oneOf:
            - type: array
              items:
                type: object
            - type: object
    GetMediaFileContentResponseSchema:
      type: object
      properties:
        content:
          type: string
        mimeType:
          type: string
        fileName:
          type: string
        editable:
          type: boolean
        format:
          type: string
          enum:
            - markdown
            - plaintext
            - html
            - unpreviewable
        etag:
          type: string
      required:
        - content
        - mimeType
        - fileName
        - editable
      additionalProperties: false
    UpdateMediaFileContentResponseSchema:
      type: object
      properties:
        mediaId:
          type: string
          minLength: 1
        mimeType:
          type: string
        fileSize:
          type: integer
          minimum: 0
        etag:
          type: string
      required:
        - mediaId
        - mimeType
        - fileSize
      additionalProperties: false
security:
  - apiToken: []
