> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beeos.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# List agents owned by the caller.



## OpenAPI

````yaml /openapi/beeos-platform-v1.yaml get /api/v1/agents
openapi: 3.1.0
info:
  title: BeeOS OpenAPI (user contract)
  version: 1.1.0
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
  description: |
    The **BeeOS OpenAPI** user contract. The sole implementer is
    **[`services/openapi-gateway`](../services/openapi-gateway)** (`openapi-gw`,
    local `:8095`). SDKs (Go / TypeScript) published under
    [`sdks/beeos-ai-sdk-go`](../../sdks/beeos-ai-sdk-go) and
    [`sdks/beeos-ai-sdk`](../../sdks/beeos-ai-sdk) are generated directly from
    this document.

    The main user Gateway (`services/gateway`, `:9080`) serves web / mobile /
    desktop clients and is **not** described by this document. It maintains its
    own route set independent of the SDK contract.

    **Auth:** every operation requires either a user **JWT** or a **`oag_`**
    User API Key, both on `Authorization: Bearer`. Agent-level protocol
    endpoints (A2A JSON-RPC with `bak_`, MCP) live in
    [beeos-agent-integration-v1.yaml](beeos-agent-integration-v1.yaml) and are
    hosted by A2A Gateway / MCP Gateway — not by `openapi-gateway`.

    ## Changelog

    ### 1.1.0 (ADR-0022 — dual-layer message storage)

    BREAKING for clients that depended on full streaming chunk replay
    via the persistent message log:

    * `GET /api/v1/agents/{agentId}/conversations/{convId}/messages`
      and `GET /api/v1/agents/{agentId}/tasks/{taskId}/messages` now
      **default-filter out** ephemeral streaming chunks
      (`agent_reply_delta`, `agent_thought_chunk`,
      `agent_message_chunk`). Add `?include_deltas=true` to opt back in.
    * `latest_offset` continues to reflect the TRUE server-side max
      offset across both filtered and unfiltered rows, so `since=` /
      cursor-style pagination is unaffected.
    * SSE `/events` is **NOT** affected — live consumers continue to
      receive every envelope as it happens.
    * Backwards-compatible recovery path: SSE `/events` may now emit
      a `backfill_truncated` event frame when the client reconnects
      with `Last-Event-ID` before the oldest retained chunk. SDKs
      that don't recognise the frame should treat it as a hint to
      resume from `oldest_redis_offset` instead (the field carrying
      the smallest offset still readable on the per-channel stream).
    * **Clarified contract**: SSE event `offset` is strictly monotonic
      per channel but **NOT guaranteed contiguous** (ADR-0022 §1.2).
      Producer-side write failures may leave small holes; clients MUST
      use `offset > since`, never `offset == since + 1`.

    BREAKING for clients that depended on the `oag_` API key scope
    vocabulary:

    * The per-route scope gate (`agents:read`, `agents:write`,
      `tasks:read`, `tasks:write`, `files:read`, `files:write`,
      `instances:read`, `instances:write`, plus the `admin:*` wildcard)
      has been **removed entirely**. The 37 routes that previously
      required scopes are now governed exclusively by owner-ACL — an
      `oag_` User API Key inherits full access to its owner's resources.
    * The `403 insufficient_scope` error code is no longer emitted.
      Existing callers that branched on it should fold the case into
      their generic 403 / `forbidden` handler.
    * SDK clients that explicitly passed `scopes` to `createAPIKey`
      should drop that argument. The Web / Desktop UIs no longer render
      scope badges on existing keys.
    * Existing `oag_` keys continue to work unchanged — the owner_id
      binding is preserved, and no key needs to be re-issued.

    BREAKING for clients of `GET /api/v1/providers` that read the
    `capabilities` object on the wire:

    * 7 of the 8 `capabilities` field names are now **camelCase**
      (`longRunning`, `browserUse`, `codeExec`, `fileSystem`,
      `customImage`, `maxDurationSec`, `costModel`) — matching this
      OpenAPI contract. Previous wire emitted **snake_case**
      (`long_running`, `browser_use`, …) because the server DTO bound
      `capabilities` to the raw protobuf message
      (`*pb.ProviderCapabilities`) whose auto-generated `json` tags
      use snake_case. SDK consumers (`@beeos-ai/sdk`,
      `github.com/beeos-ai/sdk-go`) generated from this spec were
      **already unable** to read any `capabilities` field — the
      camelCase property names produced by the generator never
      matched the snake_case the server actually sent. Raw HTTP
      callers that hand-coded against the old snake_case names must
      migrate; SDK callers gain access to the field set for the first
      time without code changes.
    * Note: the `device` boolean was always a single word — its wire
      name (`device`) is unchanged. Only the seven multi-word
      capability fields are affected by the rename.
    * `meta`, `id`, `name`, `description`, `version` are unchanged.
    * A new drift-guard (`internal/dto/contract_test.go`) now locks
      the server DTO to the spec property names, so the two cannot
      silently diverge again.

    ### 1.0.0

    Initial OpenAPI contract (ADR-001).
