Metadata-Version: 2.4
Name: barbara-api-sdk
Version: 0.5.0
Summary: Official Python SDK for the Barbara Edge AI platform API
Project-URL: Homepage, https://github.com/Barbaraedge/barbara-api-sdk-python
Project-URL: Documentation, https://barbaraedge.github.io/barbara-api-sdk-python/
Project-URL: Issues, https://github.com/Barbaraedge/barbara-api-sdk-python/issues
Project-URL: Changelog, https://github.com/Barbaraedge/barbara-api-sdk-python/blob/main/CHANGELOG.md
Author-email: Barbara <support@barbara.tech>
License-Expression: MIT
License-File: LICENSE
Keywords: api,barbara,edge-ai,iot,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: bandit>=1.7; extra == 'dev'
Requires-Dist: h2<5,>=4; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
Requires-Dist: ruff>=0.6; extra == 'docs'
Provides-Extra: http2
Requires-Dist: h2<5,>=4; extra == 'http2'
Description-Content-Type: text/markdown

<div align="center">

# Barbara API SDK for Python

**Official Python SDK for the [Barbara](https://barbara.tech) Edge AI platform API.**

Typed, synchronous and asynchronous clients for managing nodes, clusters, applications, models, and related resources.

**[Full documentation](https://barbaraedge.github.io/barbara-api-sdk-python/)**

[![CI](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/ci.yml)
[![Security](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/security.yml)
[![Docs](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/docs.yml/badge.svg?branch=main)](https://barbaraedge.github.io/barbara-api-sdk-python/)
[![PyPI version](https://img.shields.io/pypi/v/barbara-api-sdk?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/barbara-api-sdk/)
[![Python versions](https://img.shields.io/pypi/pyversions/barbara-api-sdk?logo=python&logoColor=white)](https://pypi.org/project/barbara-api-sdk/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Typed](https://img.shields.io/badge/typing-typed-blue.svg)](https://peps.python.org/pep-0561/)

</div>

---

## 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](#requirements) · [Installation](#installation) · [Authentication](#authentication) · [Quick start](#quick-start) · [Usage](#usage) · [Error handling](#error-handling) · [Architecture](#architecture) · [API reference](#api-reference) · [Examples](#examples) · [Roadmap](#roadmap) · [Contributing](#contributing) · [License](#license)

## Requirements

- Python **3.9** or later

## Installation

```bash
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](https://onboarding.barbara.tech). Client credentials are issued by [Barbara support](mailto:support@barbara.tech).

By default, the client reads credentials from environment variables:

```bash
export BBR_API_USERNAME="..."
export BBR_API_PASSWORD="..."
export BBR_API_CLIENT_ID="..."
export BBR_API_CLIENT_SECRET="..."
```

```python
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:

```python
from barbara import BarbaraClient, BarbaraConfig

config = BarbaraConfig(
    client_id="...",
    client_secret="...",
    username="...",
    password="...",
)
client = BarbaraClient(config)
```

## Quick start

```python
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.

```python
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](https://academy.barbara.tech/platform/node-lifecycle/node-management/) in Academy.

```python
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](https://academy.barbara.tech/platform/workload-config/secrets/) in Academy.

```python
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](https://academy.barbara.tech/platform/workload-config/global-config/) (node-scoped; see [Application configuration types](https://academy.barbara.tech/platform/workload-config/app-config-types/) for the full picture — a workload's own App Config is set through `client.nodes.workloads`, below).

```python
client.nodes.set_global_config("<node-id>", config={"threshold": 5})
config = client.nodes.get_global_config("<node-id>")
```

#### Docker credentials

```python
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](https://academy.barbara.tech/platform/node-lifecycle/general-info/) in Academy.

```python
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_location` is returned as a raw dict rather than a typed object, since node location payloads vary in shape.

> [!WARNING]
> `set_location` currently returns a `500 Internal Server Error` regardless 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](https://academy.barbara.tech/platform/node-lifecycle/barbara-core-updates/) in Academy.

```python
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

```python
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](https://academy.barbara.tech/platform/workload-config/volumes/) in Academy.

> [!NOTE]
> `create_docker_volume` doesn't return an id. Use `list_docker_volumes` to look one up before calling `delete_docker_volume`.

#### Telemetry

See [Telemetry](https://academy.barbara.tech/platform/node-lifecycle/telemetry/) in Academy.

```python
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](https://academy.barbara.tech/platform/apps-and-models/docker-apps/) and [Marketplace apps](https://academy.barbara.tech/platform/apps-and-models/marketplace-apps/) in Academy.

```python
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_config` is a Marketplace/Model-only concept — it's the workload's Compose Config (ports/env/volumes), rewritten into its `docker-compose.yml` at deploy time. Docker workloads control their own compose file directly, so `create_docker_workload`/`update_docker_workload` take no `compose_config` argument.

A workload's own App Config (as opposed to the node-scoped Global Config above):

```python
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](https://academy.barbara.tech/platform/apps-and-models/models/) in Academy.

```python
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](https://academy.barbara.tech/platform/network/network/) in Academy.

```python
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]
> `hostname` and `iptables` have no dedicated `GET` endpoint on the wire either — `get_hostname`/`get_iptables` read them back from the node's own document instead (same one `client.nodes.get()` returns), decoding what the wire base64-encodes.

```python
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_interface` requires `parent_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_exists` instead key off the VLAN's own `name`, not the parent's — the two are not interchangeable.

```python
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_server` doesn't return the created entry's id either. Read the current list (including each entry's id, needed for `update_ntp_server`/`delete_ntp_server`) from `client.nodes.get("<node-id>").raw["deviceConfig"]["ntpServers"]` instead; each entry's `systemServer` flag 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](https://academy.barbara.tech/platform/high-availability/clusters/) in Academy.

```python
clusters = client.clusters.list()
cluster = client.clusters.get("<cluster-id>")
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

```python
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_node` and `secondary_nodes` take 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.

```python
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](https://academy.barbara.tech/platform/high-availability/add-applications/) in Academy.

```python
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`:

```python
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](https://academy.barbara.tech/platform/apps-and-models/app-library/) in Academy.

```python
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"]
)
```

> [!TIP]
> `create` and `create_version` upload files (icon, installable artifact) as `multipart/form-data`. Pass a local file path, and the SDK reads the file and builds the request for you.

### Models

See [Models](https://academy.barbara.tech/platform/apps-and-models/models/) in Academy.

```python
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]
> `sha256` and `size` for 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](https://academy.barbara.tech/platform/workload-config/app-config-types/) in Academy.

```python
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](https://academy.barbara.tech/platform/node-lifecycle/nodes-list/) in Academy for group management in the Panel.

```python
group = client.groups.create(
    name="floor-2-sensors",
    description="All floor 2 nodes",
    node_ids=["<node-id-1>", "<node-id-2>"],
)
```

### Users

See [Organization](https://academy.barbara.tech/platform/users/organization/) in Academy.

```python
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](https://academy.barbara.tech/developers/alert-manager/) app in Academy.

```python
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`:

```python
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.** `BarbaraClient` and `AsyncBarbaraClient` expose the same resources (`.nodes`, `.clusters`, `.applications`, ...) with identical method signatures.
- **Automatic token refresh.** A request that receives a `401` is 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`, and `BarbaraPermissionError` subclass `BarbaraApiError` so 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](https://academy.barbara.tech/) 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](#examples). When building `path` yourself, 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, but `client.request(...)` takes `path` as-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](https://barbaraedge.github.io/barbara-api-sdk-python/)**. For the underlying HTTP API itself, see the [Barbara API documentation](https://prod.bap.barbara.tech/documentation/).

## Examples

The [`examples/`](examples/) directory has complete, runnable scripts for common use cases:

| Script | Description |
|---|---|
| [`hello_world.py`](examples/hello_world.py) | The first script to run: confirm your credentials work and list your nodes |
| [`node_info.py`](examples/node_info.py) | Read a node's configuration and latest telemetry |
| [`check_barbara_core_updates.py`](examples/check_barbara_core_updates.py) | Check nodes for an outdated Barbara Core, optionally update them |
| [`network_audit.py`](examples/network_audit.py) | Audit a node's network config (interfaces, hostname, VPN, proxy, iptables, NTP), optionally add an NTP server |
| [`clone_node.py`](examples/clone_node.py) | Clone a node's workloads, config, and docker volumes onto another node |
| [`clone_tool.py`](examples/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`](examples/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`](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](https://github.com/Barbaraedge/barbara-api-sdk-python/issues). See [`RELEASING.md`](RELEASING.md) for the branch model and release process this repository follows.

## License

Distributed under the **MIT License**. See [`LICENSE`](LICENSE) for details.
