> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phoneuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Get a key, connect an MCP client, and drive a real phone in about ten minutes.

Five steps: sign in, get a key, paste one block of config, drive a device, read
the run back as evidence. You need nothing installed beyond an MCP client.

<Steps>
  <Step title="Sign in">
    The console is at
    [console.phonebase.co](https://console.phonebase.co/sign-up). Everything
    below assumes an account and an organization, and the key in the next step
    is minted from a signed in session, so this is the step that has to come
    first.

    Signing up takes an email address and a password, and confirms the address
    with a six digit code. The organization is created for you on the way in;
    you can rename it later in Settings.

    Keep the tab in front while you submit. A human check runs in the
    background and can stall in a tab you have switched away from; if nothing
    happens, see [Signing up](/help/troubleshooting#signing-up).
  </Step>

  <Step title="Get a key">
    Every hosted request authenticates with an org-scoped API key: a single
    bearer string that starts with `pbk_`.

    **Mint one yourself** from the `/keys` screen in the console. You get a key
    scoped to your org, carrying the scopes you pick, and optionally narrowed to
    a subset of your devices. Either role can mint: you do not need to be an
    org admin. What minting does need is a signed in console session, because an
    API key cannot mint another key. The scopes you request cannot exceed the
    scopes your own session holds. It is the `/v1/api-keys` surface described in
    [Authentication](/mcp/auth#getting-a-key).

    No device yet either? [Buy, drive and return a
    device](/guides/first-device) is the whole loop, starting from the console.

    The full token is shown to you exactly once. Only a hash is stored, so
    nobody (including Phonebase) can read it back later. Put it in an
    environment variable and treat it like a password:

    ```bash theme={null}
    export PHONEBASE_API_KEY="pbk_<keyId>_<secret>"
    ```

    What the key can do is pinned at issuance: its [scopes](/mcp/auth#scopes)
    decide which tools exist for you, and it can optionally be narrowed to a
    subset of your org's devices. If a tool seems missing later, that is your
    key's scope set, not a fault.
  </Step>

  <Step title="Connect">
    Phonebase speaks MCP over HTTP. Point any MCP client at the hosted control
    plane with one block of config:

    ```json theme={null}
    {
      "mcpServers": {
        "phonebase": {
          "type": "http",
          "url": "https://mcp.phonebase.co/mcp",
          "headers": { "Authorization": "Bearer pbk_<keyId>_<secret>" }
        }
      }
    }
    ```

    With Claude Code that is one command:

    ```bash theme={null}
    claude mcp add --transport http phonebase https://mcp.phonebase.co/mcp \
      --header "Authorization: Bearer $PHONEBASE_API_KEY"
    ```

    Your client's tool list is the connection test: if the phonebase tools
    appear, you are authenticated and in. Which tools appear is decided by your
    key's scopes. To check without a client in the way (the endpoint requires
    an `Accept` header naming both content types):

    ```bash theme={null}
    curl -sS https://mcp.phonebase.co/mcp \
      -H "Authorization: Bearer $PHONEBASE_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      --data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
    ```

    Running against your own machine instead? Local mode and the stdio
    transport are covered in [MCP config](/getting-started/mcp-config).
  </Step>

  <Step title="Drive">
    Every session follows the same four beats: acquire a lease, observe, act
    under that lease, release.

    ```text theme={null}
    list_devices     see what is reachable, and what each device can do
    acquire_device   take an exclusive lease, and keep the leaseId
    screenshot       observe, and keep the frameId
    tap              act, under that lease
    release_device   hand the device back
    ```

    Call `list_devices` first and read the `capabilities` on each device before
    you call anything else. Capabilities differ between devices, and an action
    the hardware cannot perform is refused rather than silently dropped.

    If `list_devices` comes back empty, that is provisioning, not a fault: no
    devices have been attached to your org yet, or your key was narrowed to a
    subset that is itself empty. Add one from **Devices → Add device** in the
    console; that screen names what is available to buy right now.

    An empty list is not how an unreachable device shows up. A device your org
    owns is always listed, and when the control plane cannot currently reach it
    the entry says so in `status` instead of going missing. Two statuses mean
    that, and the difference between them is whether anything answered.
    `offline` is a positive report that the device is not up: its gateway is
    disconnected, or the provider named it and said it was not running.
    `unknown` means nothing answered at all: the provider's fleet listing
    failed or never mentioned this device, or the row names no reachable
    transport. Either way the device exists and is yours, and it needs
    attention on the device side rather than a new device.

    Treat anything other than `online` as not drivable. A check written as
    `status === "offline"` silently misses the `unknown` case, which is the
    one you get when the provider stops answering for a device.

    `leaseId` is optional on every action and observation tool: a gesture
    runs under the lease your organization holds, or takes the device for you.
    Coordinates are always on a normalized 0 to 1000 grid, which is why
    the same tap works across screen sizes. Bounds in the `ui_snapshot` tree are
    already on that grid, so a value you read there can be tapped directly.

    Naming the frame does not put your action on a clock. Take as long as you
    like between the screenshot and the tap, and take more screenshots in
    between if you want to: neither ages a frame out. What the frame buys you
    is the answer to the only question that matters here, reported on every
    action as `others`: did another hand act on this device since you looked,
    and was it a person or an agent.

    When a task needs different hardware, the device you name is the only thing
    that changes. The tool calls do not.
  </Step>

  <Step title="Read the run back">
    The run comes back as evidence rather than as a claim.

    Every successful result carries typed `structuredContent`, shaped by the
    `outputSchema` that tool publishes. Every action returns an outcome
    recording when the device committed it, not merely that the request was
    accepted.

    Pass an `idempotencyKey` with an action and you can look that outcome up
    later with `get_receipt`. Reusing the same key on a retry replays what was
    recorded instead of performing the gesture a second time. A key whose
    action failed terminally is the exception: it replays the failure and asks
    you to retry under a new key. See [Reading a trace](/getting-started/trace).

    Unknown or misspelled parameters are refused by the schema, so a typo fails
    loudly instead of travelling on to a real phone.
  </Step>
</Steps>

## When a first run refuses

The three refusals new integrations actually hit, and what each is telling you:

1. **A tool you expected is missing from the tool list.** That is your key's
   scopes, not a fault: an out-of-scope tool is never registered on your
   connection, so it genuinely does not exist there. The run tools need
   `runs:start` **and** `devices:act`. See [Authentication](/mcp/auth#scopes).
2. **`acquire_device` answers `device_unavailable` with reason `leased`.**
   Leases are exclusive per device across your whole org: someone else holds
   it, or your own earlier session never released. Leases expire on their own,
   so waiting works, and the holder saw `expiresAt` in their acquire response.
   Your organization must hold the device. If you supply a `leaseId`, it must
   match the current lease; a stale or invented id returns `lease_mismatch`
   without moving the phone. Omit the id to use the current lease or, when
   permitted, take a free device. If the org holds none and the gesture cannot
   take one, the refusal is `lease_required`.
3. **`stale_frame`.** The screen changed between your observation and your
   action, and the action was refused before anything physical happened. Your
   idempotency key was not consumed: observe again, decide again, act again
   under the same key.

Everything else (error shapes, every code, retry semantics) is in
[Troubleshooting](/help/troubleshooting) and the [error reference](/mcp/errors).

## Where to go next

<Columns cols={2}>
  <Card title="Tool reference" href="/mcp/reference">
    Every tool, its parameters, and the scope it needs.
  </Card>

  <Card title="Device tiers" href="/devices/tiers">
    What the control plane can reach, and how faithful each tier is.
  </Card>

  <Card title="CLI" href="/cli/overview">
    The same control plane from a terminal, published to npm as `phonebase`.
  </Card>

  <Card title="Agent tier" href="/agent/overview">
    Hand a goal to a worker instead of driving each step yourself.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.