Custom roles and SCIM provisioning
Enterprise organizations can define custom roles as explicit permission sets, assign them organization-wide or as per-project overrides, and provision users and groups from their identity provider with SCIM 2.0. Built-in roles are fixed permission sets inside the same evaluator, so there is one authorization path.
Custom roles
Custom roles contain explicit permissions such as datasets.list,
datasets.get and models.list. The permission catalog is generated from the
API authorization matrix: every operation is a permission, and adding an
endpoint extends the catalog. Wildcards and unknown permission names are
rejected with invalid_argument, naming the unknown strings. Built-in owner,
admin, member and viewer names are reserved. The role administration
endpoints are under /v1/orgs/{org_id}/roles.
Governance permissions
The governance evidence operations are permissions like any other:
| Permission | Grants |
|---|---|
model_inventory.list | The organization model inventory (console sessions only). |
models.governance.get, slots.governance.get | Reading a model's or a slot's governance fields. |
models.governance.update, slots.governance.update | Writing them. |
models.card.export | Exporting a model's pinned card. |
dossier_templates.list | Listing the dossier templates. |
dossiers.create, dossiers.list, dossiers.get, dossiers.download | Assembling, listing, reading and downloading dossiers. |
A reviewer's custom role can, for example, hold model_inventory.list and the
dossier permissions without the rest of admin.
Customer map permissions
The customer map operations are permissions like any other. Applications are refused all of them.
| Permission | Built-in floor | API key scope | Grants |
|---|---|---|---|
customer_map.get | viewer | read | Reading the current customer map. |
customer_map.settings.get | viewer | read | Reading the map's update settings. |
customer_map.editions.list | viewer | read | Reading the history of the map. |
customer_map.refresh | member | admin | Update now: starting one new map on the map's fixed recipe. The caller chooses no columns, sample size or seed. Only one update runs at a time, and the map's cooldown applies. |
customer_map.settings.put | admin | admin | Changing how often and when the map updates, pausing it, the cooldown, the slot it follows and pinning one map. |
customer_map.refresh is the only map write a member holds. Starting an export
(exports.create) stays with admin. A custom role created before these
permissions existed does not hold them; create or edit the role to add them.
Update now has limits of its own, because map updates meter nothing and never
appear in usage. Apart from an Admin console session (one that holds
customer_map.settings.put), every caller, every API key included, waits at
least 15 minutes after the last successful update whatever the map's cooldown,
waits a growing backoff (2, 4, 8 ... minutes, up to that cooldown) after updates
that failed, and starts at most 24 updates in 24 hours for each map. An Admin may
retry a failed update at once, but never inside the cooldown after a successful
one.
Reading the map, its settings and its history shows only what the caller's
purposes allow: a slot the caller may not use is left out, not refused, and so
are its pin and next update time. Update now always works on the map's stored
slot: the slot an Admin chose, and only it (if it stops qualifying, Update now is
refused and scheduled updates fail visibly), or, when the server chooses, the
stored slot while it still qualifies. A caller who may not use that slot's
purpose is refused (purpose_not_allowed) before anything else is answered.
Scheduled updates stop, waiting for an Admin, when the credential they run as
may no longer use that purpose.
Assign an organization role through
PUT /v1/orgs/{org_id}/members/{user_id}/role. A project override at
PUT /v1/projects/{project_id}/members/{user_id}/role replaces the organization
role for that project's operations; DELETE on the same path restores
inheritance. Organization operations always use the organization role, and
permission sets are never combined across scopes.
An administrator can only grant permissions they hold in that organization:
creating, updating or assigning a role whose permissions exceed the actor's own
returns permission_denied with the excess permissions in error.details.
The same bound applies to whoever or whatever they act on. To change, downgrade,
remove or deactivate a member, replace or edit a role, or revoke an invitation,
the administrator must hold every permission that member, role or invitation
holds now, as well as every permission of any role they would grant. A narrower
administrator therefore cannot downgrade an administrator, remove one in two steps
(downgrade, then remove), strip permissions from a role that more privileged
members hold, or cancel an invitation it could not have issued; each is refused
with permission_denied and the excess permissions. For a member this covers
the organization role and every project override, and removing a project
override must also keep the role it uncovers within the actor's permissions.
Existing owner safeguards, including protection of the last owner, continue to
apply; owner can never be a custom role, a SCIM default or a group mapping
target. Operations such as deactivating an organization or deleting and
restoring a project belong to the built-in owner role by default. Because a
role can only contain permissions its author holds, only an owner can place
them in a custom role; delegate them deliberately, as each role change is
audited.
A role that is still referenced cannot be deleted: the request returns
failed_precondition with organization and project assignment counts and any
SCIM, invitation or SSO references. Permission changes apply to the next request.
Roles survive endpoint changes
A role stores permission names, and a SCIM token stores the ceiling of the
administrator who minted it as permission names. Both are evaluated against the
permission catalog of the running release on every request. A release that adds
an endpoint changes no existing grant: roles, SCIM tokens, invitations and SSO
defaults created earlier keep working, and the new permission is available to
roles created afterwards. A permission that a release removes grants nothing
(only that permission is denied; the role's other grants keep working), and the
role still reads, edits and assigns. Writing a role that names a permission the
running release does not know is a client error (invalid_argument); a
description-only edit leaves the stored names untouched.
Custom roles next to the v1 member, invitation and SSO schemas
The v1 member, invitation and SSO schemas are frozen: Membership.role,
MemberUpdateRequest.role, UserOrgMembership.role, Invite.role,
InviteCreateRequest.role and SsoConfig/SsoConfigUpdate.default_role remain
the closed Role, InviteRole and SsoDefaultRole enums, so existing API clients
keep working in every organization. They accept only the built-in names. A
member, pending invitation or SSO default that holds a custom role appears in
those responses as viewer, the least-privileged built-in role, never as a
broader one. The exact role is available through additive operations:
| Need | Operation |
|---|---|
| Read a member's organization role | GET /v1/orgs/{org_id}/members/{user_id}/role |
| Read a member's project override | GET /v1/projects/{project_id}/members/{user_id}/role |
| Assign or replace a role | PUT on the same two paths |
| Invite with a built-in or custom role | POST /v1/orgs/{org_id}/role-invites |
| List invitations with exact roles | GET /v1/orgs/{org_id}/role-invites |
| Read or set the SSO just-in-time role | GET/PUT /v1/orgs/{org_id}/sso/default-role |
Revoke an invitation with DELETE /v1/orgs/{org_id}/invites/{invite_id} and
redeem it with POST /v1/invites:accept whichever operation created it. The full
PUT /v1/orgs/{org_id}/sso replaces the whole configuration, including the
default role, with its built-in value (member when omitted); set a custom
default afterwards. Changing a member to a built-in role through
PATCH /v1/orgs/{org_id}/members/{user_id} replaces a custom role.
Two consequences of the projection need care. Access-review and audit tooling
that reads Membership.role lists custom-role holders as viewer whatever
permissions their role grants, so point such tooling at
GET /v1/orgs/{org_id}/members/{user_id}/role and the role endpoints. And a v1
read-modify-write cannot round-trip a custom role: reading a member and sending
the displayed viewer back through PATCH (or sending a configuration back
through the full PUT /v1/orgs/{org_id}/sso) is a change to the built-in role
and replaces the custom one.
SCIM tokens
An administrator mints or revokes the organization's SCIM token through
/v1/orgs/{org_id}/scim-token. Token creation accepts default_role, which
defaults to member and cannot be owner. Its tsc_ secret is shown once.
Minting replaces the previous token; revoked credentials stop authenticating
within five seconds. A token can never grant more than the administrator who
minted it held at that moment (its permission ceiling): its grants are exactly
that administrator's permissions, not a value the caller chooses, and its
default role must be within them. As an organization-level credential, it keeps
that ceiling if the administrator is later downgraded or removed: revoke or
re-mint it to change it. Any administrator with the permission can revoke or
replace the token whatever its ceiling, because revoking only removes access;
the replacement carries the replacing administrator's grants, never the
replaced token's. Session tokens and project API keys cannot authenticate
SCIM requests, and a SCIM token cannot authenticate /v1 requests.
SCIM token creation accepts an optional Idempotency-Key. With a key, the
server retains one successful issuance effect for 24 hours in the addressed
organization. The first 201 response contains the bearer once; the receipt
stores no bearer or response body. Every later authorized request with that key
in that organization returns 400 failed_precondition whose error.details
contains type: precondition_failure, reason: scim_token_already_issued, the
issued token_id and issued_at, and no secret. A retry never mints, rotates
or revokes a token and cannot restore a revoked bearer. After 24 hours, or with
a new key, a request performs an explicit new issuance. This exception applies
only to SCIM token creation; other POST idempotency behavior is unchanged.
SCIM 2.0 protocol
SCIM is served at https://api.staging.tessera.zone/scim/v2 with Users, Groups and
ServiceProviderConfig. Its contract is api/scim-openapi.yaml. Unlike the
normal /v1 API, SCIM uses application/scim+json, standard camelCase field
names such as userName, one-based startIndex pagination and RFC 7644
ListResponse and Error objects. It does not use the Tessera data/next_cursor
or error envelopes.
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "400",
"scimType": "invalidFilter",
"detail": "Unsupported filter attribute"
}
User filters support userName eq "dispatch@fleetwise.example" and
externalId eq "idp-123". Usernames are email addresses unique within the
organization; externalId round-trips unchanged. A duplicate provisioning
request returns 409. active=false through PUT or PATCH, and DELETE on a user,
soft-deactivate that organization's membership: the user's sessions are denied
for that organization on their next request, and reactivation restores access.
Identity and audit records are retained. Provisioning does not require SSO.
A token cannot change a member who holds more than it does. Deactivating or reactivating a user, or replacing the role of a group member, whose current permissions (the organization role and any project override) exceed the token's permission ceiling returns a SCIM 403 and changes nothing. De-provisioning an administrator therefore needs a token minted by an administrator whose permissions cover that member's; owners are never changed by SCIM.
Group mappings are administered through
/v1/orgs/{org_id}/scim-group-mappings. Each group display name maps to one
non-owner role. A mapped membership change applies that organization role;
removal restores the previous applicable assignment. When two mapped groups
conflict, the most recently applied assignment wins and a scim.group_conflict
audit event records the conflict. Permission count never determines precedence.
The supported PATCH subset is replacing a user's active state and
adding/removing group members. Unsupported operations return a SCIM 501 error
without partial mutation. ServiceProviderConfig reports PATCH and filtering
support; bulk operations, sorting, password changes and ETags are unsupported.
Requests exceeding the per-token limit receive a SCIM 429 error with
Retry-After.
Each token is bound to one organization. A resource outside that organization returns the same 404 as an absent resource. Role and SCIM mutations produce audit events (SCIM actions use the token ID as the actor); token plaintext is never included in them.
SCIM attribute handling
SCIM attribute names are case-insensitive; responses use canonical SCIM names. Duplicate case variants are rejected. Returned read-only IDs and metadata may be sent back in POST/PUT and are ignored; unknown role, permission and organization attributes are rejected. Optional values may be cleared with null; empty arrays also clear multi-valued values.
On user creation, omitted active defaults to true. Explicit null clears
active and grants no access. On replacement, both omitted and null active
clear it; only explicit true reactivates membership. Unassigned active is
returned as null. The supported active PATCH operation accepts true/false
only; use PUT to clear.