Account
This is the self-service surface for the calling user themselves — their own profile, sessions, linked social identities, and audit log. Endpoints here always act on the bearer-token holder; there is no :userId path parameter because that would let one user act on another.
For admin-side operations on other users see Users (workspace members) and End users (signed-in users of your OIDC clients).
All endpoints require a bearer access token for the user — see Authentication.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/account |
Profile + workspace memberships |
PATCH |
/v1/account |
Update name / locale |
POST |
/v1/account/email-change |
Request an email change |
POST |
/v1/account/password-change |
Change password |
GET |
/v1/account/sessions |
List active sessions |
POST |
/v1/account/sessions/:id/revoke |
Revoke one session |
POST |
/v1/account/sessions/revoke-all |
Revoke every session except the current one |
GET |
/v1/account/linked-accounts |
List linked social identities |
DELETE |
/v1/account/linked-accounts/:provider |
Unlink a provider |
GET |
/v1/account/audit |
Read the calling user's audit feed |
Get profile
GET /v1/account
Returns the user with their workspace memberships and a few derived flags.
{
"data": {
"id": "usr_01KPG…",
"email": "adi@forjio.com",
"name": "Adhya Pranata Sakti",
"emailVerified": true,
"locale": "en-ID",
"hasPassword": true,
"mfaEnabled": true,
"createdAt": "2025-12-04T10:42:00.000Z",
"lastLoginAt": "2026-05-12T22:01:11.000Z",
"memberships": [
{
"role": "owner",
"account": {
"id": "acc_01KPG…",
"name": "Forjio Headquarters",
"slug": "forjio-headquarters"
},
"joinedAt": "2025-12-04T10:42:00.000Z"
}
]
}
}
hasPassword: false means the user signs in via social only and there is no password set yet — the change-password endpoint requires currentPassword to be omitted in that case.
Update profile
PATCH /v1/account
Request body
| Field | Type | Description |
|---|---|---|
name |
string | null | Display name. null clears. |
locale |
string (≤10) | BCP 47 locale tag. Used to localise emails. |
Email and password are managed via the dedicated endpoints below.
Request an email change
POST /v1/account/email-change
Initiates a two-step email change. Huudis sends a verification link to the new address; clicking it actually swaps the email.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | The new email. Lowercased before insert. |
password |
string | yes (if user has one) | Re-auth confirmation. Required unless the user has no password (social-only). |
Errors include EMAIL_TAKEN (the new email is already a Huudis user), INVALID_CREDENTIALS (wrong password), and SAME_EMAIL.
200 OK returns { pending: true, newEmail }. The old email remains active until the new one is verified.
Change password
POST /v1/account/password-change
Request body
| Field | Type | Required | Description |
|---|---|---|---|
currentPassword |
string | conditional | Required if the user already has a password. Omit if hasPassword: false. |
newPassword |
string (≥10) | yes | Subject to Huudis's strength rules — rejected if WEAK_PASSWORD. |
On success, every other session is revoked (the current session is kept so the user isn't kicked out of the tab they just changed their password from). The user gets a "password changed" confirmation email.
List sessions
GET /v1/account/sessions
Returns the calling user's live sessions across every browser, mobile app, and CLI. The current session is flagged current: true.
{
"data": [
{
"id": "sess_01KPG…",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) …",
"ip": "203.0.113.5",
"createdAt": "2026-05-12T22:01:00.000Z",
"lastUsedAt": "2026-05-12T23:59:00.000Z",
"expiresAt": "2026-05-26T22:01:00.000Z",
"current": true
}
]
}
Revoke a single session
POST /v1/account/sessions/:id/revoke
Kills one session immediately. 400 CANNOT_REVOKE_CURRENT if you targeted your own — use /v1/auth/logout instead for that.
Revoke every other session
POST /v1/account/sessions/revoke-all
Revokes every session except the current one. 200 OK returns { revokedCount: 4 }. Useful as a "lost my phone" panic button.
Linked accounts
GET /v1/account/linked-accounts
Returns the user's social-identity links plus whether they have a password.
{
"data": {
"hasPassword": true,
"providers": [
{ "id": "soc_01KPG…", "provider": "google", "email": "adi@gmail.com", "linkedAt": "2026-04-01T00:00:00.000Z" }
]
}
}
Unlink a provider
DELETE /v1/account/linked-accounts/:provider
Path: :provider is google or apple.
Removes the social-identity link. Sign-in via that provider stops working.
Errors
| Status | error.code |
When |
|---|---|---|
400 |
LAST_SIGNIN_METHOD |
Removing this link would leave the user with no way to sign in (no password and no other linked provider). Set a password first. |
404 |
LINK_NOT_FOUND |
No link for that provider. |
A confirmation email goes to the user's primary email after a successful unlink.
Audit log
GET /v1/account/audit?limit=50&cursor=…&event=…&outcome=success&since=…&until=…
Returns the calling user's own audit-log entries. Cursor-paginated — pass nextCursor from the previous response. Filters:
| Param | Description |
|---|---|
event |
Exact event type (e.g. auth.login, account.profile_updated). |
outcome |
success | failure | denied. |
since / until |
ISO 8601 datetime bounds. |
Response
{
"data": {
"entries": [
{
"id": "alog_01KPG…",
"timestamp": "2026-05-12T22:01:11.000Z",
"event": "auth.login",
"outcome": "success",
"ip": "203.0.113.5",
"userAgent": "Mozilla/5.0 …",
"metadata": { "method": "password" }
}
],
"nextCursor": "alog_01KPF…"
}
}
Workspace admins get a broader, workspace-scoped audit feed at a separate endpoint (planned). The current /v1/account/audit is strictly the calling user's own entries.
The profile object
| Field | Type | Description |
|---|---|---|
id |
string (usr_…) |
The calling user's ID. |
email |
string | Primary email. |
name |
string | null | Display name. |
emailVerified |
boolean | |
locale |
string | BCP 47 tag. |
hasPassword |
boolean | false for social-only accounts. |
mfaEnabled |
boolean | Any verified MFA device. |
createdAt |
ISO 8601 | |
lastLoginAt |
ISO 8601 | null | Last successful sign-in. |
memberships |
array | Workspace memberships with role + account. |
Self-service vs. admin paths
Self-service (/v1/account/*) |
Admin (/v1/iam/* or /v1/ops/*) |
|
|---|---|---|
| Required token | The user's own bearer | A workspace admin's bearer |
| Acts on | The token holder | A different user, by :id |
| Email change | Two-step (link to new address) | Direct — admins can force-verify |
| Password change | Requires current password | Admin reset (sends new temp) |
| Session revoke | Own sessions only | n/a — admins can revoke OIDC refresh tokens, not Huudis sessions, for end users |
Don't try to admin yourself with the admin paths — the role guards prevent owner self-demote and member self-remove. Use the self-service paths.
Events
| Event type | Fires on |
|---|---|
huudis.session.created.v1 |
A new session (web or OIDC) is issued. |
huudis.session.revoked.v1 |
A session (or refresh token) is revoked. |
Profile changes, password changes, and email changes don't emit webhook events — they're audit-log-only.
Next
- Users — admin-side workspace member management.
- MFA — enrol and manage second factors.
- Workspaces — switching the active workspace.