# Terminal setup

Source: https://phonebox.dev/docs/api-reference/setup

> The two requests a terminal or an agent makes to get an API key without the console, after a person confirms a short code in the browser.



Terminal setup gives a terminal or an agent its first API key. The terminal starts a setup request, a person opens a link, signs in and confirms the code they see in their terminal, and the terminal then receives a new key. `phonebox setup` in the CLI runs these requests for you.

These are the only endpoints that take no API key. Everything else in [Conventions](/docs/api-reference) applies: JSON bodies are checked strictly, errors use the [envelope](/docs/api-reference/errors), and a request that carries an `Origin` header fails with `403 browser_requests_not_allowed`.

## Start terminal setup [#start-terminal-setup]

`POST /v1/setup` needs no API key.

Starts a setup request. The body is optional.

| Field    | Type   | Default | Rules                                                                                                                                                                     |
| -------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client` | string | None    | A name for the terminal, from 1 to 60 printable ASCII characters, such as `Claude Code on tals-mbp`. The person sees it when they confirm, and the key is named after it. |

```json title="POST /v1/setup"
{
  "client": "Claude Code on tals-mbp"
}
```

```bash
curl https://phonebox.dev/v1/setup \
  -H "Content-Type: application/json" \
  -d '{"client": "Claude Code on tals-mbp"}'
```

It answers `201 Created`:

```json title="Response: setup"
{
  "setup_token": "pbs_Rk3vQ8bN2xLm5TyW9pZc4HdJ7aGf6sEu1oVi0XqBnKw",
  "user_code": "K7QM-2XHD",
  "verification_url": "https://phonebox.dev/connect?code=K7QM-2XHD",
  "expires_in": 900,
  "interval": 3
}
```

| Field              | Type    | Meaning                                                                                                                                                          |
| ------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setup_token`      | string  | The terminal's secret, `pbs_` followed by 43 characters. Only it can collect the key, so never show it or send it anywhere but [the poll](#poll-terminal-setup). |
| `user_code`        | string  | The code the person checks against their terminal. On its own it gives no access.                                                                                |
| `verification_url` | string  | Where the person signs up or signs in and confirms the code. Show it, or open it in their browser.                                                               |
| `expires_in`       | integer | The seconds the person has to confirm: 900, which is 15 minutes.                                                                                                 |
| `interval`         | integer | The seconds to wait between polls: 3.                                                                                                                            |

| Error                   | When                                                                                                |
| ----------------------- | --------------------------------------------------------------------------------------------------- |
| `400 validation_failed` | `client` is empty, longer than 60 characters or not printable ASCII, or the body has another field. |
| `429 rate_limited`      | More than 10 requests in a minute from one IP address.                                              |

## Poll terminal setup [#poll-terminal-setup]

`POST /v1/setup/token` needs no API key.

Asks whether the person has confirmed the request. Send it every `interval` seconds until it answers `approved` or fails.

| Field         | Type   | Rules                                                                              |
| ------------- | ------ | ---------------------------------------------------------------------------------- |
| `setup_token` | string | Required. The `setup_token` that [starting setup](#start-terminal-setup) returned. |

```json title="POST /v1/setup/token"
{
  "setup_token": "pbs_Rk3vQ8bN2xLm5TyW9pZc4HdJ7aGf6sEu1oVi0XqBnKw"
}
```

While the person hasn't answered, it returns `200 OK` with:

```json title="Response: setup status"
{
  "status": "pending"
}
```

Once they have confirmed, it returns the key, exactly once:

```json title="Response: setup status"
{
  "status": "approved",
  "api_key": "pbx_Hq2sT7vYc4Kd9mLx3NbW6pZr8Jf1GaEu5oVi0XqBnRk",
  "project": "My project"
}
```

| Field     | Type   | Meaning                                                                                                             |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `status`  | string | `pending` or `approved`.                                                                                            |
| `api_key` | string | With `approved`: the new key. Store it now, because no later request returns it.                                    |
| `project` | string | With `approved`: the name of the project the key belongs to. A new account gets a first project named `My project`. |

The key is an Agent key with no expiry, named after `client`, such as `Claude Code on tals-mbp (terminal setup)`, or `Terminal setup` without one. It is listed on the console's [API keys](/app/keys) page, where it can be revoked. [Authentication and keys](/docs/getting-started/authentication) says what an Agent key can do.

| Error                   | When                                                                            |
| ----------------------- | ------------------------------------------------------------------------------- |
| `400 validation_failed` | `setup_token` is missing or isn't a setup token, or the body has another field. |
| `403 setup_denied`      | The person declined the request in the browser.                                 |
| `404 not_found`         | No setup request has this token.                                                |
| `409 setup_expired`     | The code wasn't confirmed, or the key wasn't collected, within 15 minutes.      |
| `409 setup_used`        | The request already delivered its key.                                          |
| `409 key_limit`         | The project the person chose already has 50 active keys.                        |
| `429 rate_limited`      | More than 60 polls in a minute for one token.                                   |

After `setup_denied`, `setup_expired` or `setup_used`, start a new request. The key is never sent twice, so a poll whose answer was lost ends in `setup_used`: start again, and revoke the lost key in the console.

Next, the account needs credit before it can run a phone: [create a checkout link](/docs/api-reference/account#create-a-checkout-link) with the new key.
