openapi: 3.0.3
info:
  title: naturali.ai — Channel Kinds API
  version: 1.0.0
  description: >
    The surfaces and predicates each connectable channel kind publishes
    (design: CHANNELS-ROUTING.md §3.4/§3.5) — what a route's `surface` and
    `match` can name, read straight off the adapter and predicate registries
    so an app can render a route's condition builder without hardcoding
    either vocabulary.
  contact:
    name: naturali.ai
    url: https://naturali.ai
servers:
  - url: '{baseUrl}'
    description: Host of your naturali.ai deployment; every path carries the /v1 prefix.
    variables:
      baseUrl:
        description: Base host URL.
        default: https://api.naturali.ai
tags:
  - name: Channels
    description: Connect and manage a project's messaging surfaces (WhatsApp, Discord).
security:
  - bearerAuth: []
  - oauth2:
      - mcp:access
paths:
  /v1/channel-kinds:
    get:
      tags: [Channels]
      summary: List channel kinds
      description: >
        Every connectable kind, its surfaces, and the predicates its routes
        can match on (engine-wide ones like `address_known` plus its own,
        like Discord's `guild_id`).
      operationId: listChannelKinds
      responses:
        '200':
          description: Every channel kind.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelKindList'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
    oauth2:
      type: oauth2
      description: >-
        A connected app's OAuth access token, issued by this API's authorization
        server (discovery: /.well-known/oauth-authorization-server). Its one scope
        carries every operation, confined to the projects the user chose when
        approving the app.
      flows:
        authorizationCode:
          authorizationUrl: https://api.naturali.ai/authorize
          tokenUrl: https://api.naturali.ai/token
          refreshUrl: https://api.naturali.ai/token
          scopes:
            mcp:access: Every operation this API serves, on the projects the grant covers.
  responses:
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: >-
            Structured error. Every error response uses this shape, so `code` can
            be read without first checking the type of `error`.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: >-
                Machine-readable error code: lower_snake for an error naturali
                raises (`access_denied`), UPPER_SNAKE for one the runtime reports
                (`RESOURCE_NOT_FOUND`).
              example: access_denied
            message:
              type: string
              description: Human-readable explanation.
              example: Your role in this project does not carry this action.
            details:
              type: object
              additionalProperties: true
              description: >-
                Structured context for an error naturali raises, such as the
                `resource` and `limit` of a `plan_limit_reached`.
            meta:
              type: object
              description: Structured context for an error the runtime reports.
    ChannelSurface:
      type: object
      properties:
        name:
          type: string
          description: Stable, wire-visible id, unique within the kind.
          example: guild_thread
        title:
          type: string
          example: Server thread
        shared:
          type: boolean
          description: Several humans share one conversation here (a thread, a Slack channel).
          example: true
      required: [name, title, shared]
    ChannelPredicate:
      type: object
      properties:
        name:
          type: string
          example: guild_id
        type:
          type: string
          description: The value shape this predicate's `match` entry expects.
          example: string
      required: [name, type]
    ChannelKind:
      type: object
      properties:
        kind:
          type: string
          enum: [whatsapp, discord]
          example: discord
        surfaces:
          type: array
          items:
            $ref: '#/components/schemas/ChannelSurface'
        predicates:
          type: array
          items:
            $ref: '#/components/schemas/ChannelPredicate'
      required: [kind, surfaces, predicates]
    ChannelKindList:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ChannelKind'