servers:
  - url: https://openapi.beeos.ai
    description: Production
  - url: https://openapi-staging.beeos.ai
    description: Staging
  - url: http://localhost:8095
    description: Local dev (goreman)
security:
  - bearerAuth: []
tags:
  - name: deploy
    description: Provider / region / model catalog.
  - name: instances
    description: Agent instance lifecycle (deploy, start, stop, restart, destroy).
  - name: computer
    description: |
      Synchronous desktop control (linux / windows / mac instances):
      screenshot, click, move, scroll, type, key. Coordinates are
      absolute device pixels (origin top-left); query
      `GET /instances/{id}/computer` for the live screen geometry and
      supported action set. Android instances are controlled via the
      `mobile` tag instead (calling `computer` on an android instance
      returns 404). Aligns with Anthropic / OpenAI "computer use".
  - name: mobile
    description: |
      Synchronous mobile (android) control: screenshot, tap, swipe,
      scroll, type, key, press_button. Coordinates are absolute device
      pixels. Query `GET /instances/{id}/mobile` for live geometry and
      supported actions. Desktop instances are controlled via the
      `computer` tag (calling `mobile` on a desktop instance returns 404).
  - name: agents
    description: Agent listing, details, and synchronous invocation.
  - name: tasks
    description: |
      Async agent tasks (the canonical task core — see ADR-0017
      `0017-unified-task-core`). `tasks` differs from `agents/invoke` in three
      ways: it returns 202 immediately with a `task_id` (no blocking on the
      reply), exposes a polling endpoint plus an SSE event stream, and supports
      cancel/continue lifecycle controls.
  - name: conversations
    description: >
      Long-lived multi-turn dialogs. Unlike tasks (single-shot, finite, terminal

      status), conversations are open until the caller closes them — messages

      are individually persisted and addressable by offset. Use the conversation

      API when you need to interleave caller and agent turns over an extended

      session; use `tasks` when a single prompt has a bounded reply with
      optional

      pause-and-resume.
  - name: files
    description: |
      Presigned URLs for caller uploads (`POST /files/presign-upload`) and
      result downloads (`GET /files/presign-download`) — the canonical
      attachment mechanism shared by `agents/{id}/invoke` and
      `agents/{id}/tasks`. Callers obtain a short-lived signed URL, upload the
      bytes directly to object storage (S3 / MinIO), and pass the resulting
      `file_id` in subsequent invoke / task requests via `attachments`.
