# Connecting

Connecting takes two steps: ask REST for a socket URL, then open a WebSocket to it. Runnable
SDK examples are at the bottom of this page. Without an SDK, see
[Writing a client](/websocket/raw-client).

## 1. Get a socket URL

Call `GET /v1/phone-controller` with your API token:

```bash
curl https://0bull.net/api/v1/phone-controller \
  -H "Authorization: Bearer <your-token>" \
  -H "Accept: application/json"
```

The reply holds everything you need to connect:

```json
{
  "phones": [
    {
      "slot": "b3f1c0de-0000-4000-8000-000000000001",
      "name": "slot4",
      "video_live": true,
      "input_present": true,
      "can_control": true,
      "model": "iPhone 13",
      "os_version": "17.4"
    }
  ],
  "socket_url": "wss://0bull.net/farm-ws?t=<short-lived-token>",
  "farm_online": true
}
```

- **`phones`** lists what your account can see right now. Each phone is keyed **`slot`**, the
  UUID every phone fun takes. `name` is a label for people, and passing it where a slot is
  wanted matches no phone. `can_control` tells you whether this token may send input to it.
- **`socket_url`** is the address to connect to. Use it as given, including its query string.
- **`farm_online`** is `false` when the phones cannot be reached at the moment. You still get
  your list, with `video_live` and `input_present` reported as `false`.

Live video is not part of this API. Viewing a phone screen is a dashboard feature.

## 2. Connect within 10 minutes

`socket_url` carries a short-lived token, good for **10 minutes**. It is scoped to your
account, to the phones you may view or control, and to your API token's abilities. Connect
before it expires. After that, call `/v1/phone-controller` again for a fresh URL.

The socket token is checked **only at connect**. That is the one thing it does. An open
connection keeps working long after the 10 minutes are up, for hours if you like. The
connection's own lifetime is not the token's.

Every fun you call still re-checks the **API token** behind the connection. Revoke that
token, let it expire, or narrow its abilities, and calls on an already-open connection start
failing with `403`.

`401` never appears on the socket. A missing or invalid API token stops you at the REST call,
before there is anything to connect to.

## 3. Stay connected

Send WebSocket pings on an interval, around every 20 seconds, so idle connections are not
dropped by anything in between. Both SDKs do this for you.

## 4. Reconnect with a fresh URL

When the connection drops, mint a new `socket_url` and connect again. Do not reuse the old
one: its token is minted per session and will usually have expired.

**Nothing is replayed.** Events that fired while you were away are gone. After a reconnect,
read back the state you care about rather than assuming you saw every change:

- a run, with `/app/phones/runs/get`
- a submission, with `/app/submissions/get`
- a billing request, with `/app/billing/requests/get`

Both SDKs reconnect on their own with a fresh URL. Calls that were in flight when the
connection dropped fail rather than being sent again, so retry those yourself.

## With the SDK

`client.socket()` returns an unconnected socket. In Python, entering it connects and leaving
it closes. In TypeScript, the socket starts unconnected, so `await connect()` before using
it, and close it when you are done.

Install with `pip install 0bull`, or with `npm install @0bull/sdk` on Node 22 or newer, and
set `ZEROBULL_API_TOKEN` so the client finds your token. See the
[Python SDK](/sdks/python) and [TypeScript SDK](/sdks/typescript) pages.

<CodeTabs syncKey="lang">

```python title="Python"
from zerobull import ZeroBull

with ZeroBull() as client:
    with client.socket() as socket:
        for phone in socket.phones.list():
            print(phone.slot, phone.name, phone.can_control)
```

```ts title="TypeScript"
import { ZeroBull } from "@0bull/sdk";

const client = new ZeroBull();
const socket = client.socket();
await socket.connect();

try {
  for (const phone of await socket.phones.list()) {
    console.log(phone.slot, phone.name, phone.can_control);
  }
} finally {
  socket.close();
}
```

</CodeTabs>

### The async Python client

It has the same methods. `ping_interval` is 20 seconds by default on both clients.

```python
import asyncio

from zerobull import AsyncZeroBull


async def main() -> None:
    async with AsyncZeroBull() as client:
        async with client.socket(ping_interval=20) as socket:
            for phone in await socket.phones.list():
                print(phone.slot, phone.name, phone.can_control)


asyncio.run(main())
```

## Next

- [Writing a client](/websocket/raw-client): the same connection in plain frames, no SDK.
- [Frames and replies](/websocket/frames): what to send and how to read what comes back.
- [Events](/websocket/events): the pushes that arrive without being asked for.
- [Phones](/websocket/phones): the funs that drive a phone.
