---
title: "MCP tools | FoxAuth"
description: "Every tool the administrative MCP control plane publishes, with its consequence class and input schema."
canonical: "https://foxauth.dev/docs/reference/mcp-tools/"
lastmod: "2026-10-19T13:13:27+03:00"
---
MCP tools

Documents unreleased changes made after v1.1.0 (commit 16f191bb)

This page is generated from the main branch. The published image
ghcr.io/redfox-soft/oauth-server-ts:latest is v1.1.0
and may not have everything listed here.

Served at POST /mcp when mcp.enabled is on. Each tool re-dispatches into the admin route it names, so it runs the console's own checks and writes the console's own audit entry. High-consequence tools require a second call confirming the specific operation.

Reads

whoami
The administrator this agent is acting as: their id, email, whether they are a super administrator, and the teams they belong to. `can` states what they may create — `createProject`, `addressForms` (the address forms they may give a project: `path`, `subdomain` and, for a super administrator, `host`; the first is the one to offer by default), `createTeam` — and `instance`, whether the instance-wide operations are theirs. While plans are enforced it also states what the plan still allows them: `nameSlug` (false when a new project is given a generated address name, so `slug` must be left out of project_create), `projects` and `teams` (`count` against `max`; null when unlimited).
Route GET /admin/api/me

resource_list
Every protected resource (API) declared in a project, sorted by identifier: the audiences this server mints tokens for on that project's behalf, with the scopes each recognises, how its tokens are verified, and their lifetime. `namespace` is `project` for a project with an address of its own — whose identifiers are unique to it — and `root` for one served at the root, which shares its namespace with the other root-served projects; only this project's declarations are listed either way.
Route GET /admin/api/projects/:id/resources

resource_get
One declared protected resource. The resourceId is the canonical resource identifier itself, percent-encoded into the path, and names the declaration in this project: another tenant may declare the same identifier in its own.
Route GET /admin/api/projects/:id/resources/:resourceId

resource_vouching_check
Whether a declared resource's own protected resource metadata currently vouches for it — describes this identifier and lists the issuer its tokens carry — found by the MCP discovery order (a 401 challenge's resource_metadata, then the path-inserted, then the root well-known). A diagnostic: it blocks nothing, and returns only a status, the step that answered, a reason, and the expected issuer.
Route GET /admin/api/projects/:id/resources/:resourceId/vouching

client_list
The OAuth clients (applications) of a project: those an administrator created and those that registered themselves at the project's own address (`registeredDynamically: true` — listed and readable, but changed only through their registration access token). Each carries its `corsOrigins`. Never returns a client secret.
Route GET /admin/api/projects/:id/clients

client_get
One OAuth client: redirect URIs, grant types, auth method, and other registered metadata. Never returns its secret — a secret is shown once at creation or rotation and is not readable afterwards.
Route GET /admin/api/projects/:id/clients/:clientId

admin_list
The administrator accounts of this instance, each marked when it is a super administrator.
Route GET /admin/api/admins

admin_sign_in_readiness_get
What turning password sign-in to the console off would do: whether it is on, how many super administrators could still sign in through an identity provider of the Administrators project, and which administrators could not. Turning it off is refused to an agent: only a console session that signed in through such a provider may.
Route GET /admin/api/admins/sign-in-readiness

team_list
The teams this administrator belongs to (every team, for a super administrator). A team owns projects — and through them their applications and APIs — and belonging to it is what grants access to them; each row counts what it owns as `owns.projects`.
Route GET /admin/api/teams

team_get
One team: its name, kind, and the administrators in it — each with `role`, `email` and `active` (false for a deactivated account), owners first. `owns` names the projects it owns, which are what keep it from being deleted. `can` states what the server will accept from the caller on it: `manageMembers` (team_member_update, team_member_remove), `invite` (team_invite, team_invitation_revoke), `addExisting` (team_member_add — a super administrator only; anyone else adds people with team_invite), `rename` (team_update) and `delete`; a personal team can be renamed and nothing else, and the System team nothing. Readable by its members and super administrators.
Route GET /admin/api/teams/:id

team_invitation_list
Pending invitations into a team. Never returns an invitation token — a token exists only in the mail it was sent in.
Route GET /admin/api/teams/:id/invitations

scope_get
The console's active team and the teams available to switch to. An agent has no console session: name a team per call instead of switching.
Route GET /admin/api/scope

project_list
The projects: every one for a super-administrator, otherwise those the caller manages. For a super-administrator the list includes the administrators' own project, marked `reserved: "administrators"`; nobody else sees it. Each row carries `can` and `blockers`, as project_get does, and `addressForm` (root, path, subdomain, host or unaddressed), `issuer` (null when unaddressed), `hostReached` (a request has arrived at its subdomain or hostname; false for a project at a path) and `held` (a provisioning connection of the project is holding a deprovisioning). A project scheduled for deletion is still listed, with `deletion` as project_get describes it.
Route GET /admin/api/projects

project_get
One project: its name, owning team, and registration and verification settings. Its groups of end users are read with project_group_list. A super-administrator may read the administrators' own project here (`reserved: "administrators"`); its accounts are read with admin_list, not through the project. `can` states which operations the server will accept from the caller on it: `manageUsers` (project_user_*, the identity tools, project_group_member_add/remove, and reading providers, provisioning and groups), `edit` (federation_provider_create/update/delete, provisioning_connection_create/update/delete/release, provisioning_credential_*, project_group_create/update/delete/assign_connection, project_user_assign_connection, project_key_*), `editSettings` (project_update), `readActivity` (project_activity_get), `changeAddress` (project_address_change), `move` (project_owner_change) and `delete`; a false flag is a refusal, so do not try it. `changeAddress` and `delete` also depend on the project's state for some callers: an owner of its team may change a path or subdomain address only while no application, identity provider or provisioning connection depends on it, and a member who is not an owner may delete it only while it holds no accounts. `blockers` is the state behind them: `addressable` is false for a project that signs with the instance keys, `dependents` counts the applications, providers and connections depending on it and `accounts` the accounts it holds — each null when it could not change the caller's answer and so was not read. `addressForm` is root, path, subdomain, host or unaddressed; `issuer` and `discoveryUrl` are what a client is configured with (null when unaddressed); `applications` and `apis` count what it holds (client_list, resource_list). `edit` also governs its applications and APIs (client_create/update/delete, client_secret_rotate, resource_declare/update/delete). Its counts are read with project_summary_get. A project scheduled for deletion carries `deletion` — `scheduledAt`, `purgeAt` (the date it will be destroyed) and `scheduledBy` — and answers nobody at its address until then: every change to it or to anything it holds is refused with 409 until project_restore brings it back, while reading keeps working. Deleting a project stays console-only for agents even where `can.delete` is true.
Route GET /admin/api/projects/:id

project_summary_get
Count what a project holds, computed when asked: its accounts (`users`), how many are unverified, without an authenticator and without any linked upstream identity; its groups of end users (`groups`); its provisioning connections and how many are holding a deprovisioning (`held`); its identity providers (`providers.enabled`, and `providers.creating` — enabled ones that create accounts on first sign-in); whether mail delivery is configured; and whether federation is switched on for the instance (`federationEnabled` — when false no project's sign-in page shows a provider button). `withoutEnabledLink` counts accounts with no link to an enabled identity provider (who could not sign in were passwords turned off) and `passwordWithoutAuthenticator` accounts with no authenticator whose password is not known to be absent (who an authenticator requirement would ask to enrol); `applications` and `apis` count the applications and declared APIs it holds; on the default project a sign-in change's preview also counts the applications signing in there. `activeThisMonth` is present only to a caller with `can.readActivity`, and absent rather than zero before counting began. Counts only, never who. `plan`, while plans are enforced (to a super administrator always), is the project's plan: its `limits`, `features`, `usage`, `warnings` (`near` at 80% of a limit, `reached` at 100%) and `signUpsPaused` (new self-service accounts paused at the monthly-active-user cap; sign-ins never are); null for a System project, which has none. Readable by anyone who may read the project (project_get's `can.read`).
Route GET /admin/api/projects/:id/summary

project_activity_get
Read a project's monthly active users: this month so far, each day of the month and the previous 12 months, in total and by kind of activity (local sign-in, upstream sign-in, renewal) and directory-provisioned accounts. A super administrator may also read the reserved projects, by id; a deleted project can be read by id by a super administrator and by members of the team that owned it last, and is refused to anyone else exactly as an unknown id is. The answer names the project (`name`) and, for a deleted one, `deletedAt` and the team that last owned it (`lastOwner`, null when not recorded). Counts only, never who.
Route GET /admin/api/projects/:id/activity

Input (query)

{

"type": "object",

"properties": {

"month": {

"type": "string"

}

}

}

