Skip to content

Cloud addresses

The BUSY cloud publishes two separate APIs on one host, and they are not interchangeable — a token for one is refused by the other.

Surface Address Token In this library
Device API /busybar bar-scope yes, as cloud mode
Account API / account-scope no

Choosing an environment

A bar can be pointed at a non-production cloud. Switching is rare and applies to everything a process talks to, so the normal way is the environment:

BUSYLIB_CLOUD_URL=https://api.dev.busy.app python your_script.py

Nothing in your code changes: BusyBar(token=...) picks that host up, and the default stays production when the variable is unset.

For the cases where one process needs more than one environment — a test sweep, a tool comparing two clouds — the host can be named on the client instead:

bb = BusyBar(
    addr="https://api.dev.busy.app",
    token="<bar-scope token for that environment>",
    is_cloud=True,
)

is_cloud is what makes that address a cloud. Without it the address is taken for a device, so requests go to /api with the device's token header instead of /busybar with a bearer one, and fail without explaining themselves. Omitting addr still means the configured cloud host, so existing calls are unchanged.

Note that you cannot ask a bar which cloud it uses when you are reaching it through that cloud - you would need the answer to make the connection. In practice whoever points a bar at a non-production environment knows it, and everyone else wants the default.

The helpers below follow the same host, so a bar on a development cloud gets that environment's documentation rather than production's. Both accept an explicit host if you need to ask about another one.

Cloud mode talks to the device API. It is the same set of endpoints a bar serves locally, with /api replaced by /busybar, so every client method works unchanged:

from busylib import BusyBar

bb = BusyBar(token="<bar-scope token>")  # no address means cloud
print(bb.version().api_semver)

Status streaming is the one exception: /api/status/ws is local only, and cloud mode refuses it rather than attempting an upgrade the cloud rejects.

Documentation per firmware version

The device documentation is versioned, selected by firmware version — 1.1.1, not the API version 25.0.0. A bar reports its firmware under status().firmware.version, which gives you the page describing what that particular bar serves:

device_docs_url

device_docs_url(firmware_version: str | None = None, *, host: str | None = None) -> str

Return the device API documentation, optionally for one firmware version.

The cloud keeps the documentation of every published firmware, selected by version rather than by API version - 1.1.1, not 25.0.0. Passing the value from status().firmware.version gives the page describing the endpoints that particular bar actually serves.

device_docs_url() 'https://api.busy.app/busybar/docs' device_docs_url("1.0.2") 'https://api.busy.app/busybar/docs?urls.primaryName=1.0.2'

The same document is available machine-readable, which is how you can ask what a firmware supports without having that bar to hand:

device_spec_url

device_spec_url(firmware_version: str | None = None, *, host: str | None = None) -> str

Return the machine-readable OpenAPI document for a firmware version.

This is the same content the documentation page renders, which makes it the way to ask what a given firmware supports without having that bar to hand.

device_spec_url("1.0.2") 'https://api.busy.app/busybar/openapi.yaml?Name=1.0.2'

Putting the two together, this links a connected bar to its own reference:

from busylib import BusyBar, cloud

with BusyBar("10.0.4.20") as bb:
    firmware = bb.status().firmware
    print(cloud.device_docs_url(firmware.version if firmware else None))

Why these live in code

These addresses move. The cloud host was renamed before launch while this library kept the old name, and cloud mode was unusable for months as a result. Importing them from busylib.cloud means a rename is one edit rather than a search through guides and docstrings.