Organizations & Departments
The deployment is implicit. It is created at boot, there is exactly one, and there is no object to manage — its policy lives on the Settings page.
The organization is the first level that discriminates anything. Two organizations can see different provider catalogues, route differently, bill against different credentials, and authenticate against different identity providers. A deployment cannot, because there is only one.
Departments sit inside an organization and narrow it further.
Deployment (implicit — Settings page) └── Organization └── Department └── UserBoth levels are renameable. Under Settings → Access, relabel them to match your own terminology — “Teams” and “Projects”, say — and the new words appear throughout the console.
Organizations
The list
| Column | What it shows |
|---|---|
| Name | The name, with the slug beneath it |
| Policy | ”Inherits everything”, or a count and a summary of the first few overrides |
| Created | Relative time, with the exact timestamp on hover |
| Actions | Edit, Delete |
Name and Created are sortable. Search matches name and slug. Clicking a row opens the edit drawer — there is no separate detail page.
Creating and editing
| Field | Required | Notes |
|---|---|---|
| Name | Yes | Shown here, in user records and in the navigation |
| Slug | Yes | Lower-case letters, numbers and hyphens, up to 50 characters. Unique across the deployment. |
| Policy | — | The same editor Settings uses, so the fields you set here are visibly the ones you are overriding |
| Providers and models | — | Edit only. See Scope Overrides. |
| Sign-in and provisioning | — | Edit only. SSO and SCIM, below. |
The slug is derived from the name until you edit it yourself, and never auto-changes on an existing record.
The slug is what a user types to sign in with SSO. It is the identifier in
GET /api/auth/sso/:slug/initiate, so renaming it changes the sign-in URL for
everyone in that organization. It is not used anywhere in admin URLs, which key
on the ID.
Creating an organization requires deployment scope — there is no level above an organization for a scoped session to be acting in. Deleting one does too.
Deleting
An organization with departments or users under it cannot be deleted. The
console counts both before offering the confirmation and, when either is
non-zero, shows a blocker with a link to the departments concerned rather than a
confirm button. The API refuses the same case with 409 CONFLICT and the count.
Unlike users and API keys, an organization really is removed.
Policy
Each organization may narrow what it inherits from the deployment — never widen it. The editor shows the inherited value beside each field so an override is legible as an override.
A saved organization sends its whole settings object, because the server
replaces it outright: omitting a key is how an override is cleared.
Be aware that most of the policy cascade is stored rather than acted on. Today
only maxRequestsPerMinute, cachingEnabled, allowedProviders and
allowedModels are read by anything at request time. The console says so rather
than implying otherwise.
Endpoints
| Method | Path | Permission | Scope |
|---|---|---|---|
GET | /api/admin/organizations | organizations:read | Filtered to the acting scope |
GET | /api/admin/organizations/:id | organizations:read | assertWithinScope |
POST | /api/admin/organizations | organizations:create | Deployment scope required |
PUT | /api/admin/organizations/:id | organizations:update | assertWithinScope |
DELETE | /api/admin/organizations/:id | organizations:delete | Deployment scope required |
PUT | /api/admin/organizations/:id/sso | organizations:update | assertWithinScope |
POST | /api/admin/organizations/:id/scim-token | organizations:update | assertWithinScope |
DELETE | /api/admin/organizations/:id/scim-token | organizations:update | assertWithinScope |
GET accepts page, limit (capped at 100), sort (name, slug,
createdAt, updatedAt), order and search.
PUT reads only name, slug and settings. Credentials, SSO and SCIM cannot
be changed through it — they have their own endpoints, so a rename cannot carry
an identity change with it.
A department-scoped session listing organizations receives an empty list, not every organization. The Organizations collection has no organization column to filter on, so an unfiltered query would return all of them; the filter is made deliberately unsatisfiable instead.
Departments
The list
Name, Organization, Policy, Created, Actions. Name and Created are
sortable; there is also an organization dropdown filter, whose selection is
mirrored into the URL as ?organizationId=… so a link from an organization
lands pre-filtered.
If no organizations exist, New department is disabled and says why.
Creating and editing
| Field | Required | Notes |
|---|---|---|
| Name | Yes | |
| Slug | Yes | Unique within its organization, not across the deployment |
| Organization | Yes | Create only. Fixed afterwards. |
| Policy | — | Inherits the deployment merged with its organization |
| Providers and models | — | Edit only |
A department cannot be moved between organizations. organizationId is
required at creation and omitted from the update schema entirely, so there is no
request that reassigns one. To move a department, create it under the other
organization and reassign its users.
There is no SSO or SCIM section on a department. Identity is configured at the organization.
Endpoints
Departments are guarded by the users resource, not a departments one.
| Method | Path | Permission |
|---|---|---|
GET | /api/admin/departments | users:read |
GET | /api/admin/departments/:id | users:read |
POST | /api/admin/departments | users:create |
PUT | /api/admin/departments/:id | users:update |
DELETE | /api/admin/departments/:id | users:delete |
GET additionally accepts organizationId as a narrowing filter. It narrows
within the acting scope and cannot escape it.
That permission mapping has a consequence worth planning around: the built-in
org-admin role holds users: [create, read, update] but not
users:delete, so an organization administrator can create and rename
departments and cannot delete one. dept-admin holds users: [read] only.
A department with users in it cannot be deleted; the API returns 409 CONFLICT
with the count.
Per-organization SSO
Each organization can authenticate against its own identity provider. Users
reach it at /api/auth/sso/<organization-slug>/initiate; the deployment’s own
provider uses the reserved slug system.
There is no fallback. If an organization has no enabled SSO configuration, its sign-in is refused rather than quietly borrowing the deployment’s provider — an organization borrowing the system provider would be authenticated by an authority entitled to system scope. The refusal message is deliberately the same whether the organization has no SSO or does not exist, so the endpoint cannot be used to enumerate organizations.
Configure it under Sign-in and provisioning on the organization, or with
PUT /api/admin/organizations/:id/sso:
{ "enabled": true, "provider": "oidc", "config": { "issuerUrl": "https://your-org.okta.com", "clientId": "...", "clientSecret": "...", "scopes": "openid email profile" }, "claimedDomains": ["example.com"], "groupRoleMappings": { "Engineering": "org-admin" }}provider is oidc or saml. A SAML config takes entryPoint, issuer and
cert instead.
Four behaviours worth knowing:
- Secrets are write-only and merge-on-write. The incoming
configis merged over the stored one, so an edit that is not a secret rotation may leaveclientSecretout and keep the existing value. Reads reportclientSecretSetandcertSetrather than the values. Switching protocol replaces the config wholesale instead of merging, because a SAML certificate has no meaning under OIDC. - Claimed domains are enforced at login. For an organization-initiated sign
in, the email’s domain must be one the organization has claimed, or the login
is refused. Claiming a domain requires deployment scope — an organization
administrator cannot claim one for itself. Domains are unique across the
deployment, and a clash is refused with
409naming the organization that holds it. - Group mappings write memberships.
groupRoleMappingsmaps an IdP group name to a role slug, and roles are re-synced on every login. Only the membership belonging to that issuer is replaced, so an SSO login cannot erase a locally assigned one, or one from another organization’s IdP. - Licensing.
sso-oidcorsso-samlis checked when SSO is being enabled. Turning it off always works, whatever the licence says.
SCIM provisioning
POST /api/admin/organizations/:id/scim-token issues a bearer token for that
organization and returns it once, alongside the base URL to give your IdP:
{ "data": { "token": "...", "prefix": "aigw_scim_...", "scimBaseUrl": "https://gateway.example.com/scim/v2" }}Only a hash and the prefix are stored. If the token is lost, issue a new one.
DELETE .../scim-token disables SCIM for the organization and clears the stored
hash. It is deliberately not licence-gated: a licence that has lapsed must
not leave a provisioning token you cannot revoke.
Issuing a token requires the scim licence feature.
Placement, and the gotcha it hides
A user’s organizationId and departmentId are a default scope hint, not
authority. Authority comes from the per-scope memberships described in
Users & Roles. But the two are linked: when you edit
a user through the admin, its locally-managed membership is recomputed from its
placement, and the scope is the narrowest level named.
- Department set → membership at department scope.
- Organization set, no department → membership at organization scope.
- Neither set → membership at deployment scope.
So a deployment-scoped role does not take effect on an account pinned to a
department. It reads wide and acts narrow, because containment comes from the
scope the session is acting in — the scope recorded on a role is descriptive
and no enforcement path reads it. An account that needs deployment-wide
authority must have no organization and no department.
Clearing them is possible, with one asymmetry and one side effect:
- On update,
organizationIdanddepartmentIdacceptnull, andnullclears the field. Omitting the key means “leave unchanged”. - On create, they accept a string or nothing.
nullis a validation error — omit the field instead. - Clearing both promotes the account’s locally-managed membership to deployment scope, silently, because that is what “neither level named” infers. That is usually what you wanted if you were clearing them deliberately, and a surprise if you were not.
The promotion cannot be used to escalate: role assignment is bounded separately, and an administrator cannot attach a role they do not themselves hold. An organization-scoped administrator is refused the clearing operation outright, because a target with no organization is not within their scope.
Related
- Scope Overrides — what a scope changes about providers and models
- Users & Roles — memberships, the scope switcher and role assignment
- Budgets — spending limits at each level
- Settings — the deployment policy these inherit from