activity_overview
List every project on the instance with its monthly active users, including the default and administrators projects and deleted projects, each with its customer (the team that owns it, or owned it when it was deleted) and its last 13 months and the month's days. Also answers each customer's summed figures and contacts (its owners' emails), the instance's totals, and the projects whose figure rose or fell sharply between the last two closed months. A customer's figure is a sum of its projects', not a count of distinct people. Counts only, never which end users. A super administrator reads the whole instance; any other administrator reads only the projects owned by teams they belong to and the deleted projects those teams owned last, with those teams as the customers and every total computed over those projects alone — the reserved projects are never included for them.
Route GET /admin/api/activity

Input (query)

{

"type": "object",

"properties": {

"month": {

"type": "string"

}

}

}

project_user_list
A page of the end-user accounts held in one project, ordered by email, as `{ users, totalResults }`. `q` finds people whose email or username starts with it, in any letter case, or whose id is exactly it. Narrow with `status` (active, deactivated, locked), `managedBy` (`console` for people administrators manage, or a provisioning connection's id), `verified` and `authenticator` (yes or no). Page with `startIndex` (1-based) and `count` (1–200, default 50). Each user carries their groups; `passwordSet` says whether a password anyone chose opens the account, when known. Read one person in full with project_user_get.
Route GET /admin/api/projects/:id/users

Input (query)

{

"additionalProperties": false,

"type": "object",

"properties": {

"q": {

"minLength": 1,

"maxLength": 254,

"type": "string"

},

"status": {

"anyOf": [

{

"const": "active",

"type": "string"

},

{

"const": "deactivated",

"type": "string"

},

{

"const": "locked",

"type": "string"

}

]

},

"managedBy": {

"minLength": 1,

"maxLength": 128,

"type": "string"

},

"verified": {

"anyOf": [

{

"const": "yes",

"type": "string"

},

{

"const": "no",

"type": "string"

}

]

},

"authenticator": {

"anyOf": [

{

"const": "yes",

"type": "string"

},

{

"const": "no",

"type": "string"

}

]

},

"startIndex": {

"pattern": "^[1-9][0-9]{0,8}$",

"type": "string"

},

"count": {

"pattern": "^(?:[1-9]|[1-9][0-9]|1[0-9][0-9]|200)$",

"type": "string"

}

}

}

project_user_get
One end-user of a project, as a row of project_user_list, and — when they are locked — who locked them: `lockedLocally.byEmail` is that administrator's email, or null when their account no longer exists.
Route GET /admin/api/projects/:id/users/:uid

project_user_session_list
The applications an end-user is signed in to in this project, as `{ applications }`: per application its `clientId` and `clientName`, `signedInAt`, `lastTokenAt` (when this server last issued it a token for the person — not when the application used one) and `offline` (it holds offline access no live session carries). A row with `clientId: null` is a sign-in that has reached no application yet. No network address, browser or device is stored, so none is returned. Sign the person out of one application with project_user_application_sign_out.
Route GET /admin/api/projects/:id/users/:uid/sessions

federation_provider_list
The upstream identity providers configured on a project, in the order their buttons appear on its sign-in page. `onSignInPage` says whether a provider’s button is there now: false for a disabled provider, and for every provider while sign-in through providers is switched off on the instance (the `federation.enabled` setting), which is the usual reason a connected provider is missing from the sign-in page. `signInWarnings` names a button that would confuse people: `duplicate_brand` (a second enabled Google, Apple or GitHub connection — two identical brand buttons; two Microsoft organisations are normal and do not warn) and `named_like_brand` (a provider not recognised but named like one that is, so its button reads like that brand without its mark). With more than six providers the sign-in page shows five and folds the rest under "More sign-in options", never folding a recognised provider. Never returns a provider client secret.
Route GET /admin/api/projects/:id/federation

federation_provider_get
One upstream identity provider of a project, with what it takes to run it: `callbackUri` (the redirect URI to register at the provider) and `callbackStability` (`provisional` while the project's address may still change), `recognised` (the catalogue entry it matches, or null), `logoutSignals` (whether the provider can send Global Token Revocation and back-channel logout at all — never for GitHub), `capabilities` (whether this instance accepts each), `boundConnection` (the SCIM provisioning connection bound to it, or null), `linkedUsers` (how many of the project's users are linked to it — the people a disable or delete affects), `recommendedCorrelation` (the claim and SCIM attribute a provisioning connection should match on) and `warnings` (`federation_off`: enabled, but sign-in through providers is switched off on the instance, so its button is on no sign-in page until a super administrator turns on `federation.enabled`; `duplicate_brand`: another enabled connection to the same Google, Apple or GitHub, so people see two identical brand buttons; `named_like_brand`: not recognised, but named like a provider that is). Never returns its client secret.
Route GET /admin/api/projects/:id/federation/:providerId

project_group_list
A project's groups of end users, each with its member count and, for one a SCIM directory manages, the connection that owns it. Group names are what relying parties see in the `groups` claim.
Route GET /admin/api/projects/:id/groups

project_group_get
One group of the project's end users: its name, external id and owning connection if a directory manages it, and its member count.
Route GET /admin/api/projects/:id/groups/:gid

project_group_member_list
A page of a group's members (id, email, userName), up to 1,000 at a time, with the total. `q` narrows it to members whose email or username starts with it, in any letter case, or whose id is exactly it; the total is then the matching members'.
Route GET /admin/api/projects/:id/groups/:gid/members

Input (query)

{

"additionalProperties": false,

"type": "object",

"properties": {

"startIndex": {

"type": "string"

},

"count": {

"type": "string"

},

"q": {

"minLength": 1,

"maxLength": 254,

"type": "string"

}

}

}

provisioning_connection_list
The SCIM provisioning connections of a project, each with its SCIM base URL, token endpoint, client id, managed-user count and warnings. Never returns a secret or a static token.
Route GET /admin/api/projects/:id/provisioning-connections

provisioning_connection_get
One SCIM provisioning connection: everything a directory administrator needs to configure it, which credentials it holds, which kinds of credential this instance would issue it now (`availableCredentialKinds`), and how many users and groups it manages. Never returns a secret or a static token.
Route GET /admin/api/projects/:id/provisioning-connections/:connectionId

provisioning_hold_get
Whom a SCIM provisioning connection held by its mass-deprovisioning guard has been refused the deactivation or deletion of: `{held: false}`, or `{held: true, since, count, refused, total}` where each refused row names the user (`userId`, `email`, `userName`), the last refused `operation`, `firstAt`, `lastAt` and `attempts`, most recent first, at most 1,000 rows. Releasing the hold (provisioning_connection_release) lets the directory's retries of exactly these go through — read this first.
Route GET /admin/api/projects/:id/provisioning-connections/:connectionId/hold

provisioning_hold_list
Every SCIM provisioning connection held by its mass-deprovisioning guard, across the projects project_list shows the caller: `{count, connections}`, each with `projectId`, `projectName`, `connectionId`, `displayName`, `since` (when the hold began) and `refused` (how many removals it has refused so far), oldest hold first. While a connection is held, the people the directory removes keep their access; read provisioning_hold_get before releasing one.
Route GET /admin/api/provisioning-holds

provisioning_directory_list
How to connect each known directory to a project over SCIM, in the directory's own words: `directories`, one each for `entra` (Microsoft Entra ID), `okta` and `generic`, with `displayName`, `recommendedCredential` and `fallbackCredential` (the kind to pass to provisioning_credential_issue), `recommendedCorrelation`, `labels` (the directory's names for the SCIM base URL, token endpoint, client id, client secret and static token), the ordered `steps`, known `pitfalls`, and `values` — this project's SCIM base URL and token endpoint, null when it has no SCIM address. Set a connection's `directory` to the matching id. Returns no credential.
Route GET /admin/api/projects/:id/provisioning-connections/directories

federation_catalogue_list
What it takes to connect a recognised identity provider to a project: the ordered steps, where to perform them, and the exact callback address to register with the upstream. Returns no configured provider and no credential. Connect one by passing that provider catalogueId to federation_provider_create with the client id and secret it issued.
Route GET /admin/api/projects/:id/federation/catalogue

federation_identity_list
The upstream identities linked to one end-user account, by provider.
Route GET /admin/api/projects/:id/users/:uid/identities

settings_get
The editable server settings, with the catalogue describing each one and its current value.
Route GET /admin/api/settings

plan_list
The plan catalogue: each plan’s limits (monthly active users per project, projects per administrator, applications and APIs per project; null is unlimited), its features (enterpriseSignIn, customSlug, customDomain), whether it is the default a new project is put on, and how many projects are on it. Plans limit tenants only while the plans.enabled setting is on, and never a super administrator or a System project.
Route GET /admin/api/plans

smtp_settings_get
The outbound mail settings. The password is never returned, only whether one is set.
Route GET /admin/api/settings/smtp

sentry_settings_get
The Sentry integration settings. The ingestion credential is never returned, only whether one is set.
Route GET /admin/api/settings/sentry

jwks_list
The instance (root) signing keys, each with its state — published (verifies, may be promoted once promotableAt has passed; signedUntil when it signed before a promotion replaced it), signing (one per algorithm), or retired (still verifies until removableAt, then hidden). Nothing here needs a restart. No key material is returned; the public keys are at the root /jwks.
Route GET /admin/api/jwks

project_key_list
The signing keys of a project that has an address of its own, each with its state — published (verifies, may be promoted once promotableAt has passed; signedUntil when it signed before a promotion replaced it), signing (one per algorithm), or retired (still verifies until removableAt). No key material is returned; the public keys are at the project's jwks_uri. A project served at the root signs with the instance keys (jwks_list).
Route GET /admin/api/projects/:id/keys

audit_list
The administrative audit trail for the teams this administrator belongs to, newest first, filterable by actor, action, target, surface and time window. A super administrator sees the whole instance, including actions that belong to no team. Filter `viaSurface=mcp` for actions taken through an agent, `viaSurface=scim` for changes a provisioning connection made and `viaSurface=upstream` for sign-outs an upstream identity provider requested and `viaSurface=self-service` for applications an end user removed from their own connected-apps page. `project=<id>` returns everything that happened to or in one project: its own settings, address, keys and providers, the users, groups, provisioning resources and applications within it (those entries carry `projectId`). `connection=<id>` returns what one provisioning connection did and what administrators did to it. Actions and target types name projects, teams and groups: actions `project.*`, `team.*` and `group.*` (a group of end users), with target types `Project`, `Team` and `Group`; `retiredproject.*` and `RetiredProject` are entries about the former project entity, which no longer exists. `actionPrefix` matches actions starting with the text, such as `project.key.`, and must name whole segments: a prefix that ends partway into a first segment several actions share is refused. Filters only narrow what the caller may already read. Key actions carry the `kid` they acted on. An entry whose actor is a provisioning connection (`connection:<id>`), an upstream identity provider (`upstream:<project>:<provider>`) or an end user (`enduser:<project>:<account>`) carries `actorName`, its name or address now, or null when it no longer exists. An entry means an authorized actor reached the point of applying a change, not that the change took effect.
Route GET /admin/api/audit

Input (query)

{

"type": "object",

"properties": {

"actor": {

"type": "string"

},

"action": {

"type": "string"

},

"targetType": {

"type": "string"

},

"targetId": {

"type": "string"

},

"targetScope": {

"type": "string"

},

"viaSurface": {

"type": "string"

},

"viaClientId": {

"type": "string"

},

"project": {

"type": "string"

},

"connection": {

"type": "string"

},

"actionPrefix": {

"type": "string"

},

"from": {

"type": "string"

},

"to": {

"type": "string"

},

"page": {

"type": "string"

},

"pageSize": {

"type": "string"

}

}

}

error_list
Recorded internal server faults, newest first, one entry per distinct fault with an exact occurrence count. Filterable by error code, route, surface, status, client, actor and time window. Only defects are recorded — routine client rejections such as a bad grant or a wrong password never appear. A non-zero `dropped` means recording fell behind and the list is incomplete.
Route GET /admin/api/errors

Input (query)

{

"type": "object",

"properties": {

"errorCode": {

"type": "string"

},

"route": {

"type": "string"

},

"surface": {

"type": "string"

},

"status": {

"type": "string"

},

"clientId": {

"type": "string"

},

"actor": {

"type": "string"

},

"from": {

"type": "string"

},

"to": {

"type": "string"

},

"limit": {

"type": "string"

},

"offset": {

"type": "string"

}

}

}

error_summary
How much is failing over a time window, broken down by error code and by endpoint, most frequent first. Counts sum occurrences rather than distinct faults, so one fault seen 900 times outranks nine seen once. Takes a window only — use error_list to filter by client, actor, status or surface.
Route GET /admin/api/errors/summary

Input (query)

{

"type": "object",

"properties": {

"from": {

"type": "string"

},

"to": {

"type": "string"

}

}

}

error_purge_preview
How many recorded faults, and how many occurrences, a purge with these filters would remove. Reads only — nothing is deleted. Available whether or not this deployment lets an agent perform the purge itself, because reading the consequence of a deletion is not destructive.
Route GET /admin/api/errors/purge-preview

Input (query)

{

"type": "object",

"properties": {

"errorCode": {

"type": "string"

},

"route": {

"type": "string"

},

"surface": {

"type": "string"

},

"status": {

"type": "string"

},

"clientId": {

"type": "string"

},

"actor": {

"type": "string"

},

"from": {

"type": "string"

},

"to": {

"type": "string"

},

"limit": {

"type": "string"

},

"offset": {

"type": "string"

}

}

}

error_by_reference
Resolve the reference identifier a caller reported (it looks like err_XXXXXXXXXXXXXXXX) to the one fault it belongs to, and which occurrence it was. Answers not-found rather than an empty result, so a mistyped reference is distinguishable from a record that has aged out.
Route GET /admin/api/errors/reference/:reference

error_get
One recorded fault with its retained samples: where it arose, how often, when first and last seen. Samples are capped, so a very frequent fault keeps its earliest and its most recent occurrences while the count stays exact. No credential value is ever stored, so none can be read here.
Route GET /admin/api/errors/:id

Ordinary writes

resource_declare
Declare an MCP server (or any API) as a protected resource of this project, so this server will mint tokens whose audience is exactly that resource. The scope list is the baseline a general-purpose client requests in full, not a catalogue — an omnibus scope is refused. Tokens default to a self-contained JWT the resource verifies against this server's published keys, with a fifteen-minute lifetime. Nothing about the resource is fetched: the identifier is unique within the project, so it need not be reachable or deployed yet. `scopeDescriptions` says, per scope, what it lets an app do (`description`, a phrase that follows "The app will be able to") and whether the app only sees something (`kind: "see"`) or acts (`"act"`, the default); the consent page shows these to the person deciding, and a scope without one is shown only by its technical name. A project without an address of its own shares the root namespace, where only a super administrator may declare, amend or remove a resource.
Route POST /admin/api/projects/:id/resources · audit action resource.create

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"identifier",

"name",

"scopes"

],

