Skip to main content

API

Four services, one set of conventions.

ServiceAspire nameHolds
Library set APIapi-librarysetTag groups, tags, library sets, variables, values, import
Account APIapi-accountOrganizations, users, groups, permissions, API keys
Identity APIapi-identityJWT minting, renewal, API key exchange, JWK generation
Audit APIapi-auditThe change log

Each exposes an OpenAPI document at /openapi/v1.json, with a JWT auth option wired into the UI.

Looking for what an endpoint requires?

API permissions has every endpoint in the product with its permissions in one table.

Authentication

Every request carries a JWT in the x-tagshape-auth header (or a cookie of the same name, from a browser). See JWT for how to get one, and Headers for the full list.

The handful of endpoints that are anonymous are listed in API permissions.

Organization scoping

Every query is filtered by the org claim. An ID belonging to another organization answers 404, not 403 — as far as the query is concerned the record is not there.

The organization is never a parameter. There is no way to ask a service about another organization's data, and no endpoint accepts an organization ID for that purpose. (GET /users/{id}/permissions takes an organizationId query parameter, but it is anonymous and exists for session renewal.)

Status codes

CodeUsed for
200 OKRead succeeded, or a write returning the resulting record
201 CreatedPOST /users/me/apikeys only
204 No ContentAPI key deletion
400 Bad RequestMalformed input, or a valid request asking for something the caller may not have — such as a never-expiring key
401 UnauthorizedAn API key that named nobody; an unauthenticated audit write of a non-allow-listed type
403 ForbiddenPermissions insufficient. Body does not say which.
404 Not FoundNot there, or not in your organization
409 ConflictTwo values of a variable carrying the same tag combination
500 Internal Server ErrorA fault on our side

Errors use RFC 7807 problem details where the framework produces them:

{
"title": "Forbidden",
"detail": "You do not hold the permissions required for this request.",
"status": 403
}

List responses

Collection endpoints answer with a wrapper carrying a total alongside the items, so a pager knows how many pages there are:

{
"total": 42,
"items": []
}

items is a list for record collections, and an object keyed by name for the variable-value endpoints, whose whole purpose is to be poured into a settings file.

The total is counted before paging and off the unordered query — how results are ordered makes no difference to how many there are.

Paging, sorting and filtering

Supported by the audit list endpoints:

ParameterDefault
take10
skip0
sortFieldLoggedAt
sortDescendingfalse

Filtering uses POST /filter with a body of filter objects rather than a query string, so a filter can carry more than a string comparison. See the audit API.

Versioning and concurrency

Tag groups, library sets and variables carry a version that increments on update, plus createdAt, createdById, updatedAt and updatedById. Send the version you read back on a PUT to detect a concurrent change.

The library set API does not store users, so it asks the account API to put names to the IDs it holds — via POST /users/names/lookup, which answers only about IDs it is given. That is why records come back with both a raw createdById and a resolved createdBy: { id, name }.

Auditing

Every create, update and delete writes an entry to the audit API, carrying the before and after state as JSON. The one deliberate exception is import, which writes one entry describing the whole operation with counts rather than one entry per entity — a file with hundreds of variables in it would otherwise spend longer being audited than imported.

See Audit entries.

Service discovery

Services find each other through .NET Aspire service discovery rather than configured URLs:

ServiceCalls
AccountAudit
IdentityAccount, Audit
Library setAccount, Audit
Auth UIIdentity, Account, Audit, Library set
Management UIAccount, Audit, Library set

See Architecture.