openapi: 3.1.0
info:
  title: ClawBud Platform API
  version: "1.2.0"
  description: |
    Public REST API for driving your own ClawBud agent from your own code.

    ## Authentication
    Every request carries `Authorization: Bearer clawbud_live_<48 hex>`.
    Keys are created in the dashboard (Settings → Agent API), shown once,
    and stored hashed. A key maps to the owning account and its ready gateway.

    ## Multiple agents
    An account can manage multiple built-in OpenClaw agents on the same
    dedicated VM and gateway. Each named agent has its own identity, workspace,
    conversation sessions, and memory files. Optional ClawBud security policies
    add one non-root Docker container per agent, fixed tool profiles, private
    workspace access, and manager-to-worker delegation allowlists. The gateway
    process and provider credentials remain shared. Pass `agentId` on a message
    request to choose a named agent; omit it to use the primary `main` agent.

    ## Rate limits
    | Surface | Limit |
    |---|---|
    | `/v1/agents` reads | 60 requests/min per account |
    | `/v1/agents` create/delete | 10 requests/min per account |
    | `/v1/agent/messages` (+ `/stream`, `/async`) | 30 requests/min per account gateway |
    | `/v1/agent/cron-events` | 12 requests/min per account gateway |
    | `/v1/knowledge/documents` write/delete | 30 requests/min per account gateway |
    | `/v1/knowledge/documents` list | 60 requests/min per account gateway |
    | `/v1/team/messages` | 10 relays/min per user (shared with the `team ask` skill) |
    | `/v1/team/tasks` create | 20 requests/min per user |
    | `/v1/team/*` reads | 60 requests/min per user |

    Exceeding a limit returns `429` — back off and retry.

    ## Webhooks
    Register a webhook (dashboard / `agent-api-keys` edge function,
    `set-webhook` action) to receive `message.completed`, `message.failed`
    and `team.activity` events. Each delivery is an HTTPS POST with body
    `{"event": "...", "data": {...}, "ts": 1718000000000}` and header
    `X-ClawBud-Signature: sha256=<hex>` — the hex HMAC-SHA256 of the **raw
    request body** computed with your webhook secret (returned exactly once
    at registration). Verify by recomputing the HMAC and comparing in
    constant time. Failed deliveries are retried once after 30 seconds,
    then dropped. Webhook URLs must be public HTTPS (localhost and
    private-IP literals are rejected; redirects are never followed).
servers:
  - url: https://provisioner.clawbud.ai
    description: Production
security:
  - apiKey: []
tags:
  - name: Agent
    description: Manage agents and send messages (sync, streaming, async).
  - name: Hunter Private Integrations
    description: Restricted server-to-server integrations enabled for the Hunter owner account.
  - name: Knowledge
    description: Manage your personal knowledge base (documents your agent can search).
  - name: Team
    description: Agent-collaboration teams — members, agent-to-agent messages, task delegation.