"properties": {

"identifier": {

"minLength": 1,

"type": "string"

},

"name": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"scopes": {

"type": "array",

"items": {

"type": "string"

}

},

"scopeDescriptions": {

"type": "object",

"patternProperties": {

"^(.*)$": {

"additionalProperties": false,

"type": "object",

"required": [

"description"

],

"properties": {

"description": {

"minLength": 1,

"maxLength": 200,

"type": "string"

},

"kind": {

"anyOf": [

{

"const": "see",

"type": "string"

},

{

"const": "act",

"type": "string"

}

]

}

}

}

}

},

"tokenFormat": {

"anyOf": [

{

"const": "jwt",

"type": "string"

},

{

"const": "opaque",

"type": "string"

}

]

},

"accessTokenTTL": {

"minimum": 30,

"maximum": 86400,

"multipleOf": 1,

"type": "number"

},

"trailingSlashSignificant": {

"type": "boolean"

}

}

}

resource_update
Change a declared resource's name, scopes, scope descriptions, token format or lifetime. `scopeDescriptions` replaces the whole map; changing `scopes` without it drops the descriptions of removed scopes. The identifier itself cannot be amended: it is the audience of every token already issued, so replacing it is a delete and a fresh declaration.
Route PATCH /admin/api/projects/:id/resources/:resourceId · audit action resource.update

Input (body)

{

"additionalProperties": false,

"type": "object",

"properties": {

"name": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"scopes": {

"type": "array",

"items": {

"type": "string"

}

},

"scopeDescriptions": {

"type": "object",

"patternProperties": {

"^(.*)$": {

"additionalProperties": false,

"type": "object",

"required": [

"description"

],

"properties": {

"description": {

"minLength": 1,

"maxLength": 200,

"type": "string"

},

"kind": {

"anyOf": [

{

"const": "see",

"type": "string"

},

{

"const": "act",

"type": "string"

}

]

}

}

}

}

},

"tokenFormat": {

"anyOf": [

{

"const": "jwt",

"type": "string"

},

{

"const": "opaque",

"type": "string"

}

]

},

"accessTokenTTL": {

"minimum": 30,

"maximum": 86400,

"multipleOf": 1,

"type": "number"

}

}

}

client_create
Register an OAuth client (application) in a project; it signs that project’s people in for its whole life, and no operation moves it to another. corsOrigins lists the web origins whose browser code may read its responses — an invalid origin refuses the whole list. Only the project’s own client_credentials clients may obtain machine tokens for the APIs declared in it. A confidential client’s generated secret is returned exactly once, here, and is never readable again. partialConsent lets the person untick individual permissions on the consent page — turn it on only for an application that copes with receiving fewer permissions than it asked for. logo takes a data:image/png, image/jpeg or image/webp base64 URL of at most 64 KiB (null removes it), and policyUri and tosUri take https addresses; the consent page shows them only to this organization’s own users, the links only when they are on a redirect URI’s host. primaryColor takes a colour written #rrggbb (null removes it); the sign-in, two-step and consent pages paint it, shifted to the nearest legible shade where needed, only for this organization’s own users — the response’s paintedPrimaryColor shows the light and dark shades they will see.
Route POST /admin/api/projects/:id/clients · audit action client.create

Input (body)

{

"type": "object",

"required": [

"grantTypes",

"tokenEndpointAuthMethod"

],

"properties": {

"clientName": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"applicationType": {

"anyOf": [

{

"const": "web",

"type": "string"

},

{

"const": "native",

"type": "string"

}

]

},

"grantTypes": {

"minItems": 1,

"type": "array",

"items": {

"anyOf": [

{

"const": "authorization_code",

"type": "string"

},

{

"const": "refresh_token",

"type": "string"

},

{

"const": "client_credentials",

"type": "string"

},

{

"const": "urn:ietf:params:oauth:grant-type:device_code",

"type": "string"

},

{

"const": "urn:openid:params:grant-type:ciba",

"type": "string"

}

]

}

},

"redirectUris": {

"maxItems": 50,

"type": "array",

"items": {

"maxLength": 2000,

"type": "string"

}

},

"postLogoutRedirectUris": {

"maxItems": 50,

"type": "array",

"items": {

"maxLength": 2000,

"type": "string"

}

},

"tokenEndpointAuthMethod": {

"anyOf": [

{

"const": "none",

"type": "string"

},

{

"const": "client_secret_basic",

"type": "string"

},

{

"const": "client_secret_post",

"type": "string"

},

{

"const": "client_secret_jwt",

"type": "string"

},

{

"const": "private_key_jwt",

"type": "string"

}

]

},

"scope": {

"type": "string"

},

"requireConsent": {

"type": "boolean"

},

"partialConsent": {

"type": "boolean"

},

"backchannelTokenDeliveryMode": {

"anyOf": [

{

"const": "poll",

"type": "string"

},

{

"const": "ping",

"type": "string"

}

]

},

"backchannelClientNotificationEndpoint": {

"type": "string"

},

"authorizationDetailsTypes": {

"type": "array",

"items": {

"type": "string"

}

},

"corsOrigins": {

"maxItems": 50,

"type": "array",

"items": {

"maxLength": 300,

"type": "string"

}

},

"jwks": {

"type": "object",

"required": [

"keys"

],

"properties": {

"keys": {

"type": "array",

"items": {

"type": "object",

"patternProperties": {

"^(.*)$": {}

}

}

}

}

},

"jwksUri": {

"type": "string"

},

"tokenEndpointAuthSigningAlg": {

"type": "string"

},

"idTokenSignedResponseAlg": {

"type": "string"

},

"authorizationSignedResponseAlg": {

"type": "string"

},

"requestObjectSigningAlg": {

"type": "string"

},

"requireSignedRequestObject": {

"type": "boolean"

},

"requirePushedAuthorizationRequests": {

"type": "boolean"

},

"dpopBoundAccessTokens": {

"type": "boolean"

},

"backchannelLogoutUri": {

"type": "string"

},

"backchannelLogoutSessionRequired": {

"type": "boolean"

},

"subjectType": {

"anyOf": [

{

"const": "public",

"type": "string"

},

{

"const": "pairwise",

"type": "string"

}

]

},

"sectorIdentifierUri": {

"type": "string"

},

"logo": {

"maxLength": 87440,

"type": "string"

},

"primaryColor": {

"maxLength": 32,

"type": "string"

},

"policyUri": {

"type": "string"

},

"tosUri": {

"type": "string"

}

}

}

client_update
Change a client's registered metadata — redirect URIs, grant types, and the rest. partialConsent lets the person untick individual permissions on the consent page — turn it on only for an application that copes with receiving fewer permissions than it asked for. logo takes a data:image/png, image/jpeg or image/webp base64 URL of at most 64 KiB (null removes it), and policyUri and tosUri take https addresses; the consent page shows them only to this organization’s own users, the links only when they are on a redirect URI’s host. primaryColor takes a colour written #rrggbb (null removes it); the sign-in, two-step and consent pages paint it, shifted to the nearest legible shade where needed, only for this organization’s own users — the response’s paintedPrimaryColor shows the light and dark shades they will see.
Route PATCH /admin/api/projects/:id/clients/:clientId · audit action client.update

Input (body)

{

"type": "object",

"properties": {

"clientName": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"applicationType": {

"anyOf": [

{

"const": "web",

"type": "string"

},

{

"const": "native",

"type": "string"

}

]

},

"grantTypes": {

"minItems": 1,

"type": "array",

"items": {

"anyOf": [

{

"const": "authorization_code",

"type": "string"

},

{

"const": "refresh_token",

"type": "string"

},

{

"const": "client_credentials",

"type": "string"

},

{

"const": "urn:ietf:params:oauth:grant-type:device_code",

"type": "string"

},

{

"const": "urn:openid:params:grant-type:ciba",

"type": "string"

}

]

}

},

"redirectUris": {

"maxItems": 50,

"type": "array",

"items": {

"maxLength": 2000,

"type": "string"

}

},

"postLogoutRedirectUris": {

"maxItems": 50,

"type": "array",

"items": {

"maxLength": 2000,

"type": "string"

}

},

"tokenEndpointAuthMethod": {

"anyOf": [

{

"const": "none",

"type": "string"

},

{

"const": "client_secret_basic",

"type": "string"

},

{

"const": "client_secret_post",

"type": "string"

},

{

"const": "client_secret_jwt",

"type": "string"

},

{

"const": "private_key_jwt",

"type": "string"

}

]

},

"scope": {

"type": "string"

},

"requireConsent": {

"type": "boolean"

},

"partialConsent": {

"type": "boolean"

},

"backchannelTokenDeliveryMode": {

"anyOf": [

{

"const": "poll",

"type": "string"

},

{

"const": "ping",

"type": "string"

}

]

},

"backchannelClientNotificationEndpoint": {

"type": "string"

},

"corsOrigins": {

"maxItems": 50,

"type": "array",

"items": {

"maxLength": 300,

"type": "string"

}

},

"authorizationDetailsTypes": {

"type": "array",

"items": {

"type": "string"

}

},

"jwks": {

"anyOf": [

{

"type": "object",

"required": [

"keys"

],

"properties": {

"keys": {

"type": "array",

"items": {

"type": "object",

"patternProperties": {

"^(.*)$": {}

}

}

}

}

},

{

"type": "null"

}

]

},

"jwksUri": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"tokenEndpointAuthSigningAlg": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"idTokenSignedResponseAlg": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"authorizationSignedResponseAlg": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"requestObjectSigningAlg": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"requireSignedRequestObject": {

"anyOf": [

{

"type": "boolean"

},

{

"type": "null"

}

]

},

"requirePushedAuthorizationRequests": {

"anyOf": [

{

"type": "boolean"

},

{

"type": "null"

}

]

},

"dpopBoundAccessTokens": {

"anyOf": [

{

"type": "boolean"

},

{

"type": "null"

}

]

},

"backchannelLogoutUri": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"backchannelLogoutSessionRequired": {

"anyOf": [

{

"type": "boolean"

},

{

"type": "null"

}

]

},

"subjectType": {

"anyOf": [

{

"anyOf": [

{

"const": "public",

"type": "string"

},

{

"const": "pairwise",

"type": "string"

}

]

},

{

"type": "null"

}

]

},

"sectorIdentifierUri": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"logo": {

"anyOf": [

{

"maxLength": 87440,

"type": "string"

},

{

"type": "null"

}

]

},

"primaryColor": {

"anyOf": [

{

"maxLength": 32,

"type": "string"

},

{

"type": "null"

}

]

},

"policyUri": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

},

"tosUri": {

"anyOf": [

{

"type": "string"

},

{

"type": "null"

}

]

}

}

}

admin_create
Create an administrator account.
Route POST /admin/api/admins · audit action admin.create

Input (body)

{

"type": "object",

"required": [

"email",

"password"

],

"properties": {

"email": {

"format": "email",

"type": "string"

},

"password": {

"minLength": 12,

"type": "string"

}

}

}

team_create
Create a team. The administrator this agent acts for becomes its first owner — a team with no owner would be unreachable the instant it existed.
Route POST /admin/api/teams · audit action team.create

Input (body)

{

"type": "object",

"required": [

"name"

],

"properties": {

"name": {

"minLength": 1,

"maxLength": 100,

"type": "string"

}

}

}

team_update
Rename a team. Requires being an owner of that team, which is a membership kind rather than an instance role.
Route PATCH /admin/api/teams/:id · audit action team.update

Input (body)

{

"type": "object",

"properties": {

"name": {

"minLength": 1,

"maxLength": 100,

"type": "string"

}

}

}

team_member_add
Add an existing administrator to a team as an owner or a plain member. This grants them everything the team owns, in one call, without their agreement — so it is a super administrator’s operation only; anyone else adds people with team_invite, which the person accepts. Refused for a personal team, which cannot be shared.
Route POST /admin/api/teams/:id/members · audit action team.member.add

Input (body)

