Skip to main content

Managing access

Access in TagShape is: permissions go on groups, people go in groups. There is nothing else.

Setting up a group​

POST /groups
{
"name": "Readers",
"description": "Can see the library but not change it",
"permissions": [
"TagGroupList", "TagGroupRead",
"TagList", "TagRead",
"VariableList", "VariableRead"
]
}

Needs UserCreate, and the ability to grant each permission listed. See granting versus holding.

Two checks run beyond the endpoint permission:

  • Grant check — a permission being added that you cannot grant is rejected, and named in the response.
  • Dependency check — a group carrying AuditRead without AuditList is refused. The permission would do nothing, and a group that silently does nothing is worse than a rejected save.

Restricting a group to specific library sets​

The six Variable* permissions can each be narrowed to specific library sets instead of applying to every one in the organization — see Library-set scoping for the full model. Add a librarySetScopes entry alongside the permission it narrows:

PUT /groups/{id}
{
"id": "…", "version": 3, "name": "Payments team",
"permissions": ["VariableList", "VariableRead", "VariableUpdate"],
"librarySetScopes": [
{ "permission": "VariableUpdate", "variableSetIds": [12, 47] }
]
}

Here the group can list and read every library set in the organization, but only update variables in sets 12 and 47. Leaving VariableUpdate out of librarySetScopes entirely — or omitting the field altogether — means it applies everywhere, which is the default and unchanged behaviour.

Needs nothing beyond what granting the permission for every set already needs: whoever could put VariableUpdate on a group without restriction could always have restricted it to one set. The same grant check and dependency check above apply to the scope too - naming VariableUpdate for sets the group does not have VariableUpdate for at all is refused, as is scoping a permission the group does not hold.

Two things worth knowing before narrowing a permission:

  • Creating a library set is never scoped. VariableCreate restricted to specific sets still lets its holder create variables inside them, but a brand-new library set — root or nested — always needs VariableCreate held for every set.
  • Batch import and export refuse a scoped grant outright. Either operation can touch any library set named in the file, so POST /import and GET /export need every relevant Variable* permission unrestricted.

Adding people​

PUT /groups/{id}/members
{ "userIds": ["9c1e7a4d-…", "3f52a18b-…"] }

Needs UserUpdate and no grant check — adding somebody to a group is not the same act as deciding what the group may do. The same membership can also be set from the other side, through PUT /users/{id}/groups.

Changes take effect at the member's next token refresh, not immediately. With the default one-hour token that is at most an hour.

A worked set of groups​

A shape that works for most organizations:

GroupPermissionsFor
ReadersTagGroupList, TagGroupRead, TagList, TagRead, VariableList, VariableReadAnyone who needs to see the library
EditorsReaders + TagGroupCreate/Update/Delete, TagCreate/Update/Delete, VariableCreate/Update/DeletePeople who maintain configuration
Secret readersVariableRead, VariableReadSecretsThe few who need actual secret values
ImportersBatchProcessingImportPipeline accounts that apply a library file
ExportersBatchProcessingExportAccounts that pull a backup or a copy for another organization
DeploymentVariableRead, VariableReadSecretsAccounts behind deployment API keys
AuditorsAuditList, AuditReadCompliance and investigation
AdministratorsUserList…UserDelete (also governs groups), PermissionsAllPeople who manage access
PlatformPermissionsAllGlobal, GlobalOrganizationList…GlobalOrganizationDelete, AuditGlobalList, AuditGlobalReadWhoever runs the installation

Two things worth noticing.

Secret readers is separable from Editors. Writing a sensitive value needs only VariableUpdate, so the people who maintain configuration need not be the people who can read the secrets in it.

Administrators holds PermissionsAll, not every permission. PermissionsAll lets them hand out any non-global permission without their being able to use any of them. An administrator can put VariableReadSecrets on a group without becoming a reader of secrets.

Global permissions​

Six permissions reach beyond one organization, and only somebody holding PermissionsAllGlobal can hand any of them out:

PermissionsAllGlobal, PermissionsImpersonateUser, AuditGlobalList, AuditGlobalRead, and the five Organization* permissions.

Keep them to a single platform group with very few members.

Issuing API keys​

A key carries the permissions of the account that owns it, so the way to scope a key is to scope its account.

  1. Create a service account for the consumer — not a person's account.
  2. Put it in a group holding only what the consumer needs.
  3. Make sure whoever will issue the key holds UserCreateServiceAccountApiKey (which also needs UserRead).
  4. That person opens the service account in the management UI and creates the key there, or calls POST /users/{id}/apikeys.

A service account has no login, so it cannot create a key for itself — which is what UserCreateServiceAccountApiKey is for. Keep it to the few people who may stand up machine credentials, since a key acts as the account with whatever its groups grant.

For a key that never expires the person issuing it also needs ProfileNeverExpiresApiKey. Think twice: a key that never expires is one that cannot be forgotten about safely, which is why it is a separate decision.

A person who wants a key for their own account is a different case: they hold ProfileCreateApiKey and make it from their profile.

See API keys.

Reviewing access​

QuestionWhere
What can this person do?GET /users/{id} — their groups and the permissions those carry
Who is in this group?GET /groups/{id}
What keys does this account hold?GET /users/{id}/apikeys — needs UserRead
Who changed a group, and when?Audit log, entryType of Group
What permissions exist?GET /permissions

Removing access​

To removeDo
One person's accessTake them out of the group, or delete the account (UserDelete)
A capability from everyone in a groupTake the permission off the group (UserUpdate)
A machine credentialDELETE /users/{id}/apikeys/{apiKeyId} (UserDelete)

Deleting an account does not retroactively invalidate an already-minted JWT. A token remains valid until it expires — at most an hour by default. Where that matters, shorten Jwt:DurationInMinutes.

Common mistakes​

Granting AuditRead alone. It depends on AuditList and does nothing without it. The group save will refuse it.

Granting VariableReadSecrets alone. Same reason — it depends on VariableRead.

Expecting PermissionsAll to let somebody do things. It governs granting, not doing. Somebody with only PermissionsAll cannot read a single variable.

Expecting a permission change to take effect immediately. It reaches the caller at their next token refresh.

Expecting a library-set scope to reach a set's children. Library sets can nest, but a scope naming a parent set does not extend to sets beneath it — each one that should be covered has to be named.

Scoping VariableCreate and expecting to create a new library set with it. A scoped grant only reaches creating a variable inside a set already in scope. Creating the set itself always needs VariableCreate unrestricted.