openapi: 3.0.3
info:
  title: Guayaba Public API v1
  version: "1.0"
  description: |
    Public REST API for managing AI agents on the Guayaba platform.

    ## Authentication

    All endpoints require an API key sent as a **Bearer token**:

    ```
    Authorization: Bearer g_master_<token>
    ```

    Keys are created from the Guayaba dashboard (Settings → API Keys).

    ### Key Format

    | Type | Prefix | Length |
    |---|---|---|
    | Master Key | `g_master_` | 57 chars (9-char prefix + 48 hex) |
    | Agent Key  | `g_agent_`  | 56 chars (8-char prefix + 48 hex) |

    Keys are shown **only once** at creation time. The server stores a SHA-256 hash — lost keys cannot be recovered, only regenerated.

    ### Subscription Requirement

    All endpoints require the key owner to have an **active subscription** (status: `active` or `trialing`).
    If the subscription is canceled, past due, or absent, every request returns `403`:

    ```json
    { "error": "Forbidden", "message": "An active subscription is required to use the API" }
    ```

    This check runs **after** key validation and **before** any route-specific authorization.

    ## Key Types & Scopes

    ### Master Key (`g_master_`)
    - One per user. Creating a new one revokes the previous (and cascade-revokes all agent keys).
    - Full access to all endpoints and all agents owned by the user.

    ### Agent Key (`g_agent_`)
    - One per agent. Tied to a single agent by `agent_id`.
    - Cannot access other agents (returns `403`).
    - Access controlled by **scopes** assigned at creation time.

    ### Scopes

    | Scope | Description | Default |
    |---|---|---|
    | `agent:read` | View agent details and status | Always included |
    | `agent:manage` | Start, stop, pause, reload. Manage sessions and archives | No |
    | `channels` | Manage Telegram pairing (list, approve, revoke, reject) | No |
    | `files` | Upload files to the agent | No |
    | `chat` | Send messages to the agent via API | No |

    A master key implicitly has all scopes.

    ## Billing & Credits

    Certain write operations consume credits. Credits are charged only on success (HTTP 2xx).

    | Operation | Credits | Endpoint |
    |---|---|---|
    | Create agent | 10 | `POST /agents` |
    | Update agent | 10 | `PUT /agents/{id}` |
    | Start agent | 5 | `POST /agents/{id}/start` |
    | Reload config | 1 | `POST /agents/{id}/reload` |
    | Chat message | 1 + LLM | `POST /agents/{id}/chat` |
    | Stop / Pause / Delete agent | 0 | — |
    | All read operations | 0 | — |

    Insufficient credits returns `402` **before** executing the operation.

    > Credit costs are provisional and subject to change.

    ## Caching

    Catalog endpoints return `Cache-Control: public, max-age=600, s-maxage=600` (10-minute TTL).
    All other endpoints return fresh data.

    ## Error Responses

    Errors follow this structure:
    ```json
    { "error": "ErrorCategory", "message": "Human-readable explanation" }
    ```

    | Code | Meaning |
    |---|---|
    | `401` | Missing, invalid, or revoked API key |
    | `402` | Insufficient credits |
    | `403` | No active subscription / key lacks scope / wrong agent / master key required |
    | `404` | Agent or resource not found |
    | `422` | Validation errors |
    | `502` | Proxy request to agent container failed |
    | `503` | Agent container not running |

    ## Agent Status Values

    | Status | Description |
    |---|---|
    | `created` | Agent created but never started |
    | `active` | Agent is provisioning / booting |
    | `running` | Agent is running and accepting requests |
    | `paused` | Agent is paused (container alive, not processing) |
    | `stopped` | Agent is stopped (no container) |
    | `failed` | Agent failed to start or crashed |

servers:
  - url: https://api.guayaba.run/api/v1
    description: Production

tags:
  - name: Agents
    description: CRUD operations on AI agents
  - name: Runtime
    description: Agent lifecycle control (start, stop, pause, reload, health)
  - name: Catalogs
    description: Read-only reference data (frameworks, regions, hardware, models). Cached 10 min.
  - name: Channels
    description: Telegram pairing management. Requires `channels` scope.
  - name: Files
    description: Upload files to an agent. Requires `files` scope.
  - name: Chat
    description: Send messages to an agent. Supports sync and SSE streaming. Requires `chat` scope.
  - name: Sessions
    description: Manage conversation sessions and archives. Requires `agent:manage` scope.
  - name: Billing
    description: Subscription, tiers, and credits. Requires master key.