{

"type": "object",

"required": [

"userId",

"role"

],

"properties": {

"userId": {

"minLength": 1,

"type": "string"

},

"role": {

"anyOf": [

{

"const": "owner",

"type": "string"

},

{

"const": "member",

"type": "string"

}

]

}

}

}

team_member_update
Promote a member to owner or demote an owner to member. The last owner can be neither demoted nor removed. Owner-only.
Route PATCH /admin/api/teams/:id/members/:userId · audit action team.member.update

Input (body)

{

"type": "object",

"required": [

"role"

],

"properties": {

"role": {

"anyOf": [

{

"const": "owner",

"type": "string"

},

{

"const": "member",

"type": "string"

}

]

}

}

}

team_invite
Invite somebody into a team by email, as an owner or a plain member. Creates an administrator account for them when they accept, if the address has none. Owner-only. Refused for a personal team, which cannot be shared. An owner invitation is judged against the invitee’s plan when it is accepted, not when it is sent. Bounded, to protect the instance’s mail: one invitation per address and team every 10 minutes and at most 5 a day, 100 a day per inviter, and 50 open invitations per team — a refusal carries `retryAfter` in seconds where waiting helps.
Route POST /admin/api/teams/:id/invitations · audit action invitation.create

Input (body)

{

"type": "object",

"required": [

"email",

"role"

],

"properties": {

"email": {

"format": "email",

"maxLength": 320,

"type": "string"

},

"role": {

"anyOf": [

{

"const": "owner",

"type": "string"

},

{

"const": "member",

"type": "string"

}

]

}

}

}

team_invitation_revoke
Withdraw a pending invitation before it is accepted. Owner-only.
Route DELETE /admin/api/teams/:id/invitations/:inviteId · audit action invitation.revoke

project_create
Create a project. `displayName` is the name its people know the service by, shown at the top of the page where they allow an app access (the consent page); without it that page shows the project's address. A project cannot be created unreachable: at creation it has no providers, so password login cannot be switched off. `totpRequired` makes a password sign-in also require a one-time code from an authenticator app; it governs password sign-in only and never gates a federated one. A `slug` is served as a path beneath this instance, or with `addressForm: 'subdomain'` as a subdomain of its host (`<slug>.<instance host>`); a `host` is a hostname of its own. Each form must be listed in `can.addressForms` from whoami — the operator's `projects.addressForms` setting decides between path and subdomain for a team — and creating at all needs `can.createProject`. While plans are enforced and whoami's `can.nameSlug` is false, leave `slug` out: a random eight-character name is generated in the chosen form, and naming one is refused with `code: 'plan_limit'`; a refusal with that code names the limit and the plan, and only a larger plan, set by a super administrator, lifts it. `teamId` names the team it belongs to — one this administrator is a member of (a super administrator: any team but another administrator's personal team); without it, the administrator's active team.
Route POST /admin/api/projects · audit action project.create

Input (body)

{

"type": "object",

"required": [

"name"

],

"properties": {

"name": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"displayName": {

"minLength": 1,

"maxLength": 60,

"type": "string"

},

"slug": {

"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",

"maxLength": 63,

"type": "string"

},

"host": {

"minLength": 1,

"maxLength": 253,

"type": "string"

},

"addressForm": {

"anyOf": [

{

"const": "path",

"type": "string"

},

{

"const": "subdomain",

"type": "string"

}

]

},

"passwordLogin": {

"type": "boolean"

},

"registrationOpen": {

"type": "boolean"

},

"emailVerificationRequired": {

"type": "boolean"

},

"verificationMethod": {

"anyOf": [

{

"const": "link",

"type": "string"

},

{

"const": "code",

"type": "string"

}

]

},

"totpRequired": {

"type": "boolean"

},

"teamId": {

"minLength": 1,

"type": "string"

}

}

}

project_update
Change a project's name, the name shown to its people (`displayName`; null removes it), or its registration, verification and second-factor settings. Editing the project itself needs membership of the project's team — project_get states it as `can.editSettings`. `totpRequired` governs password sign-in only — it is accepted but inert while `passwordLogin` is off, and it never gates a federated sign-in, so it is not a way to secure a project that signs in through an upstream provider. Requiring verification is refused on every project until mail delivery is configured. A change that tightens who can get in — `passwordLogin` off, `emailVerificationRequired` or `totpRequired` (while passwords are on) turned on, or `registrationOpen` turned on for the default or the administrators' project — changes nothing when called alone: it answers with a preview, `consequences` (one sentence per change, counted), `expect` (the counts) and `changes`; call again with the same settings, `confirm: true` and that `expect` to apply it. If the counts changed since, it is refused with 409 and the fresh preview. Other changes apply at once. A super-administrator configures the administrators' own project here too: opening its registration lets anyone register a console account (the answer carries an advisory while verification is off); requiring verification is also refused until the acting administrator has verified their own address; turning password sign-in off is refused unless the caller's console session signed in through one of its identity providers and a super administrator stays reachable — so an agent, having no console session, is always refused it; with `emailVerificationRequired` off people sign in unverified and applications are told `email_verified: false` until each address is proven; on the administrators' project, sign-up (`registrationOpen`) may be open only while `emailVerificationRequired` is on, so open both in one change and close sign-up before turning verification off. Each refusal names its remedy.
Route PATCH /admin/api/projects/:id · audit action project.update

Input (body)

{

"type": "object",

"properties": {

"name": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"displayName": {

"anyOf": [

{

"minLength": 1,

"maxLength": 60,

"type": "string"

},

{

"type": "null"

}

]

},

"passwordLogin": {

"type": "boolean"

},

"registrationOpen": {

"type": "boolean"

},

"emailVerificationRequired": {

"type": "boolean"

},

"verificationMethod": {

"anyOf": [

{

"const": "link",

"type": "string"

},

{

"const": "code",

"type": "string"

}

]

},

"totpRequired": {

"type": "boolean"

},

"confirm": {

"type": "boolean"

},

"expect": {

"type": "object",

"properties": {

"withoutEnabledLink": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

"unverified": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

"passwordWithoutAuthenticator": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

"applications": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

}

}

}

}

}

project_user_create
Create an end-user account in a project. Without `password` no password opens it: the person signs in through an identity provider or sets one through password reset. With `invite: true` (never together with `password`) the person is mailed a link to choose their own password, valid for 7 days; refused before the account is created when the project does not sign people in with a password or mail delivery is not configured, and the answer carries `invitation: sent` or `failed` — a failed send leaves the account, and project_user_invite sends again. Its address starts verified only when the project does not require verification; setting the password through the link verifies it. The person starts with their address unverified in every project, whatever it requires; it becomes verified when they follow a reset or verification link (project_user_verification_send) or an administrator marks it (project_user_mark_verified), and applications are told `email_verified` accordingly.
Route POST /admin/api/projects/:id/users · audit action enduser.create

Input (body)

{

"type": "object",

"required": [

"email"

],

"properties": {

"email": {

"minLength": 3,

"type": "string"

},

"password": {

"minLength": 8,

"type": "string"

},

"claims": {

"type": "object",

"patternProperties": {

"^(.*)$": {}

}

},

"invite": {

"type": "boolean"

}

}

}

project_user_invite
Mail an end-user a link to choose their own password, valid for 7 days and once; choosing it also verifies their address. Any earlier reset or invitation link of theirs stops working, and a password they already have keeps working until they choose a new one. The answer never carries the link. Refused for a user a provisioning connection manages, when the project does not sign people in with a password, when mail delivery is not configured, for a user who cannot sign in (deactivated or locked), and when a message was sent too recently (429 with `retryAfter` seconds).
Route POST /admin/api/projects/:id/users/:uid/invitation · audit action enduser.invitation.send

project_user_mark_verified
Mark an end-user's address verified, once it has been confirmed some other way; a verification message still in their inbox stops working. Refused for a user a provisioning connection manages — their address trust is the connection's setting — and, with nothing recorded, for one already verified.
Route POST /admin/api/projects/:id/users/:uid/verified · audit action enduser.verified.mark

project_user_application_sign_out
Sign an end-user out of one application: that application is told through back-channel logout, its grants end with every token under them — offline refresh tokens included — and it must ask for consent again. The person's other applications and their sign-in stay as they were. Works on users a provisioning connection manages. 404 when the person is not signed in to that application; read where they are with project_user_session_list.
Route POST /admin/api/projects/:id/users/:uid/sessions/:clientId/sign-out · audit action enduser.application.signout

project_user_verification_send
Mail an end-user a fresh verification message in the project's current method. The answer never carries the link or code. Refused when the address is already verified, when mail delivery is not configured, and when a message was sent too recently.
Route POST /admin/api/projects/:id/users/:uid/verification · audit action enduser.verification.send

project_user_update
Change an end-user's active state, claims or profile. `profile` replaces the whole profile of a user administrators manage (refused for one a provisioning connection manages) and reaches tokens as standard claims under the scope the `claims` setting maps them to: `name.givenName` → given_name, `name.familyName` → family_name, `name.middleName` → middle_name, `name.formatted` (else `displayName`) → name, `nickName` → nickname, `locale` → locale (a BCP 47 tag), `timezone` → zoneinfo (a time-zone name such as Europe/Berlin) and the primary of `phoneNumbers` → phone_number (international form, +14155550100); an invalid value is refused with 422 naming `profile.<field>`. Where the profile sets a claim the stored claims also hold, the profile's wins. Their groups are changed with project_group_member_add and project_group_member_remove. Claims replace the account's whole set; sub, email, email_verified and the protocol claims are refused, and a claim is released to a client only once the `claims` setting names it under a scope. Deactivating ends the user's access at once — every session and token, with a logout notice to each relying party registered for one — but keeps the account; reactivating restores sign-in only, never the old tokens or consents.
Route PATCH /admin/api/projects/:id/users/:uid · audit action enduser.update

Input (body)

{

"type": "object",

"properties": {

"active": {

"type": "boolean"

},

"claims": {

"type": "object",

"patternProperties": {

"^(.*)$": {}

}

},

"profile": {

"additionalProperties": false,

"type": "object",

"properties": {

"name": {

"additionalProperties": false,

"type": "object",

"properties": {

"formatted": {

"maxLength": 2048,

"type": "string"

},

"familyName": {

"maxLength": 256,

"type": "string"

},

"givenName": {

"maxLength": 256,

"type": "string"

},

"middleName": {

"maxLength": 256,

"type": "string"

},

"honorificPrefix": {

"maxLength": 256,

"type": "string"

},

"honorificSuffix": {

"maxLength": 256,

"type": "string"

}

}

},

"displayName": {

"maxLength": 256,

"type": "string"

},

"nickName": {

"maxLength": 256,

"type": "string"

},

"title": {

"maxLength": 256,

"type": "string"

},

"preferredLanguage": {

"maxLength": 256,

"type": "string"

},

"locale": {

"maxLength": 256,

"type": "string"

},

"timezone": {

"maxLength": 256,

"type": "string"

},

"phoneNumbers": {

"maxItems": 10,

"type": "array",

"items": {

"additionalProperties": false,

"type": "object",

"required": [

"value"

],

"properties": {

"value": {

"maxLength": 256,

"type": "string"

},

"type": {

"maxLength": 64,

"type": "string"

},

"primary": {

"type": "boolean"

}

}

}

},

"emails": {

"maxItems": 10,

"type": "array",

"items": {

"additionalProperties": false,

"type": "object",

"required": [

"value"

],

"properties": {

"value": {

"maxLength": 256,

"type": "string"

},

"type": {

"maxLength": 64,

"type": "string"

},

"primary": {

"type": "boolean"

}

}

}

},

"enterprise": {

"additionalProperties": false,

"type": "object",

"properties": {

"employeeNumber": {

"maxLength": 256,

"type": "string"

},

"costCenter": {

"maxLength": 256,

"type": "string"

},

"organization": {

"maxLength": 256,

"type": "string"

},

"division": {

"maxLength": 256,

"type": "string"

},

"department": {

"maxLength": 256,

"type": "string"

},

"manager": {

"additionalProperties": false,

"type": "object",

"required": [

"value"

],

"properties": {

"value": {

"maxLength": 256,

"type": "string"

}

}

}

}

}

}

}

}

}