paths:
  /v1/integrations/israeli-bank/hapoalim/status:
    get:
      tags: [Hunter Private Integrations]
      operationId: getHunterHapoalimStatus
      summary: Read Ron's Bank Hapoalim connector status
      description: Restricted to Ron Magen's Hunter account. Never returns credentials.
      responses:
        '200':
          description: Sanitized connector status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  configured: { type: boolean }
                  secure: { type: boolean }
                  gatewayActive: { type: boolean }
                  connected: { type: boolean }
                  validatedAt: { type: [string, 'null'], format: date-time }
                  errorCode: { type: [string, 'null'] }
                  accountCount: { type: integer }
                  transactionCount: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '502': { $ref: '#/components/responses/AgentError' }
  /v1/integrations/israeli-bank/hapoalim/credentials:
    put:
      tags: [Hunter Private Integrations]
      operationId: updateHunterHapoalimCredentials
      summary: Securely replace Ron's Bank Hapoalim business credentials
      description: Streams credentials to Ron's private VM, validates with one read-only transaction fetch, and never stores or returns the secret in ClawBud's database.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [userCode, password]
              properties:
                userCode: { type: string, minLength: 4, maxLength: 64 }
                password: { type: string, minLength: 8, maxLength: 256 }
      responses:
        '200':
          description: Credentials saved and the read-only bank check succeeded.
        '400': { description: Invalid credential shape. }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { description: Credentials were saved but the read-only validation requires attention. }
        '502': { $ref: '#/components/responses/AgentError' }
  /v1/agents:
    get:
      tags: [Agent]
      operationId: listAgents
      summary: List the account's OpenClaw agents
      description: Returns the same gateway-backed agent list shown in the ClawBud dashboard.
      responses:
        '200':
          description: Agents and the account-wide quota.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AgentList' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }
    post:
      tags: [Agent]
      operationId: createAgent
      summary: Create a named OpenClaw agent on the account gateway
      description: |
        Creates a separate OpenClaw identity, workspace, memory files, and
        conversation namespace on the existing dedicated VM. It does not
        provision another VM. `maxAgents` includes the primary `main` agent.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateAgentRequest' }
      responses:
        '201':
          description: Agent created and configured.
          content:
            application/json:
              schema:
                type: object
                required: [agent, meta]
                properties:
                  agent: { $ref: '#/components/schemas/Agent' }
                  meta: { $ref: '#/components/schemas/AgentQuota' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409':
          description: Agent ID already exists or the account agent limit was reached.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '413': { $ref: '#/components/responses/TooLarge' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agents/{agentId}:
    parameters:
      - name: agentId
        in: path
        required: true
        schema: { type: string, pattern: '^[a-z0-9][a-z0-9_-]{0,63}$' }
    get:
      tags: [Agent]
      operationId: getAgent
      summary: Get one OpenClaw agent
      responses:
        '200':
          description: Agent found.
          content:
            application/json:
              schema:
                type: object
                required: [agent]
                properties:
                  agent: { $ref: '#/components/schemas/Agent' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404':
          description: Agent not found.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }
    delete:
      tags: [Agent]
      operationId: deleteAgent
      summary: Delete a named OpenClaw agent
      description: The primary `main` agent cannot be deleted.
      responses:
        '200':
          description: Agent deleted.
          content:
            application/json:
              schema:
                type: object
                required: [ok, deletedAgentId, meta]
                properties:
                  ok: { type: boolean, const: true }
                  deletedAgentId: { type: string }
                  meta: { $ref: '#/components/schemas/AgentQuota' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404':
          description: Agent not found.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agents/{agentId}/policy:
    parameters:
      - name: agentId
        in: path
        required: true
        schema: { type: string, pattern: '^[a-z0-9][a-z0-9_-]{0,63}$' }
    get:
      tags: [Agent]
      operationId: getAgentPolicy
      summary: Get an agent's hierarchy and isolation policy
      responses:
        '200':
          description: Effective policy summary. Baseline config and infrastructure credentials are never returned.
          content:
            application/json:
              schema:
                type: object
                required: [agentId, policy]
                properties:
                  agentId: { type: string }
                  policy: { $ref: '#/components/schemas/AgentPolicy' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }
    patch:
      tags: [Agent]
      operationId: updateAgentPolicy
      summary: Apply or disable a server-controlled agent security policy
      description: |
        Profiles are compiled by ClawBud. Raw Docker options, bind mounts,
        Linux users, capability overrides, and tool lists are not accepted.
        Configure a manager before assigning workers to it. Set
        `profile: standard` to restore the agent's pre-management settings.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AgentPolicyInput' }
      responses:
        '200':
          description: Policy applied and persisted.
          content:
            application/json:
              schema:
                type: object
                required: [ok, agentId, policy]
                properties:
                  ok: { type: boolean, const: true }
                  agentId: { type: string }
                  policy: { $ref: '#/components/schemas/AgentPolicy' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409':
          description: Invalid hierarchy transition, missing manager, or a manager still owns workers.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agent-hierarchy:
    get:
      tags: [Agent]
      operationId: getAgentHierarchy
      summary: List agents with their manager and worker relationships
      responses:
        '200':
          description: Flat nodes plus root IDs for easy tree construction.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AgentHierarchy' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agent/messages:
    post:
      tags: [Agent]
      operationId: sendMessage
      summary: Send a message to your agent (synchronous)
      description: Waits up to 240 seconds for the agent to finish its turn.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageRequest'
      responses:
        '200':
          description: The agent's reply.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageReply'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '413': { $ref: '#/components/responses/TooLarge' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agent/attachments/{token}:
    get:
      tags: [Agent]
      operationId: downloadAgentAttachment
      summary: Download an attachment emitted by an agent
      description: |
        Uses the same bearer API key as the message request. The encrypted token
        is account- and gateway-bound and expires after 15 minutes. Internal VM
        paths are never exposed.
      parameters:
        - in: path
          name: token
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Attachment bytes with a validated Content-Type and Content-Disposition.
          content:
            image/png: { schema: { type: string, format: binary } }
            image/jpeg: { schema: { type: string, format: binary } }
            video/mp4: { schema: { type: string, format: binary } }
            application/pdf: { schema: { type: string, format: binary } }
            application/octet-stream: { schema: { type: string, format: binary } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agent/sessions:
    get:
      tags: [Agent]
      operationId: listAgentSessions
      summary: List recent Hunter CRM conversations
      description: |
        Returns direct `hunter-crm-` sessions for the selected agent. Channel,
        cron, subagent and other internal OpenClaw sessions are excluded.
      parameters:
        - in: query
          name: agentId
          schema:
            type: string
            pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
            default: main
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 100, default: 40 }
      responses:
        '200':
          description: Safe session summaries ordered by recent activity.
          content:
            application/json:
              schema:
                type: object
                required: [agentId, sessions]
                properties:
                  agentId: { type: string }
                  sessions:
                    type: array
                    items:
                      type: object
                      required: [id, title, preview, updatedAt, createdAt, hasAutomation]
                      properties:
                        id: { type: string }
                        title: { type: string, nullable: true }
                        preview: { type: string, nullable: true }
                        updatedAt: { type: string, format: date-time, nullable: true }
                        createdAt: { type: string, format: date-time, nullable: true }
                        hasAutomation: { type: boolean }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agent/sessions/{session}/history:
    get:
      tags: [Agent]
      operationId: getAgentSessionHistory
      summary: Read one existing Hunter CRM conversation
      description: |
        Returns sanitized user and assistant messages for an existing direct
        Hunter CRM session. This endpoint never creates a session.
      parameters:
        - in: path
          name: session
          required: true
          schema: { type: string, pattern: '^hunter-crm-[A-Za-z0-9._-]+$' }
        - in: query
          name: agentId
          schema:
            type: string
            pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
            default: main
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 500, default: 200 }
        - in: query
          name: paged
          description: Opt in to visible-message pagination. Limit is capped at 100; textless tool records are traversed without exposing them.
          schema: { type: integer, enum: [1] }
        - in: query
          name: offset
          description: Use 0 initially, then the returned nextOffset. Never derive it from the visible message count.
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        '200':
          description: Sanitized conversation history.
          content:
            application/json:
              schema:
                type: object
                required: [agentId, session, messages, hasMore]
                properties:
                  agentId: { type: string }
                  session: { type: object }
                  messages:
                    type: array
                    items:
                      type: object
                      required: [id, role, content, timestamp, attachments]
                      properties:
                        id: { type: string }
                        role: { type: string, enum: [user, assistant] }
                        content: { type: string }
                        timestamp: { type: string, format: date-time, nullable: true }
                        attachments:
                          type: array
                          items: { $ref: '#/components/schemas/MessageAttachment' }
                  hasMore: { type: boolean }
                  nextOffset: { type: integer, nullable: true }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agent/cron-events:
    get:
      tags: [Agent]
      operationId: listAgentCronEvents
      summary: List recent authoritative OpenClaw cron executions
      description: |
        Reads the selected agent's scheduler state directly from OpenClaw and
        returns the latest execution for each job when it is newer than
        `after`. Intended for embedding apps that display background run cards.
        One-time `at` jobs intended for embedded delivery must be created with
        `deleteAfterRun: false` so their completed run remains collectible.
      parameters:
        - in: query
          name: agentId
          schema:
            type: string
            pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
            default: main
        - in: query
          name: after
          required: true
          description: ISO-8601 timestamp or Unix epoch milliseconds. Values older than seven days are clamped.
          schema:
            oneOf:
              - { type: string, format: date-time }
              - { type: integer }
      responses:
        '200':
          description: Scheduler executions newer than the cursor.
          content:
            application/json:
              schema:
                type: object
                required: [agentId, events, cursor]
                properties:
                  agentId: { type: string }
                  events:
                    type: array
                    items: { $ref: '#/components/schemas/CronUiEvent' }
                  cursor:
                    type: integer
                    description: Server epoch milliseconds to use as the next `after` cursor.
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/integrations/hunter-crm/agent-instructions:
    post:
      tags: [Agent]
      operationId: installHunterCrmAgentInstructions
      summary: Install Hunter CRM chat-delivery rules on Ron's bound agents
      description: |
        Ron-only management operation. Installs the managed Hunter CRM
        chat-delivery skill and its mandatory bootstrap rule on every agent
        currently bound to a Hunter employee. Other ClawBud accounts and
        unbound agents are not modified.
      responses:
        '200':
          description: The skill and instructions were installed and the gateway is healthy.
          content:
            application/json:
              schema:
                type: object
                required: [ok, count, agentIds]
                properties:
                  ok: { type: boolean, const: true }
                  count: { type: integer, minimum: 1, maximum: 16 }
                  agentIds:
                    type: array
                    items: { type: string }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/agent/messages/stream:
    post:
      tags: [Agent]
      operationId: streamMessage
      summary: Send a message and stream the reply (SSE)
      description: |
        Same auth and body as `/v1/agent/messages`, but the response is
        `text/event-stream`. Events:
        - `delta` — `{"text": "…incremental chunk…"}`
        - `status` — real OpenClaw lifecycle status, currently
          `{"type":"compaction","phase":"start|end",…}`
        - `done` — `{"agentId": "…", "reply": "…", "attachments": [],
          "events": [], "usage": …, "runId": "…"}`
        - `error` — `{"error": "…"}`
        Opt in with `responseFeatures: [live_events]` for native assistant
        segments (`delta` includes `replace:true`, `segmentId`, `runId`), safe
        tool/lifecycle statuses, and `turn` events. Replace the segment's text
        rather than appending it. `done.yielded:true` releases the parent turn;
        delegated work may still be running. It may have an empty reply.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageRequest'
      responses:
        '200':
          description: Server-Sent Events stream (`delta`/`status`* → `done` | `error`).
          content:
            text/event-stream:
              schema:
                type: string
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '413': { $ref: '#/components/responses/TooLarge' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /v1/agent/sessions/{session}/events:
    get:
      tags: [Agent]
      operationId: watchSessionEvents
      summary: Observe live events for an owned Hunter CRM session
      description: |
        Read-only SSE observer using the same gateway connection as the chat.
        Sends no prompt and does not cancel runs on disconnect. Events are
        ready, delta, status and turn. Reconnect after the 180-second window
        and refresh bounded history to recover events missed while offline.
        Tool arguments, results, reasoning contents and other sessions are
        excluded. This is server-to-server; keep the API key out of browsers.
      parameters:
        - in: path
          name: session
          required: true
          schema: { type: string, pattern: '^hunter-crm-[A-Za-z0-9._-]+$' }
        - in: query
          name: agentId
          schema: { type: string, default: main }
      responses:
        '200':
          description: Native live event stream; completion is scoped by runId.
          content:
            text/event-stream:
              schema: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /v1/agent/messages/async:
    post:
      tags: [Agent]
      operationId: sendMessageAsync
      summary: Send a message asynchronously (webhook completion)
      description: |
        Validates and queues the message, then returns immediately with a
        `runId`. When the agent finishes, the outcome is delivered to your
        registered webhook as `message.completed` (`{runId, agentId, reply, usage}`)
        or `message.failed` (`{runId, agentId, error}`). Register a webhook first —
        without one the run still executes but the outcome is dropped.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageRequest'
      responses:
        '202':
          description: Queued. The outcome arrives on your webhook.
          content:
            application/json:
              schema:
                type: object
                required: [runId, agentId, status]
                properties:
                  runId:
                    type: string
                    examples: ["api-async-7f3c9e2a-0000-0000-0000-000000000000"]
                  status:
                    type: string
                    const: queued
                  agentId:
                    type: string
                    examples: [main, research]
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '413': { $ref: '#/components/responses/TooLarge' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /v1/knowledge/documents:
    post:
      tags: [Knowledge]
      operationId: addKnowledgeDocument
      summary: Add a document to your personal knowledge base
      description: |
        Text/markdown only (max 2 MB). The document lands in your agent's
        personal knowledge vault and becomes searchable via the agent's
        `kb search` skill. Filenames are sanitized into a slug; the response
        returns the canonical `slug` used for retrieval and deletion.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename, text]
              properties:
                filename:
                  type: string
                  maxLength: 200
                  description: Document name. Extension optional (defaults to `.md`); must be a text/document type.
                  examples: ["pricing-faq.md"]
                text:
                  type: string
                  description: Document content (markdown or plain text, max 2 MB).
      responses:
        '200':
          description: Ingested and indexed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  slug: { type: string, examples: ["pricing-faq"] }
                  count: { type: integer, description: Total documents in the knowledge base. }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '413': { $ref: '#/components/responses/TooLarge' }
        '415':
          description: Unsupported file type.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '422':
          description: Ingestion failed (e.g. no extractable text).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }
    get:
      tags: [Knowledge]
      operationId: listKnowledgeDocuments
      summary: List your personal knowledge base documents
      responses:
        '200':
          description: Documents currently in the knowledge base.
          content:
            application/json:
              schema:
                type: object
                properties:
                  documents:
                    type: array
                    items:
                      $ref: '#/components/schemas/KnowledgeDocument'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/knowledge/documents/{name}:
    delete:
      tags: [Knowledge]
      operationId: deleteKnowledgeDocument
      summary: Delete a document from your personal knowledge base
      parameters:
        - name: name
          in: path
          required: true
          description: The document slug (from the list/ingest response).
          schema:
            type: string
            maxLength: 200
      responses:
        '200':
          description: Deleted (idempotent) and reindexed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  count: { type: integer }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/NoAgent' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/team/members:
    get:
      tags: [Team]
      operationId: listTeamMembers
      summary: List your team's members
      description: Requires an active team membership (403 otherwise).
      responses:
        '200':
          description: Team roster.
          content:
            application/json:
              schema:
                type: object
                properties:
                  team: { type: string, description: Team name. }
                  teamId: { type: string, format: uuid }
                  role: { type: string, description: Your role in the team. }
                  members:
                    type: array
                    items:
                      $ref: '#/components/schemas/TeamMember'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/ForbiddenTeam' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/team/messages:
    post:
      tags: [Team]
      operationId: sendTeamMessage
      summary: Send a message to a teammate's agent and get its reply
      description: |
        Relays your message to the target teammate's agent (same team only)
        and returns its reply. Server-side guards: no self-send, the target
        must be an active member of YOUR team, and messages that are already
        team relays are rejected (loop guard). 10 relays/min per user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to, message]
              properties:
                to:
                  type: string
                  format: email
                  description: Teammate's email (must be an active member of your team).
                message:
                  type: string
                  maxLength: 16000
      responses:
        '200':
          description: The teammate agent's reply.
          content:
            application/json:
              schema:
                type: object
                properties:
                  reply: { type: string }
                  runId: { type: [string, "null"] }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/ForbiddenTeam' }
        '404':
          description: No active teammate with that email.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '413': { $ref: '#/components/responses/TooLarge' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

  /v1/team/tasks:
    post:
      tags: [Team]
      operationId: createTeamTask
      summary: Delegate a task to a teammate
      description: |
        Creates a team task and (best-effort) notifies the assignee's agent.
        The notification failing never fails the task creation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to, title]
              properties:
                to:
                  type: string
                  format: email
                title:
                  type: string
                  maxLength: 200
                detail:
                  type: string
                  maxLength: 4000
      responses:
        '200':
          description: Task created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  task:
                    type: object
                    properties:
                      id: { type: string, format: uuid }
                      title: { type: string }
                      status: { type: string }
                      to: { type: string, format: email }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/ForbiddenTeam' }
        '404':
          description: No active teammate with that email.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }
    get:
      tags: [Team]
      operationId: listTeamTasks
      summary: List your team's tasks (open first)
      responses:
        '200':
          description: Team tasks ordered open → in_progress → done → cancelled.
          content:
            application/json:
              schema:
                type: object
                properties:
                  tasks:
                    type: array
                    items:
                      $ref: '#/components/schemas/TeamTask'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/ForbiddenTeam' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/AgentError' }

