Skip to main content

Library set API

Aspire name api-libraryset. This is the API the product exists for: the library itself, and the endpoint that resolves it.

Every route is scoped to the organization in the caller's JWT.

Tag groups

MethodPathPermission
GET/taggroupsTagGroupList
GET/taggroups/{id}TagGroupRead
POST/taggroupsTagGroupCreate
PUT/taggroups/{id}TagGroupUpdate
DELETE/taggroups/{id}TagGroupDelete
GET /taggroups/1
{
"id": 1,
"version": 2,
"name": "Environment",
"description": "Where the code is running",
"tags": [
{ "id": 1, "name": "Production", "tagGroupId": 1 },
{ "id": 2, "name": "Staging", "tagGroupId": 1 }
],
"createdBy": { "id": "…", "name": "Neil Docherty" },
"createdById": "…",
"createdAt": "2026-08-01T10:00:00Z",
"updatedBy": { "id": "…", "name": "Neil Docherty" },
"updatedById": "…",
"updatedAt": "2026-08-04T11:30:00Z"
}

Deleting a group cascades to its tags, and the join rows to values cascade from either side.

Tags

MethodPathPermission
GET/tagsTagList
GET/tags/{id}TagRead
POST/tagsTagCreate
PUT/tags/{id}TagUpdate
DELETE/tags/{id}TagDelete
POST /tags
{ "name": "Production", "tagGroupId": 1 }

Library sets

Called variable sets in the API, library sets in the UI and import file.

MethodPathPermission
GET/variablesetsVariableList
GET/variablesets/{id}VariableRead
POST/variablesetsVariableCreate
PUT/variablesets/{id}VariableUpdate
DELETE/variablesets/{id}VariableDelete
POST /variablesets
{ "name": "orders-service", "description": "Config for the orders service" }

GET /variablesets/{id} returns the set with its variables and their values, with sensitive values withheld unless the caller holds VariableReadSecrets. Deleting a set cascades to its variables and their values.

Variables

MethodPathPermission
GET/variablesVariableList
GET/variables/{id}VariableRead
POST/variablesVariableCreate
PUT/variables/{id}VariableUpdate
DELETE/variables/{id}VariableDelete
POST /variables
{
"name": "DatabaseConnection",
"variableSetId": 1,
"values": [
{ "value": "Server=localhost;Database=orders", "sensitive": false, "tags": [] },
{ "value": "Server=prod.internal;Database=orders", "sensitive": false, "tags": ["Environment/Production"] },
{ "value": "sk-live-…", "sensitive": true, "tags": ["Environment/Production", "Region/EU"] }
]
}

tags accepts "Group/Tag" strings or numeric tag IDs, mixed freely in one list. A tag that names nothing is skipped rather than refused.

Two values of the same variable carrying the same tag combination are refused with 409 Conflict.

200 OK
{
"id": 1,
"version": 1,
"name": "DatabaseConnection",
"variableSetId": 1,
"values": [
{
"id": 1,
"value": "Server=localhost;Database=orders",
"sensitive": false,
"hint": null,
"hash": null,
"tags": []
}
],
"createdAt": "…", "createdBy": "…", "updatedAt": "…", "updatedBy": "…"
}

Values come back ordered by ID, so a variable does not list its values differently between reads.

Variable values

The consumption endpoints. Both take the same parameters and run the same resolution; they differ in what they hand back.

MethodPathPermission
GET/variablevaluesVariableRead
GET/variablevalues/previewVariableRead
Parameter
setRepeated. At least one required — none is 400. A set that does not exist is 404.
tagRepeated, Group/Tag form. Optional.
GET /variablevalues?set=1&set=2&tag=Environment/Production&tag=Region/EU
/variablevalues
{
"total": 2,
"items": {
"DatabaseConnection": "Server=eu-prod.internal;Database=orders",
"LogLevel": "Warning"
}
}
/variablevalues/preview
{
"total": 1,
"items": {
"DatabaseConnection": {
"id": 3,
"value": "Server=eu-prod.internal;Database=orders",
"sensitive": false,
"hint": null,
"hash": null,
"tags": [
{ "id": 1, "name": "Production", "tagGroupId": 1 },
{ "id": 3, "name": "EU", "tagGroupId": 2 }
]
}
}
}

See Resolution for the matching rule and how each endpoint treats secrets.

Import

MethodPathPermission
POST/importBatchProcessingImport (and the fifteen it depends on)
ParameterDefault
replaceExistingfalsetrue empties the organization's library first

A whole library in one request, one transaction and one save. The file is the contract, so the payload is shaped after the file rather than after the models: it says "library sets", and nothing in it carries an ID. Names are the only identity a file has, so names are what an import matches on.

POST /import
{
"tagGroups": {
"Environment": ["Production", "Staging"],
"Region": ["EU", "US"]
},
"librarySets": {
"orders-service": {
"DatabaseConnection": [
{ "value": "Server=localhost;Database=orders", "sensitive": false, "scopes": [] },
{ "value": "Server=prod.internal;Database=orders", "sensitive": false, "scopes": ["Environment/Production"] }
],
"ApiToken": [
{ "value": "sk-live-…", "sensitive": true, "scopes": ["Environment/Production"] }
]
}
}
}
200 OK
{
"replaced": false,
"tagGroupsCreated": 2, "tagGroupsSkipped": 0,
"tagsCreated": 4, "tagsSkipped": 0,
"librarySetsCreated": 1, "librarySetsSkipped": 0,
"variablesCreated": 2, "variablesSkipped": 0,
"valuesCreated": 3,
"tagGroupsDeleted": 0, "tagsDeleted": 0, "librarySetsDeleted": 0,
"variablesDeleted": 0, "valuesDeleted": 0,
"notes": ["Tag group 'Environment' already existed and was left alone"]
}

A rejected file is rolled back, not merely uncommitted — so a rejected replacing import leaves the emptied library exactly as it found it.

Auditing is one entry for the whole import, carrying the counts without the notes. Re-importing an unchanged file is the ordinary repeat case and produces a note per entity left alone, which is worth telling the caller and not worth shipping to another service as a record of nothing having changed.

See Importing a library.