Skip to main content

Importing a library

POST /import takes a whole library — tag groups, tags, library sets, variables and values — in one request, one transaction and one save. That is the point of it: importing a file with hundreds of variables should not be hundreds of requests assembled by the page.

Permission

BatchProcessingImport

One permission that stands for sixteen. It declares a dependency on every tag group, tag and variable permission there is, because an import creates all three and a replacing import deletes all three — and the middleware requires the whole closure. See API permissions.

VariableReadSecrets is not among them. An import writes values, sensitive ones included, and is never shown one back.

The file

The payload is shaped after the file rather than after the models. It says "library sets" where the API says variable sets, and nothing in it carries an ID — names are the only identity a file has, so names are what an import matches on.

library.json
{
"tagGroups": {
"Environment": ["Production", "Staging", "Development"],
"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"] },
{ "value": "Server=eu-prod.internal;Database=orders", "sensitive": false, "scopes": ["Environment/Production", "Region/EU"] }
],
"ApiToken": [
{ "value": "sk-live-9f2a7c4e18b6d05", "sensitive": true, "scopes": ["Environment/Production"] }
]
},
"common": {
"LogLevel": [
{ "value": "Debug", "sensitive": false, "scopes": [] },
{ "value": "Warning", "sensitive": false, "scopes": ["Environment/Production"] }
]
}
}
}
Field
tagGroupsGroup name → list of tag names
librarySetsSet name → variable name → list of values
valueThe string
sensitiveMarks it a secret; the hint and hash are derived on import
scopesThe tags this value applies under, as Group/Tag strings

Note scopes, not tags — the file's own word for it.

Merging

The default. POST /import with no query parameter.

Anything already present by name is left alone and reported as skipped; anything new is created. Nothing reaches inside a variable that already exists, so re-importing an unchanged file is a no-op that reports itself as one.

curl -X POST "$LIBRARY_API/import" \
-H "x-tagshape-auth: $JWT" \
-H "Content-Type: application/json" \
--data @library.json

Replacing

curl -X POST "$LIBRARY_API/import?replaceExisting=true" \
-H "x-tagshape-auth: $JWT" \
-H "Content-Type: application/json" \
--data @library.json
This deletes everything first

The organization's entire library — every tag group, tag, library set, variable and value — is removed before the file is applied. Once emptied there is nothing left to recognise, so a replacing import matches against nothing and numbers everything from one.

The deletion is done in the database rather than by loading and removing entity by entity. Both roots cascade all the way down, and the rows joining a value to its tags cascade from either side, so nothing is orphaned. Loading it all first would mean fetching every secret the organization holds only to count it and throw it away.

The summary

200 OK
{
"replaced": false,
"tagGroupsCreated": 2, "tagGroupsSkipped": 0,
"tagsCreated": 5, "tagsSkipped": 0,
"librarySetsCreated": 2,"librarySetsSkipped": 0,
"variablesCreated": 3, "variablesSkipped": 0,
"valuesCreated": 7,
"tagGroupsDeleted": 0, "tagsDeleted": 0, "librarySetsDeleted": 0,
"variablesDeleted": 0, "valuesDeleted": 0,
"notes": []
}

The Deleted counts are what a replacing import took away; they are zero on a merge. notes explains what was skipped and why.

Failure

A rejected file is refused with 400 and a list of reasons, and the transaction is rolled back rather than merely not committed — so a rejected replacing import leaves the emptied library exactly as it found it.

Auditing

One entry for the whole import, with the action Merge or Replace, the organization as the entity, and the counts as the after state. Not one entry per entity: a file with hundreds of variables in it would spend longer being audited than imported, and what happened was one act, which the counts describe in full.

The notes are left out of the audit entry. Re-importing an unchanged file produces a note per entity left alone — worth telling the caller, not worth shipping to another service as a record of nothing having changed.

From the management UI

The Library import page does the same thing with a file picker, a Monaco editor for reviewing the JSON before sending it, and a replace toggle. It shows the same summary. See The management UI.

Practical notes

  • Export first if you are replacing. There is no undo. Read your library sets via GET /variablesets and keep the result.
  • Merging is safe to repeat. It is the right choice for a library kept in version control and applied on every pipeline run.
  • Tags must exist or be declared. A scopes entry naming a tag that is neither in the file nor already in the library is skipped, which produces a value that matches less specifically than intended rather than an error.
  • Secrets in a file are secrets in a file. If your library holds sensitive values, the file holds them in plaintext. Treat it accordingly.