Library sets and variables
A library set is a named grouping of variables that a consumer fetches as a unit. A variable is a named thing inside one, and it holds one or more values.
The API and the database call it a variable set; the management UI and the import file call it a library set.
They are the same record. This documentation says "library set" in prose and variableset where a path or field
name requires it.
Choosing library sets
A library set is the unit of consumption, so the question to ask is what gets fetched together. Common shapes:
- One per service —
orders-service,billing-service. Each deployment fetches exactly its own. - One shared plus one per service — a
commonset holding logging levels and shared endpoints, fetched alongside the service's own. - One per team — where a team's services share most of their configuration.
Fetching several at once is supported and normal: ?set=1&set=2. Where two sets both hold a variable of the same
name, they are walked in name order and the later one wins.
Variables and values
A variable has a name and a list of values. Each value is a string, a sensitive flag, and a set of tags.
{
"name": "DatabaseConnection",
"variableSetId": 1,
"values": [
{ "value": "Server=localhost;Database=orders", "tags": [] },
{ "value": "Server=prod.internal;Database=orders", "tags": ["Environment/Production"] },
{ "value": "Server=eu-prod.internal;Database=orders", "tags": ["Environment/Production", "Region/EU"] }
]
}
Tags may be given either as "Group/Tag" strings or as numeric tag IDs; both forms work in the same list. A tag
that names nothing is silently skipped rather than refused, so a typo produces a value that matches less
specifically than you expected rather than an error — worth checking with the
preview endpoint after a bulk change.
Value uniqueness
No two values of the same variable may carry the same tag combination. Attempting it is refused with
409 Conflict. Two values with no tags is the same violation: there can only be one default.
Versioning and authorship
Library sets, tag groups and variables all carry:
| Field | Meaning |
|---|---|
version | Incremented on update. Used for optimistic concurrency. |
createdAt / createdById | When and by whom the record was first written |
updatedAt / updatedById | When and by whom it was last changed |
The user IDs are stamped from the JWT. The library set API does not store users, so when it returns a record it
asks the account API to put names to those IDs — via POST /users/names/lookup, which answers only about IDs it
is given. That is why createdBy and updatedBy come back as { id, name } objects alongside the raw
createdById and updatedById.
Every create, update and delete also writes an audit entry.
Reading a library set
curl "$LIBRARY_API/variablesets/1" -H "x-tagshape-auth: $JWT"
{
"id": 1,
"version": 3,
"name": "orders-service",
"description": "Config for the orders service",
"variables": [
{
"id": 1,
"name": "DatabaseConnection",
"variableSetId": 1,
"values": [
{ "id": 1, "value": "Server=localhost;Database=orders", "sensitive": false, "hint": null, "hash": null, "tags": [] },
{ "id": 2, "value": "Server=prod.internal;Database=orders", "sensitive": false, "hint": null, "hash": null,
"tags": [{ "id": 1, "name": "Production", "tagGroupId": 1 }] }
]
}
],
"createdBy": { "id": "…", "name": "Neil Docherty" },
"createdAt": "2026-08-01T10:00:00Z",
"updatedBy": { "id": "…", "name": "Neil Docherty" },
"updatedAt": "2026-08-12T14:22:00Z"
}
Values are returned ordered by ID, so the same variable does not list its values differently between reads.
Sensitive values come back blank unless the caller holds VariableReadSecrets — see
Sensitive values.
Deleting
A library set cascades the whole way down: its variables, and their values. A tag group cascades to its tags. The join rows between a value and its tags cascade from either side, so nothing is left orphaned.