components:
  securitySchemes:
    PublicApiKey:
      type: http
      scheme: bearer
      description: |
        API key sent as Bearer token. Two types:
        - Master key (`g_master_...`): full access to all agents and endpoints.
        - Agent key (`g_agent_...`): scoped to a single agent with specific permissions.

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          example: "Unauthorized"
        message:
          type: string
          example: "Invalid or revoked API key"

    Agent:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        bio:
          type: array
          items:
            type: string
        knowledge:
          type: array
          items:
            type: string
        channels:
          type: array
          items:
            type: string
        model_provider:
          type: string
        status:
          type: string
          enum: [created, active, running, paused, stopped, removed, failed]
        settings:
          type: object
          properties:
            model:
              type: string
              example: openai/gpt-4.1
            thinking:
              type: string
              enum: [off, low, medium, high]
              default: medium
            extra_instructions:
              type: string
              nullable: true
            trusted_proxies:
              type: array
              items:
                type: string
            secrets:
              type: object
              description: Channel secrets (key-value map)
              additionalProperties:
                type: string
            POST_IMMEDIATELY:
              type: boolean
            POST_INTERVAL_MIN:
              type: integer
            POST_INTERVAL_MAX:
              type: integer
            TWITTER_POLL_INTERVAL:
              type: integer
            ACTION_TIMELINE_TYPE:
              type: string
              enum: [following, foryou]
            MAX_ACTIONS_PROCESSING:
              type: integer
            ACTION_INTERVAL:
              type: integer
        secrets_configured:
          type: object
          description: Map of secret names to boolean (set or not). Values are never exposed.
          additionalProperties:
            type: boolean
          example:
            TELEGRAM_BOT_TOKEN: true
            OPENROUTER_API_KEY: true
        face_image_path:
          type: string
          nullable: true
        face_image_generation_prompt:
          type: string
          nullable: true
        auto_generation_prompt:
          type: string
          nullable: true
        auto_enhancement_prompt:
          type: string
          nullable: true
        is_featured:
          type: boolean
        last_boot_execution:
          type: string
          format: date-time
          nullable: true
        last_stop_execution:
          type: string
          format: date-time
          nullable: true
        uptime_total_seconds:
          type: integer
        user_id:
          type: string
          format: uuid
        framework_id:
          type: string
          format: uuid
        framework:
          $ref: '#/components/schemas/CatalogFramework'
        hardware_config:
          type: object
          nullable: true
          properties:
            region:
              $ref: '#/components/schemas/CatalogRegion'
            ram:
              $ref: '#/components/schemas/CatalogHardwareOption'
            disk:
              $ref: '#/components/schemas/CatalogHardwareOption'
            gpu:
              $ref: '#/components/schemas/CatalogHardwareOption'
            cpu:
              $ref: '#/components/schemas/CatalogHardwareOption'
        client_links:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              link:
                type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    AgentSettingsInput:
      type: object
      description: Input schema for creating or updating an agent
      properties:
        name:
          type: string
          maxLength: 255
        llm_provider_name:
          type: string
          example: openrouter
        llm_provider_model:
          type: string
          example: openai/gpt-4.1
        bio:
          type: array
          maxItems: 50
          items:
            type: string
            maxLength: 2000
        knowledge:
          type: array
          maxItems: 100
          items:
            type: string
            maxLength: 5000
        channels:
          type: array
          maxItems: 10
          items:
            type: string
        model_provider:
          type: string
        settings:
          type: object
          properties:
            model:
              type: string
              maxLength: 255
            thinking:
              type: string
              enum: [off, low, medium, high]
            extra_instructions:
              type: string
              maxLength: 10000
            secrets:
              type: object
              description: "Key-value map. Send null or \"\" to delete a secret; omit the key to keep it."
              additionalProperties:
                type: string
                nullable: true
            trusted_proxies:
              type: array
              maxItems: 10
              items:
                type: string
            POST_IMMEDIATELY:
              type: boolean
            POST_INTERVAL_MIN:
              type: integer
              minimum: 1
              maximum: 10080
            POST_INTERVAL_MAX:
              type: integer
              minimum: 1
              maximum: 10080
            TWITTER_POLL_INTERVAL:
              type: integer
              minimum: 10
              maximum: 86400
            ACTION_TIMELINE_TYPE:
              type: string
              enum: [following, foryou]
            MAX_ACTIONS_PROCESSING:
              type: integer
              minimum: 1
              maximum: 100
            ACTION_INTERVAL:
              type: integer
              minimum: 1
              maximum: 10080
        auto_generation_prompt:
          type: string
          maxLength: 10000
        auto_enhancement_prompt:
          type: string
          maxLength: 10000
        framework_id:
          type: string
          format: uuid
        region_id:
          type: string
          format: uuid
        ram_id:
          type: string
          format: uuid
        disk_id:
          type: string
          format: uuid
        gpu_id:
          type: string
          format: uuid
        cpu_id:
          type: string
          format: uuid

    CatalogFramework:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          example: openclaw
        name:
          type: string
          example: OpenClaw
        description:
          type: string
        icon:
          type: string
          nullable: true
        available:
          type: boolean
        tag:
          type: string
          nullable: true

    CatalogRegion:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          example: eu-central-1
        label:
          type: string
          example: "EU (Frankfurt)"
        city:
          type: string
          example: Frankfurt
        coords:
          type: object
          properties:
            lat:
              type: number
              format: float
            lng:
              type: number
              format: float
        available:
          type: boolean

    CatalogHardwareOption:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        label:
          type: string
        description:
          type: string
          nullable: true
        available:
          type: boolean

    CatalogChannel:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          example: telegram
        label:
          type: string
          example: Telegram
        description:
          type: string
        icon:
          type: string
          nullable: true
        available:
          type: boolean
        always_on:
          type: boolean

    CatalogModel:
      type: object
      properties:
        id:
          type: string
          format: uuid
        model_id:
          type: string
          example: anthropic/claude-sonnet-4.6
        provider:
          type: string
          example: openrouter
        name:
          type: string
          example: Claude Sonnet 4.6
        description:
          type: string
          nullable: true
        is_free:
          type: boolean
        is_default:
          type: boolean

    Session:
      type: object
      properties:
        key:
          type: string
        sessionId:
          type: string
          format: uuid
        sessionFile:
          type: string
        label:
          type: string
          nullable: true
        updatedAt:
          type: integer
          description: Unix timestamp in milliseconds
        chatType:
          type: string
        lastChannel:
          type: string
        origin:
          type: object

    ChatMessage:
      type: object
      properties:
        id:
          type: string
        role:
          type: string
          enum: [user, assistant]
        content:
          description: String or array of content blocks
          oneOf:
            - type: string
            - type: array
              items:
                type: object
                properties:
                  type:
                    type: string
                  text:
                    type: string
        timestamp:
          type: integer

    Archive:
      type: object
      properties:
        filename:
          type: string
        label:
          type: string
          nullable: true
        archivedAt:
          type: string
        sessionId:
          type: string

    Subscription:
      type: object
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        tier_slug:
          type: string
          example: pro
        status:
          type: string
          enum: [active, trialing, past_due, canceled, incomplete, incomplete_expired, unpaid, paused]
        stripe_subscription_id:
          type: string
          nullable: true
        stripe_customer_id:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        monthly_credits:
          type: integer
        current_period_start:
          type: string
          format: date-time
          nullable: true
        current_period_end:
          type: string
          format: date-time
          nullable: true
        trial_start:
          type: string
          format: date-time
          nullable: true
        trial_end:
          type: string
          format: date-time
          nullable: true
        cancel_at_period_end:
          type: boolean
          nullable: true
        cancel_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        tier:
          $ref: '#/components/schemas/Tier'

    Tier:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          example: pro
        name:
          type: string
          example: Pro
        monthly_credits:
          type: integer
        price:
          type: number
          format: float
        features:
          type: array
          nullable: true
          items:
            type: string
        max_running_agents:
          type: integer
          nullable: true
        allowed_channels:
          type: array
          nullable: true
          items:
            type: string
        can_buy_credit_packages:
          type: boolean
        trial_days:
          type: integer
        is_active:
          type: boolean
        sort_order:
          type: integer

    CreditBreakdown:
      type: object
      properties:
        total_credits:
          type: integer
        subscription_credits:
          type: integer
        purchased_credits:
          type: integer

    ValidationError:
      type: object
      properties:
        message:
          type: string
          example: The given data was invalid.
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          example:
            message: ["The message field is required."]

  responses:
    Unauthorized:
      description: Unauthorized — Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Unauthorized
            message: Invalid or revoked API key

    SubscriptionRequired:
      description: Forbidden — Active subscription required
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: Forbidden
              message:
                type: string
                example: An active subscription is required to use the API

    InsufficientCredits:
      description: Payment Required — Insufficient credits
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: Payment Required
              message:
                type: string
              required_credits:
                type: integer
              available_credits:
                type: integer

    ScopeForbidden:
      description: Forbidden — Key lacks the required scope
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Forbidden
            message: "This API key lacks the required scope: agent:manage"

    MasterKeyRequired:
      description: Forbidden — Requires a master API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Forbidden
            message: This endpoint requires a master API key