paths:
  /api/v1/agents:
    get:
      tags:
        - agents
      summary: List agents owned by the caller.
      operationId: listAgents
      parameters:
        - name: instance_id
          in: query
          required: false
          schema:
            type: string
            maxLength: 128
          description: Filter agents by instance ID.
        - name: instanceId
          in: query
          required: false
          schema:
            type: string
            maxLength: 128
          description: CamelCase alias for `instance_id`.
        - name: status
          in: query
          required: false
          schema:
            type: string
          description: Filter agents by runtime/directory status, for example `active`.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 20
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 0
      responses:
        '200':
          description: Paginated list of agents.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentListResponse'
              examples:
                empty:
                  summary: No agents yet
                  value:
                    success: true
                    data: []
                    total: 0
                single:
                  summary: One agent
                  value:
                    success: true
                    data:
                      - id: agent_abc123
                        instanceId: instance_xyz
                        name: summariser
                        status: active
                        visibility: private
                        mcpEnabled: false
                        capabilities:
                          multipleConversations: false
                          loadConversation: false
                          createConversation: false
                          canvas: false
                          terminal: false
                          webSsh: false
                          fileSystem: false
                          image: true
                          audio: false
                          mcp: false
                          cron: false
                          streaming: true
                          pushNotifications: false
                          a2a: true
                          extendedAgentCard: false
                          modelsList: true
                          modelsSet: true
                          skillsList: true
                          skillsInstall: false
                          skillsUninstall: false
                          skillsUpdate: false
                        conversationTransport: acp
                        createdAt: '2026-05-14T18:00:00Z'
                        updatedAt: '2026-05-14T18:00:00Z'
                    total: 1
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        5XX:
          $ref: '#/components/responses/ServerError'