project_user_totp_clear
Clear an end-user's authenticator enrolment. Their existing authenticator stops working at once and their sessions end; they set a new one up at their next sign-in. Use when someone has lost the device holding their codes. Their password, grants and tokens are untouched.
Route DELETE /admin/api/projects/:id/users/:uid/totp · audit action enduser.totp.clear

project_user_lock
Lock an end-user out, at once: every session and token ends and relying parties registered for back-channel logout are told. Works on users a provisioning connection manages, and the connection cannot undo it — only project_user_unlock can. Use when an account is suspected compromised.
Route POST /admin/api/projects/:id/users/:uid/lock · audit action enduser.lock

Input (body)

{

"type": "object",

"required": [

"reason"

],

"properties": {

"reason": {

"minLength": 1,

"maxLength": 500,

"type": "string"

}

}

}

project_user_sign_out
Sign an end-user out everywhere: every session and token ends and relying parties registered for back-channel logout are told, but the account stays active and unlocked, so they can sign in again at once (and consent afresh). Works on users a provisioning connection manages. Use for a lost or stolen device; use project_user_lock when the person must not get back in.
Route POST /admin/api/projects/:id/users/:uid/sign-out · audit action enduser.signout

project_user_bulk_activate
Reactivate up to 100 end-users of a project, named by id. A locked user stays locked — unlocking is project_user_unlock, one person at a time. Answers `{ results }`, one per id: `changed`, `skipped` with a `reason` (`provisioned`, `already_active`, `not_found`) or `failed`. Each person gets their own audit entry.
Route POST /admin/api/projects/:id/users/bulk/activate · audit action enduser.bulk.activate

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"userIds"

],

"properties": {

"userIds": {

"minItems": 1,

"maxItems": 100,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 128,

"type": "string"

}

}

}

}

project_user_bulk_sign_out
Sign up to 100 end-users of a project out everywhere, named by id, each as project_user_sign_out would; users a provisioning connection manages included. Answers `{ results }`, one per id: `changed`, `skipped` (`not_found`) or `failed`. Each person gets their own audit entry.
Route POST /admin/api/projects/:id/users/bulk/sign-out · audit action enduser.bulk.signout

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"userIds"

],

"properties": {

"userIds": {

"minItems": 1,

"maxItems": 100,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 128,

"type": "string"

}

}

}

}

federation_provider_create
Configure an upstream identity provider on a project. Available whether or not federation is switched on, so a provider can be prepared first. On the Administrators project (`admin`) this decides who can sign in to the console: only a super administrator may, and each such change must be confirmed.
Route POST /admin/api/projects/:id/federation · audit action federation.provider.create

Input (body)

{

"type": "object",

"required": [

"clientId"

],

"properties": {

"catalogueId": {

"minLength": 1,

"type": "string"

},

"id": {

"minLength": 1,

"maxLength": 32,

"type": "string"

},

"displayName": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"issuer": {

"minLength": 1,

"type": "string"

},

"clientId": {

"minLength": 1,

"type": "string"

},

"clientSecret": {

"minLength": 1,

"type": "string"

},

"tenant": {

"minLength": 1,

"type": "string"

},

"teamId": {

"minLength": 1,

"type": "string"

},

"keyId": {

"minLength": 1,

"type": "string"

},

"signingKey": {

"minLength": 1,

"type": "string"

},

"enabled": {

"type": "boolean"

},

"scopes": {

"maxItems": 50,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 200,

"type": "string"

}

},

"emailTrusted": {

"type": "boolean"

},

"provisioning": {

"anyOf": [

{

"const": "jit",

"type": "string"

},

{

"const": "existing_only",

"type": "string"

}

]

},

"allowedEmailDomains": {

"maxItems": 100,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 253,

"type": "string"

}

},

"emailClaim": {

"minLength": 1,

"type": "string"

},

"acceptsGlobalTokenRevocation": {

"type": "boolean"

},

"acceptsBackChannelLogout": {

"type": "boolean"

}

}

}

federation_provider_update
Change an upstream provider's configuration, or enable/disable it. On the Administrators project (`admin`) this decides who can sign in to the console: only a super administrator may, and each such change must be confirmed.
Route PATCH /admin/api/projects/:id/federation/:providerId · audit action federation.provider.update

Input (body)

{

"type": "object",

"properties": {

"displayName": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"issuer": {

"minLength": 1,

"type": "string"

},

"clientId": {

"minLength": 1,

"type": "string"

},

"clientSecret": {

"minLength": 1,

"type": "string"

},

"tenant": {

"minLength": 1,

"type": "string"

},

"teamId": {

"minLength": 1,

"type": "string"

},

"keyId": {

"minLength": 1,

"type": "string"

},

"signingKey": {

"minLength": 1,

"type": "string"

},

"enabled": {

"type": "boolean"

},

"scopes": {

"maxItems": 50,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 200,

"type": "string"

}

},

"emailTrusted": {

"type": "boolean"

},

"provisioning": {

"anyOf": [

{

"const": "jit",

"type": "string"

},

{

"const": "existing_only",

"type": "string"

}

]

},

"allowedEmailDomains": {

"maxItems": 100,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 253,

"type": "string"

}

},

"emailClaim": {

"minLength": 1,

"type": "string"

},

"acceptsGlobalTokenRevocation": {

"type": "boolean"

},

"acceptsBackChannelLogout": {

"type": "boolean"

}

}

}

federation_provider_move
Move an upstream provider one place `up` or `down` in the order its project's sign-in page shows the buttons — the order federation_provider_list returns. Changes nothing but the order; refused (409) when the provider is already first or last. Answers `{providers}` in the new order. On the Administrators project (`admin`) only a super administrator may, and each move must be confirmed.
Route POST /admin/api/projects/:id/federation/:providerId/move · audit action federation.provider.move

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"direction"

],

"properties": {

"direction": {

"error": "direction must be up or down",

"anyOf": [

{

"const": "up",

"type": "string"

},

{

"const": "down",

"type": "string"

}

]

}

}

}

provisioning_connection_create
Create a SCIM provisioning connection on a project, bound to one of its federation providers. That provider stops creating accounts on first sign-in: from now on the directory creates them. Holds no credential until one is issued. `directory` (entra, okta or generic) chooses whose words the console uses for it; absent, it is inferred from the provider.
Route POST /admin/api/projects/:id/provisioning-connections · audit action provisioning.connection.create

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"displayName",

"providerId"

],

"properties": {

"displayName": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"providerId": {

"minLength": 1,

"maxLength": 32,

"type": "string"

},

"correlation": {

"additionalProperties": false,

"type": "object",

"required": [

"claim",

"attribute"

],

"properties": {

"claim": {

"minLength": 1,

"maxLength": 64,

"type": "string"

},

"attribute": {

"anyOf": [

{

"const": "externalId",

"type": "string"

},

{

"const": "userName",

"type": "string"

}

]

}

}

},

"emailTrust": {

"anyOf": [

{

"const": "trusted",

"type": "string"

},

{

"const": "untrusted",

"type": "string"

}

]

},

"threshold": {

"error": "threshold must be null, or a count of 1–100000 deprovisionings within a windowSeconds of 300–604800 seconds (5 minutes to 7 days)",

"anyOf": [

{

"type": "null"

},

{

"additionalProperties": false,

"type": "object",

"required": [

"count",

"windowSeconds"

],

"properties": {

"count": {

"minimum": 1,

"maximum": 100000,

"multipleOf": 1,

"type": "number"

},

"windowSeconds": {

"minimum": 300,

"maximum": 604800,

"multipleOf": 1,

"type": "number"

}

}

}

]

},

"directory": {

"error": "directory must be entra, okta or generic",

"anyOf": [

{

"const": "entra",

"type": "string"

},

{

"const": "okta",

"type": "string"

},

{

"const": "generic",

"type": "string"

}

]

}

}

}

provisioning_connection_update
Rename, enable or disable a SCIM provisioning connection, change its correlation rule or email trust, or set its mass-deprovisioning guard: threshold {count 1–100000, windowSeconds 300–604800} holds the connection after that many deactivations or deletions within the window; null removes it (refused while held). Disabling refuses every SCIM request through it and leaves its users as they are; no edit releases a hold. `directory` (entra, okta or generic) changes only whose words the console uses for it.
Route PATCH /admin/api/projects/:id/provisioning-connections/:connectionId · audit action provisioning.connection.update

Input (body)

{

"additionalProperties": false,

"type": "object",

"properties": {

"displayName": {

"minLength": 1,

"maxLength": 100,

"type": "string"

},

"enabled": {

"type": "boolean"

},

"correlation": {

"additionalProperties": false,

"type": "object",

"required": [

"claim",

"attribute"

],

"properties": {

"claim": {

"minLength": 1,

"maxLength": 64,

"type": "string"

},

"attribute": {

"anyOf": [

{

"const": "externalId",

"type": "string"

},

{

"const": "userName",

"type": "string"

}

]

}

}

},

"emailTrust": {

"anyOf": [

{

"const": "trusted",

"type": "string"

},

{

"const": "untrusted",

"type": "string"

}

]

},

"threshold": {

"error": "threshold must be null, or a count of 1–100000 deprovisionings within a windowSeconds of 300–604800 seconds (5 minutes to 7 days)",

"anyOf": [

{

"type": "null"

},

{

"additionalProperties": false,

"type": "object",

"required": [

"count",

"windowSeconds"

],

"properties": {

"count": {

"minimum": 1,

"maximum": 100000,

"multipleOf": 1,

"type": "number"

},

"windowSeconds": {

"minimum": 300,

"maximum": 604800,

"multipleOf": 1,

"type": "number"

}

}

}

]

},

"directory": {

"error": "directory must be entra, okta or generic",

"anyOf": [

{

"const": "entra",

"type": "string"

},

{

"const": "okta",

"type": "string"

},

{

"const": "generic",

"type": "string"

}

]

}

}

}

provisioning_credential_retire
End a SCIM provisioning connection's overlapping previous client secret ("oauth") or static token ("static_token") now, with every token the previous secret obtained; the current value and its tokens keep working. Refused (404) when there is no previous value that still works. Check that the directory uses the new value first — provisioning_connection_get says `stillUsedByDirectory` while it does not.
Route DELETE /admin/api/projects/:id/provisioning-connections/:connectionId/credentials/:kind/previous · audit action provisioning.credential.retire

provisioning_credential_revoke
Revoke a SCIM provisioning connection's OAuth credential ("oauth") or static token ("static_token"), with every token it obtained.
Route DELETE /admin/api/projects/:id/provisioning-connections/:connectionId/credentials/:kind · audit action provisioning.credential.revoke

project_group_create
Create a group of end users in a project. The name must be unique in the project in any letter case, and it is the value relying parties receive in the `groups` claim.
Route POST /admin/api/projects/:id/groups · audit action group.create

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"displayName"

],

"properties": {

"displayName": {

"type": "string"

}

}

}

project_group_update
Rename a group of end users. Relying parties see the new name in the next token each member receives, so an authorization rule keyed on the old name stops matching. A group a SCIM directory manages is read-only here.
Route PATCH /admin/api/projects/:id/groups/:gid · audit action group.update

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"displayName"

],

"properties": {

"displayName": {

"type": "string"

}

}

}

project_group_member_add
Add end users of the project to a group, up to 1,000 at a time. Users already in it are left as they are. Provisioned users may be added to a group administrators keep; a group a SCIM directory manages is read-only here.
Route POST /admin/api/projects/:id/groups/:gid/members · audit action group.member.add

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"userIds"

],

