Barbara API SDK for Python
Official Python SDK for the Barbara Edge AI platform API.
Typed, synchronous and asynchronous clients for managing nodes, clusters, applications, models, and related resources.
Overview
The Barbara API covers the full platform: nodes, clusters, applications, models, and everything needed to configure them, over plain HTTP. This SDK wraps it in a typed client, so you get autocomplete, structured response objects, and automatic token refresh instead of raw requests calls and hand-rolled JSON payloads. A synchronous client and an asyncio-native client expose the same methods, so you can start with BarbaraClient and move to AsyncBarbaraClient later without relearning the API.
Contents: Requirements · Installation · Authentication · Quick start · Usage · Error handling · Architecture · API reference · Examples · Roadmap · Contributing · License
Requirements
- Python 3.9 or later
Installation
pip install barbara-api-sdk
Authentication
The SDK authenticates against the Barbara API using OAuth2 password grant. You need four credentials, referred to as Barbara API Credentials:
| Credential | Description |
|---|---|
BBR_API_USERNAME |
Your Barbara Panel username |
BBR_API_PASSWORD |
Your Barbara Panel password |
BBR_API_CLIENT_ID |
OAuth2 client ID, provided by Barbara |
BBR_API_CLIENT_SECRET |
OAuth2 client secret, provided by Barbara |
[!NOTE] Panel credentials can be created at onboarding.barbara.tech. Client credentials are issued by Barbara support.
By default, the client reads credentials from environment variables:
export BBR_API_USERNAME="..."
export BBR_API_PASSWORD="..."
export BBR_API_CLIENT_ID="..."
export BBR_API_CLIENT_SECRET="..."
from barbara import BarbaraClient
client = BarbaraClient.from_env()
Additional optional environment variables:
| Variable | Description | Default |
|---|---|---|
BBR_API_URL |
Barbara API base URL | https://prod.bap.barbara.tech |
BBR_AUTH_URL |
Barbara auth server base URL | https://prod.auth.barbara.tech/auth |
BBR_REALM |
Authentication realm | bbr_prod |
Credentials can also be supplied explicitly instead of through environment variables:
from barbara import BarbaraClient, BarbaraConfig
config = BarbaraConfig(
client_id="...",
client_secret="...",
username="...",
password="...",
)
client = BarbaraClient(config)
Quick start
from barbara import BarbaraClient
with BarbaraClient.from_env() as client:
for node in client.nodes.list():
print(node.node_name, node.status)
node = client.nodes.resolve("my-node-01")
Async usage
AsyncBarbaraClient mirrors BarbaraClient method for method: only await differs.
import asyncio
from barbara import AsyncBarbaraClient
async def main():
async with AsyncBarbaraClient.from_env() as client:
nodes = await client.nodes.list()
asyncio.run(main())
Usage
Nodes
See Node management in Academy.
nodes = client.nodes.list(search="sensor")
node = client.nodes.get("<node-id>")
node = client.nodes.resolve("my-node-01") # look up by node name
nodes = client.nodes.list_by_tags(["production", "line-3"]) # any of the given tags
client.nodes.reboot("<node-id>")
client.nodes.poweroff("<node-id>")
Node global secrets
Barbara-managed secrets shared by every workload on the node — distinct from a Marketplace app's own App Secrets and from Docker-native Swarm Secrets. See Secrets in Academy.
client.nodes.create_global_secrets("<node-id>", {"wifi-psk": "s3cr3t"})
secrets = client.nodes.list_global_secrets("<node-id>")
client.nodes.delete_global_secret("<node-id>", "<secret-id>")
Node global configuration
Corresponds to the Panel's Global Config (node-scoped; see Application configuration types for the full picture — a workload's own App Config is set through client.nodes.workloads, below).
client.nodes.set_global_config("<node-id>", config={"threshold": 5})
config = client.nodes.get_global_config("<node-id>")
Docker credentials
client.nodes.create_docker_credentials(
"<node-id>", [{"user": "bob", "password": "s3cr3t", "server": "docker.io"}]
)
creds = client.nodes.list_docker_credentials("<node-id>")
print(creds[0].server, creds[0].user) # password is never returned by the API
Node identity: name, tags, location, safety actions
See General info in Academy.
client.nodes.update_name("<node-id>", "floor-2-sensor-01")
client.nodes.add_tag("<node-id>", "production")
client.nodes.set_location("<node-id>", lat=40.4168, lng=-3.7038, city="Madrid")
location = client.nodes.get_location("<node-id>")
client.nodes.update_safety_actions(
"<node-id>", trigger_threshold=90, stop_apps=True, prune_volumes=True
)
[!NOTE]
get_locationis returned as a raw dict rather than a typed object, since node location payloads vary in shape.
[!WARNING]
set_locationcurrently returns a500 Internal Server Errorregardless of the payload sent. Use the Panel to update a node's location until this is resolved.
Barbara Core updates
Barbara Core is the single versioned package that bundles a node's OS and Node Manager together (e.g. Barbara Core 1.10.1.471) — Panel's own update modal shows one version number, not two separate firmwares. See Barbara Core updates in Academy.
client.nodes.update_barbara_core("<node-id>", "update")
client.nodes.update_barbara_core(
"<node-id>", "schedule", schedule_timestamp="2026-01-01T03:00:00Z"
)
client.nodes.cancel_barbara_core_update("<node-id>")
Docker maintenance and volumes
client.nodes.prune_docker("<node-id>", "prunevolumes")
client.nodes.prune_docker_all("<node-id>")
client.nodes.restart_docker_daemon("<node-id>")
client.nodes.create_docker_volume("<node-id>", "shared-cache")
volumes = client.nodes.list_docker_volumes("<node-id>")
client.nodes.delete_docker_volume("<node-id>", volumes[0]["_id"])
See Volumes in Academy.
[!NOTE]
create_docker_volumedoesn't return an id. Uselist_docker_volumesto look one up before callingdelete_docker_volume.
Telemetry
See Telemetry in Academy.
latency = client.nodes.get_telemetry_latency("<node-id>")
client.nodes.set_telemetry_latency("<node-id>", 30)
telemetry = client.nodes.get_last_telemetry("<node-id>")
print(telemetry["disk"], telemetry["alive"])
Node workloads
Deploy and manage applications running on a node: Docker apps (bring-your-own image), Marketplace apps, and Model deployments. See Docker apps and Marketplace apps in Academy.
client.nodes.workloads.create_docker_workload(
"<node-id>",
app_version_id="<app-version-id>",
application_id="<application-id>",
)
client.nodes.workloads.create_marketplace_workload(
"<node-id>",
app_version_id="<app-version-id>",
application_id="<application-id>",
name="my-workload",
compose_config=[{"name": "modelservice", "ports": {"PORT_NUMBER": "9083"}}],
)
client.nodes.workloads.start("<node-id>", "<workload-id>")
client.nodes.workloads.stop("<node-id>", "<workload-id>")
logs = client.nodes.workloads.get_logs("<node-id>", "<workload-id>")
[!NOTE] Creation and update calls do not return the resulting workload state. Call
client.nodes.workloads.get(...)afterwards if you need it.
[!NOTE]
compose_configis a Marketplace/Model-only concept — it's the workload's Compose Config (ports/env/volumes), rewritten into itsdocker-compose.ymlat deploy time. Docker workloads control their own compose file directly, socreate_docker_workload/update_docker_workloadtake nocompose_configargument.
A workload's own App Config (as opposed to the node-scoped Global Config above):
client.nodes.workloads.set_app_config("<node-id>", "<workload-id>", config={"threshold": 5})
config = client.nodes.workloads.get_app_config("<node-id>", "<workload-id>")
Model workloads
Same body shape as marketplace workloads, deploying a model application version instead. compose_config must exactly match the service template declared by that model version. See Models in Academy.
client.nodes.workloads.create_model_workload(
"<node-id>",
app_version_id="<model-app-version-id>",
application_id="<model-application-id>",
name="my-model-workload",
compose_config=[{"name": "modelservice", "ports": {"PORT_NUMBER": "8501"}}],
)
Node network
The Networking card on the Node Details page in Panel: physical interfaces (Ethernet, WiFi, Mobile), VLANs, hostname, and the Advanced section (VPN, Proxy, Standalone Mode, IPTables). See Network Card overview in Academy.
network = client.nodes.network.get("<node-id>")
client.nodes.network.update_ethernet_interface("<node-id>", "eno1", dhcp=True)
client.nodes.network.enable_proxy("<node-id>", url="http://proxy.example.com:8080")
client.nodes.network.enable_vpn("<node-id>")
client.nodes.network.start_vpn("<node-id>")
[!NOTE]
hostnameandiptableshave no dedicatedGETendpoint on the wire either —get_hostname/get_iptablesread them back from the node's own document instead (same oneclient.nodes.get()returns), decoding what the wire base64-encodes.
hostname = client.nodes.network.get_hostname("<node-id>")
client.nodes.network.set_hostname("<node-id>", "floor-2-sensor-01")
iptables = client.nodes.network.get_iptables("<node-id>") # {"iptables_conf": ..., "iptables_id": ...}
client.nodes.network.update_iptables("<node-id>", iptables_conf="-A INPUT -j ACCEPT")
[!NOTE]
create_vlan_interfacerequiresparent_iface_name(the physical interface the VLAN sits on) — this field is entirely missing from the Barbara OpenAPI spec, confirmed instead against Panel's own request payload. It's also the URL path used for creation;delete_vlan_interface/vlan_interface_existsinstead key off the VLAN's ownname, not the parent's — the two are not interchangeable.
client.nodes.network.create_vlan_interface(
"<node-id>",
"eno1", # parent physical interface
vlan_id=100,
name="vlan100", # the new VLAN's own name
dhcp=True, ip="", dns="", gateway="", metric=10,
auto_dns=True, ip_aliases=[], dns_aliases=[],
)
client.nodes.network.vlan_interface_exists("<node-id>", "vlan100")
client.nodes.network.delete_vlan_interface("<node-id>", "vlan100")
[!NOTE] There is no
GET/list endpoint for NTP servers —create_ntp_serverdoesn't return the created entry's id either. Read the current list (including each entry's id, needed forupdate_ntp_server/delete_ntp_server) fromclient.nodes.get("<node-id>").raw["deviceConfig"]["ntpServers"]instead; each entry'ssystemServerflag marks Barbara's own default servers.
Every write in this resource does a best-effort, non-blocking check of the caller's token role before sending the request, and emits a UserWarning if it looks insufficient — the API's own response is always the final authority.
Clusters
See Clusters in Academy.
clusters = client.clusters.list()
cluster = client.clusters.get("<cluster-id>")
cluster = client.clusters.resolve("floor-2-cluster") # look up by cluster name
client.clusters.update("<cluster-id>", "new-name")
client.clusters.create_global_secrets("<cluster-id>", {"db-password": "s3cr3t"})
client.clusters.set_global_config("<cluster-id>", config={"threshold": 5})
Creating a cluster and managing membership
client.clusters.create(
"floor-2-cluster",
primary_node={
"nodeId": "<node-id>",
"labels": "eyJ6b25lIjogImZsb29yLTIifQ==", # base64 JSON: {"zone": "floor-2"}
"restrictSwarmTrafficToInterface": False,
"advertiseAddr": "10.0.0.5",
},
enable_cluster_volumes=True,
)
client.clusters.join_node(
"<cluster-id>",
"<node-id>",
labels={"zone": "floor-2"},
restrict_swarm_traffic_to_interface=False,
advertise_addr="10.0.0.6",
)
client.clusters.pause_node("<cluster-id>", "<node-id>")
client.clusters.drain_node("<cluster-id>", "<node-id>")
client.clusters.set_node_active("<cluster-id>", "<node-id>")
client.clusters.leave_node("<cluster-id>", "<node-id>")
[!NOTE]
primary_nodeandsecondary_nodestake the cluster networking configuration as dicts matching the API schema.
Cluster-wide Docker volumes
Unlike node-level Docker volumes, these give a cluster's workloads Swarm-managed high availability — a workload that needs a volume to survive a node failing over must use one of these.
client.clusters.create_swarm_volume("<cluster-id>", "shared-cache")
client.clusters.delete_swarm_volume("<cluster-id>", "<volume-id>")
Docker-native objects declared in an app's own docker-compose.yml (Swarm Config/Swarm Secrets, distinct from the Barbara-managed Global Config/Global Secrets above) are cleaned up the same way: delete_all_swarm_configs, delete_swarm_config, delete_all_swarm_secrets, delete_swarm_secret.
Cluster workloads
The cluster-level equivalent of node workloads: deploy an application across every node in a cluster. Barbara's product docs only ever call this "Workload" — there's no separate "stack" concept in Panel, at either scope. See Add applications in Academy.
client.clusters.workloads.create_docker_workload(
"<cluster-id>",
app_version_id="<app-version-id>",
application_id="<application-id>",
)
client.clusters.workloads.delete("<cluster-id>", "<workload-id>")
Model workloads work the same way, using create_model_workload:
client.clusters.workloads.create_model_workload(
"<cluster-id>",
app_version_id="<model-app-version-id>",
application_id="<model-application-id>",
name="my-model-workload",
compose_config=[{"name": "modelservice", "ports": {"PORT_NUMBER": "8501"}}],
)
Applications
See App Library in Academy.
apps = client.applications.list()
client.applications.create(
"edge-app", "Long description", "Barbara", docker=True, icon_path="./icon.png"
)
client.applications.create_version(
"<application-id>", "./app-v1.tar", "1.0.0", ["amd64"], ["Initial release"]
)
Deploying a Marketplace/Model app from its published template
A Marketplace or Model workload's compose_config/app_secrets must echo back every service declared in the app version's template, or the API rejects the request — default_services builds that starting point from the template's own defaults, ready to hand to create_marketplace_workload/create_model_workload as-is or after overriding individual ports/volumes/env:
app_version = client.applications.get_version("<application-id>", "<app-version-id>")
compose_config, app_secrets = client.applications.default_services(app_version)
client.nodes.workloads.create_marketplace_workload(
"<node-id>",
app_version_id="<app-version-id>",
application_id="<application-id>",
name="my-workload",
compose_config=compose_config,
app_secrets=app_secrets,
)
[!TIP]
createandcreate_versionupload files (icon, installable artifact) asmultipart/form-data. Pass a local file path, and the SDK reads the file and builds the request for you.
Models
See Models in Academy.
models = client.models.list()
client.models.create(
"anomaly-detector", "Long description", "Barbara", model_type=0, engine=0
)
client.models.create_version("<model-id>", "./model.onnx", "1.0.0", ["Initial release"])
[!TIP]
sha256andsizefor a model version are computed automatically from the artifact, so you don't need to pass them yourself.
Config Repository
Reusable, named configuration documents — typed application or global — that a workload's own App Config or a node's/cluster's own Global Config can reference by id (config_id), rather than being one itself. See Application configuration types in Academy.
config = client.configs.create(
name="sensor-thresholds",
description="Per-node alert thresholds",
config={"temperature_max": 80},
)
print(config.config_type) # "application" or "global"
client.nodes.set_global_config("<node-id>", config_id=config.id)
Groups
See Nodes list in Academy for group management in the Panel.
group = client.groups.create(
name="floor-2-sensors",
description="All floor 2 nodes",
node_ids=["<node-id-1>", "<node-id-2>"],
)
Users
See Organization in Academy.
users = client.users.list()
page = client.users.paginate(offset=0, size=50)
print(page.items[0].role) # UserRole.ADMINISTRATOR / SUPERVISOR / EDITOR / VIEWER, or None
Alerts
See the Alert Manager app in Academy.
alerts = client.alerts.list()
client.alerts.ack("<alert-id>")
events = client.alerts.list_events(node_id="<node-id>")
Error handling
All API errors raise a subclass of BarbaraApiError:
from barbara import BarbaraApiError, BarbaraAuthError, BarbaraNotFoundError, BarbaraPermissionError
try:
client.nodes.resolve("unknown-node")
except BarbaraNotFoundError:
...
except BarbaraPermissionError:
...
except BarbaraApiError as e:
print(e.status, e.body)
Architecture
- One client, one resource tree.
BarbaraClientandAsyncBarbaraClientexpose the same resources (.nodes,.clusters,.applications, ...) with identical method signatures. - Automatic token refresh. A request that receives a
401is retried once with a freshly fetched token. - Typed models. Response entities are plain dataclasses. Every entity keeps the original API payload in
.raw. - Typed exceptions.
BarbaraNotFoundError,BarbaraAuthError, andBarbaraPermissionErrorsubclassBarbaraApiErrorso callers can handle specific failure modes. - Product naming throughout. Classes, methods, and fields follow Barbara's own product terminology (Node, Workload, App Config vs. Global Config, Barbara Core, ...) rather than internal API/wire jargon — see the Barbara Academy docs linked throughout this README for the concepts behind each resource.
- An escape hatch for everything else. Every resource method calls
client.request(method, path, ...)internally, and the same authenticated, token-refreshing request method is available directly for any endpoint not yet wrapped by a typed resource. See the examples. When buildingpathyourself, percent-encode any value that isn't a fixed literal (urllib.parse.quote(value, safe="")). Every typed resource method does this for its own id parameters, butclient.request(...)takespathas-is.
API reference
| Resource | Description |
|---|---|
client.nodes |
Node lifecycle, global secrets, global config, docker credentials, Barbara Core updates, and actions (reboot, provision, ...) |
client.nodes.workloads |
Docker/Marketplace/Model applications deployed on a node |
client.nodes.network |
The Networking card — interfaces, hostname, VLANs, VPN, Proxy, Standalone Mode, IPTables, NTP servers |
client.clusters |
Cluster lifecycle, global secrets, global config, docker credentials, and Swarm-managed volumes |
client.clusters.workloads |
Docker/Marketplace/Model applications deployed across a cluster |
client.applications |
Application catalog and versions |
client.models |
Model catalog and versions |
client.configs |
Config Repository — reusable, named configuration documents |
client.groups |
Node groups |
client.users |
Company users (read-only) |
client.alerts |
Alerts and alert events |
Full generated API reference (every method, parameter, and return type): barbaraedge.github.io/barbara-api-sdk-python. For the underlying HTTP API itself, see the Barbara API documentation.
Examples
The examples/ directory has complete, runnable scripts for common use cases:
| Script | Description |
|---|---|
hello_world.py |
The first script to run: confirm your credentials work and list your nodes |
node_info.py |
Read a node's configuration and latest telemetry |
check_barbara_core_updates.py |
Check nodes for an outdated Barbara Core, optionally update them |
network_audit.py |
Audit a node's network config (interfaces, hostname, VPN, proxy, iptables, NTP), optionally add an NTP server |
clone_node.py |
Clone a node's workloads, config, and docker volumes onto another node |
clone_tool.py |
Back up a node's configuration to a file, restore it onto one or more nodes, or wipe a node |
deployment_dashboard.py |
Live terminal dashboard monitoring a fleet against a target deployment state |
Some scripts also demonstrate calling an endpoint through client.request(...) directly (the same low-level method every typed resource is built on) for functionality this SDK doesn't wrap yet.
See examples/README.md for what each one covers, how to configure and run it, and ideas for extending it.
Roadmap
The following areas of the Barbara API are not yet covered by this SDK:
- App Secrets (Marketplace-only, per-app secrets, distinct from a node's Global Secrets)
Contributing
Bug reports and pull requests are welcome on GitHub Issues. See RELEASING.md for the branch model and release process this repository follows.
License
Distributed under the MIT License. See LICENSE for details.
Release files for barbara-api-sdk 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| barbara_api_sdk-0.6.0.tar.gz | 139.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| barbara_api_sdk-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 200.8 kB
Release files / barbara_api_sdk-0.6.0.tar.gz
| Download URL | barbara_api_sdk-0.6.0.tar.gz |
|---|---|
| Size | 139.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
700b2656911ff184f1c3edd5cbe2604cd16c02d4e612c9021be5f96bf79723fd
|
|
BLAKE2b-256 checksum How to use checksums |
454c3fe084a08ddb6deb963f02cd8d810db72ddb27ba77bfe11c497f43199f29
|
| 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 3, 2026.
Transparency logRelease files / barbara_api_sdk-0.6.0-py3-none-any.whl
| Download URL | barbara_api_sdk-0.6.0-py3-none-any.whl |
|---|---|
| Size | 61.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cda5ced7e14e3df0cb82dbd190759573b236adbf9bc0eef12860b860905c65e7
|
|
BLAKE2b-256 checksum How to use checksums |
c524016ce3508f5fdd4ecdfe94a4dd345d358d0a760f4b2e6d56340bb20bf529
|
| 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 3, 2026.
Transparency log