components:
  schemas:
    AgentListResponse:
      type: object
      required:
        - success
        - data
        - total
      properties:
        success:
          type: boolean
          const: true
        data:
          type: array
          items:
            $ref: '#/components/schemas/AgentDTO'
        total:
          type: integer
          format: int64
        projection:
          $ref: '#/components/schemas/InstanceProjectionMetadata'
    AgentDTO:
      type: object
      required:
        - id
        - instanceId
        - name
        - status
        - visibility
        - mcpEnabled
        - capabilities
        - conversationTransport
      properties:
        id:
          type: string
        instanceId:
          type: string
        name:
          type: string
        description:
          type: string
        displayName:
          type: string
        status:
          type: string
        slug:
          type: string
        mcpEnabled:
          type: boolean
        defaultModelId:
          type: string
          maxLength: 256
          pattern: >-
            ^[a-z0-9][a-z0-9._-]{0,63}(?:/[A-Za-z0-9][A-Za-z0-9._-]{0,127}){1,7}(?::[A-Za-z0-9][A-Za-z0-9._-]{0,63})?$
          description: >-
            Safe canonical provider/model selector with an optional tag.
            Runtime-local keys, credentials, endpoint configuration, and opaque
            high-entropy values are never exposed.
        visibility:
          type: string
          enum:
            - private
            - public
            - marketplace
            - unlisted
        capabilities:
          $ref: '#/components/schemas/AgentCapabilitiesDTO'
        conversationTransport:
          type: string
          enum:
            - acp
            - message-service
          description: Conversation/message transport selected by the agent directory.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    InstanceProjectionMetadata:
      type: object
      required:
        - acceptedEpoch
        - acceptedRevision
        - canonicalHash
        - lastSyncedAt
        - syncState
        - agentsComplete
        - skillsComplete
      description: >-
        Accepted runtime-directory projection fence for the requested instance.
        Present only for an owned, instance-scoped list after a runtime
        projection has been accepted.
      properties:
        acceptedEpoch:
          type: string
          pattern: ^(0|[1-9][0-9]*)$
        acceptedRevision:
          type: string
          pattern: ^(0|[1-9][0-9]*)$
        canonicalHash:
          type: string
          pattern: ^[0-9a-f]{64}$
        lastSyncedAt:
          type: string
          format: date-time
        syncState:
          type: string
          enum:
            - synced
        agentsComplete:
          type: boolean
        skillsComplete:
          type: boolean
    ErrorResponse:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          const: false
        error:
          $ref: '#/components/schemas/Error'
    AgentCapabilitiesDTO:
      type: object
      required:
        - multipleConversations
        - loadConversation
        - createConversation
        - canvas
        - terminal
        - webSsh
        - fileSystem
        - image
        - audio
        - mcp
        - cron
        - streaming
        - pushNotifications
        - a2a
        - extendedAgentCard
        - modelsList
        - modelsSet
        - skillsList
        - skillsInstall
        - skillsUninstall
        - skillsUpdate
      properties:
        multipleConversations:
          type: boolean
        loadConversation:
          type: boolean
        createConversation:
          type: boolean
        canvas:
          type: boolean
        terminal:
          type: boolean
        webSsh:
          type: boolean
        fileSystem:
          type: boolean
        image:
          type: boolean
        audio:
          type: boolean
        mcp:
          type: boolean
        cron:
          type: boolean
        streaming:
          type: boolean
        pushNotifications:
          type: boolean
        a2a:
          type: boolean
        extendedAgentCard:
          type: boolean
        modelsList:
          type: boolean
        modelsSet:
          type: boolean
        skillsList:
          type: boolean
        skillsInstall:
          type: boolean
        skillsUninstall:
          type: boolean
        skillsUpdate:
          type: boolean
    Error:
      type: object
      required:
        - code
        - message
      properties:
        type:
          type: string
          description: |
            RFC 9457 problem-type slug. Most SDK consumers key on
            `code` rather than `type`; this field is kept for
            structural parity with the wider apierror catalogue.
          enum:
            - api_error
            - invalid_request_error
            - authentication_error
            - permission_error
            - rate_limit_error
            - not_found_error
            - conflict_error
            - validation_error
        code:
          type: string
          description: >
            Stable machine-readable error code. See

            [docs/reference/errors.md](https://github.com/beeos-ai/beeos/blob/main/docs/reference/errors.md)

            for the complete catalogue.
          examples:
            - agent_not_found
            - agent_service_unavailable
            - service_timeout
            - conflict
            - forbidden
            - invalid_param
            - rate_limited
            - internal_error
        message:
          type: string
          description: Human-readable explanation; presentation-only, never key on this.
        request_id:
          type: string
          description: |
            Per-request correlation id propagated end-to-end (also returned
            in the `X-Request-Id` response header). Include this value when
            reporting errors so the server-side trace can be located.
        param:
          type: string
          description: |
            Field path of the offending parameter for validation-style
            errors (`code` in `missing_param` / `invalid_param`). Empty for
            errors that don't reference a specific field.
        metadata:
          type: object
          additionalProperties:
            type: string
          description: |
            Optional structured key-value hints for the client (retry
            strategy, related resource ids, etc.). Always a string-to-string
            map.
  responses:
    BadRequest:
      description: >-
        Malformed request body, query parameter, or path parameter (`code` is
        one of `invalid_json` / `invalid_body` / `invalid_param` /
        `missing_param`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: >-
        Missing or invalid `Authorization` header (`code` is one of
        `unauthorized` / `invalid_token` / `missing_token`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: >-
        Per-caller × endpoint quota exceeded. `code` is `rate_limited`. Honour
        `Retry-After` if present.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServerError:
      description: >
        Server-side fault. `code` is one of:


        - `internal_error` (500) — unexpected

        - `agent_offline` / `agent_service_unavailable` (503) — agent or
        messaging
          subsystem unreachable
        - `auth_unavailable` / `auth_transient` (503) — Auth gRPC transient

        - `service_timeout` (504) — gateway gave up waiting for the agent


        See
        [docs/reference/errors.md](https://github.com/beeos-ai/beeos/blob/main/docs/reference/errors.md).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Pass a user **JWT** or a **`oag_`** User API Key on the
        `Authorization: Bearer <token>` header. Both are validated by
        `openapi-gateway` against the Auth service.

        Both credential types are **user-scoped**: every key (and every
        JWT) is bound to exactly one owner, and every route grants the
        caller full access to that owner's own resources. Cross-tenant
        access is denied by owner-ACL inside the handlers — there is no
        per-route scope vocabulary on this API.

        > **Removed in v1.1.0:** the legacy `agents:*` / `tasks:*` /
        > `files:*` / `instances:*` scope set has been dropped together
        > with the `403 insufficient_scope` error. Existing `oag_` keys
        > automatically gain full owner-level access and do **not** need
        > to be re-issued. SDK calls that previously passed `scopes` to
        > `createAPIKey` should drop the argument. See the changelog at
        > the bottom of this spec for the full migration note.

````