"properties": {

"userIds": {

"minItems": 1,

"maxItems": 1000,

"type": "array",

"items": {

"minLength": 1,

"type": "string"

}

}

}

}

project_group_member_remove
Remove one end user from a group. Their sessions and tokens are untouched; the next token they receive no longer names the group.
Route DELETE /admin/api/projects/:id/groups/:gid/members/:uid · audit action group.member.remove

project_key_generate
Generate a signing key for a project with an address of its own. The key is published at once and signs only once promoted, which the server allows after the publication window so every instance serves it first. Bounded: at most 12 keys held (signing, published, or retired and still verifying) and 20 made in a day, revoked ones counted; past either the answer is 409 with `reason: "key_limit"`.
Route POST /admin/api/projects/:id/keys · audit action project.key.generate

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"alg"

],

"properties": {

"alg": {

"anyOf": [

{

"const": "PS256",

"type": "string"

},

{

"const": "PS384",

"type": "string"

},

{

"const": "PS512",

"type": "string"

},

{

"const": "RS256",

"type": "string"

},

{

"const": "RS384",

"type": "string"

},

{

"const": "RS512",

"type": "string"

},

{

"const": "ES256",

"type": "string"

},

{

"const": "ES384",

"type": "string"

},

{

"const": "ES512",

"type": "string"

},

{

"const": "EdDSA",

"type": "string"

},

{

"const": "Ed25519",

"type": "string"

}

]

}

}

}

project_key_promote
Make a published key the one the project signs with in its algorithm. The key it replaces in that algorithm keeps verifying. Refused before the key's promotableAt.
Route POST /admin/api/projects/:id/keys/:kid/promote · audit action project.key.promote

project_key_rotate
Rotate one algorithm of a project's keys in one step. When a published key of that algorithm is past its promotableAt, the oldest such key starts signing (`promoted`), the previous signer returns to published and keeps verifying what it signed (`demoted`), and a new key is generated and published as the next one (`generated`). When no key is ready, only the next key is generated, nothing changes who signs, and `rotatableAt` says from when the algorithm can rotate. Never promotes a key inside its publication window. Refused for a project served at the root (its keys are the instance's) and for an unsupported algorithm. Bounded: at most 12 keys held (signing, published, or retired and still verifying) and 20 made in a day, revoked ones counted; past either the answer is 409 with `reason: "key_limit"`.
Route POST /admin/api/projects/:id/keys/rotate · audit action project.key.rotate

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"alg"

],

"properties": {

"alg": {

"anyOf": [

{

"const": "PS256",

"type": "string"

},

{

"const": "PS384",

"type": "string"

},

{

"const": "PS512",

"type": "string"

},

{

"const": "RS256",

"type": "string"

},

{

"const": "RS384",

"type": "string"

},

{

"const": "RS512",

"type": "string"

},

{

"const": "ES256",

"type": "string"

},

{

"const": "ES384",

"type": "string"

},

{

"const": "ES512",

"type": "string"

},

{

"const": "EdDSA",

"type": "string"

},

{

"const": "Ed25519",

"type": "string"

}

]

}

}

}

smtp_settings_test
Send a test message through the stored mail settings to the address of the administrator who authorized this connection, never anyone else. Answers with that address when the mail server accepted it; when it refused, the error carries the server’s own reply (for example "535 Authentication failed" or a rejected sender), which is what to fix in smtp_settings_update. Accepted does not mean delivered: ask the administrator whether it arrived, and whether in spam.
Route POST /admin/api/settings/smtp/test · audit action smtp.settings.test

High-consequence writes (two-call confirmation)

resource_delete
Remove a declared protected resource. Token issuance for that audience stops on the next request, so a live integration loses access as its current tokens expire. High-consequence: describe it and confirm before running.
Route DELETE /admin/api/projects/:id/resources/:resourceId · audit action resource.delete

client_secret_rotate
Issue a new secret for a confidential client and invalidate the old one. The new secret is returned exactly once. Anything still using the old secret stops working immediately.
Route POST /admin/api/projects/:id/clients/:clientId/secret · audit action client.secret.rotate

client_delete
Permanently delete an OAuth client and revoke what was issued to it. Irreversible.
Route DELETE /admin/api/projects/:id/clients/:clientId · audit action client.delete

admin_update
Activate or deactivate an administrator, or correct their email address. A changed address must be verified again before the administrator can sign in while the administrators' project requires verification, and a super-administrator cannot change their own address while it does. Never grants or withdraws super-administrator status — that is admin_super_grant and admin_super_withdraw.
Route PATCH /admin/api/admins/:id · audit action admin.update

Input (body)

{

"type": "object",

"properties": {

"active": {

"type": "boolean"

},

"email": {

"format": "email",

"type": "string"

}

}

}

admin_deactivate
Deactivate an administrator: the account is kept but can no longer sign in. Refused for the last active super-administrator.
Route DELETE /admin/api/admins/:id · audit action admin.deactivate

admin_super_grant
Make an administrator a super administrator — a member of Super administrators, with authority over the whole instance. Refused for an inactive administrator.
Route POST /admin/api/admins/:id/super-admin · audit action admin.superadmin.grant

admin_super_withdraw
Withdraw super-administrator status from an administrator. Refused for the last active super administrator.
Route DELETE /admin/api/admins/:id/super-admin · audit action admin.superadmin.withdraw

admin_sign_out
Sign an administrator out everywhere, for a lost or stolen device: every console session they hold ends, and every grant and token issued to them at the console's issuer is revoked, agents' tokens included. The account is untouched and they can sign in again at once. Answers `sessions`, the number of console sessions ended. Refused on your own account (use Log out in the console).
Route POST /admin/api/admins/:id/sign-out · audit action admin.signout

admin_totp_clear
Reset an administrator's authenticator, for a lost device: the enrolment is cleared along with any failed attempts, and they are signed out everywhere as admin_sign_out does; they set a new one up at their next sign-in. Answers `sessions`. Refused on your own account, and when the administrator has no authenticator.
Route DELETE /admin/api/admins/:id/totp · audit action admin.totp.clear

team_member_remove
Remove an administrator from a team. They lose access to every project, application and end-user the team owns, on their very next request. The last owner cannot be removed. Owner-only.
Route DELETE /admin/api/teams/:id/members/:userId · audit action team.member.remove

project_address_change
Move a project to a different address: a path segment (`slug`), a subdomain of this instance (`slug` with `addressForm: 'subdomain'`) or a hostname of its own (`host`), never two. Moving between path and subdomain keeps the slug. Called without `confirm`, it changes nothing and answers with a preview naming what the change reaches — `applications`, `connections`, `providers` (each with the `newCallback` its upstream registration needs) and `resources` — the issuers `from` and `to`, and `expect`, their counts. Called with `confirm: true` and that `expect`, it performs the move: the project's issuer identifier becomes the new address, the previous address stops answering, and everyone signed in signs in again. If what it reaches changed since the preview, it is refused with 409 and the fresh preview. A super administrator may change any project's address; an owner of its team may change a path or subdomain address while no application, identity provider or provisioning connection depends on it, into a form listed in `can.addressForms`; only a super administrator may give it a hostname; a project served at the root cannot be moved at all — project_get states it as `can.changeAddress`, with `blockers.dependents` as the reason.
Route POST /admin/api/projects/:id/address · audit action project.address.change

Input (body)

{

"type": "object",

"properties": {

"slug": {

"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",

"maxLength": 63,

"type": "string"

},

"host": {

"minLength": 1,

"maxLength": 253,

"type": "string"

},

"addressForm": {

"anyOf": [

{

"const": "path",

"type": "string"

},

{

"const": "subdomain",

"type": "string"

}

]

},

"confirm": {

"type": "boolean"

},

"expect": {

"type": "object",

"required": [

"applications",

"connections",

"providers",

"resources"

],

"properties": {

"applications": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

"connections": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

"providers": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

"resources": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

}

}

}

}

}

project_owner_change
Move a project, with its applications and APIs, to another team (`teamId`). Requires owning the team it is in now and belonging to the destination; another administrator's personal team is never a destination. End users, clients and the sign-in address are unaffected. Called without `confirm`, it changes nothing and answers with what would move (`applications`, `apis`); with `confirm: true` it moves it. project_get states the right as `can.move`.
Route PUT /admin/api/projects/:id/owner · audit action project.owner.change

Input (body)

{

"type": "object",

"required": [

"teamId"

],

"properties": {

"teamId": {

"minLength": 1,

"type": "string"

},

"confirm": {

"type": "boolean"

}

}

}

project_restore
Bring back a project that is scheduled for deletion (project_get carries `deletion` with `purgeAt`, the date it will be destroyed): it answers at its address again, exactly as before it was deleted, and every session and token it issued that has not expired is valid again. A provisioning directory that tried to reach it while it was off may need a manual resync. Requires the right to delete it, decided over the accounts it holds now — project_get's `can.delete`. Refused with 409 when the project is not scheduled, or while its destruction is already under way. Deleting a project, and destroying a scheduled one at once, stay in the admin console.
Route POST /admin/api/projects/:id/restore · audit action project.restore

project_user_email_change
Change an end-user's login address. The new address is unverified, and a verification message sent to the old one no longer verifies anything; nothing is mailed by the change — send one with project_user_verification_send. Refused for a user a provisioning connection manages, and for an address another account in the project holds.
Route POST /admin/api/projects/:id/users/:uid/email · audit action enduser.email.change

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"email"

],

"properties": {

"email": {

"minLength": 3,

"maxLength": 254,

"type": "string"

}

}

}

project_user_unlock
Lift an end-user's lock. Nothing is restored: they sign in afresh, and only if their account is also active.
Route POST /admin/api/projects/:id/users/:uid/unlock · audit action enduser.unlock

project_user_password_reset
Replace an end-user's password. The previous one is unrecoverable and the account holder is not asked. With `signOut: true` every session and token they hold ends too, and relying parties registered for back-channel logout are told; without it they stay signed in.
Route POST /admin/api/projects/:id/users/:uid/password · audit action enduser.password.reset

Input (body)

{

"type": "object",

"required": [

"password"

],

"properties": {

"password": {

"minLength": 8,

"type": "string"

},

"signOut": {

"type": "boolean"

}

}

}

project_user_delete
Permanently delete an end-user account and everything issued to it; relying parties registered for back-channel logout are told the user signed out. Irreversible.
Route DELETE /admin/api/projects/:id/users/:uid · audit action enduser.delete

project_user_bulk_deactivate
Deactivate up to 100 end-users of a project, named by id, each exactly as project_user_update with `active: false` would: their sessions and tokens end and relying parties are told. Answers `{ results }`, one per id in the order given: `changed`, `skipped` with a `reason` (`provisioned`, `already_inactive`, `not_found`) or `failed` with a `reason`. Each person gets their own audit entry. Repeating the call is harmless.
Route POST /admin/api/projects/:id/users/bulk/deactivate · audit action enduser.bulk.deactivate

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"userIds"

],

"properties": {

"userIds": {

"minItems": 1,

"maxItems": 100,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 128,

"type": "string"

}

}

}

}

project_user_bulk_delete
Permanently delete up to 100 end-users of a project, named by id, each as project_user_delete would, with everything issued to them. Irreversible. Answers `{ results }`, one per id: `changed`, `skipped` with a `reason` (`provisioned`, `not_found`) or `failed` with a `reason` and, when records survived the sweep, `failedAreas`. Each person gets their own audit entry.
Route POST /admin/api/projects/:id/users/bulk/delete · audit action enduser.bulk.delete

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"userIds"

],

"properties": {

"userIds": {

"minItems": 1,

"maxItems": 100,

"type": "array",

"items": {

"minLength": 1,

"maxLength": 128,

"type": "string"

}

}

}

}

federation_provider_delete
Remove an upstream identity provider from a project. Deliberately available even with federation switched off, so a deployment can delete a provider it no longer trusts. Users who signed in only through it lose that route in.
Route DELETE /admin/api/projects/:id/federation/:providerId · audit action federation.provider.delete

