---
title: "Run actions"
method: POST
path: "/v1/phones/{id}/actions"
tags: ["phone"]
---

# Run actions

`POST /v1/phones/{id}/actions`

## Request

- Base URL: `https://phonebox.dev`
- URL: `https://phonebox.dev/v1/phones/{id}/actions`
- Auth: HTTP bearer

## Path parameters

- `id` string, required

## Request body

- object
  - `actions` union[], required
    - union
      - object
        - `type` 'tap', required
        - `target` union, required
          - object
            - `x` integer, required
            - `y` integer, required
          - object
            - `ref` integer, required
            - `snapshot_id` string, required
          - object
            - `text` string, required
            - `nth` integer
          - object
            - `id` string, required
          - object
            - `desc` string, required
      - object
        - `type` 'double_tap', required
        - `target` union, required
          - object
            - `x` integer, required
            - `y` integer, required
          - object
            - `ref` integer, required
            - `snapshot_id` string, required
          - object
            - `text` string, required
            - `nth` integer
          - object
            - `id` string, required
          - object
            - `desc` string, required
      - object
        - `type` 'long_press', required
        - `target` union, required
          - object
            - `x` integer, required
            - `y` integer, required
          - object
            - `ref` integer, required
            - `snapshot_id` string, required
          - object
            - `text` string, required
            - `nth` integer
          - object
            - `id` string, required
          - object
            - `desc` string, required
        - `duration_ms` integer
      - object
        - `type` 'swipe', required
        - `from` object, required
          - `x` integer, required
          - `y` integer, required
        - `to` object, required
          - `x` integer, required
          - `y` integer, required
        - `duration_ms` integer
      - object
        - `type` 'scroll', required
        - `direction` 'up' | 'down' | 'left' | 'right', required
        - `target` union
          - object
            - `x` integer, required
            - `y` integer, required
          - object
            - `ref` integer, required
            - `snapshot_id` string, required
          - object
            - `text` string, required
            - `nth` integer
          - object
            - `id` string, required
          - object
            - `desc` string, required
        - `amount` number
      - object
        - `type` 'type', required
        - `text` string, required
        - `clear` boolean
        - `submit` boolean
      - object
        - `type` 'key', required
        - `key` union, required
          - 'enter' | 'delete' | 'tab' | 'escape' | 'space' | 'up' | 'down' | 'left' | 'right' | 'page_up' | 'page_down' | 'move_home' | 'move_end' | 'menu' | 'search' | 'volume_up' | 'volume_down'
          - integer
      - object
        - `type` 'back', required
      - object
        - `type` 'home', required
      - object
        - `type` 'recents', required
      - object
        - `type` 'notifications', required
      - object
        - `type` 'open_app', required
        - `package` string, required
        - `activity` string
      - object
        - `type` 'close_app', required
        - `package` string, required
        - `clear_data` boolean
      - object
        - `type` 'open_url', required
        - `url` string, required
        - `package` string
      - object
        - `type` 'wait', required
        - `ms` integer
        - `duration_ms` integer
      - object
        - `type` 'wait_for', required
        - `target` union, required
          - object
            - `text` string, required
            - `nth` integer
          - object
            - `id` string, required
          - object
            - `desc` string, required
        - `gone` boolean
        - `timeout_ms` integer
  - `observe` 'none' | 'ui' | 'screenshot' | 'both'

## Response `200`

One result per action run; the batch stops at the first failure. If nothing reached the phone, the request itself fails.

