---
title: "Node identity, tags, groups & safety actions"
sidebar_label: "Node identity & tags"
description: "Organize your fleet with the SDK: node name, location, tags, groups, and automatic disk-pressure safety actions, field by field."
lastmod: "2026-09-03"
---

import { SdkVersionNotice } from '@site/src/components/VersionNotice';
import { UpdateInProgressNotice } from '@site/src/components/VersionNotice';
import ConceptLink from '@site/src/components/ConceptLink';

<SdkVersionNotice sdkArticleVersion="v0.6.0" />
<!--
<UpdateInProgressNotice />
-->

## Overview

Before you can target a subset of your fleet, you need a way to describe it. The SDK gives you two independent tools for that: **tags**, free-form labels on a single node, and **groups**, named collections of nodes with their own `id`. Alongside those, [`client.nodes`](/api/barbara-api-sdk-python/reference/nodes/) also covers a node's identity (name, GPS location) and its **safety actions**, an automatic reaction to disk pressure. All four are the same data you'd otherwise edit from the [General Info](/platform/node-lifecycle/general-info/) card on the Node Details page.

:::info[Concept]
**Safety Actions.** An automatic reaction Barbara Panel can trigger when a node's disk usage crosses a threshold: stopping workloads and/or pruning docker resources on their own, before a human needs to step in.

<ConceptLink to="/platform/node-lifecycle/advanced-actions/" />
:::

