Mobilebox
Menu
API referenceDocs pages and contents

On this page

API reference

Mobilebox is an API + MCP runtime for Android 14 arm64 sandboxes. Bring your own agent. The service does not run a language model or accept a natural-language task endpoint.

Authentication

Send Authorization: Bearer YOUR_API_KEY on phone-control /v1/* and /mcp requests. Exception: /v1/account/keys is browser-session-only account management, not an agent API; create/list/revoke keys in the dashboard. Issued agent keys cannot become browser sessions. Browser login and API-key authentication are separate. Get your key from the account dashboard; do not send a browser session cookie as an API bearer key. Existing SDK/CLI variables remain MOBILESANDBOX_API_KEY and MOBILESANDBOX_URL for compatibility. Public product name: Mobilebox.

bash
export MSB=https://mobilebox.dev
# Load MOBILESANDBOX_API_KEY from your secret manager or shell environment.
curl --fail-with-body "$MSB/v1/account" \
  -H "Authorization: Bearer $MOBILESANDBOX_API_KEY"

JSON bodies require Content-Type: application/json. Unknown fields, malformed JSON, and wrong types fail with 400 before the requested operation. JSON bodies are limited to 256 KB. Installation accepts separate binary uploads up to 300 MiB.

Phone lifecycle

Method and pathRequestResult
POST /v1/phonesOptional name, country, idle_timeout, max_duration, timezone, locale, location, proxy; optional Idempotency-Key header201 phone, possibly still creating
GET /v1/phonesNonedata array of this project’s phones
GET /v1/phones/{id}NonePhone metadata and status
POST /v1/phones/{id}/startOptional idle_timeout, max_durationStarts or waits on existing phone
POST /v1/phones/{id}/parkNo bodyShuts down, keeps apps/data/logins
DELETE /v1/phones/{id}No body204; deletes phone and data
GET /v1/phones/{id}/sessionsNoneSession timestamps, billed seconds, costs, end reasons
GET /v1/accountNoneProject id, spent_usd, balance_usd, configured price; email when available in a browser session

Create, get, and start accept wait=ready or wait=parked and timeout=0 to 55 whole seconds. Default timeout: 50. A wait timing out can still return a creating/starting phone successfully. Inspect status and poll GET on the same id until ready, failed, or your own deadline; do not create duplicates.

json
{
  "id": "ph_example",
  "object": "phone",
  "name": "smoke-test",
  "status": "ready",
  "created_at": "2026-01-01T00:00:00.000Z",
  "settings": { "timezone": "Europe/Berlin", "locale": "de-DE" },
  "session": {
    "id": "ses_example",
    "started_at": "2026-01-01T00:00:00.000Z",
    "ready_at": "2026-01-01T00:00:25.000Z",
    "last_active_at": "2026-01-01T00:00:25.000Z",
    "idle_timeout": 300,
    "max_duration": 900,
    "reserved_usd": "0.150000"
  },
  "failure": null
}

This is an illustrative shape, not a recorded session. Settings reflect the configured fields. Phone status can be creating, starting, ready, parking, parked, failed, or deleted. Inspect failure.code and failure.message when failure is present; failed phones can be started again.

Defaults: idle_timeout 300 seconds, max_duration 900 seconds. Their ranges are 60 to 3,600 seconds and 60 to 10,800 seconds respectively. A session reserves its maximum charge against available balance; inspect the account and session endpoints for the configured rate and actual billed usage. Default capacity is 3 running and 10 total retained phones per project. All keys in the same project share its balance and these limits; issuing another key does not add capacity. Parked phones count toward the total phone limit.

Retry a create safely

Send an Idempotency-Key header (1 to 64 printable ASCII characters with no space at either end; a UUID is safest) with POST /v1/phones to make the create safe to retry after a lost response. The MCP create_phone tool takes the same value as idempotency_key; the SDK as create({ idempotencyKey }); the CLI as create --idempotency-key.

Retry within the windowResult
Same key, same options201 with the same phone in its current state, including parked or failed. No new phone, session, reservation, boot, or limit/credit check.
Same key, other options409 idempotency_conflict; the error does not repeat your input
Same key, phone was deleted410 phone_deleted; use a new key for a new phone
Rejected create (400, 402, 429, 503)Nothing is claimed; fix the cause and retry with the same key

Keys are scoped to the project, not the API key. A key holds for 24 hours from the phone’s creation, and longer while that phone is still creating; after that the same key creates a new phone. Options are compared as the phone would run them: omitted idle_timeout, max_duration and name equal their defaults, country equals the timezone, locale and location it sets, and a null proxy equals none. The wait and timeout query parameters are not options. Later changes to the phone do not make a retry a conflict. Without the header, every POST creates a phone as before. The key covers create only; start, park and delete have no key.

HTTP removes spaces at the ends of a header value before the API sees it, so a raw REST key " abc " arrives as "abc". The SDK, CLI and MCP refuse such keys with 400 invalid_request rather than change them. The key, its phone and the reservation are saved together before the phone boots, so they survive an API crash or restart. A host power loss can lose the most recent create, along with its key and phone.

Read the screen

GET /v1/phones/{id}/observe returns app, elements, and text. Add ?format=text for a compact numbered list. GET /v1/phones/{id}/screenshot returns image/png. The phone must be ready.

text
[4] Button "Save" (540,1080)
[5] TextField "Draft" [focused] (540,865)

Illustrative lines: ref, element type, text, state, center. Observe again after a screen change; old refs may fail with stale_ref. A screenshot is useful for images, maps, or canvases absent from the accessibility tree.

Send actions

POST /v1/phones/{id}/actions accepts an actions array of 1 to 20 items. It returns results and a fresh screen. It stops on first failure; earlier actions stay applied. HTTP 200 can contain a failed action: inspect every results[i].ok. Do not replay an entire batch blindly.

json
{
  "actions": [
    { "type": "open_app", "package": "com.android.settings" },
    { "type": "home" }
  ]
}
json
{
  "results": [{ "type": "tap", "ok": false,
    "error": { "code": "stale_ref", "message": "Illustrative error",
      "next": "Observe again and use a current target.", "retryable": false } }],
  "screen": { "app": "com.android.settings", "elements": [], "text": "Illustrative screen" }
}

The example shows the failure envelope, not a live response. Screen app and element details are described in OpenAPI. A batch stops starting new actions after 120 seconds. Use wait_for for a screen condition; avoid long blind sleeps.

Target vocabulary

ShapeMeaning
{"text":"Save"}Exact match, then contains; optional nth starts at 1
{"ref":4}Reference from the most recent observed screen
{"id":"com.app:id/save"}Resource id
{"desc":"Navigate up"}Accessibility content description
{"x":540,"y":1080}Raw pixels; verify with a current screenshot

Action vocabulary

typeFields and limits
taptarget
long_presstarget; duration_ms 100 to 10,000, default 800
swipefrom and to as [x,y]; duration_ms 50 to 5,000, default 300
scrolldirection: up, down, left, right
scroll_totext; direction up/down (default down); max_swipes 1 to 30 (default 10)
typetext up to 2,000 chars; target optional; clear/submit default false
keykey: back, home, enter, delete, recents, tab
back / homeNo additional fields
open_app / close_apppackage; close_app backgrounds then force-stops
waitms 0 to 10,000
wait_fortext; gone default false; timeout_ms 100 to 30,000, default 10,000

Coordinates are integers from 0 to 10,000. Text targets are nonempty and up to 500 characters. Ref range: 1 to 10,000. Text nth range: 1 to 100. type needs a focused field: tap it first or pass target. type_not_applied means text did not reach it.

Install and manage apps

POST /v1/phones/{id}/apps accepts binary APK/.apks/.xapk bytes, or JSON {"url":"https://download.example/app.apk"}. Optional grant_permissions=true is accepted in the query or JSON install body. The response is 201 with package, version_code, and version_name. Split selection uses arm64-v8a, density, language, base and feature splits.

GET /v1/phones/{id}/apps lists user-installed packages and version_code. DELETE /v1/phones/{id}/apps/{package} uninstalls and deletes app data, returning 204. Updating retains data when the signature is compatible; signature_mismatch requires an intentional fresh-install decision, not automatic uninstall.

Configure device settings

GET /v1/phones/{id}/device reads adapter-reported settings. PATCH accepts timezone (IANA), locale (BCP-47), location {lat,lng}, and proxy (a supplied socks5:// URL or null for direct egress). These settings persist across park/start. Changing locale restarts the UI; observe again afterward.

Country on create is a shortcut for timezone, locale, and GPS. It does not provide a country-specific IP. Proxy credentials are redacted from phone metadata; keep secrets out of your own logs and screenshots too.

MCP mapping

The authenticated /mcp endpoint uses stateless Streamable HTTP. Tools: list_phones, create_phone, start_phone, park_phone, delete_phone, install_app, list_apps, uninstall_app, observe, act, screenshot, set_device. Call tools/list through a compatible client for the actual input schemas. create_phone accepts idempotency_key with the same rules as the Idempotency-Key header. MCP installation accepts a URL; use REST for local binary uploads. REST exposes account/session/device-read endpoints beyond the current tool set.

MCP tool errors set isError and include code, message, and next guidance in text. An act result can contain a failed action within its normal result text; inspect the full result. Keep your API key out of model-visible prose.

Source: docs/REFERENCE.md in the repo, rendered at build time.