> ## 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 all of the caller's tasks across every agent.

> Cross-agent task list (P2-B). Returns every task channel the
caller owns, newest first — the unified "my tasks" view for
SDKs that need to render a multi-agent inbox without
fanning out per-agent.

Same wire shape and `next_since` pagination as the
per-agent variant at
`GET /api/v1/agents/{agentId}/tasks`. Filtering an existing
per-agent call is equivalent to passing `agent_id`
explicitly.

Authorization: the caller's `owner_id` is the only ACL
gate. Tasks fan out across agents the caller has invoked,
whether or not they still have visibility on those agents
today — possession of the task row itself proves access at
submit time.

Only `protocol=openapi` channels surface here. Long-running
conversations live at `GET /api/v1/agents/{agentId}/conversations`
and are not mixed in to keep status semantics stable (tasks
have a deadline; conversations don't).




## OpenAPI

````yaml /openapi/beeos-platform-v1.yaml get /api/v1/tasks
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/tasks:
    get:
      tags:
        - tasks
      summary: List all of the caller's tasks across every agent.
      description: |
        Cross-agent task list (P2-B). Returns every task channel the
        caller owns, newest first — the unified "my tasks" view for
        SDKs that need to render a multi-agent inbox without
        fanning out per-agent.

        Same wire shape and `next_since` pagination as the
        per-agent variant at
        `GET /api/v1/agents/{agentId}/tasks`. Filtering an existing
        per-agent call is equivalent to passing `agent_id`
        explicitly.

        Authorization: the caller's `owner_id` is the only ACL
        gate. Tasks fan out across agents the caller has invoked,
        whether or not they still have visibility on those agents
        today — possession of the task row itself proves access at
        submit time.

        Only `protocol=openapi` channels surface here. Long-running
        conversations live at `GET /api/v1/agents/{agentId}/conversations`
        and are not mixed in to keep status semantics stable (tasks
        have a deadline; conversations don't).
      operationId: listUserTasks
      parameters:
        - name: agent_id
          in: query
          required: false
          description: |
            Narrow the result to a single agent. Equivalent to
            calling `GET /agents/{agentId}/tasks` directly; kept on
            this endpoint so callers can collapse their pagination
            logic across the "all agents" and "single agent" paths.
          schema:
            type: string
            maxLength: 128
        - name: instance_id
          in: query
          required: false
          description: |
            Narrow the result to tasks running on a specific instance
            (across any agent on that instance). Combine with
            `state=open` to answer "is this instance currently running
            a task?" — a non-empty result means busy. Device control
            commands (computer / mobile) are NOT counted here.
          schema:
            type: string
            maxLength: 128
        - name: state
          in: query
          required: false
          description: |
            Filter by channel state. `active` is the legacy wire
            alias for v2's `open`; `all` returns both. Default:
            `active`.
          schema:
            type: string
            enum:
              - active
              - open
              - closed
              - all
            default: active
        - name: since
          in: query
          required: false
          description: Opaque pagination cursor from the previous page's `next_since`.
          schema:
            type: string
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            format: int32
            minimum: 1
            maximum: 100
            default: 50
      responses:
        '200':
          description: Paged list of tasks across all agents.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListTasksResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        5XX:
          $ref: '#/components/responses/ServerError'
components:
  schemas:
    ListTasksResponse:
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          required:
            - data
          properties:
            data:
              type: object
              required:
                - tasks
              properties:
                tasks:
                  type: array
                  items:
                    $ref: '#/components/schemas/TaskSummary'
                next_since:
                  type: string
                  description: |
                    Pagination cursor. Empty when the caller has reached
                    the end of results.
    SuccessEnvelope:
      type: object
      required:
        - success
      properties:
        success:
          type: boolean
          const: true
    TaskSummary:
      type: object
      required:
        - task_id
        - agent_id
        - state
      properties:
        task_id:
          type: string
          description: Stable task identifier (== underlying channel_id).
        agent_id:
          type: string
        caller_id:
          type: string
          description: |
            The owner user id of this task. Always equal to the caller
            for tasks they themselves submitted; populated from the
            channel metadata so it survives across sessions.
        state:
          type: string
          enum:
            - active
            - closed
          description: |
            Channel-level state: `active` while the task is open (the
            agent may still be working / paused at `input_required`),
            `closed` after terminal reply / cancel / timeout. NOT the
            derived `TaskStatus` — use `getTask` for that.
        close_reason:
          type: string
          description: |
            Populated when `state=closed`. Mirrors the underlying
            channel's `closed_reason` so SDKs can branch without
            re-pulling the message log. Canonical values include
            `completed`, `canceled`, `timeout`.
        metadata:
          type: object
          additionalProperties:
            type: string
        deadline_at:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          const: false
        error:
          $ref: '#/components/schemas/Error'
    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'
    Forbidden:
      description: Caller doesn't own the resource. `code` is `forbidden`.
      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.

````