Get user details (auditor-only)
const url = 'https://example.com/api/admin/users/example';const options = {method: 'GET', headers: {'X-Ssoossh-Key-Secret': '<X-Ssoossh-Key-Secret>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://example.com/api/admin/users/example \ --header 'X-Ssoossh-Key-Secret: <X-Ssoossh-Key-Secret>'Returns full identity details, disable state, and counts of certificates and service enrollments for one user.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”User ID
Responses
Section titled “Responses”User details
object
CertificateCount is how many certificates have been issued to this user.
CreatedAt is when the user first authenticated.
Directory is the user’s directory bookkeeping row, absent when they have never been enriched. It is what answers “why is this person missing a group” from data already stored.
Also absent whenever ldap.enabled is false, even for a user who has a row. Switching the directory off stops the sync, so everything in that row is frozen at whatever the last pass read, and the server no longer acts on any of it — the session identity falls back to the OIDC values on the next request. Presenting frozen data beside live data with no way to tell them apart is how someone ends up granting access on a principal list that has not been true for months.
object
Attributes is the stored field map — the same values the login path merges onto the identity, and the last-known-good cache it falls back to when the directory is unreachable.
object
ConsecutiveMisses is how many passes have observed the absence. Reporting only: the disable is decided on elapsed time, since a pass count measures replica count rather than absence.
DirectoryID is the entry’s unique, immutable identifier (the attribute named by ldap.id_attribute), and the anchor that survives a rename or a move between OUs. Empty when ldap.id_attribute is unconfigured, which leaves resolution on the DN-then-filter path.
DN is the entry’s distinguished name from the last successful read.
FirstMissingAt is when the entry was first found to be missing, and what the auto-disable threshold is measured against. Absent means the entry is currently resolving.
LastSeenAt is the last successful read of the entry; LastSyncedAt is the last pass that reached the directory at all. They differ exactly when the entry is missing.
DirectoryEnabled mirrors ldap.enabled, and is what disambiguates an absent Directory. Without it, “no directory record” means either “this person has never been enriched” or “the directory is switched off and their record is being withheld” — two states that call for opposite actions from whoever is reading the page.
DirectoryOverrides names each identity field the directory currently supplies in place of the OIDC value, with both sides shown. Empty when LDAP is disabled, unconfigured, or has never resolved this person — in which case the OIDC values above are what the server acts on.
object
Effective is what the directory supplies, and therefore what the server actually acts on for this field.
Field is the destination name: “other_accounts”, “service_accounts”, “name”, or an operator-chosen extra field.
OIDC is what the ID token supplied for this field at the last login, shown even though it lost — a claim mapping that is quietly wrong is invisible otherwise. Empty when the token carried nothing.
DisabledAt is when an admin disabled this user. Omitted if not disabled.
DisabledByUserID and DisabledByUsername identify the admin that disabled this user. Both omitted if not disabled.
DisabledReason is why the account was disabled, required at the API and shown here because this is where it meets the person deciding whether to re-enable. Omitted if not disabled.
DisabledSource says what disabled the account: “admin”, “soc” or “ldap_sync”. It is what makes auto-re-enable safe — the sync clears only its own disables — so it belongs next to the reason rather than only in the database. Omitted if not disabled, and absent on rows predating the column.
Email is the user’s email from OIDC, possibly empty. Not an identifier either, for the same reason as Username.
ExtraFields are operator-configured extra claims, decoded from stored JSON map.
object
Groups are the persisted group memberships. Both capture paths while ldap.enabled is true; OIDC only when it is false, on the same reasoning as Directory below — the directory rows are frozen, so they are neither shown nor used for notification fan-out until the directory is switched back on. Never an authorization input (see https://mnestor.github.io/ssoossh/internals/invariants/): this is what the server recorded, shown so an operator can see why a notification reached someone, or why a group they expected is missing.
object
FirstSeenAt survives a refresh that keeps the membership, so “since when” is answerable; LastSeenAt is the last capture that saw it.
Name is the reduced, comparable name: a memberOf DN is reduced to its CN before it is stored.
Source is “oidc” or “ldap”. The two capture paths never collide and a name can appear under both.
ID is the stable user identifier.
Name is the person’s human-readable name. Display only: it is not a principal, not a key ID input, and never an authorization input. Empty when neither the identity provider nor the directory supplied one.
NotificationPreferences is every notification kind the server knows how to send, with what applies to this user and whether they chose it. Registry order, then any stored rows for kinds no longer registered.
object
Default is what would apply with no stored choice, so a reader can see at a glance which of these the user actually decided.
Enabled is what applies: the stored choice where there is one, the registered default otherwise.
Explicit reports whether the user has stored a choice for this kind. False means Enabled is the registered default.
Registered reports whether the kind is still in the server’s registry. False marks a stored row left behind by a removed kind.
Title and Description are the registry’s own wording. Empty for a stored row whose kind is no longer registered — a downgrade or a removed kind leaves rows nothing answers to, and they are still shown rather than dropped, since the choice is real and still stored.
UpdatedAt is when the choice was last changed, absent when there is no stored choice to have changed.
OIDCFields reports, per identity destination, whether authentication.fields names a claim that populates it. It is what separates “the ID token carried nothing for this field” from “nothing was ever asked of the ID token for this field” — two states that read identically on the page and call for opposite actions.
It also decides how a directory value is described. A field with no configured claim has no OIDC side to override, so calling the directory value an override there names a conflict that does not exist; the directory is simply the only source.
Keyed by destination name: “other_accounts”, “service_accounts”, “name”, and one entry per configured extra field.
object
OtherAccounts, ServiceAccounts, ExtraFields and Name above are the OIDC capture — exactly what the ID token carried at the last login, as stored on the users row. They are deliberately not the values the server acts on: a configured ldap.fields entry overrides its OIDC counterpart wholesale, and DirectoryOverrides below names every field where that is currently happening and what the effective value is.
Showing the OIDC half even when it is overridden is the point. An operator debugging “why is this person missing a principal” needs to see both sides and which one won; showing only the winner makes a misconfigured claim mapping invisible.
ServiceAccounts are service accounts from OIDC, decoded from stored JSON.
ServiceEnrollmentCount is how many live (not expired) service enrollments this user approved. Provenance, not a consequence of disabling them: the codes belong to their service accounts and keep working (see https://mnestor.github.io/ssoossh/concepts/service-certificates/).
Subject is the unique account identifier, read from the claim named by authentication.fields.subject (“sub” by default). It is the only value a login is keyed by, and the only one guaranteed not to move.
UpdatedAt is when the user’s identity was last refreshed at login.
Username is the OIDC claim username. Not an identifier — it changes when a person is renamed, which is what Subject exists to survive.
Example generated
{ "certificate_count": 1, "created_at": "example", "directory": { "attributes": { "additionalProperty": [ "example" ] }, "consecutive_misses": 1, "directory_id": "example", "dn": "example", "first_missing_at": "example", "last_seen_at": "example", "last_synced_at": "example" }, "directory_enabled": true, "directory_overrides": [ { "effective": [ "example" ], "field": "example", "oidc": [ "example" ] } ], "disabled_at": "example", "disabled_by_user_id": "example", "disabled_by_username": "example", "disabled_reason": "example", "disabled_source": "example", "email": "example", "extra_fields": { "additionalProperty": "example" }, "groups": [ { "first_seen_at": "example", "last_seen_at": "example", "name": "example", "source": "example" } ], "id": "example", "name": "example", "notification_preferences": [ { "default": true, "description": "example", "enabled": true, "explicit": true, "kind": "example", "registered": true, "title": "example", "updated_at": "example" } ], "oidc_fields": { "additionalProperty": true }, "other_accounts": [ "example" ], "service_accounts": [ "example" ], "service_enrollment_count": 1, "subject": "example", "updated_at": "example", "username": "example"}Not authenticated
object
Data is always null on an error response.
Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.
ErrorCode is a machine-readable error classifier. One of the error_code constants from internal/apitypes; use this to decide whether to retry or branch on the failure mode.
Example
{ "error": "certificate request \"9f1c…\" not found", "error_code": "not_found"}Not authorized as auditor
object
Data is always null on an error response.
Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.
ErrorCode is a machine-readable error classifier. One of the error_code constants from internal/apitypes; use this to decide whether to retry or branch on the failure mode.
Example
{ "error": "certificate request \"9f1c…\" not found", "error_code": "not_found"}User not found
object
Data is always null on an error response.
Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.
ErrorCode is a machine-readable error classifier. One of the error_code constants from internal/apitypes; use this to decide whether to retry or branch on the failure mode.
Example
{ "error": "certificate request \"9f1c…\" not found", "error_code": "not_found"}