# Set up Mobilebox

You are an agent and your user wants Mobilebox: cloud Android phones you drive over MCP or REST, $0.01 a minute and ready in 19.4 s in our November 2026 benchmark. Follow these steps in order; each one is safe to run again. Your user tells you their email address; you get an API key at once and can start right away. Later they read you a 6-digit code from their inbox to keep the workspace. You do the rest. New workspaces get free credit; the API tells you the balance.

Tell your user that in one sentence before you start.

**One click instead:** a client that signs in to MCP servers itself (Claude or ChatGPT connectors, Claude Code, Cursor, VS Code) needs no key. Add `https://mobilebox.dev/mcp` as a connector or server with no header; the client opens a browser where your user signs in and clicks Allow. In Claude Code: `claude mcp add --transport http mobilebox https://mobilebox.dev/mcp`, then `/mcp` to sign in. Then go to step 4. The steps below are for clients that need a key, and for REST.

## 1. Get a key, without showing it

Ask your user for their email address. If the link you were given to this file has a `via` (e.g. `setup.md?via=hn`), send it along as `"via":"VIA"` (here `"via":"hn"`); otherwise leave it out. Name the key after the client (`claude-code`, `cursor`, `codex`, …).

The response can hold a new API key, once. Never print it, paste it into chat, write it to a log or commit it. Save the response straight to a private file and store the key from there. Ask your user where they keep secrets; without an answer, use their shell profile:

```bash
umask 077
curl -sS -X POST https://mobilebox.dev/v1/signup/email \
  -H 'content-type: application/json' \
  -d '{"email":"USER_EMAIL","key_name":"claude-code","via":"VIA"}' -o ~/.mobilebox-signup.json
node -e '
const fs = require("fs"), home = require("os").homedir(), file = home + "/.mobilebox-signup.json"
const r = JSON.parse(fs.readFileSync(file, "utf8")); fs.rmSync(file)
if (!r.api_key) { console.log(JSON.stringify(r, null, 2)); process.exit(r.claimed ? 0 : 1) }
const profile = home + (process.env.SHELL?.endsWith("zsh") ? "/.zshrc" : "/.bashrc")
fs.appendFileSync(profile, `\nexport MOBILESANDBOX_API_KEY=${r.api_key}\n`)
delete r.api_key; console.log("saved to", profile); console.log(JSON.stringify(r, null, 2))'
```

Other secret stores work the same way: macOS Keychain (`security add-generic-password -s mobilebox -a "$USER" -w …`), 1Password (`op item create`), or a git-ignored `.env`. Each new shell needs the variable: `source` the profile first.

What comes back (`200` either way; an email with a 6-digit code is on its way in both cases):

- **A key** (`api_key`, saved by the script): a new workspace for this address, ready now. Tell your user `message_for_user` ("Check your inbox for a 6-digit code from Mobilebox and tell me it to keep this workspace."), then go on to step 3 without waiting. Until the code is confirmed the workspace has a small trial balance (`balance_usd`) and runs one phone at a time; without the code it closes at `expires_at` (7 days) and its phones are deleted.
- **No key** (`{"sent": true, "next": …}`): this address already has a workspace. Ask your user for the code from the email, then step 2 gives you a key for it.
- `429`: wait `retry_after` seconds, then retry. Any other error: do what its `next` says.

## 2. Confirm with the code

When your user gives you the code (it works once, for 10 minutes; step 1 again sends a new one and never a second key), send it with your key from step 1, if you have one:

```bash
umask 077
curl -sS -X POST https://mobilebox.dev/v1/signup/verify \
  -H 'content-type: application/json' -H "Authorization: Bearer ${MOBILESANDBOX_API_KEY:-none}" \
  -d '{"email":"USER_EMAIL","code":"CODE","key_name":"claude-code"}' -o ~/.mobilebox-signup.json
node -e '
const fs = require("fs"), home = require("os").homedir(), file = home + "/.mobilebox-signup.json"
const r = JSON.parse(fs.readFileSync(file, "utf8")); fs.rmSync(file)
if (!r.api_key) { console.log(JSON.stringify(r, null, 2)); process.exit(r.claimed ? 0 : 1) }
const profile = home + (process.env.SHELL?.endsWith("zsh") ? "/.zshrc" : "/.bashrc")
fs.appendFileSync(profile, `\nexport MOBILESANDBOX_API_KEY=${r.api_key}\n`)
delete r.api_key; console.log("saved to", profile); console.log(JSON.stringify(r, null, 2))'
```

