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
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 callGET /v1/phones/{id}/observe.- Read the screen. Each line looks like
[4] Row "Device name" — Pixel (540,550): ref, type, text, secondary text, state, center. actwith 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.- Repeat 2 to 3. Batch the steps you’re sure of; observe when you aren’t.
park_phonewhen 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.
Source: SKILL.md in the repo, rendered at build time.