users
13 endpoints.
/api/users/me Bearer tokenRead the caller's account
The account the presented token resolves to: canonical profile and sign-in methods. Accounts come into existence on first contact, so this always answers for a valid token.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The caller's account. | UserProfile |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 500 | The account could not be read. | ErrorResponse |
/api/users/me/identities/{identity_id} Bearer tokenUnlink one of the caller's sign-in methods
Detaches a sign-in method from the caller's own account. The last method cannot be unlinked — that would lock the account out; ask a superuser to detach it deliberately.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| identity_id | path | string (uuid) | yes | The identity's id, from the account's identity listing. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The unlinked sign-in method. | UserIdentity |
| 400 | The identity is the account's last sign-in method. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | The identity does not exist, or belongs to another account. | ErrorResponse |
| 500 | The identity could not be detached. | ErrorResponse |
/api/users Bearer tokenCreate an account known only by email
Creates a user whose OIDC subject is not yet known, with a pending email link at the named provider: the first sign-in there whose `email` claim matches (subject to the provider's email verification strictness) attaches its identity to this account instead of creating a new one. Refused when the address already has a pending link or is the profile email of a live account — attach the known identity or merge instead. Requires a superuser's access token.
| Field | Type | Required | Description |
|---|---|---|---|
| display_name | string | null | no | Starting display name; the first sign-in's name claim refreshes it. 1–200 characters |
| string | yes | The address the person will sign in with; compared case-insensitively. 3–300 characters | |
| provider_id | string | yes | Sign-in provider UUID id or slug the person is expected to sign in with. at least 1 character |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | The new account and its pending email link. | CreatedUser |
| 400 | The request body failed validation, or the email is not an address. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No sign-in provider has that id or slug. | ErrorResponse |
| 409 | That address already has a pending link or belongs to a live account. | ErrorResponse |
| 500 | The account could not be created. | ErrorResponse |
/api/users/{user_id}/email-links Bearer tokenList an account's email links
Every email link of the account, pending and already claimed, oldest first. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The account's email links, oldest first. | array of UserEmailLink |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No user has that id. | ErrorResponse |
| 500 | The email links could not be read. | ErrorResponse |
/api/users/{user_id}/email-links Bearer tokenAdd a pending email link to an account
Names an address whose first sign-in at the given provider should attach its identity to this account (subject to the provider's email verification strictness). Refused when the address already has a pending link or is the profile email of a *different* live account. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
| Field | Type | Required | Description |
|---|---|---|---|
| string | yes | The address the person will sign in with; compared case-insensitively. 3–300 characters | |
| provider_id | string | yes | Sign-in provider UUID id or slug the person is expected to sign in with. at least 1 character |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | The pending email link. | UserEmailLink |
| 400 | The request body failed validation, or the email is not an address. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No user or provider has that id. | ErrorResponse |
| 409 | That address already has a pending link or belongs to another account. | ErrorResponse |
| 500 | The email link could not be created. | ErrorResponse |
/api/users/{user_id}/email-links/{link_id} Bearer tokenRemove a pending email link
Withdraws a link before any sign-in has claimed it. Links already claimed are the account's history and cannot be removed. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
| link_id | path | string (uuid) | yes | The email link's id, from the account's email link listing. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The removed email link. | UserEmailLink |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No pending email link with that id belongs to the user. | ErrorResponse |
| 500 | The email link could not be removed. | ErrorResponse |
/api/users/{user_id} Bearer tokenUpdate an account's profile or standing
Sets the account's display name (null clears it) and/or switches it off and on with `disabled`; omitted fields keep their values. The name is the canonical profile the app shows everywhere, and a later sign-in whose ID token carries a name claim refreshes it again. Disabling is the reversible way to keep someone out: sign-in and tokens are refused and open sessions end, while everything the account owns stays put until it is enabled again (permanent deletion lives at /users/{user_id}/permanent-deletion). A merged (tombstoned) account cannot be edited — its survivor holds the profile — and a caller cannot disable their own account. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
| Field | Type | Required | Description |
|---|---|---|---|
| display_name | string | null | no | The name shown for the account everywhere; null clears it. 1–200 characters |
| disabled | boolean | no | true switches the account off: its sessions end on their next page load, its tokens stop resolving, and sign-in is refused, while everything it owns stays. false switches it back on. A caller cannot disable their own account. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The updated account. | UserAccount |
| 400 | The request body failed validation, the account was merged away, or the caller tried to disable their own account. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No user has that id. | ErrorResponse |
| 500 | The account could not be updated. | ErrorResponse |
/api/users/{user_id}/identities Bearer tokenList an account's sign-in methods
Every identity attached to the account. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The account's sign-in methods, oldest first. | array of UserIdentity |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No user has that id. | ErrorResponse |
| 500 | The identities could not be read. | ErrorResponse |
/api/users/{user_id}/identities Bearer tokenAttach a sign-in method to an account
Links a (provider, subject) pair to the account — the administrative side of identity linking, for provider migrations and rescues. An identity already attached to a *different* account is refused: merge or detach it first. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
| Field | Type | Required | Description |
|---|---|---|---|
| provider_id | string | yes | Sign-in provider UUID id or slug. at least 1 character |
| subject | string | yes | The `sub` claim the account's owner presents at that provider. 1–255 characters |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | The attached sign-in method. | UserIdentity |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No user or provider has that id. | ErrorResponse |
| 409 | That identity already belongs to a different account. | ErrorResponse |
| 500 | The identity could not be attached. | ErrorResponse |
/api/users/{user_id}/identities/{identity_id} Bearer tokenDetach a sign-in method from an account
Removes one identity from the account. Detaching the *last* one locks the account out of signing in (its history remains) and is refused unless `force=true` — the deliberate offboarding or compromised-credential case. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
| identity_id | path | string (uuid) | yes | The identity's id, from the account's identity listing. |
| force | query | string | no | Set true to detach the account's last sign-in method. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The detached sign-in method. | UserIdentity |
| 400 | The identity is the account's last sign-in method and force is not set. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | The user or the identity does not exist. | ErrorResponse |
| 500 | The identity could not be detached. | ErrorResponse |
/api/users/{user_id}/merge Bearer tokenMerge an account into another
Absorbs the account at {user_id} into into_user_id: identities move over, every reference is repointed at the survivor (redundant memberships, grants and duplicates resolve in the survivor's favour), and the absorbed account is tombstoned. The fix for the duplicate a first sign-in creates before its identity is linked. One transaction, irreversible. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
| Field | Type | Required | Description |
|---|---|---|---|
| into_user_id | string (uuid) | yes | The surviving account everything is repointed at. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The merge's outcome. | MergeUsersResponse |
| 400 | The merge is refused: a self-merge, or a tombstoned participant. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | One of the accounts does not exist. | ErrorResponse |
| 500 | The merge failed; nothing was changed. | ErrorResponse |
/api/users/{user_id}/permanent-deletion Bearer tokenPreview permanently deleting an account
Counts everything DELETE on this path would remove: sign-in methods, memberships and role grants, the learning records, certification awards, sign-offs, attempts and roster entries that are the person's, and how many rows created or approved by them stay behind without attribution. Nothing is changed. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | What the deletion would take with it. | DeletionImpact |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No user has that id. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/users/{user_id}/permanent-deletion Bearer tokenPermanently delete an account
Hard-deletes the account and everything that is the person's — sign-in methods, memberships, role grants, learning records with their evidence files, certification awards, sign-offs given and received, quiz and SCORM attempts, event roster entries, imported grades, job aid views and pending email links — and drops their name from rows they created or approved for others. Duplicates previously merged into the account go with it. Irreversible; disabling the account (PATCH /users/{user_id}) is the reversible alternative. Preview the cost with GET first. A caller cannot delete their own account. Requires a superuser's access token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes | The account's id (`users.id`). |
Responses
| Status | Description | Body |
|---|---|---|
| 204 | The account and everything it owned are gone. | — |
| 400 | The caller tried to delete their own account. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The access token's subject is not a superuser. | ErrorResponse |
| 404 | No user has that id. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |