Skip to main content

Organizations

An organization is the tenancy boundary. Every record in TagShape belongs to exactly one, and nothing reaches across.

What an organization holds​

Held inRecords
Account APIUsers, groups and their permissions, identity providers, branding, account patterns, API keys
Library set APITag groups, tags, library sets, variables, values, client certificates
Audit APILog entries
Account APIA 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.