> ## 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.

# Open an installed app by package id or display name.

> Runtime options vary. Portal/BeeRunner supports package or display-name launch and explicit activity, but rejects a nonempty locale override with action_unsupported.



## OpenAPI

````yaml /openapi/beeos-platform-v1.yaml post /api/v1/instances/{id}/mobile/open_app
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`.

    **Atomic control admission:** Computer/Mobile `info` shares a 300/minute
    owner-scoped budget; all other Computer/Mobile atomic operations together
    share 60/minute. Equivalent MCP atomic tools consume the same budgets.
    Capacity replenishes continuously (GCRA), with an initial burst equal to
    the minute rate. Switching credentials, devices or protocols does not reset
    capacity. Exhaustion returns 429 `rate_limited` with `Retry-After`; an
    unavailable quota store returns 503 `control_rate_limit_unavailable` before
    dispatch. These limits do not define billing or device task concurrency.

    ## 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/instances/{id}/mobile/open_app:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          maxLength: 128
    post:
      tags:
        - mobile
      summary: Open an installed app by package id or display name.
      description: >-
        Runtime options vary. Portal/BeeRunner supports package or display-name
        launch and explicit activity, but rejects a nonempty locale override
        with action_unsupported.
      operationId: mobileOpenApp
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenAppRequest'
      responses:
        '200':
          $ref: '#/components/responses/ControlActionOK'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/ServerError'
        5XX:
          $ref: '#/components/responses/ServerError'
components:
  schemas:
    OpenAppRequest:
      type: object
      title: OpenAppRequest
      required:
        - app
      properties:
        app:
          type: string
          description: Android package id or installed app display name.
        activity:
          type: string
        locale:
          type: string
          description: Optional BCP-47 per-app locale (Android 13+, best effort).
    SuccessEnvelope:
      type: object
      required:
        - success
      properties:
        success:
          type: boolean
          const: true
    ActionResult:
      type: object
      title: ActionResult
      description: Generic acknowledgement that a mutating control action completed.
      required:
        - ok
      properties:
        ok:
          type: boolean
    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:
    ControlActionOK:
      description: |
        Standard `{ success, data }` envelope; `data` is an
        [`ActionResult`](#/components/schemas/ActionResult) (`{ ok: true }`)
        acknowledging the device action completed. Device-reported
        failures map to 400 `action_unsupported` / `invalid_param`,
        409 `device_busy`, 503 `device_offline`, or 504 `service_timeout`.
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/SuccessEnvelope'
              - type: object
                required:
                  - data
                properties:
                  data:
                    $ref: '#/components/schemas/ActionResult'
    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'
    NotFound:
      description: >-
        The agent / task / instance / conversation / webhook referenced doesn't
        exist or isn't visible to the caller. `code` is typically
        `agent_not_found`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Conflict:
      description: >
        Resource is in a state that doesn't allow this operation. `code` is
        `conflict`.


        Two distinct cases for agent refusal:

        - **Legacy pre-output refused** (e.g. `agent_busy` from the agent
        runtime BEFORE any output is streamed): the gateway maps this to **409
        Conflict** with `code = task_rejected`. No task body is produced.

        - **v3 in-band refused** (agent started a streaming reply, then
        transitioned the v3 envelope to `state = refused` with a refusal
        `body`): the invoke endpoint returns **200 OK** with `is_error = true`
        and the body carrying the refusal text. The terminal `stop_reason =
        refused` lets clients distinguish this from a successful completion.
        **NOT** a 409.


        Other 409 triggers: task already terminal, duplicate idempotency key on
        a changed payload.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: >-
        Request quota exceeded; `code` is `rate_limited`. Honour `Retry-After`.
        Computer/Mobile atomic quotas are owner-scoped and shared with
        equivalent MCP tools. Atomic errors also include retry_after_seconds,
        limit, remaining and reset_after_seconds as strings in error.metadata.
      headers:
        Retry-After:
          description: >-
            Minimum wait in seconds before requesting admission again. Does not
            guarantee the next request will be admitted.
          schema:
            type: integer
            minimum: 1
        X-RateLimit-Limit:
          description: Per-minute capacity of this request's bucket.
          schema:
            type: integer
            minimum: 1
        X-RateLimit-Remaining:
          description: Remaining capacity in the bucket.
          schema:
            type: integer
            minimum: 0
        X-RateLimit-Reset:
          description: Seconds until full capacity replenishes; not a Unix timestamp.
          schema:
            type: integer
            minimum: 0
      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

        - `control_rate_limit_unavailable` (503) — atomic control admission
          could not be checked; no device command was dispatched

        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.

````