Organizations
An organization is the tenancy boundary. Every record in TagShape belongs to exactly one, and nothing reaches across.
What an organization holds
| Held in | Records |
|---|---|
| Account API | Users, groups and their permissions, identity providers, branding, account patterns, API keys |
| Library set API | Tag groups, tags, library sets, variables, values, client certificates |
| Audit API | Log entries |
| Account API | A credit balance: used credits, against the pricing band's included credits plus any additional credits |
How the boundary is enforced
Every query is filtered by the org claim on the caller's JWT. The consequence worth knowing is that an ID
belonging to another organization does not produce a 403 — it produces a 404. As far as the query is
concerned the record is not there, and saying otherwise would confirm that an ID exists.
The exceptions are the global organization permissions (GlobalOrganizationList, GlobalOrganizationRead,
GlobalOrganizationCreate, GlobalOrganizationUpdate, GlobalOrganizationDelete, GlobalOrganizationCertificateManage), which by
their nature reach beyond a single organization. See Permissions model for what
global means and who may hand one out.
Identity providers
An organization brings its own. Each provider record holds the client ID, client secret and any tenant identifier needed to run a login, and the login page offers whichever ones the organization has configured. Microsoft Entra ID and Google are supported. See Identity providers.
Credentials can be tested before they are saved, via POST /organizations/identityproviders/check — the check
runs server-side rather than from the browser, because Entra's token endpoint allows the CORS preflight and then
omits the Access-Control-Allow-Origin header from the response, so a browser never gets the reply.
Account patterns
An organization can say that email addresses matching a pattern may have an account created for them
automatically at first login. When someone signs in successfully with a provider but has no account,
POST /users/provision checks their address against the organization's patterns; a match creates the account,
and no match refuses the login.
A pattern that cannot be compiled is logged and passed over rather than failing the login.
Branding
An organization can supply a logo and custom CSS, both served to the login page by
GET /organizations/{id}/signin. The CSS is filtered against an allow-list of rules rather than injected
wholesale.
Client certificates
An organization can hold up to two certificates — primary and replacement — that a client uses to encrypt secrets before they reach TagShape. Each is either generated by TagShape (a self-signed certificate, one year's validity) or uploaded by the organization itself, pasted in as a PEM certificate and private key pair.
The private key never leaves Key Vault. The library set API's SQL record holds only a thumbprint, a validity window, which slot the certificate occupies, and whether it was generated or uploaded — never the key material, and never anything the API could turn back into a downloadable certificate. See Data stores for how that split works and why it is enforced at the Key Vault permission level rather than only by what the endpoints choose to return.
Rotating swaps which slot each certificate occupies — whichever was primary becomes the replacement and vice versa — without touching either certificate's content, then re-encrypts every sensitive Variable Value the organization holds against the new primary. It needs both slots filled to mean anything.
Managing a certificate — generating, uploading, rotating or removing one — needs the global
GlobalOrganizationCertificateManage permission, kept separate from GlobalOrganizationUpdate because reissuing the
certificate an organization's secrets are encrypted against is a more sensitive act than editing the rest of
its record. See Library set API for the endpoints.
Which organization is being signed in to
The auth UI resolves the organization per request. Where nothing in front of the deployment sets it, the
x-tagshape-org header names it — that is how the local Aspire host runs it.
Deleting an organization
DELETE /organizations/{id} needs the global GlobalOrganizationDelete. A disabled organization (active: false)
stops answering GET /organizations/{id}/signin — an ID naming a disabled organization and an ID naming no
organization get the same 404, because in both cases there is nothing there to sign in to.