---
name: mobilebox
description: Use when a task needs an Android phone: an app with no API or website, a mobile-only flow, or testing an Android build. You operate the phone through its screen.
---

# Mobilebox

API + MCP first. Mobile Sandboxes, controllable by your agents. You get Android 14 arm64 sandboxes in the cloud; these are virtual Android environments, not physical handsets. Your existing agent supplies the reasoning. You read the screen as a numbered list of elements, act on it, and park the phone when done. Parked phones are shut down and free; apps, data and logins stay, and apps start fresh after a resume. Running phones cost per second.

## The loop

1. `create_phone` (MCP) / `POST /v1/phones?wait=ready` / `node src/cli.ts create`. MCP and the CLI return the first screen once ready. REST create returns phone metadata; inspect status, poll the same id if still booting, then call `GET /v1/phones/{id}/observe`.
2. Read the screen. Each line looks like `[4] Row "Device name" — Pixel (540,550)`: ref, type, text, secondary text, state, center.
3. `act` with a few actions at once, e.g. `[{"type":"open_app","package":"com.android.settings"},{"type":"tap","target":{"text":"About phone"}}]`. You get per-action results and the new screen.
4. Repeat 2–3. Batch the steps you're sure of; observe when you aren't.
5. `park_phone` when done, even when the task failed.

## Targets

| target | meaning |
|---|---|
| `{"text":"Sign in"}` | best default: exact match, then contains; `nth` picks among matches |
| `{"ref":3}` | from the last screen you saw; fails with `stale_ref` if the screen moved on |
| `{"id":"com.app:id/login"}` / `{"desc":"Navigate up"}` | resource id / content description |
| `{"x":540,"y":1200}` | raw pixels, for what the list can't show (use a screenshot) |

## Actions

`tap`, `long_press`, `swipe {from,to}`, `scroll {direction}`, `scroll_to {text}` (scrolls a list until the text is on screen; use it before tapping something below the fold), `type {text, clear?, submit?, target?}`, `key {back|home|enter|delete|recents|tab}`, `back`, `home`, `open_app {package}`, `close_app {package}` (sends it to the background, then force-stops it, like swiping it away in recents; the next `open_app` starts it fresh), `wait {ms}`, `wait_for {text, gone?, timeout_ms}`.

## Apps

`install_app {url}` (MCP) / `POST /v1/phones/{id}/apps` with the file bytes or `{"url": …}`: an `.apk`, or a split bundle (`.apks`, `.xapk`) from which the phone's splits are picked (arm64-v8a, its screen density, its language, plus base and feature splits). Then `open_app`. Updates keep the app's data; a different signing key is refused (`signature_mismatch`). Phones are arm64-v8a, Android 14. `list_apps`, `uninstall_app` (deletes the app's data). Fresh apps may open with a permission dialog first: answer it like a user would.

`type` needs a focused text field: tap the field first (or pass `target`). If the text does not arrive you get `type_not_applied` instead of a silent success.

## When something fails

Each error carries `next`, the exact thing to do instead, and `retryable` (the same call can succeed later). Read `next` before retrying. A batch stops at the first failed action, and the rest don't run; a batch also stops starting new actions after 120 s (`batch_timeout`).

Limits: 20 actions per batch, JSON bodies up to 256 KB, `?wait=ready&timeout=` up to 55 s per call (poll again while it says `creating`/`starting`). Unknown fields and wrong types are a 400 that changes nothing. The CLI exits 1 when a call or an action fails.

Capacity: a project runs up to 3 phones at once and keeps up to 10 (parked ones included). All API keys in the same project share these limits and its balance; creating another key does not add capacity. Past that, create (or start) is a 429 `phone_limit` that names a phone to park or delete. A full host answers 503 `box_full` with `retry_after` seconds (also the `Retry-After` header). Boots queue, 2 at a time, so under load a create can take a few polls longer.


## Authentication and discovery

Use an API key as Authorization: Bearer YOUR_API_KEY for phone-control REST and MCP calls. `/v1/account/keys` is browser-session-only key management, not an agent endpoint. Browser session cookies are separate. Read /llms.txt for the documentation index, /openapi.json for the REST schema, /docs/reference for response contracts, and /docs/errors for recovery. MCP is stateless Streamable HTTP at /mcp. Call tools/list for the actual input schemas. Keep keys in client secrets, not in model-visible prompts. The existing SDK/CLI use MOBILESANDBOX_API_KEY and MOBILESANDBOX_URL for compatibility.

## Verify, then stop

A successful tap proves an input, not an outcome. Read the returned screen and verify the saved result. REST actions can return HTTP 200 with results[i].ok=false; MCP act includes failures in its result text. Earlier actions remain applied after a failed batch. Observe and continue from current state; do not blindly repeat a submit or other side effect.

Make creates safe to retry: pass a fresh unique `idempotency_key` (a UUID; no space at either end) to `create_phone` (REST: `Idempotency-Key` header; CLI: `--idempotency-key`). If you lose the response, retry with the same key and options: you get the same phone, not a second one, for 24 h. Other options with that key are a 409 `idempotency_conflict`; a deleted phone is a 410 `phone_deleted`. Without a key, list phones before retrying. Preserve the phone id for cleanup. Park on completion or failure when data should remain; delete disposable test phones. If cleanup itself fails, report it and inspect the phone rather than assuming it stopped.

Virtual Android is not a guarantee of Google Play services, device certification, SIM/camera behavior, third-party app compatibility, physical-device performance, or an iOS runtime. Verify the exact application and workflow you need.
