# Quickstart

Source: https://phonebox.dev/docs/getting-started/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 [#1-set-up-from-your-terminal]

```bash
curl -fsSL https://phonebox.dev/install | sh
```

The script installs the `phonebox` CLI with npm and runs `phonebox setup`, which asks you to sign in in your browser:

1. **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.
2. **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 25` asks 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](/app/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](#set-up-in-the-console).

## 2. Create a phone [#2-create-a-phone]

```bash
phonebox create --name quickstart
```

The phone exists, and bills, from the moment the command starts. Before it waits, the command prints the new phone's ID on stderr:

```text
{"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:

```json title="Response: phone"
{
  "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:

```bash
export PHONEBOX_PHONE=ph_4vn6q2x7k5ma
```

## 3. Read the screen [#3-read-the-screen]

```bash
phonebox look
```

```text
snapshot 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](/docs/using-phones/observe) describes the format and the JSON form.

## 4. Tap and type [#4-tap-and-type]

Tap the search field by its text, then type into it:

```bash
phonebox tap --text "Search apps"
```

```text
{"index":0,"type":"tap","ok":true,"ms":184,"resolved":{"ref":1,"center":[540,250]}}
```

```bash
phonebox type "chrome"
```

```text
{"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:

```bash
phonebox look
```

```text
snapshot 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](/docs/using-phones/actions) lists every action and error.

## 5. Park the phone [#5-park-the-phone]

```bash
phonebox park
```

```json title="Response: phone"
{
  "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-same-flow-over-http]

The HTTP examples read the key from `PHONEBOX_API_KEY`. `phonebox token` prints the key that setup saved:

```bash
export PHONEBOX_API_KEY=$(phonebox token)
```

Create the phone:

```json title="POST /v1/phones"
{
  "name": "quickstart"
}
```

```bash
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:

```bash
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](/docs/using-phones/observe):

```bash
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:

```json title="POST /v1/phones/{id}/actions"
{
  "actions": [
    { "type": "tap", "target": { "text": "Search apps" } },
    { "type": "type", "text": "chrome" }
  ],
  "observe": "ui"
}
```

```bash
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:

```json title="Response: 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:

```bash
curl -X POST https://phonebox.dev/v1/phones/ph_4vn6q2x7k5ma/park \
  -H "Authorization: Bearer $PHONEBOX_API_KEY"
```

## Set up in the console [#set-up-in-the-console]

Everything `phonebox setup` does is also in the console:

1. [Sign up](/sign-up). Your first project is created automatically with $2 starter credit. No card is required.
2. Open [API keys](/app/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](/docs/getting-started/authentication) explains the presets and scopes.
3. Use your starter credit to try a phone. Add more in [Billing](/app/billing) when needed, starting at $10.
4. Install the CLI: `npm install -g https://phonebox.dev/downloads/phonebox-0.1.0.tgz`. The package holds both the `phonebox` CLI and the [TypeScript SDK](/docs/interfaces/sdk).

`PHONEBOX_API_KEY` always wins over a key that setup saved.

## Next steps [#next-steps]

* [Set up with your coding agent](/docs/getting-started/coding-agent) so that your agent drives phones by itself.
* Read [Concepts](/docs/getting-started/concepts) for how sessions, parking and billing work.
* Browse the [CLI](/docs/interfaces/cli) and the [REST API](/docs/interfaces/rest) for everything else.