webhooks:
  message.completed:
    post:
      summary: An async agent run finished successfully
      description: |
        Sent when a run started via `POST /v1/agent/messages/async` completes.
        Verify `X-ClawBud-Signature` (sha256=<hex HMAC-SHA256 of the raw body
        with your webhook secret>) before trusting the payload.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event: { type: string, const: message.completed }
                data:
                  type: object
                  properties:
                    runId: { type: string }
                    agentId: { type: string }
                    reply: { type: string }
                    usage: { type: [object, "null"] }
                ts: { type: integer, description: Epoch milliseconds. }
      responses:
        '200':
          description: Return any 2xx to acknowledge. Non-2xx is retried once after 30s.
  message.failed:
    post:
      summary: An async agent run failed
      description: Delivered to subscribers of `message.failed` (or `message.completed`).
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event: { type: string, const: message.failed }
                data:
                  type: object
                  properties:
                    runId: { type: string }
                    agentId: { type: string }
                    error: { type: string }
                ts: { type: integer }
      responses:
        '200':
          description: Return any 2xx to acknowledge.
  team.activity:
    post:
      summary: Something happened in your team
      description: |
        Fan-out of the team activity feed (agent messages, KB changes, task
        lifecycle) to every active team member with a registered webhook.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event: { type: string, const: team.activity }
                data:
                  type: object
                  properties:
                    teamId: { type: string, format: uuid }
                    actorUserId: { type: string, format: uuid }
                    kind: { type: string, examples: [agent_message, kb_add, kb_delete, task_created, task_status] }
                    summary: { type: string }
                    meta: { type: object }
                ts: { type: integer }
      responses:
        '200':
          description: Return any 2xx to acknowledge.

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: clawbud_live_<48 hex>
      description: |
        Created in the ClawBud dashboard (Settings → Agent API). The plaintext
        is shown once; only its SHA-256 hash is stored. Server-side use only —
        never embed a key in client-side code.
  schemas:
    Agent:
      type: object
      required: [id, name, model, isDefault, status, policy]
      properties:
        id:
          type: string
          pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
          examples: [main, research]
        name: { type: string }
        model: { type: string }
        isDefault: { type: boolean }
        status: { type: string, const: running }
        policy: { $ref: '#/components/schemas/AgentPolicy' }
    AgentPolicy:
      type: object
      required: [role, profile, managerAgentId, memoryMode, toolAllow, toolDeny, isolation, status, lastError, policyVersion]
      properties:
        role:
          type: string
          enum: [standalone, manager, worker]
        profile:
          type: string
          enum: [standard, manager-orchestrator, worker-restricted, worker-a2a, worker-readonly, worker-coder]
        managerAgentId:
          type: [string, "null"]
          pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
        memoryMode:
          type: string
          enum: [private, read_only, disabled]
        toolAllow:
          type: array
          items: { type: string }
          description: Pinned non-privileged connector and cron grants that survive policy re-application.
        toolDeny:
          type: array
          items: { type: string }
          description: Pinned additional tool denials that survive policy re-application.
        isolation:
          type: object
          required: [enabled, backend, scope, user, network]
          properties:
            enabled: { type: boolean }
            backend: { type: [string, "null"], enum: [docker, null] }
            scope: { type: [string, "null"], enum: [agent, null] }
            user: { type: [string, "null"], enum: ['1000:1000', null] }
            network:
              type: [string, "null"]
              enum: [none, null]
              description: No direct Docker network. Allowed gateway-side web tools may still access the web.
        status:
          type: string
          enum: [applying, active, error]
        lastError: { type: [string, "null"] }
        policyVersion: { type: integer, minimum: 1 }
    AgentPolicyInput:
      type: object
      required: [profile]
      additionalProperties: false
      properties:
        profile:
          type: string
          enum: [standard, manager-orchestrator, worker-restricted, worker-a2a, worker-readonly, worker-coder]
        role:
          type: string
          enum: [standalone, manager, worker]
          description: Optional assertion. It must match the selected profile.
        managerAgentId:
          type: [string, "null"]
          pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
          description: Required for worker profiles; ignored profiles are rejected.
        memoryMode:
          type: string
          enum: [private, read_only, disabled]
          description: Profiles only accept enforceable combinations. Coder and manager profiles require private.
        toolAllow:
          type: array
          maxItems: 64
          items: { type: string, pattern: '^[a-z][a-z0-9_.:-]{0,127}$' }
          description: Explicit business connector or cron grants. Privileged gateway, shell, session, group, messaging and UI grants are rejected.
        toolDeny:
          type: array
          maxItems: 64
          items: { type: string, pattern: '^[a-z][a-z0-9_.:-]{0,127}$' }
          description: Additional explicit denials.
    AgentHierarchyNode:
      allOf:
        - { $ref: '#/components/schemas/Agent' }
        - type: object
          required: [childAgentIds]
          properties:
            childAgentIds:
              type: array
              items:
                type: string
                pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
    AgentHierarchy:
      type: object
      required: [agents, roots]
      properties:
        agents:
          type: array
          items: { $ref: '#/components/schemas/AgentHierarchyNode' }
        roots:
          type: array
          items:
            type: string
            pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
    AgentQuota:
      type: object
      required: [count, maxAgents, remaining]
      properties:
        count: { type: integer, minimum: 0 }
        maxAgents: { type: integer, minimum: 1, description: Includes the primary main agent. }
        remaining: { type: integer, minimum: 0 }
    AgentList:
      type: object
      required: [agents, meta]
      properties:
        agents:
          type: array
          items: { $ref: '#/components/schemas/Agent' }
        meta: { $ref: '#/components/schemas/AgentQuota' }
    CreateAgentRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 50
          description: Display name containing at least one Latin letter or digit. A lowercase OpenClaw agent ID (up to 64 characters) is derived from it.
          examples: [Research]
        model:
          type: string
          maxLength: 200
          description: Optional OpenClaw model ID. Omit to inherit the gateway default.
        soulMd:
          type: string
          maxLength: 65536
          description: Optional SOUL.md instructions for this agent.
        policy:
          $ref: '#/components/schemas/AgentPolicyInput'
    MessageRequest:
      type: object
      required: [message]
      properties:
        message:
          type: string
          maxLength: 16000
          description: The prompt.
        agentId:
          type: string
          pattern: '^[a-z0-9][a-z0-9_-]{0,63}$'
          default: main
          description: Named agent to receive this message. Omit for the primary agent.
        session:
          type: string
          pattern: '^[A-Za-z0-9._-]{1,80}$'
          maxLength: 80
          default: api
          description: Conversation key — reuse the same value to keep context across calls.
        attachments:
          type: array
          maxItems: 5
          description: Base64 files. Maximum 10 MB per file and 20 MB combined.
          items: { $ref: '#/components/schemas/MessageAttachmentInput' }
        responseFeatures:
          type: array
          maxItems: 2
          uniqueItems: true
          description: |
            Optional response metadata. `cron_events` compares authoritative
            OpenClaw scheduler state before and after the turn.
            `automation_events` is the legacy passive parser for configured
            agent output and should be verified by the embedding application.
          items:
            type: string
            enum: [automation_events, cron_events, live_events]
    MessageAttachmentInput:
      type: object
      required: [name, mimeType, content]
      additionalProperties: false
      properties:
        name: { type: string, minLength: 1, maxLength: 180 }
        mimeType:
          type: string
          description: A supported image, video, audio, PDF, Office, text, JSON, or ZIP MIME type.
        content:
          type: string
          format: byte
          description: Base64-encoded file bytes.
    MessageAttachment:
      type: object
      required: [id, name, mimeType, size, kind, token]
      additionalProperties: false
      properties:
        id: { type: string }
        name: { type: string }
        mimeType: { type: string }
        size: { type: integer, minimum: 1, maximum: 26214400 }
        kind: { type: string, enum: [image, video, audio, pdf, file] }
        token:
          type: string
          description: Encrypted, account-bound download token valid for 15 minutes.
    MessageReply:
      type: object
      required: [agentId, reply, attachments, events]
      properties:
        agentId: { type: string }
        reply: { type: string, description: The agent's text reply. }
        attachments:
          type: array
          items: { $ref: '#/components/schemas/MessageAttachment' }
        events:
          type: array
          description: Structured UI events. Cron events are verified from OpenClaw scheduler state; legacy automation events require embedder verification.
          items:
            oneOf:
              - { $ref: '#/components/schemas/AgentUiEvent' }
              - { $ref: '#/components/schemas/CronUiEvent' }
        runId: { type: [string, "null"] }
        usage:
          type: [object, "null"]
          description: The gateway's usage object when present.
    AgentUiEvent:
      type: object
      required: [type, action, id]
      additionalProperties: false
      properties:
        type: { type: string, enum: [automation] }
        action: { type: string, enum: [created, updated, activated, paused, archived] }
        id: { type: string, minLength: 8, maxLength: 128 }
    CronSchedule:
      type: object
      additionalProperties: false
      properties:
        kind: { type: [string, "null"] }
        expr: { type: [string, "null"] }
        tz: { type: [string, "null"] }
        at: { type: [string, "null"] }
        everyMs: { type: [number, "null"] }
    CronUiEvent:
      type: object
      required: [type, action, id, eventId, name, enabled, schedule]
      additionalProperties: false
      properties:
        type: { type: string, const: cron }
        action: { type: string, enum: [created, updated, resumed, paused, deleted, executed] }
        id: { type: string }
        eventId: { type: string }
        name: { type: string }
        enabled: { type: boolean }
        schedule: { $ref: '#/components/schemas/CronSchedule' }
        task: { type: [string, "null"] }
        occurredAt: { type: [string, "null"], format: date-time }
        lastRunAt: { type: [string, "null"], format: date-time }
        nextRunAt: { type: [string, "null"], format: date-time }
        lastRunStatus: { type: [string, "null"] }
        runId: { type: [string, "null"] }
        durationMs: { type: [number, "null"] }
        reply: { type: string }
        attachments:
          type: array
          items: { $ref: '#/components/schemas/MessageAttachment' }
        usage:
          type: [object, "null"]
          additionalProperties: true
        error: { type: [string, "null"] }
    KnowledgeDocument:
      type: object
      properties:
        slug: { type: string }
        title: { type: string }
        source: { type: string }
        ingestedAt: { type: integer, description: Epoch milliseconds. }
        size: { type: integer, description: Bytes. }
        abstract: { type: string }
    TeamMember:
      type: object
      properties:
        name: { type: string }
        email: { type: string, format: email }
        role: { type: string }
    TeamTask:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        detail: { type: string }
        status: { type: string, enum: [open, in_progress, done, cancelled] }
        from: { type: string, format: email }
        to: { type: string, format: email }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Error:
      type: object
      required: [error]
      properties:
        error: { type: string }
  responses:
    BadRequest:
      description: Missing or invalid request fields.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: Missing, invalid, or revoked API key.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: The requested agent or resource does not exist.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: This account is not enabled for the Agent API yet.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    ForbiddenTeam:
      description: Teams not enabled for this account, or you are not in a team.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NoAgent:
      description: No ready agent / gateway for this account.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    TooLarge:
      description: Payload exceeds the documented size cap.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: Rate limited — back off and retry.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    AgentError:
      description: The agent or its VM errored, was unreachable, or timed out.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
