Skip to main content

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.

One thing, two names

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 common set 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.

Access can be scoped to specific sets

A group's VariableList/Read/ReadSecrets/Create/Update/Delete may each be restricted to specific library sets instead of every one in the organization — see Library-set scoping. Resolving a value can pull one in from an ancestor set (see resolution), so reading via /variablevalues needs VariableRead for every set actually reached, not only the ones named in set.

Variables and values​

A variable has a name, a type, whether it is sensitive, and a list of values. Both type and sensitivity are properties of the variable, not of any one of its values — every value of a variable is the same type, and either all of a variable's values are sensitive or none of them are.

Type​

TypeStored inNotes
StringvalueThe default when a variable does not say otherwise.
MultilinevalueText spanning more than one line. Presented as a multi-line editor in the management UI.
NumbernumberValue
BooleanbooleanValueCan never be sensitive, and can never be switched to or from another type once the variable has been saved.
JsonvaluePresented with a JSON-aware editor (Monaco) in the management UI.
Certificatevalue

Every type other than Boolean can be switched to any other non-Boolean type after the variable has been saved — String to Multiline, Json to Certificate, and so on. Boolean is fixed at creation: a variable cannot be created or later changed into Boolean, and a Boolean variable cannot be changed into anything else.

POST /variables
{
"name": "DatabaseConnection",
"variableSetId": 1,
"type": "String",
"sensitive": false,
"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"] }
]
}
POST /variables — a number variable
{
"name": "MaxConnections",
"variableSetId": 1,
"type": "Number",
"values": [
{ "numberValue": 100, "tags": [] }
]
}

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.

Name uniqueness​

Two variables in the same set may not share a name without regard to case — ApiKey and apikey are the same name as far as a set is concerned. Attempting to create or rename one into a collision is refused with 409 Conflict.

This follows from variable substitution: a @[name] marker matches a variable's name case-insensitively too, so two variables differing only by case would be indistinguishable to anything that referenced either of them.

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:

FieldMeaning
versionIncremented on update. Used for optimistic concurrency.
createdAt / createdByIdWhen and by whom the record was first written
updatedAt / updatedByIdWhen 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,
"type": "String",
"sensitive": false,
"values": [
{ "id": 1, "value": "Server=localhost;Database=orders", "numberValue": null, "booleanValue": null,
"sensitive": false, "hint": null, "hash": null, "tags": [] },
{ "id": 2, "value": "Server=prod.internal;Database=orders", "numberValue": null, "booleanValue": null,
"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.