End users
An end user is someone who signed into one of your OIDC clients — the people using your product, not the people administrating your Huudis workspace. They have a usr_… ID just like workspace members, but they don't have a Huudis dashboard login; they only ever see Huudis through your app's sign-in screen.
This page covers the admin API for the end-user directory: listing, viewing details, revoking, sending password resets, force-verifying emails, and impersonating for support.
The list is workspace-scoped: an end user is "in" your workspace if and only if they hold a live OIDC consent for at least one of your workspace's OIDC clients.
All endpoints require a bearer admin JWT — see Authentication.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/ops/end-users |
List end users of the active workspace |
GET |
/v1/ops/end-users/:id |
Detail view |
POST |
/v1/ops/end-users/:id/revoke |
Revoke consents + refresh tokens (kicks them out of your app) |
POST |
/v1/ops/end-users/:id/send-password-reset |
Trigger a Huudis-hosted reset email |
POST |
/v1/ops/end-users/:id/verify-email |
Force-verify the user's email (skip the link click) |
POST |
/v1/ops/end-users/:id/impersonate |
Start a short-lived session as the user (support tool) |
POST |
/v1/ops/end-users/stop-impersonation |
Exit back to the operator's own session |
POST |
/v1/ops/end-users/:id/disable |
Forjio-internal only. Globally disable a Huudis user. |
POST |
/v1/ops/end-users/:id/enable |
Forjio-internal only. Re-enable. |
List end users
GET /v1/ops/end-users
Returns users who hold at least one live consent for an OIDC client owned by the active workspace. Sorted by firstSignInAt descending.
Response
{
"data": [
{
"id": "usr_01KPG…",
"email": "ibu.rina@cafe-sumur.id",
"name": "Ibu Rina",
"emailVerified": true,
"disabled": false,
"createdAt": "2026-03-04T11:00:00.000Z",
"lastLoginAt": "2026-05-12T17:55:00.000Z",
"firstSignInAt": "2026-03-04T11:02:00.000Z",
"clientCount": 2
}
]
}
clientCount is the number of your workspace's OIDC clients this user has consented to — useful for spotting "power users" of your suite.
Retrieve an end user
GET /v1/ops/end-users/:id
Returns the user's profile plus the per-client consent list scoped to your workspace. Other workspaces' consents are not included — Huudis never leaks them across customers.
Response
{
"data": {
"id": "usr_01KPG…",
"email": "ibu.rina@cafe-sumur.id",
"name": "Ibu Rina",
"emailVerified": true,
"disabled": false,
"disabledAt": null,
"mfaEnabled": false,
"createdAt": "2026-03-04T11:00:00.000Z",
"lastLoginAt": "2026-05-12T17:55:00.000Z",
"consents": [
{
"id": "ocs_01KPG…",
"clientId": "oc_meja_studio",
"clientName": "MejaStudio",
"scopes": ["openid", "profile", "email"],
"consentedAt": "2026-03-04T11:02:00.000Z",
"revokedAt": null
}
]
}
}
Errors
| Status | error.code |
When |
|---|---|---|
404 |
NOT_FOUND |
No user with that ID, or the user has no relationship with your workspace. Huudis returns 404 rather than 403 to avoid leaking the user's existence. |
Revoke an end user
POST /v1/ops/end-users/:id/revoke
Cuts the user off from your workspace's clients. Internally:
- All non-revoked consents for clients owned by the active workspace are stamped with
revokedAt = now. - All non-revoked refresh tokens issued by those clients are revoked, so the user can't silently mint new access tokens.
Existing access tokens stay valid until they naturally expire (typically ≤1 hour). The user can still sign in again later if they want to re-grant consent — revoke is reversible from the user's side.
204 No Content.
Send a password reset
POST /v1/ops/end-users/:id/send-password-reset
Triggers the standard Huudis password-reset flow on the user's behalf. Huudis emails a reset link; the user clicks it and lands on the Huudis-hosted password-reset page. Useful for support cases where the user can't find the reset button themselves.
You only see { "sent": true } — the token is never returned to you, even as an admin, to prevent unauthorized password resets.
Force-verify the user's email
POST /v1/ops/end-users/:id/verify-email
Sets emailVerified: true without requiring the user to click the verification link. Use sparingly — this skips the normal proof-of-control check. Audit-logged as ops.end_user.email_force_verified.
200 OK — { "emailVerified": true }.
Impersonate a user
POST /v1/ops/end-users/:id/impersonate
Starts a short-lived session as the target user, useful for reproducing support issues. The new session is tagged with impersonatedById = <your-userId> so the user's audit log shows the operator clearly.
Request body
| Field | Type | Description |
|---|---|---|
durationSeconds |
integer (60–3600) | How long the session lasts. Default 1800 (30 minutes). |
Response
{
"data": {
"impersonating": {
"userId": "usr_01KPG…",
"email": "ibu.rina@cafe-sumur.id"
},
"expiresAt": "2026-05-13T00:25:00.000Z"
}
}
A Set-Cookie: huudis_session=… header replaces the calling browser's session with the impersonated one. Subsequent requests from that browser act as the target user.
Errors
| Status | error.code |
When |
|---|---|---|
400 |
NESTED_IMPERSONATION |
The calling session is already an impersonation. Stop first, then start fresh. |
400 |
USER_DISABLED |
Target user is globally disabled. |
404 |
NOT_FOUND |
User isn't an end user of your workspace. |
Impersonation is loud in the audit log. Both the operator's audit feed and the target user's audit feed get a
ops.end_user.impersonation_startedentry with the operator's identity. The target also sees a "Someone signed in to your account" email if their settings have email notifications enabled.
Stop impersonation
POST /v1/ops/end-users/stop-impersonation
Revokes the impersonation session. The operator must sign in again normally to get back into their admin session — stopping doesn't restore the previous cookie.
204 No Content. 400 NOT_IMPERSONATING if the calling session isn't an impersonation.
Disable / enable (Forjio-internal)
POST /v1/ops/end-users/:id/disable
POST /v1/ops/end-users/:id/enable
Globally disables (or re-enables) a Huudis user across every workspace. Reserved for Forjio operations — callers must be in the special huudis Forjio-internal workspace and have role owner or admin. Returns 403 FORBIDDEN for everyone else.
Disabling cascades:
- The user's
disabledflag flips totrue. - All live sessions are revoked immediately.
- All refresh tokens are revoked — no Forjio product can mint a new access token for them.
The user's data is preserved — this is a soft suspend, not a delete.
The end-user object
| Field | Type | Description |
|---|---|---|
id |
string (usr_…) |
Same Huudis user ID format as workspace members. |
email |
string | Sign-in email. |
name |
string | null | Display name. |
emailVerified |
boolean | Whether the email has been confirmed (via link or admin force). |
disabled |
boolean | Globally disabled flag. |
disabledAt |
ISO 8601 | null | When the user was disabled. |
mfaEnabled |
boolean | Whether the user has at least one verified MFA device. |
createdAt |
ISO 8601 | User row creation. |
lastLoginAt |
ISO 8601 | null | Most recent successful sign-in across any client. |
firstSignInAt |
ISO 8601 | First sign-in to your workspace's clients (list only). |
clientCount |
integer | Number of your workspace's clients the user has consented to (list only). |
consents |
array | Per-client consent records, scoped to your workspace (detail only). |
Cross-workspace isolation
The end-user surface is strictly workspace-scoped. If a user signs into MejaStudio (your workspace) and also into Plugipay (Forjio's workspace), each workspace sees the user as an end user only through their own clients. The Huudis usr_… is shared, but:
- MejaStudio admins cannot list Plugipay end users.
- Revoke at MejaStudio only revokes MejaStudio consents; the Plugipay session keeps working.
- The user's globally disabled flag is the only thing that affects every workspace at once — and only Forjio-internal operators can flip it.
End users vs. workspace members
| Property | Workspace member | End user |
|---|---|---|
| Can log into the Huudis dashboard | yes | no |
Listed in /v1/iam/users |
yes | no |
Listed in /v1/ops/end-users |
only if they also consented to a workspace client | yes |
| Can hold IAM policies | yes | no |
Has a role (owner/admin/member) |
yes | no (just consents) |
The same usr_… can be both, depending on which clients they've consented to.
Events
| Event type | Fires on |
|---|---|
huudis.user.created.v1 |
A new end user is provisioned (first sign-up via your client). |
huudis.user.disabled.v1 |
An end user is globally disabled. |
huudis.user.enabled.v1 |
A previously disabled user is re-enabled. |
huudis.oidc.consent_granted.v1 |
An end user consents to one of your clients for the first time. |
End-user revoke and impersonation do not emit webhook events — they're audit-log only.
Next
- Consents — the user-side view of OIDC consents.
- OIDC clients — the clients end users consent to.
- Users — workspace members (the other kind of user).