federation_identity_delete
Sever one end-user's link to one upstream provider. The account survives; only the link is removed.
Route DELETE /admin/api/projects/:id/users/:uid/identities/:providerId · audit action federation.identity.delete

provisioning_connection_release
Release a SCIM provisioning connection held by its mass-deprovisioning guard, and restart its count. The directory's next retries of the deactivations and deletions it was refused go through, ending those users' access — check the directory for the mistake that tripped the guard first. Refused (409) when the connection is not held.
Route POST /admin/api/projects/:id/provisioning-connections/:connectionId/release · audit action provisioning.connection.release

provisioning_connection_delete
Delete a SCIM provisioning connection and revoke every token it obtained. Refused while it still manages any user — disable it instead.
Route DELETE /admin/api/projects/:id/provisioning-connections/:connectionId · audit action provisioning.connection.delete

provisioning_credential_issue
Issue or replace a credential for a SCIM provisioning connection: public keys (signed client assertion), a client secret (Microsoft Entra ID, Okta), or a static token (Okta without OAuth; `lifetimeDays` 90 or 365, default 365). A secret or token is returned exactly once. Replacing a secret with a secret, or a static token with a static token, keeps the current value working until the directory has used the new one and not the old one for an hour, and at most 24 hours (`previousEndsAt` in the answer); pass `overlap: false` to stop it and the tokens it obtained at once — for a leaked value. Keys and a change of kind always replace at once and revoke the old tokens. See provisioning_directory_list for which kind each directory needs.
Route POST /admin/api/projects/:id/provisioning-connections/:connectionId/credentials · audit action provisioning.credential.issue

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"kind"

],

"properties": {

"kind": {

"anyOf": [

{

"const": "key",

"type": "string"

},

{

"const": "secret",

"type": "string"

},

{

"const": "static_token",

"type": "string"

}

]

},

"jwks": {

"type": "object",

"required": [

"keys"

],

"properties": {

"keys": {

"minItems": 1,

"maxItems": 10,

"type": "array",

"items": {

"type": "object",

"patternProperties": {

"^(.*)$": {}

}

}

}

}

},

"jwksUri": {

"maxLength": 2048,

"type": "string"

},

"signingAlg": {

"maxLength": 16,

"type": "string"

},

"overlap": {

"type": "boolean"

},

"lifetimeDays": {

"error": "lifetimeDays must be 90 or 365",

"anyOf": [

{

"const": 90,

"type": "number"

},

{

"const": 365,

"type": "number"

}

]

}

}

}

project_user_assign_connection
Hand a local end-user to a SCIM provisioning connection, under the userName and/or externalId the directory knows them by, so the directory adopts them instead of creating a duplicate. Afterwards only the directory can change them.
Route POST /admin/api/projects/:id/users/:uid/connection · audit action enduser.connection.assign

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"connectionId"

],

"properties": {

"connectionId": {

"minLength": 1,

"maxLength": 64,

"type": "string"

},

"userName": {

"minLength": 1,

"maxLength": 256,

"type": "string"

},

"externalId": {

"minLength": 1,

"maxLength": 256,

"type": "string"

}

}

}

project_group_delete
Delete a group of end users and every membership in it. The users themselves are untouched. A group a SCIM directory manages is read-only here.
Route DELETE /admin/api/projects/:id/groups/:gid · audit action group.delete

project_group_assign_connection
Hand a group administrators keep to a SCIM provisioning connection, so the directory adopts it instead of failing to create one of the same name. Called with only `connectionId`, it changes nothing and answers a conflict carrying the preview: `consequences`, `expect` (`connectionId`, `managed` and `foreign` member counts) and `blockers` (up to 20 members the connection does not manage, by `id` and `email`). Apply it by calling again with `confirm: true` and that `expect`; a stale `expect` is answered with the fresh preview. Refused while any member is someone the connection does not manage. Afterwards only the directory can change it.
Route POST /admin/api/projects/:id/groups/:gid/connection · audit action group.connection.assign

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"connectionId"

],

"properties": {

"connectionId": {

"minLength": 1,

"type": "string"

},

"confirm": {

"type": "boolean"

},

"expect": {

"additionalProperties": false,

"type": "object",

"required": [

"connectionId",

"managed",

"foreign"

],

"properties": {

"connectionId": {

"type": "string"

},

"managed": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

"foreign": {

"minimum": 0,

"multipleOf": 1,

"type": "number"

}

}

}

}

}

jwks_generate
Generate a signing key for the instance key set, in any supported algorithm. The key is published at once and signs only once promoted, which the server allows after the publication window so every instance serves it first. Bounded: at most 12 keys held (signing, published, or retired and still verifying) and 20 made in a day, revoked ones counted; past either the answer is 409 with `reason: "key_limit"`.
Route POST /admin/api/jwks · audit action jwks.generate

Input (body)

{

"type": "object",

"properties": {

"alg": {

"anyOf": [

{

"const": "PS256",

"type": "string"

},

{

"const": "PS384",

"type": "string"

},

{

"const": "PS512",

"type": "string"

},

{

"const": "RS256",

"type": "string"

},

{

"const": "RS384",

"type": "string"

},

{

"const": "RS512",

"type": "string"

},

{

"const": "ES256",

"type": "string"

},

{

"const": "ES384",

"type": "string"

},

{

"const": "ES512",

"type": "string"

},

{

"const": "EdDSA",

"type": "string"

},

{

"const": "Ed25519",

"type": "string"

}

]

}

}

}

jwks_promote
Make a published key the one the instance signs with in its algorithm; its algorithm is advertised in discovery at once. The key it replaces returns to published and keeps verifying. Refused before the key's promotableAt. Every root-served client is affected, which is why it is gated.
Route POST /admin/api/jwks/:kid/promote · audit action jwks.promote

jwks_retire
Retire a key of the instance key set. The body must name the key again — { "confirm": "<kid>" } — or nothing happens. The key stops signing at once, keeps verifying until removableAt so tokens already issued stay valid, and is then hidden for good. Refused for a key that signs: promote its replacement first.
Route DELETE /admin/api/jwks/:kid · audit action jwks.retire

Input (body)

{

"type": "object",

"properties": {

"confirm": {

"type": "string"

}

}

}

jwks_rotate
Rotate one algorithm of the instance key set in one step. When a published key of that algorithm is past its promotableAt, the oldest such key starts signing (`promoted`), the previous signer returns to published and keeps verifying what it signed (`demoted`), and a new key is generated and published as the next one (`generated`). When no key is ready, only the next key is generated, nothing changes who signs, and `rotatableAt` says from when the algorithm can rotate. Never promotes a key inside its publication window. Every project served at the root signs with these keys, the administrators included, which is why it is gated. Bounded: at most 12 keys held (signing, published, or retired and still verifying) and 20 made in a day, revoked ones counted; past either the answer is 409 with `reason: "key_limit"`.
Route POST /admin/api/jwks/rotate · audit action jwks.rotate

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"alg"

],

"properties": {

"alg": {

"anyOf": [

{

"const": "PS256",

"type": "string"

},

{

"const": "PS384",

"type": "string"

},

{

"const": "PS512",

"type": "string"

},

{

"const": "RS256",

"type": "string"

},

{

"const": "RS384",

"type": "string"

},

{

"const": "RS512",

"type": "string"

},

{

"const": "ES256",

"type": "string"

},

{

"const": "ES384",

"type": "string"

},

{

"const": "ES512",

"type": "string"

},

{

"const": "EdDSA",

"type": "string"

},

{

"const": "Ed25519",

"type": "string"

}

]

}

}

}

jwks_revoke
Revoke a key of the instance key set that may be compromised. The body must name the key again — { "confirm": "<kid>" } — or nothing happens. Unlike retiring, there is no day of grace: the key leaves the key set at once and every token it signed stops verifying on this instance now and on every other within the key cache bound, for every project served at the root, the console's own sign-in included, so people may have to sign in again. When the key is the one signing, the same act puts another key in its place: the oldest published key of its algorithm past its promotableAt, or else a newly generated key that signs at once (`replacementGenerated: true`), in which case relying parties holding a cached key set may reject new tokens until they refresh it. Answers `replacement` with the key now signing. Refused for a key already revoked (409); a hidden or unknown key is 404.
Route POST /admin/api/jwks/:kid/revoke · audit action jwks.revoke

Input (body)

{

"type": "object",

"properties": {

"confirm": {

"type": "string"

}

}

}

project_key_retire
Retire a key the project no longer signs with. The body must name the key again — { "confirm": "<kid>" } — or nothing happens. It stays published until every token it could have signed has expired, then is hidden and tokens signed with it stop verifying. Refused for the signing key. High-consequence: describe it and confirm before running.
Route DELETE /admin/api/projects/:id/keys/:kid · audit action project.key.retire

Input (body)

{

"type": "object",

"properties": {

"confirm": {

"type": "string"

}

}

}

project_key_revoke
Revoke a key of a project that may be compromised. The body must name the key again — { "confirm": "<kid>" } — or nothing happens. Unlike retiring, there is no day of grace: the key leaves the key set at once and every token it signed stops verifying on this instance now and on every other within the key cache bound, so people may have to sign in again. When the key is the one signing, the same act puts another key in its place: the oldest published key of its algorithm past its promotableAt, or else a newly generated key that signs at once (`replacementGenerated: true`), in which case relying parties holding a cached key set may reject new tokens until they refresh it. Answers `replacement` with the key now signing. Refused for a key already revoked (409); a hidden or unknown key is 404. High-consequence: describe it and confirm before running.
Route POST /admin/api/projects/:id/keys/:kid/revoke · audit action project.key.revoke

Input (body)

{

"type": "object",

"properties": {

"confirm": {

"type": "string"

}

}

}

settings_update
Change server settings. The merged configuration is validated first, so a combination the server would refuse at boot is refused here instead of taking the instance down on restart. A change takes effect in the instance that serves this call: the result names what it put in force (appliedKeys), what cannot be applied to a running server (pendingRestartKeys), and what is stored but not in force here (notInForceKeys).
Route PUT /admin/api/settings · audit action settings.update

Input (body)

