Skip to main content

0bull

0bull Python SDK

A typed Python client for the 0bull API: manage posting accounts, publish videos, and control phones from Python. See 0bull.net for the product and docs.0bull.net for the full API reference.

Install

pip install 0bull
# or
uv add 0bull

The package is imported as zerobull and requires Python 3.10+.

Authentication

Create a token at 0bull.net/settings/api-tokens, scoped to the abilities your code needs:

Ability Grants
phones:read List phones, snapshots, OCR
phones:control Tap, swipe, hotkey, type, runs
accounts List, create, update, delete posting accounts
submissions List, create, cancel, delete submissions; uploads
billing Billing summary, rentals, phone count requests

Pass the token explicitly or set ZEROBULL_API_TOKEN:

from zerobull import ZeroBull

client = ZeroBull(api_token="...")
# or, with ZEROBULL_API_TOKEN set in the environment:
client = ZeroBull()

A request made with a token missing the required ability raises PermissionDeniedError.

Quickstart (sync)

from zerobull import ZeroBull

with ZeroBull() as client:
    phones = client.phones.list()
    print(phones[0].slot, phones[0].name)

Quickstart (async)

import asyncio
from zerobull import AsyncZeroBull


async def main() -> None:
    async with AsyncZeroBull() as client:
        phones = await client.phones.list()
        print(phones[0].slot, phones[0].name)


asyncio.run(main())

Phones

Coordinates for tap, swipe, and run_command's brightness level are 0-1 fractions of the screen; snapshot and ocr accept an optional width between 120 and 2000. hotkey accepts home, app_switcher, control_center, notifications, back, run_shortcut, enter, backspace, copy, cut, paste, and select_all.

phones = client.phones.list()
slot = phones[0].slot

jpeg: bytes = client.phones.snapshot(slot, width=600)
text: str = client.phones.ocr(slot)

client.phones.tap(slot, fx=0.5, fy=0.9)
client.phones.swipe(slot, fx1=0.5, fy1=0.8, fx2=0.5, fy2=0.2, steps=30)
client.phones.type(slot, "hello")
client.phones.hotkey(slot, "enter")

Runs

Commands, macros, and agent tasks all queue a Run:

run = client.phones.run_command(slot, "brightness", level=0.5)
run = client.phones.run_macro(slot, workflow="post-to-story", params={"caption": "hi"})
run = client.phones.run_agent(slot, "Open Settings and turn on Wi-Fi")

run = client.runs.wait(run, timeout=300)
print(run.status, run.result)

for run in client.runs.list(slot).iter_all():
    print(run.id, run.status)

wait() polls until the run reaches a terminal status (succeeded, failed, cancelled) and raises WaitTimeoutError if timeout elapses first.

Accounts and submissions

from pathlib import Path

account = client.accounts.create(platform="tiktok", handle="@me", slot=slot)

submission = client.submissions.create(account_id=account.id, video=Path("clip.mp4"), caption="hi")
# or video_url="https://...", or upload_id from client.uploads.upload(...)

submission = client.submissions.wait(submission, timeout=900)
client.submissions.cancel(submission.id)
client.accounts.delete(account.id)

for account in client.accounts.list().iter_all():
    print(account.id, account.handle)

submissions.create takes exactly one of video (a local file, ≤200 MB, MP4/MOV), video_url, or upload_id. submissions.wait() polls until a terminal status (published, drafted, failed, cancelled).

.list() returns a Page; iter_all() walks every page, sync or async, without manual paging.

Uploads

Upload a local file once and reuse the resulting id, for example to submit the same video to several accounts:

upload_id = client.uploads.upload("clip.mp4")
submission = client.submissions.create(account_id=account.id, upload_id=upload_id, caption="hi")

Billing

summary = client.billing.summary()
rental = client.billing.start_rental(accept_terms=True, phones=5, country="US")
change = client.billing.request_phone_count(accept_terms=True, add=2)
request = client.billing.get_request(change.request_id)

accept_terms=True confirms the caller has shown the user the terms and privacy policy, including that the rental renews monthly, before the charge is made.

WebSocket

client.socket() returns a Socket (or AsyncSocket) exposing the same resource methods as the REST client, over a connection that's authenticated and reconnected automatically:

from zerobull import RunEvent

with client.socket() as socket:
    socket.phones.tap(slot, fx=0.5, fy=0.5)
    run = socket.phones.run_macro(slot, workflow="post-to-story")
    for event in socket.events():
        if isinstance(event, RunEvent) and event.run.id == run.id and event.run.is_terminal:
            break
async with client.socket() as socket:
    phones = await socket.phones.list()
    async for event in socket.events():
        ...

events() yields RunEvent, SubmissionEvent, and BillingRequestEvent. A local file passed to submissions.create over the socket is uploaded through a signed URL on the owning client's HTTP transport first, then sent as upload_id. user, session, and any request carrying a local file upload are REST-only and raise NotImplementedError on the socket.

On an unexpected disconnect, the socket reconnects with a fresh session URL and exponential backoff, up to max_reconnect_attempts; calls in flight at the time of the disconnect raise SocketClosedError rather than being resent. Use socket.call(fun, data) to invoke a fun the SDK doesn't model yet; it gets the same error mapping as every other call.

Errors

All exceptions subclass ZeroBullError.

Condition Exception
400 BadRequestError
401 AuthenticationError
403 PermissionDeniedError
404 NotFoundError
409 ConflictError
422 ValidationError (with .errors)
429 RateLimitError (after retries, with .retry_after)
502, 503 UnavailableError
≥ 500 otherwise InternalServerError
any other status APIStatusError (base of all status errors)
network failure APIConnectionError
request or call timeout APITimeoutError
socket closed with calls in flight SocketClosedError
wait() timeout WaitTimeoutError
from zerobull import APIStatusError, ValidationError

try:
    client.submissions.create(
        account_id=account.id, video_url="https://example.com/missing.mp4", caption="hi"
    )
except ValidationError as e:
    print(e.errors)
except APIStatusError as e:
    print(e.status, e.message)

Arguments the SDK can validate locally, such as a missing google_email on a YouTube account, raise ValueError before any request is sent; only a request that reaches the server can raise ValidationError or another APIStatusError.

A REST 429 is retried automatically, honoring Retry-After, up to max_retries before RateLimitError is raised. Nothing else is retried automatically.

Configuration

ZeroBull / AsyncZeroBull:

Option Default Environment
api_token required ZEROBULL_API_TOKEN
base_url https://0bull.net/api ZEROBULL_BASE_URL
timeout 30s
max_retries (REST 429) 2
http_client none; an injected httpx.Client / httpx.AsyncClient

client.socket():

Option Default
call_timeout 60s
auto_reconnect True
max_reconnect_attempts 5
ping_interval 20s

Versioning and support

This SDK follows semantic versioning. Before 1.0.0, minor versions may include breaking changes. See CHANGELOG.md for what changed in each release.

Development

uv sync
uv run ruff check .
uv run ruff format .
uv run mypy
uv run pytest

License

MIT

Metadata

Release files for 0bull 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for 0bull 0.3.0
File Size Uploaded Provenance Actions
0bull-0.3.0.tar.gz 137.5 kB With provenance Details for 0bull-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for 0bull 0.3.0
File Interpreter ABI Platform Actions
0bull-0.3.0-py3-none-any.whl Python 3 none any Details for 0bull-0.3.0-py3-none-any.whl

Total release size: 179.1 kB

Release files / 0bull-0.3.0.tar.gz

Download URL 0bull-0.3.0.tar.gz
Size 137.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f1ee14a53f68b73877e409ef86c1b4cc19fab936157c224cae6604bd51e6b3f4
BLAKE2b-256 checksum
How to use checksums
2b295a3391c1b376adabc07c25d7bcdfb305caeed990b471a03c0cf7574a3adb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / 0bull-0.3.0-py3-none-any.whl

Download URL 0bull-0.3.0-py3-none-any.whl
Size 41.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2b8f52dc8fb578156915c59f4d46dcad64119d6cd55e110e2216ed40363b036a
BLAKE2b-256 checksum
How to use checksums
f6e10e3a537cce8ed996235013c2e465de87c19eaa3c99c17dc122af62d7f2a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Security for 0bull 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for 0bull-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page