Quickstart
Set up from your terminal with one command, then create, read, control and park your first phone.
This walkthrough takes you from nothing to a phone that you control from your terminal. You need Node.js 20 or later.
1. Set up from your terminal
curl -fsSL https://phonebox.dev/install | shThe script installs the phonebox CLI with npm and runs phonebox setup, which asks you to sign in in your browser:
- Sign in. The terminal prints a link and a short code. Open the link, sign up or sign in, check that the page shows the same code, and click Connect. A new account gets its first project and $2 starter credit here, with no card required.
- Ready to try. Setup uses your available starter credit. Only when it cannot cover a one-minute session does the terminal print a checkout link, starting at $10.
phonebox setup --amount 25asks for another top-up amount.
Back in the terminal, setup confirms your project and balance. It saved an agent key for this machine in ~/.config/phonebox/credentials.json, which every phonebox command now uses. An agent key can create, use and park phones, but it can't delete them. The key is listed under API keys, where you can revoke it.
A phone costs $0.06 a minute while it runs, billed per second, and a parked phone costs nothing. Every new account gets $2 starter credit, enough for about 33 phone-minutes.
phonebox setup is safe to run again: it skips what is already done, and it is also how you add credit later. To do the same steps in the console instead, see Set up in the console.
2. Create a phone
phonebox create --name quickstartThe phone exists, and bills, from the moment the command starts. Before it waits, the command prints the new phone's ID on stderr:
{"created":"ph_4vn6q2x7k5ma","status":"creating"}It then waits until the phone is ready, which often takes about a minute, and prints the phone as one line of JSON. Here it is formatted:
{
"id": "ph_4vn6q2x7k5ma",
"object": "phone",
"name": "quickstart",
"status": "ready",
"country": null,
"metadata": {},
"created_at": "2026-09-29T10:00:00.412Z",
"last_active_at": "2026-09-29T10:00:41.230Z",
"session": {
"id": "ses_m4q7t2w5z3c6",
"started_at": "2026-09-29T10:00:00.412Z",
"ready_at": "2026-09-29T10:00:41.230Z",
"idle_timeout": 300,
"max_duration": 900,
"parks_at": "2026-09-29T10:05:41.230Z",
"park_reason": "idle",
"reserved_usd": "0.900000"
},
"failure": null
}Billing started when you ran the command. The session reserved $0.90, enough for its default maximum length of 15 minutes, and you pay only for the seconds you use. If the wait is interrupted, the phone still exists: phonebox status with its ID and --wait waits again, and phonebox ls lists your phones, so never create a second one to replace it. parks_at says when the phone will park itself unless a request addresses it before then.
Save the phone's ID, so that the next commands can leave it out:
export PHONEBOX_PHONE=ph_4vn6q2x7k5ma3. Read the screen
phonebox looksnapshot snp_4f2k7m3q6z3a
app com.android.launcher3
keyboard hidden
screen 1080x2400
[1] EditText "Search apps" #search clickable editable (540,250)
[2] TextView "Chrome" #icon clickable (200,1900)
[3] TextView "Settings" #icon clickable (500,1900)
[4] TextView "Play Store" #icon clickable (800,1900)The first line names the snapshot that the refs belong to, which a tap by ref needs. The next lines name the app in front, the keyboard's state and the screen size. Each line after that is one element: its ref number, type, text, resource ID, flags, and its center in screen pixels. Observe the screen describes the format and the JSON form.
4. Tap and type
Tap the search field by its text, then type into it:
phonebox tap --text "Search apps"{"index":0,"type":"tap","ok":true,"ms":184,"resolved":{"ref":1,"center":[540,250]}}phonebox type "chrome"{"index":0,"type":"type","ok":true,"ms":912}Look again to see what changed. The keyboard is up, the field holds your text, and the matching app is listed:
phonebox looksnapshot snp_6w3n7k2q5r4e
app com.android.launcher3
keyboard visible, text field focused
screen 1080x2400
[1] EditText "chrome" #search clickable editable focused (540,250)
[2] TextView "Chrome" #icon clickable (200,520)If a command fails, it prints the error as JSON on stderr and exits with a non-zero code. The error's next field names what to do. Actions and targets lists every action and error.
5. Park the phone
phonebox park{
"id": "ph_4vn6q2x7k5ma",
"object": "phone",
"name": "quickstart",
"status": "parked",
"country": null,
"metadata": {},
"created_at": "2026-09-29T10:00:00.412Z",
"last_active_at": "2026-09-29T10:01:40.377Z",
"session": null,
"failure": null
}Billing stopped when you ran park. This session lasted 131 seconds and cost $0.131, and the rest of the reservation went back to your balance. The phone keeps its apps, files and signed-in accounts, and phonebox start resumes it.
The same flow over HTTP
The HTTP examples read the key from PHONEBOX_API_KEY. phonebox token prints the key that setup saved:
export PHONEBOX_API_KEY=$(phonebox token)Create the phone:
{
"name": "quickstart"
}key="quickstart-$(date +%s)" # a new key for each phone; send the same one to repeat this create
curl https://phonebox.dev/v1/phones \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $key" \
-d '{"name": "quickstart"}'The request waits up to about 50 seconds. It answers 201 Created with the ready phone, which looks like the one in step 2. If the phone isn't ready by then, it answers 202 Accepted with the phone still creating, and you wait for it with a long poll:
curl "https://phonebox.dev/v1/phones/ph_4vn6q2x7k5ma?wait=ready&timeout=50" \
-H "Authorization: Bearer $PHONEBOX_API_KEY"Read the screen. format=text returns the text form shown above, and without it you get JSON:
curl "https://phonebox.dev/v1/phones/ph_4vn6q2x7k5ma/observe?format=text" \
-H "Authorization: Bearer $PHONEBOX_API_KEY"Tap and type in one batch of actions:
{
"actions": [
{ "type": "tap", "target": { "text": "Search apps" } },
{ "type": "type", "text": "chrome" }
],
"observe": "ui"
}curl https://phonebox.dev/v1/phones/ph_4vn6q2x7k5ma/actions \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"actions": [{"type": "tap", "target": {"text": "Search apps"}}, {"type": "type", "text": "chrome"}], "observe": "ui"}'The response has each action's result and, because of "observe": "ui", the screen after the batch:
{
"results": [
{ "index": 0, "type": "tap", "ok": true, "ms": 184, "resolved": { "ref": 1, "center": [540, 250] } },
{ "index": 1, "type": "type", "ok": true, "ms": 912 }
],
"completed": 2,
"observation": {
"snapshot_id": "snp_4f2k7m3q6z3a",
"taken_at": "2026-09-29T10:01:32.118Z",
"app": { "package": "com.android.launcher3", "activity": null },
"keyboard": { "visible": true, "focused_editable": true },
"screen": { "width": 1080, "height": 2400 },
"elements": [
{
"ref": 1, "type": "EditText", "text": "chrome", "desc": null, "id": "com.android.launcher3:id/search",
"bounds": [60, 200, 1020, 300], "center": [540, 250], "state": ["clickable", "editable", "focused"]
},
{
"ref": 2, "type": "TextView", "text": "Chrome", "desc": null, "id": "com.android.launcher3:id/icon",
"bounds": [100, 420, 300, 620], "center": [200, 520], "state": ["clickable"]
}
],
"truncated": false,
"text": "app com.android.launcher3\nkeyboard visible, text field focused\nscreen 1080x2400\n[1] EditText \"chrome\" #search clickable editable focused (540,250)\n[2] TextView \"Chrome\" #icon clickable (200,520)"
}
}Park the phone. Every field of the body is optional, so this request sends none:
curl -X POST https://phonebox.dev/v1/phones/ph_4vn6q2x7k5ma/park \
-H "Authorization: Bearer $PHONEBOX_API_KEY"Set up in the console
Everything phonebox setup does is also in the console:
- Sign up. Your first project is created automatically with $2 starter credit. No card is required.
- Open API keys and create a key. Keep the Agent preset. The console shows the full key only once, so copy it into your environment right away:
export PHONEBOX_API_KEY=pbx_…. Keep the key out of your code and your prompts. Authentication and keys explains the presets and scopes. - Use your starter credit to try a phone. Add more in Billing when needed, starting at $10.
- Install the CLI:
npm install -g https://phonebox.dev/downloads/phonebox-0.1.0.tgz. The package holds both thephoneboxCLI and the TypeScript SDK.
PHONEBOX_API_KEY always wins over a key that setup saved.
Next steps
- Set up with your coding agent so that your agent drives phones by itself.
- Read Concepts for how sessions, parking and billing work.
- Browse the CLI and the REST API for everything else.