security:
  - PublicApiKey: []

paths:
  # ── Agents ────────────────────────────────────────────────
  /agents:
    get:
      tags: [Agents]
      summary: List user's agents
      description: |
        Returns a paginated list of all agents belonging to the API key owner.
        Works with any API key type (master or agent).
      parameters:
        - name: per_page
          in: query
          schema:
            type: integer
            default: 100
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        '200':
          description: Paginated list of agents
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Agent'
                  current_page:
                    type: integer
                  last_page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'

    post:
      tags: [Agents]
      summary: Create a new agent
      description: |
        Creates a new AI agent. Requires a **master key**.
        Charges **10 credits**.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentSettingsInput'
      responses:
        '201':
          description: Agent created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: Agent created successfully
                  data:
                    $ref: '#/components/schemas/Agent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/MasterKeyRequired'
        '422':
          description: Validation error

  /agents/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Agents]
      summary: Show agent details
      description: |
        Returns full details for a specific agent.
        - **Master key**: can access any of the user's agents.
        - **Agent key**: requires `agent:read` scope (always included). Can only access its linked agent.
      responses:
        '200':
          description: Agent details
          content:
            application/json:
              schema:
                type: object
                properties:
                  agent:
                    $ref: '#/components/schemas/Agent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Agent key does not have access to this agent
        '404':
          description: Agent not found

    put:
      tags: [Agents]
      summary: Update an agent
      description: |
        Updates an existing agent (PATCH semantics — only provided fields are changed).
        Requires a **master key**. Charges **10 credits**.

        **Secrets use PATCH semantics**: sending `null` or `""` for a secret key deletes it;
        omitting the `secrets` key entirely preserves all existing secrets.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentSettingsInput'
      responses:
        '200':
          description: Agent updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    $ref: '#/components/schemas/Agent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/MasterKeyRequired'
        '404':
          description: Agent not found

    delete:
      tags: [Agents]
      summary: Delete an agent
      description: |
        Deletes an agent. Stops it first if running/paused.
        Cascades to related API keys and runtime sessions.
        Requires a **master key**. No credit charge.
      responses:
        '200':
          description: Agent deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: Agent deleted successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MasterKeyRequired'
        '404':
          description: Agent not found

  # ── Runtime Control ───────────────────────────────────────
  /agents/{id}/status:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags: [Runtime]
      summary: Get agent runtime status
      description: |
        Returns current status, uptime, and last boot/stop timestamps.
        Requires `agent:manage` scope (master keys always pass).
      responses:
        '200':
          description: Agent status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [created, active, running, paused, stopped, failed, removed]
                  last_boot_execution:
                    type: string
                    format: date-time
                    nullable: true
                  last_stop_execution:
                    type: string
                    format: date-time
                    nullable: true
                  uptime_total_seconds:
                    type: integer
                  current_uptime:
                    type: integer
                    description: Seconds since last boot (0 if not running)
                  server_timestamp:
                    type: integer
                    description: Server time in epoch milliseconds
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/start:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Runtime]
      summary: Start an agent
      description: |
        Boots the agent (or resumes from paused).
        Requires `agent:manage` scope. Charges **5 credits**.
        Subject to subscription guards (max running agents, allowed channels).
      responses:
        '200':
          description: Agent started
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    type: object
                    properties:
                      agent:
                        $ref: '#/components/schemas/Agent'
                      previous_status:
                        type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/stop:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Runtime]
      summary: Stop an agent
      description: |
        Stops the agent and accumulates uptime. Requires `agent:manage` scope. No credit charge.
      responses:
        '200':
          description: Agent stopped
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/pause:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Runtime]
      summary: Pause an agent
      description: |
        Pauses the agent without stopping the container. Requires `agent:manage` scope. No credit charge.
      responses:
        '200':
          description: Agent paused
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/reload:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Runtime]
      summary: Reload agent config
      description: |
        Hot-reloads the agent configuration without restarting.
        Agent must be in `running` state. Requires `agent:manage` scope.
        Charges **1 credit**.
      responses:
        '200':
          description: Config reloaded
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  data:
                    type: object
                    properties:
                      agent:
                        $ref: '#/components/schemas/Agent'
        '400':
          description: Agent is not running
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/health:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags: [Runtime]
      summary: Agent health check
      description: |
        Returns health status for a running agent. Requires `agent:manage` scope.
      responses:
        '200':
          description: Agent is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  agent_status:
                    type: string
                  health:
                    type: object
                    properties:
                      status:
                        type: string
                      launcher:
                        type: string
                      gateway:
                        type: string
                      timestamp:
                        type: string
                        format: date-time
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'
        '503':
          description: Agent container not running

  # ── Catalogs ──────────────────────────────────────────────
  /catalogs/frameworks:
    get:
      tags: [Catalogs]
      summary: List available frameworks
      description: |
        Returns all available agent frameworks. Any API key type. Cached 10 min.
      responses:
        '200':
          description: List of frameworks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CatalogFramework'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'

  /catalogs/frameworks/{frameworkId}/clients:
    parameters:
      - name: frameworkId
        in: path
        required: true
        schema:
          type: string
    get:
      tags: [Catalogs]
      summary: List framework clients
      description: |
        Returns available clients for a specific framework. Any API key type. Cached 10 min.
      responses:
        '200':
          description: List of framework clients
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'

  /catalogs/regions:
    get:
      tags: [Catalogs]
      summary: List available regions
      description: |
        Returns all available deployment regions. Any API key type. Cached 10 min.
      responses:
        '200':
          description: List of regions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CatalogRegion'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'

  /catalogs/hardware:
    get:
      tags: [Catalogs]
      summary: List available hardware configs
      description: |
        Returns all available hardware configurations. Any API key type. Cached 10 min.
      responses:
        '200':
          description: List of hardware configs
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'

  /catalogs/models:
    get:
      tags: [Catalogs]
      summary: List available models
      description: |
        Returns all available AI models. Any API key type. Cached 10 min.
      responses:
        '200':
          description: List of models
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CatalogModel'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'

  # ── Channels / Telegram ───────────────────────────────────
  /agents/{id}/channels/telegram/pairing:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags: [Channels]
      summary: List Telegram pairing requests
      description: |
        Returns pending Telegram pairing requests for an agent. Requires `channels` scope.
      responses:
        '200':
          description: List of pairing requests
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/channels/telegram/pairing/approved:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags: [Channels]
      summary: List approved Telegram pairings
      description: |
        Returns approved Telegram pairings for an agent. Requires `channels` scope.
      responses:
        '200':
          description: List of approved pairings
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/channels/telegram/pairing/approve:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Channels]
      summary: Approve a Telegram pairing
      description: |
        Approves a pending Telegram pairing request. Requires `channels` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              properties:
                code:
                  type: string
                  description: The pairing code shown to the Telegram user
      responses:
        '200':
          description: Pairing approved
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/channels/telegram/pairing/revoke:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Channels]
      summary: Revoke a Telegram pairing
      description: |
        Revokes an approved Telegram pairing. Requires `channels` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [userId]
              properties:
                userId:
                  type: string
                  description: Telegram user ID to revoke
      responses:
        '200':
          description: Pairing revoked
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/channels/telegram/pairing/reject:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Channels]
      summary: Reject a Telegram pairing
      description: |
        Rejects a pending Telegram pairing request. Requires `channels` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              properties:
                code:
                  type: string
                  description: The pairing code to reject
      responses:
        '200':
          description: Pairing rejected
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  # ── Files ─────────────────────────────────────────────────
  /agents/{id}/files:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Files]
      summary: Upload a file to an agent
      description: |
        Uploads a file to the agent's knowledge base. Requires `files` scope.
        The agent must be running.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename, content, mimeType]
              properties:
                filename:
                  type: string
                  maxLength: 255
                content:
                  type: string
                mimeType:
                  type: string
                  maxLength: 100
      responses:
        '200':
          description: File uploaded
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  # ── Chat ────────────────────────────────────────────────
  /agents/{id}/chat:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Chat]
      summary: Send a message to an agent
      description: |
        Sends a chat message to the agent. By default returns a sync JSON response with
        the full assistant reply. Set `stream: true` to receive Server-Sent Events (SSE)
        with incremental deltas.

        **Sessions**: If no `sessionKey` is provided, a new session is created with an
        `api:` prefix. Reuse the returned `sessionKey` to continue a conversation.

        **Billing**: Each message costs 1 credit (base) plus the LLM cost charged by the runtime.

        Requires `chat` scope on agent keys. Master keys have implicit access.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
                  maxLength: 32000
                  description: The user message to send to the agent.
                  example: "What's the weather in Madrid?"
                sessionKey:
                  type: string
                  maxLength: 255
                  pattern: '^api:[a-zA-Z0-9\-]+$'
                  description: |
                    Reuse an existing session. Must start with `api:`. If omitted, a new
                    session key is generated automatically.
                  example: "api:550e8400-e29b-41d4-a716-446655440000"
                stream:
                  type: boolean
                  default: false
                  description: |
                    If `true`, the response is an SSE stream (`text/event-stream`).
                    If `false` (default), waits for the full response and returns JSON.
      responses:
        '200':
          description: Sync response with the full assistant message
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      sessionKey:
                        type: string
                        example: "api:550e8400-e29b-41d4-a716-446655440000"
                      message:
                        type: object
                        properties:
                          role:
                            type: string
                            example: assistant
                          content:
                            type: string
                            example: "The weather in Madrid is sunny, 24°C."
            text/event-stream:
              schema:
                type: string
                description: |
                  SSE stream (when `stream: true`). Events:
                  - `{"type":"session","sessionKey":"api:..."}` — first event
                  - Gateway events forwarded as-is (deltas, tool calls, etc.)
                  - `{"type":"done","sessionKey":"api:..."}` — terminal event
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/ScopeForbidden'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Agent is not running
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '502':
          description: Gateway error — agent is unreachable or returned an error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ── Sessions ──────────────────────────────────────────────
  /agents/{id}/sessions:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags: [Sessions]
      summary: List agent sessions
      description: |
        Returns active chat sessions for an agent. Requires `agent:manage` scope.
      responses:
        '200':
          description: List of sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Session'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/sessions/history:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Sessions]
      summary: Get session history
      description: |
        Returns message history for a specific session. Requires `agent:manage` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sessionKey]
              properties:
                sessionKey:
                  type: string
                limit:
                  type: integer
                  minimum: 1
                  maximum: 1000
      responses:
        '200':
          description: Session message history
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ChatMessage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/sessions/rename:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Sessions]
      summary: Rename a session
      description: |
        Renames a chat session. Requires `agent:manage` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key]
              properties:
                key:
                  type: string
                label:
                  type: string
                  maxLength: 255
                  nullable: true
      responses:
        '200':
          description: Session renamed
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/sessions/archive:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Sessions]
      summary: Archive a session
      description: |
        Archives a chat session. Requires `agent:manage` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key]
              properties:
                key:
                  type: string
      responses:
        '200':
          description: Session archived
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/sessions/delete:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Sessions]
      summary: Delete a session
      description: |
        Permanently deletes a chat session. Requires `agent:manage` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key]
              properties:
                key:
                  type: string
      responses:
        '200':
          description: Session deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  # ── Archives ──────────────────────────────────────────────
  /agents/{id}/archives:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags: [Sessions]
      summary: List archived sessions
      description: |
        Returns archived chat sessions for an agent. Requires `agent:manage` scope.
      responses:
        '200':
          description: List of archived sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Archive'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/archives/restore:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Sessions]
      summary: Restore an archived session
      description: |
        Restores an archived session back to active. Requires `agent:manage` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename]
              properties:
                filename:
                  type: string
                label:
                  type: string
                  maxLength: 255
      responses:
        '200':
          description: Session restored
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  /agents/{id}/archives/delete:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags: [Sessions]
      summary: Delete an archived session
      description: |
        Permanently deletes an archived session. Requires `agent:manage` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename]
              properties:
                filename:
                  type: string
      responses:
        '200':
          description: Archive deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ScopeForbidden'

  # ── Billing ───────────────────────────────────────────────
  /billing:
    get:
      tags: [Billing]
      summary: Show billing info
      description: |
        Returns current subscription and billing details. Requires a **master key**.
      responses:
        '200':
          description: Billing details
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MasterKeyRequired'

  /billing/tiers:
    get:
      tags: [Billing]
      summary: List subscription tiers
      description: |
        Returns available subscription tiers and pricing. Requires a **master key**.
      responses:
        '200':
          description: Subscription tiers
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Tier'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MasterKeyRequired'

  /billing/credits:
    get:
      tags: [Billing]
      summary: Get credits breakdown
      description: |
        Returns detailed credits breakdown (subscription vs purchased). Requires a **master key**.
      responses:
        '200':
          description: Credits breakdown
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/CreditBreakdown'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MasterKeyRequired'