- object
  - `results` object[], required
    - `index` integer, required
    - `type` 'tap' | 'double_tap' | 'long_press' | 'swipe' | 'scroll' | 'type' | 'key' | 'back' | 'home' | 'recents' | 'notifications' | 'open_app' | 'close_app' | 'open_url' | 'wait' | 'wait_for', required
    - `ok` boolean, required
    - `ms` number, required
    - `resolved` object
      - `ref` integer
    - `error` object: Why the action failed. retryable is false once part of it reached the phone (details.partial). After 90 s a batch starts no more actions: the next one fails with provider_timeout without running, and details.not_run counts it and the actions after it.
      - `code` 'validation_failed' | 'invalid_request' | 'invalid_api_key' | 'key_expired' | 'key_revoked' | 'unauthorized' | 'insufficient_credits' | 'spend_limit_reached' | 'project_frozen' | 'insufficient_scope' | 'phone_not_allowed' | 'browser_requests_not_allowed' | 'setup_denied' | 'not_found' | 'phone_not_found' | 'file_not_found' | 'app_not_found' | 'upload_not_found' | 'recording_not_found' | 'method_not_allowed' | 'phone_not_running' | 'phone_starting' | 'phone_parking' | 'phone_deleted' | 'phone_failed' | 'idempotency_conflict' | 'running_limit_reached' | 'phone_limit_reached' | 'install_in_progress' | 'version_downgrade' | 'upload_not_ready' | 'storage_limit_reached' | 'clipboard_unavailable' | 'recording_in_progress' | 'recording_not_ready' | 'recording_limit_reached' | 'duplicate_upload' | 'setup_expired' | 'setup_used' | 'key_limit' | 'payload_too_large' | 'target_not_found' | 'ambiguous_target' | 'stale_snapshot' | 'no_focused_field' | 'wait_timeout' | 'app_not_available' | 'invalid_apk' | 'install_failed' | 'feature_unavailable' | 'rate_limited' | 'provider_error' | 'action_outcome_unknown' | 'upload_failed' | 'capacity_unavailable' | 'phone_unavailable' | 'service_paused' | 'storage_full' | 'billing_not_configured' | 'platform_not_configured' | 'provider_timeout' | 'internal_error', required
      - `message` string, required
      - `retryable` boolean, required
      - `next` string, nullable, required
      - `details` object, required
  - `completed` integer, required
  - `observation` object: The screen after the batch; a stopped batch attempts to include its elements. If the UI read fails or the action outcome is unknown, a screenshot-only observation may be returned, without refs or snapshot_id. It is left out when neither read finishes in time.
    - `snapshot_id` string
    - `taken_at` string, date-time: ISO 8601 timestamp (UTC)
    - `app` object
      - `package` string, nullable, required
      - `activity` string, nullable, required
    - `keyboard` object
      - `visible` boolean, required
      - `focused_editable` boolean, required
    - `screen` object
      - `width` integer, required
      - `height` integer, required
    - `elements` object[]
      - `ref` integer, required
      - `type` string, required
      - `text` string, nullable, required
      - `desc` string, nullable, required
      - `id` string, nullable, required
      - `state` string[], required
    - `truncated` boolean
    - `text` string
    - `screenshot` object
      - `format` 'jpeg' | 'png', required
      - `width` integer, required
      - `height` integer, required
      - `scale` number, required
      - `data` string, required

## Other responses

- `400`: Invalid request; details.issues lists each problem as {path, message}
- `401`: Missing or invalid key
- `402`: Insufficient credits, spending limit reached, or the project is frozen
- `403`: Insufficient scope, a phone this key can't use, a browser request, or a setup request that was declined
- `404`: Not found
- `409`: Conflict: the phone's status doesn't allow this, a phone limit is reached, the Idempotency-Key was used with another body, another install is still running on the phone, a newer version of the app is on the phone, an upload that isn't ready, the project's upload storage is full, the clipboard can't be read, a recording is already running or isn't ready, the project keeps 20 recordings, a setup request that expired or already delivered its key, the project has 50 active keys
- `413`: Body too large
- `422`: The request can't be applied, such as an app that isn't in Phonebox's app library, a file that isn't an APK, an install the phone refused, or a feature this phone doesn't offer (feature_unavailable)
- `429`: Rate limited
- `500`: Unexpected server error; keep the request ID
- `502`: The phone service returned an error, or didn't confirm the action
- `503`: No capacity, the phone is briefly unavailable, Phonebox is paused, or payments aren't connected. A batch refused with this error body did nothing on the phone; a 502 or 504 without it, from a proxy or the platform, leaves the outcome unknown.
- `504`: The phone didn't respond in time

---

[API](https://skmtc.dev/phonebox/apis/phonebox-api.md) · [All operations](https://skmtc.dev/phonebox/apis/phonebox-api/llms.txt) · [OpenAPI document](https://skmtc.dev/phonebox/apis/phonebox-api/revisions/87eb2f5211b2?raw)
