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.
1. Get a socket URL
Call GET /v1/phone-controller with your API token:
Code
The reply holds everything you need to connect:
Code
phoneslists what your account can see right now. Each phone is keyedslot, the UUID every phone fun takes.nameis a label for people, and passing it where a slot is wanted matches no phone.can_controltells you whether this token may send input to it.socket_urlis the address to connect to. Use it as given, including its query string.farm_onlineisfalsewhen the phones cannot be reached at the moment. You still get your list, withvideo_liveandinput_presentreported asfalse.
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 and TypeScript SDK pages.
The async Python client
It has the same methods. ping_interval is 20 seconds by default on both clients.
Code
Next
- Writing a client: the same connection in plain frames, no SDK.
- Frames and replies: what to send and how to read what comes back.
- Events: the pushes that arrive without being asked for.
- Phones: the funs that drive a phone.