What comes back:

- `200` with `"claimed": true`: confirmed. Keep your key; the workspace now has its starter credit and no trial rules.
- `201` with a new key (saved by the script): you sent no key for this workspace, so you get a new one; a known address gets it for its existing project. A key from step 1 that you did not send stops working: use the new one.
- `400 auth_failed`: wrong or expired code. Ask for the latest one, or send a new code (step 1).
- `409 account_exists`: the address signs in with GitHub or Google. Ask your user to sign in at https://mobilebox.dev/login?via=setup, create a key under API keys and set `MOBILESANDBOX_API_KEY` themselves. Don't ask them to paste it to you.
- `429`: wait `retry_after` seconds, then retry. Any other error: do what its `next` says.

## 3. Add the MCP server

The server is `https://mobilebox.dev/mcp`, Streamable HTTP, header `Authorization: Bearer <key>`. Add it for the client you run in, reading the key from the environment, never writing it into a file you commit.

**Claude Code**

```bash
source ~/.zshrc  # or wherever you stored it
claude mcp add --transport http --scope user mobilebox https://mobilebox.dev/mcp \
  --header "Authorization: Bearer $MOBILESANDBOX_API_KEY"
```

**Cursor**: `~/.cursor/mcp.json`

```json
{ "mcpServers": { "mobilebox": { "url": "https://mobilebox.dev/mcp", "headers": { "Authorization": "Bearer ${env:MOBILESANDBOX_API_KEY}" } } } }
```

**VS Code** (Copilot agent mode): `.vscode/mcp.json`, or `MCP: Open User Configuration`

```json
{ "servers": { "mobilebox": { "type": "http", "url": "https://mobilebox.dev/mcp", "headers": { "Authorization": "Bearer ${env:MOBILESANDBOX_API_KEY}" } } } }
```

**Codex**: `~/.codex/config.toml`

```toml
[mcp_servers.mobilebox]
url = "https://mobilebox.dev/mcp"
bearer_token_env_var = "MOBILESANDBOX_API_KEY"
```

**Others** (Windsurf, Gemini CLI, Zed, Cline, …): any remote MCP entry with the URL and the header above. No MCP at all: the same key works on REST, `https://mobilebox.dev/v1` ([OpenAPI](https://mobilebox.dev/openapi.json?via=setup)).

**Python**: `pip install mobilebox` for the REST client, `pip install langchain-mobilebox` for LangChain and LangGraph tools; both read `MOBILESANDBOX_API_KEY` ([how-to](https://mobilebox.dev/blog/android-phone-for-python-ai-agent?via=setup)).

Most clients load a new server only after a restart. Don't wait for it: do step 4 over REST now, and tell your user the tools appear next session.

## 4. First phone, shown to your user

With MCP: `create_phone` (name `first-phone`), then `watch_phone`. Over REST:

```bash
curl -sS -X POST 'https://mobilebox.dev/v1/phones?wait=ready' -H "Authorization: Bearer $MOBILESANDBOX_API_KEY" \
  -H 'content-type: application/json' -d '{"name":"first-phone"}'
curl -sS -X POST https://mobilebox.dev/v1/phones/PHONE_ID/view -H "Authorization: Bearer $MOBILESANDBOX_API_KEY"
```

If the phone is still `creating` or `starting`, poll `GET /v1/phones/PHONE_ID?wait=ready` until it is `ready`. Give your user the viewer `url` (or run the `command` that comes with it): they watch the phone live while you open Settings on it. Then ask what they want to use phones for, and park the phone (`park_phone`) or delete it (`delete_phone`) when done.

## Then

Read https://mobilebox.dev/skill.md?via=setup before you drive a phone: the observe → act → verify loop, targets, apps and errors.