{

"additionalProperties": true,

"type": "object",

"properties": {

"authorization.allowOmittingSingleRegisteredRedirectUri": {

"type": "boolean",

"description": "Let a client with one registered redirect_uri omit it from requests"

},

"pkce.required": {

"type": "boolean",

"description": "Demand proof of possession on every authorization request"

},

"par.enabled": {

"type": "boolean",

"description": "Serve the pushed_authorization_request endpoint"

},

"par.allowUnregisteredRedirectUris": {

"type": "boolean",

"description": "Let authenticated PAR clients use redirect_uris they never registered"

},

"dpop.enabled": {

"type": "boolean",

"description": "Sender-constrain tokens with application-layer proof-of-possession"

},

"dpop.requireNonce": {

"type": "boolean",

"description": "Require a server-issued nonce in every DPoP proof"

},

"introspection.enabled": {

"type": "boolean",

"description": "Serve token introspection for opaque access and refresh tokens"

},

"jwtIntrospection.enabled": {

"type": "boolean",

"description": "Return introspection results as a signed JWT"

},

"responseMode.jwt.enabled": {

"type": "boolean",

"description": "Return authorization responses as a signed JWT (JARM)"

},

"fapi.enabled": {

"type": "boolean",

"description": "Apply the extra Authorization Server behaviours of the FAPI profile"

},

"clientCredentials.enabled": {

"type": "boolean",

"description": "Accept grant_type=client_credentials at the token endpoint"

},

"backchannelLogout.enabled": {

"type": "boolean",

"description": "Notify clients of sign-out over a back channel"

},

"encryption.enabled": {

"type": "boolean",

"description": "Allow encrypted UserInfo, ID Tokens and Request Objects"

},

"userinfo.enabled": {

"type": "boolean",

"description": "Serve the UserInfo endpoint"

},

"jwtUserinfo.enabled": {

"type": "boolean",

"description": "Return UserInfo as a signed JWT"

},

"revocation.enabled": {

"type": "boolean",

"description": "Serve the token revocation endpoint"

},

"globalTokenRevocation.enabled": {

"type": "boolean",

"description": "Let an upstream identity provider sign its users out of everything here"

},

"rpInitiatedLogout.enabled": {

"type": "boolean",

"description": "Let a client start sign-out and return the user to itself"

},

"claimsParameter.enabled": {

"type": "boolean",

"description": "Accept and validate the claims request parameter"

},

"mTLS.enabled": {

"type": "boolean",

"description": "Authenticate clients by TLS certificate"

},

"mTLS.trustProxyCertificateHeader": {

"type": "boolean",

"description": "Read the client certificate from the header the proxy sets"

},

"mTLS.certificateBoundAccessTokens": {

"type": "boolean",

"description": "Bind issued access tokens to the client certificate"

},

"mTLS.selfSignedTlsClientAuth": {

"type": "boolean",

"description": "Accept the self_signed_tls_client_auth method"

},

"mTLS.tlsClientAuth": {

"type": "boolean",

"description": "Accept the tls_client_auth method"

},

"deviceFlow.enabled": {

"type": "boolean",

"description": "Serve the device authorization grant for input-constrained devices"

},

"deviceFlow.charset": {

"type": "string",

"enum": [

"base-20",

"digits"

],

"description": "Which characters generated user codes are drawn from"

},

"deviceFlow.mask": {

"type": "string",

"description": "The shape of a generated user code"

},

"ciba.enabled": {

"type": "boolean",

"description": "Serve the client-initiated backchannel authentication flow"

},

"ciba.deliveryModes": {

"type": "array",

"items": {

"type": "string",

"enum": [

"poll",

"ping"

]

},

"description": "How a client collects a CIBA token once the user approves"

},

"projects.addressForms": {

"type": "string",

"enum": [

"path",

"subdomain",

"both"

],

"description": "Whether teams give projects a path, a subdomain, or either"

},

"projects.reservedHostnames": {

"type": "array",

"items": {

"type": "string"

},

"description": "Names in your domain that no project may be given"

},

"plans.enabled": {

"type": "boolean",

"description": "Limit what each team may create by its project’s plan"

},

"plans.regularTeamsPerAdministrator": {

"type": "number",

"description": "How many regular teams one administrator may own"

},

"requestObjects.enabled": {

"type": "boolean",

"description": "Accept authorization parameters inside a signed request object"

},

"requestObjects.requireSignedRequestObject": {

"type": "boolean",

"description": "Refuse authorization requests that are not signed request objects"

},

"resourceIndicators.enabled": {

"type": "boolean",

"description": "Let a client name the resource server a token is meant for"

},

"richAuthorizationRequests.enabled": {

"type": "boolean",

"description": "Accept fine-grained authorization_details on a request"

},

"richAuthorizationRequests.types": {

"type": "object",

"description": "The authorization details types this server accepts"

},

"cors.enabled": {

"type": "boolean",

"description": "Let browser apps read responses from this server"

},

"rateLimit.enabled": {

"type": "boolean",

"description": "Refuse a caller that exceeds its request allowance"

},

"rateLimit.trustedProxy": {

"type": "boolean",

"description": "Read the caller address from proxy headers"

},

"rateLimit.maxTrackedOrigins": {

"type": "number",

"description": "How many callers each route class tracks at once"

},

"rateLimit.strict.max": {

"type": "number",

"description": "Allowance for token, authorization and other costly routes"

},

"rateLimit.strict.windowSeconds": {

"type": "number",

"description": "Window the strict allowance is measured over"

},

"rateLimit.ordinary.max": {

"type": "number",

"description": "Allowance for userinfo, admin and other ordinary routes"

},

"rateLimit.ordinary.windowSeconds": {

"type": "number",

"description": "Window the ordinary allowance is measured over"

},

"rateLimit.public.max": {

"type": "number",

"description": "Allowance for assets, discovery and the key set"

},

"rateLimit.public.windowSeconds": {

"type": "number",

"description": "Window the public allowance is measured over"

},

"loginThrottle.failureCap": {

"type": "number",

"description": "Wrong passwords one address may try before sign-in shuts"

},

"loginThrottle.windowSeconds": {

"type": "number",

"description": "How long sign-in shuts the first time an address runs out"

},

"loginThrottle.windowCeilingSeconds": {

"type": "number",

"description": "The longest sign-in will ever shut for one address"

},

"federation.enabled": {

"type": "boolean",

"description": "Let a project offer sign-in through an upstream provider"

},

"mcp.enabled": {

"type": "boolean",

"description": "Let an AI agent administer this server over MCP"

},

"scim.enabled": {

"type": "boolean",

"description": "Let an enterprise directory manage a project’s users over SCIM"

},

"scim.secretCredentials": {

"type": "boolean",

"description": "Used by Entra and Okta OAuth modes; a deviation from IPSIE §4.1"

},

"scim.staticTokens": {

"type": "boolean",

"description": "For directories without an OAuth option; a deviation from IPSIE §4.1"

},

"scim.strict": {

"type": "boolean",

"description": "On only to certify against the profiles; breaks Entra and Okta defaults"

},

"scim.rateLimit.max": {

"type": "number",

"description": "SCIM requests one connection may send per window"

},

"scim.rateLimit.windowSeconds": {

"type": "number",

"description": "Window the per-connection SCIM allowance is measured over"

},

"errorStore.enabled": {

"type": "boolean",

"description": "Keep a durable record of unexpected internal faults"

},

"errorStore.retentionDays": {

"type": "number",

"description": "How long a recorded fault is kept"

},

"errorStore.maxGroups": {

"type": "number",

"description": "How many distinct faults are kept at once"

},

"errorStore.samplesPerGroup": {

"type": "number",

"description": "Full occurrences retained per distinct fault"

},

"errorStore.queueDepth": {

"type": "number",

"description": "How many records may await writing"

},

"errorStore.originCaptureLevel": {

"type": "string",

"enum": [

"omitted",

"anonymized",

"full"

],

"description": "How much of the caller address a record keeps"

},

"sentry.enabled": {

"type": "boolean",

"description": "Also send every recorded fault to a Sentry project"

},

"registration.enabled": {

"type": "boolean",

"description": "Let a client register itself at runtime"

},

"registration.issueRegistrationAccessToken": {

"type": "boolean",

"description": "Issue a registration access token to a new client"

},

"clientIdMetadataDocument.enabled": {

"type": "boolean",

"description": "Let a client identify itself with a document it hosts"

},

"registrationManagement.enabled": {

"type": "boolean",

"description": "Let a registered client update or delete itself"

},

"registrationManagement.rotateRegistrationAccessToken": {

"type": "boolean",

"description": "Issue a fresh registration access token on each update"

},

"conformIdTokenClaims": {

"type": "boolean",

"description": "Carry end-user claims in an ID Token only where the spec requires"

},

"scopes": {

"type": "array",

"items": {

"type": "string"

},

"description": "Scopes advertised in discovery"

},

"claims": {

"type": "object",

"description": "Which claims each scope releases, and which stand alone"

},

"consent.scopeDescriptions": {

"type": "object",

"description": "What each of your own scopes lets an app do, as the consent page says it"

},

"acrValues": {

"type": "object",

"description": "Which acr value each authentication this server can distinguish is reported as"

},

"clientAuthMethods": {

"type": "array",

"items": {

"type": "string",

"enum": [

"client_secret_basic",

"client_secret_jwt",

"client_secret_post",

"private_key_jwt",

"none"

]

},

"description": "Client authentication methods advertised in discovery"

}

}

}

plan_create
Add a plan to the catalogue. `default: true` makes it the plan every new project is put on; existing projects keep the plan they record. High-consequence: describe it and confirm before running.
Route POST /admin/api/plans · audit action plan.create

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"id",

"name",

"default",

"limits",

"features"

],

"properties": {

"id": {

"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",

"maxLength": 40,

"type": "string"

},

"name": {

"minLength": 1,

"maxLength": 80,

"type": "string"

},

"default": {

"type": "boolean"

},

"limits": {

"additionalProperties": false,

"type": "object",

"required": [

"monthlyActiveUsers",

"projectsPerAdministrator",

"applicationsPerProject",

"apisPerProject"

],

"properties": {

"monthlyActiveUsers": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

},

"projectsPerAdministrator": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

},

"applicationsPerProject": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

},

"apisPerProject": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

}

}

},

"features": {

"additionalProperties": false,

"type": "object",

"required": [

"enterpriseSignIn",

"customSlug",

"customDomain"

],

"properties": {

"enterpriseSignIn": {

"type": "boolean"

},

"customSlug": {

"type": "boolean"

},

"customDomain": {

"type": "boolean"

}

}

}

}

}

plan_update
Change a plan’s name, a limit or a feature — it applies at once to every project on the plan, and only to what is created next: nothing that exists is removed — or make it the default with `default: true`, which affects only projects created afterwards. High-consequence: describe it and confirm before running.
Route PATCH /admin/api/plans/:id · audit action plan.update

Input (body)

{

"additionalProperties": false,

"type": "object",

"properties": {

"name": {

"minLength": 1,

"maxLength": 80,

"type": "string"

},

"default": {

"type": "boolean"

},

"limits": {

"additionalProperties": false,

"type": "object",

"properties": {

"monthlyActiveUsers": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

},

"projectsPerAdministrator": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

},

"applicationsPerProject": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

},

"apisPerProject": {

"anyOf": [

{

"minimum": 0,

"multipleOf": 1,

"type": "number"

},

{

"type": "null"

}

]

}

}

},

"features": {

"additionalProperties": false,

"type": "object",

"properties": {

"enterpriseSignIn": {

"type": "boolean"

},

"customSlug": {

"type": "boolean"

},

"customDomain": {

"type": "boolean"

}

}

}

}

}

plan_delete
Remove a plan. Refused for the default plan and for a plan any project is on, with how many. High-consequence: describe it and confirm before running.
Route DELETE /admin/api/plans/:id · audit action plan.delete

project_plan_set
Move a tenant’s project to another plan; its team’s next creation is judged by the new plan’s limits. Refused for a System project, which carries no plan. High-consequence: describe it and confirm before running.
Route PUT /admin/api/projects/:id/plan · audit action project.plan

Input (body)

{

"additionalProperties": false,

"type": "object",

"required": [

"planId"

],

"properties": {

"planId": {

"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",

"maxLength": 40,

"type": "string"

}

}

}

smtp_settings_update
Change the outbound mail settings. The password is write-only: it can be set but never read back.
Route PUT /admin/api/settings/smtp · audit action smtp.settings.update

Input (body)

{

"type": "object",

"required": [

"host",

"port",

"secure",

"username",

"password",

"fromName",

"fromEmail"

],

"properties": {

"host": {

"type": "string"

},

"port": {

"multipleOf": 1,

"type": "number"

},

"secure": {

"type": "boolean"

},

"username": {

"type": "string"

},

"password": {

"type": "string"

},

"fromName": {

"type": "string"

},

"fromEmail": {

"type": "string"

}

}

}

sentry_settings_update
Change where recorded faults are reported. The ingestion credential is write-only: it can be set but never read back, and sending the mask keeps the stored one. Enabling this requires the error store, because the outbound event is built from the internal record. Both settings take effect in the instance that serves this call; the result reports anything stored but not in force here.
Route PUT /admin/api/settings/sentry · audit action sentry.settings.update

Input (body)

{

"type": "object",

"required": [

"enabled",

"dsn"

],

"properties": {

"enabled": {

"type": "boolean"

},

"dsn": {

"type": "string"

}

}

}

PreviousAdmin APINextAddon seams