Every method below takes a `node_id`: the internal 24-character `id` field on the [`Node`](/api/barbara-api-sdk-python/reference/models/#node) object returned by [`client.nodes.list()`](/api/barbara-api-sdk-python/reference/nodes/#list) or [`client.nodes.resolve(node_name)`](/api/barbara-api-sdk-python/reference/nodes/#resolve), covered in [Getting started](/api/barbara-api-sdk-python/guides/getting-started/). None of the examples that follow re-derive it, assume `node = client.nodes.resolve("<barbara-id>")` has already run, and use `node.id` wherever `node_id` is expected.

## Rename a node

```python
client.nodes.update_name(node.id, "floor-2-sensor-01")
```

[`update_name(node_id, name)`](/api/barbara-api-sdk-python/reference/nodes/#update_name) takes a plain string for `name` and returns `None`. This is the same value shown as the **Node Name** column in the [Nodes List](/platform/node-lifecycle/nodes-list/) and editable inline from the page header of Node Details, so pick a name meaningful for search and filtering, since it's what `client.nodes.list(search=...)` and `resolve()` both match against.

:::note[Name vs. Barbara ID]
Renaming a node **only changes its display name**. It does not change the node's Barbara ID (`deviceName`), the factory-assigned identifier used by [`add_node`](/api/barbara-api-sdk-python/reference/nodes/#add_node) and shown in [Get the Barbara ID of the node](/platform/install-barbara-core/finding-your-nodes-barbaraid/).
:::

## Tag nodes

Tags are per-node labels, useful for quick filtering without setting up a group, the same free-form values you'd type into the **Tags** field of the [General Info](/platform/node-lifecycle/general-info/) card.

```python
client.nodes.add_tag(node.id, "production")
client.nodes.update_tags(node.id, ["production", "floor-2"])
client.nodes.delete_tag(node.id, "production")
```

[`add_tag(node_id, tag)`](/api/barbara-api-sdk-python/reference/nodes/#add_tag) **appends** a single string `tag` to the node's existing tag list, without touching the others. [`update_tags(node_id, tags)`](/api/barbara-api-sdk-python/reference/nodes/#update_tags) is different in kind: it takes a full `List[str]` and **replaces the node's entire tag list** with it, so pass every tag you want to keep, not just the new one, or the ones you omit are removed. [`delete_tag(node_id, tag)`](/api/barbara-api-sdk-python/reference/nodes/#delete_tag) removes exactly one tag by its string value; there's no numeric tag id to look up first, since a tag is just a string.

To read the current tags back, fetch the node and read its raw payload, since `Node` doesn't expose a typed `tags` field:

```python
node = client.nodes.get(node.id)
print(node.raw["tags"])
```

To go the other way, from a set of tags to the nodes carrying them:

```python
production_nodes = client.nodes.list_by_tags(["production", "floor-2"])
```

[`list_by_tags(tags)`](/api/barbara-api-sdk-python/reference/nodes/#list_by_tags) returns **every node carrying at least one** of the given tags.

:::note[No server-side tag filter]
There's no server-side tag filter on the underlying list endpoint (only `search`/`groupIds`), so this pages through the whole fleet with `paginate()` and filters locally on each node's own tags. Fine for selecting a target set of nodes, **not meant for a hot path** against a very large fleet.
:::

## Group nodes

Unlike tags, a group is its own named entity with an id, useful when you want to reference "these five nodes" as a single unit elsewhere in your scripts, the same collections that populate the **Group** filter on the [Nodes List](/platform/node-lifecycle/nodes-list/).

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

[`create(name, description, node_ids, *, user_ids=None)`](/api/barbara-api-sdk-python/reference/groups/#create) on [`client.groups`](/api/barbara-api-sdk-python/reference/groups/) takes:

| Parameter | Type | What to pass |
|---|---|---|
| `name` | `str` | The group's display name, shown in Panel's Group dropdown and filter |
| `description` | `str` | Free text describing what the group is for |
| `node_ids` | `List[str]` | The internal `id` of every node to add, from `client.nodes.list()`/`resolve()` |
| `user_ids` | `Optional[List[str]]` | Barbara Panel user ids to associate with the group, from `client.users.list()` |

It **returns the created [`Group`](/api/barbara-api-sdk-python/reference/models/#group) directly**, so you can read `group.id` immediately without a follow-up `get()`. [`update(group_id, name, description, node_ids, *, user_ids=None)`](/api/barbara-api-sdk-python/reference/groups/#update) takes the same five parameters plus the `group_id` to update, and, like `update_tags`, **replaces the full membership list**, so include every node you want the group to keep, not only the ones you're adding. [`delete(group_id)`](/api/barbara-api-sdk-python/reference/groups/#delete) removes the group entirely; **it does not delete the nodes in it**.

```python
group = client.groups.update(
    group.id, "floor-2-sensors", "All floor 2 nodes, updated", ["<node-id-1>", "<node-id-2>", "<node-id-3>"],
)
client.groups.delete(group.id)
```

To see which groups a single node belongs to, call [`list_groups`](/api/barbara-api-sdk-python/reference/nodes/#list_groups) on `client.nodes` instead of `client.groups`:

```python
groups = client.nodes.list_groups(node.id)
```

:::note[Two different "groups"]
`list_groups` returns a `List[Group]`, since a node can be added to **more than one** group's `node_ids` list independently. This is a different question from the single **Group** dropdown on the General Info card: that field sets a node's *primary* group, while a node can additionally appear in any number of groups created through `client.groups.create()`.
:::

A group is purely organizational: it doesn't imply a shared deployment target the way a [cluster](/api/barbara-api-sdk-python/guides/clusters-and-stacks/) does. See [`client.groups`](/api/barbara-api-sdk-python/reference/groups/) for the full method list.

## Set a node's location

```python
client.nodes.set_location(node.id, lat=40.4168, lng=-3.7038, city="Madrid")
location = client.nodes.get_location(node.id)
```

[`set_location(node_id, *, lat, lng, country=None, city=None, region=None)`](/api/barbara-api-sdk-python/reference/nodes/#set_location) mirrors the map picker behind the **+** button next to **Location** on the [General Info](/platform/node-lifecycle/general-info/) card: `lat` and `lng` are the coordinates as floats, and `country`, `city`, `region` are optional descriptive strings stored alongside them. [`get_location(node_id)`](/api/barbara-api-sdk-python/reference/nodes/#get_location) returns a raw dict rather than a typed object, since node location payloads vary in shape depending on how the location was set.

```python
client.nodes.delete_location(node.id)
```

:::warning[Known issue]
`set_location` currently **returns a `500 Internal Server Error`** from the API regardless of the payload sent. Until this is fixed upstream, update a node's location from the [General Info](/platform/node-lifecycle/general-info/) card in Panel instead of `set_location`; `get_location` and `delete_location` are unaffected.
:::

## Configure automatic safety actions

In the product, a node under sustained disk pressure eventually can't pull new images or write logs, and the operator may not be watching a dashboard at the moment it happens. [`update_safety_actions`](/api/barbara-api-sdk-python/reference/nodes/#update_safety_actions) **reacts to that automatically**, the scripted equivalent of Panel's **Safety Actions** checkboxes: above `trigger_threshold`, the node can **stop its apps** and/or **prune docker resources** on its own. This is a separate, automatic mechanism from the manual **Docker Prune** actions in [Advanced Actions](/platform/node-lifecycle/advanced-actions/), which an operator triggers by hand rather than a disk-usage threshold.

```python
client.nodes.update_safety_actions(
    node.id, trigger_threshold=90, stop_apps=True, prune_volumes=True
)
```

| Parameter | Type | What it does |
|---|---|---|
| `trigger_threshold` | `float` | Disk usage percentage that triggers the action, strictly between `0.1` and `99.9` |
| `stop_apps` | `bool` | Stops every workload on the node, the same effect as clicking **Stop** on each [workload card](/api/barbara-api-sdk-python/guides/workload-lifecycle/) |
| `prune_all` | `bool` | Runs every prune operation below in sequence, equivalent to the **Prune All** checkbox |
| `prune_images` | `bool` | Removes unused Docker images, equivalent to the **Prune Images** checkbox |
| `prune_containers` | `bool` | Removes stopped containers, equivalent to the **Prune Containers** checkbox |
| `prune_volumes` | `bool` | Removes volumes not attached to any container, equivalent to the **Prune Volumes** checkbox |

:::note[trigger_threshold has no default]
The SDK always sends all five action flags, defaulting the ones you don't pass to `False`, and the API requires **at least one of them to be `True`**, an empty safety config isn't accepted. `trigger_threshold` has **no default**: pass a value every time you call this method, even to update just one of the flags.
:::

:::warning[This can delete data]
`prune_volumes` and `prune_all` **can delete data**. A pruned volume that held application state or logs is gone, the same caveat that applies to the manual **Docker Prune Volumes** action in Advanced Actions. Set thresholds conservatively on nodes running workloads with volumes you care about.
:::

## Summary

You now have the building blocks for organizing a fleet: **rename and locate** a node, **tag** it, **group** it with others, and give it an **automatic safety net** against disk pressure.

None of this requires touching the Barbara Panel by hand, so it scales to provisioning scripts and fleet-wide maintenance jobs alike.

For the full method list, see [`client.nodes`](/api/barbara-api-sdk-python/reference/nodes/) and [`client.groups`](/api/barbara-api-sdk-python/reference/groups/) in the Reference. To store credentials and tokens on the same nodes, continue to [Node & cluster secrets](/api/barbara-api-sdk-python/guides/node-secrets/).
