Users

A user is a person (or service) with credentials in Huudis. This resource manages the workspace members — the humans who can log into the Huudis dashboard for your account and administer it.

For people who signed into one of your OIDC clients (end users of your product, not of your Huudis workspace), see End users — that's a separate surface with different rules.

This page is about adding, editing, and removing workspace members. For the data model see Concepts → User; for the dashboard walkthrough see Portal → Managing users.

All requests on this page require a bearer admin JWT — see Authentication. Requests follow the response envelope, ID, and pagination conventions in API overview.

Endpoints

Method Path Purpose
GET /v1/iam/users List members of the active workspace
POST /v1/iam/users Add a new user directly
PATCH /v1/iam/users/:id Change role or verify state
DELETE /v1/iam/users/:id Remove a member from the workspace
POST /v1/iam/users/:id/reset-password Reset a member's password
POST /v1/iam/invites Send an invitation email
GET /v1/iam/invites List pending invites
POST /v1/iam/invites/:id/cancel Cancel an invite before it's accepted

Only members with role owner or admin can hit any of the mutation endpoints. member tokens get 403 FORBIDDEN.

List users

GET /v1/iam/users

Returns every member of the active workspace, oldest-joined first. Includes a precomputed groups array (from IAM group memberships) so the dashboard's table can render in one round-trip.

Response200 OK

{
  "data": [
    {
      "id": "usr_01KPG30SPWNKDQ9G40NET6QKA2",
      "email": "adi@forjio.com",
      "name": "Adhya Pranata Sakti",
      "emailVerified": true,
      "role": "owner",
      "joinedAt": "2026-04-21T08:12:00.000Z",
      "lastLoginAt": "2026-05-12T22:01:11.220Z",
      "createdAt": "2026-04-21T08:12:00.000Z",
      "isYou": true,
      "groups": [
        { "id": "grp_01KPG…", "name": "Engineering" }
      ]
    }
  ],
  "error": null,
  "meta": { "requestId": "req_01KPG…", "timestamp": "2026-05-12T22:01:11.221Z" }
}

isYou is the calling identity, useful for "you can't delete yourself" UI hints. lastLoginAt is null if the user has never signed in (e.g. invited but not yet accepted).

Add a user

POST /v1/iam/users

Adds a user directly to the workspace. If the email already has a Huudis user row (because they're a member of another workspace or have used another Forjio product), Huudis attaches the existing user. Otherwise it provisions a fresh usr_… and a temporary password.

Use this when you want immediate access (e.g. seeding an admin during onboarding). For the friendlier "send them an email, let them set their own password" flow, see Invite a user below.

Request body

Field Type Required Description
email string (RFC 5321, ≤200) yes The user's email. Lowercased before insert.
name string (1–120) no Display name on the member list.
password string (10–200) no Set an explicit initial password. Omit to have Huudis generate one.
role owner | admin | member no Default member. Only owners can grant owner.
emailVerified boolean no Default true. Set false if you want the user to verify their email first.
sendInviteEmail boolean no Default true. When false, Huudis won't email the temp password — useful for orchestration where you'll relay it some other way.

Response201 Created

{
  "data": {
    "id": "usr_01KPG40HMM…",
    "email": "newbie@forjio.com",
    "name": "Newbie",
    "role": "member",
    "emailVerified": true,
    "joinedAt": "2026-05-12T22:14:00.000Z",
    "tempPassword": "K9hM3pNqRtVw7X"
  }
}

tempPassword is only present when Huudis generated it (you didn't pass password) and this was a fresh user creation. It's shown exactly once. If the email already had a Huudis user, tempPassword is null because we don't reset their existing password.

Errors specific to this endpoint

Status error.code When
403 FORBIDDEN Caller isn't owner/admin, or role: "owner" was requested by a non-owner.
409 ALREADY_MEMBER The user is already a member of this workspace.
400 WEAK_PASSWORD Supplied password failed strength checks.
400 NO_ACCOUNT The calling user has no workspace membership (shouldn't happen in practice).

Update a user

PATCH /v1/iam/users/:id

Change a member's role or force their email to verified. Only the two fields below are mutable here — the user's name and email are self-managed via /v1/account (the user's own session).

Request body

Field Type Description
role owner | admin | member New role. Only owners can grant the owner role. Cannot demote the last owner — promote someone else first.
emailVerified boolean Force-flip the verification flag. Use sparingly; this skips the normal "click the link in your email" check.

Errors

Status error.code When
400 LAST_OWNER Demoting the last owner is forbidden.
403 FORBIDDEN Caller not authorized, or non-owner tried to set role: "owner".
404 RESOURCE_NOT_FOUND No member with that ID in this workspace.

Remove a user

DELETE /v1/iam/users/:id

Removes the membership row — the user no longer belongs to this workspace. Their underlying usr_… survives if they belong to any other workspace; otherwise it becomes an orphaned account that can still sign in but sees no workspaces.

204 No Content on success.

Errors

Status error.code When
400 CANT_REMOVE_SELF You called this with your own user ID — use Account → Leave workspace from your own session instead.
400 LAST_OWNER Cannot remove the last owner; promote a replacement first.
403 FORBIDDEN Caller is a plain member.

Reset a member's password

POST /v1/iam/users/:id/reset-password

Admin-side password reset. Generates a new temporary password, emails it to the target user, and returns the same plaintext to the caller so it can be relayed if the email bounces.

Response200 OK

{
  "data": { "reset": true, "tempPassword": "Qm7nXt2vK9pW4R" }
}

The user can keep using their current session until the access token rotates — reset only affects future logins. To kick them out immediately, follow up with POST /v1/account/sessions/{id}/revoke for each of their active sessions.

Invite a user

POST /v1/iam/invites

The polite alternative to direct add. Huudis stores an invite row, emails a single-use token, and sets joinedAt only after the recipient accepts at huudis.com/invites/{token}.

Request body

Field Type Description
email string Recipient. Lowercased.
role owner | admin | member Role they'll have after accepting. Default member.

If an active invite for that email already exists in this workspace, calling this again rotates the token (resends) rather than failing — idempotent in spirit.

Response201 Created

{
  "data": {
    "id": "inv_01KPG…",
    "email": "recruit@forjio.com",
    "role": "member",
    "invitedAt": "2026-05-12T22:30:00.000Z",
    "expiresAt": "2026-05-19T22:30:00.000Z"
  }
}

Invites expire 7 days after the most recent send. The token itself is not returned by the API — only the recipient (via email) ever sees it, which prevents an admin from impersonating the new user by submitting the accept flow themselves.

Errors

Status error.code When
409 ALREADY_MEMBER Email already belongs to a member of this workspace.
403 FORBIDDEN Caller can't invite (non-admin) or tried to invite as owner without being one.

List invites

GET /v1/iam/invites

Returns pending invites for the active workspace. Accepted and canceled invites are excluded by default.

Cancel an invite

POST /v1/iam/invites/:id/cancel

Marks the invite as canceled. The token still in the recipient's inbox stops working immediately — subsequent accept attempts return INVITE_NOT_FOUND.

The user object

Field Type Description
id string (usr_…) Huudis user ID. Stable forever, even across workspace membership changes.
email string Login email. Unique per Huudis instance.
name string | null Display name.
emailVerified boolean Has the user clicked the verify-email link, or did an admin force it?
role owner | admin | member Role within the active workspace. The same usr_… can have different roles in different workspaces.
joinedAt ISO 8601 When this user joined this workspace.
lastLoginAt ISO 8601 | null Last successful sign-in across any client.
createdAt ISO 8601 When the underlying usr_… was first created in Huudis.
groups array IAM groups within this workspace the user belongs to.
isYou boolean Whether this row is the calling identity (only present on GET /v1/iam/users).

Roles and what they can do

Role Add/remove members Change roles Manage OIDC clients Manage IAM Read audit log
owner yes yes (including grant owner) yes yes yes
admin yes yes (except owner) yes yes yes
member no no no no own entries only

There must always be at least one owner. Endpoints enforce this server-side — you cannot demote or delete the last one without first promoting someone else.

Events

Event type Fires on
huudis.user.created.v1 POST /v1/iam/users succeeds with a fresh user row, or someone signs up directly.
huudis.account.member_added.v1 A user joins this workspace (direct add or invite accept).
huudis.user.disabled.v1 A user is globally disabled (Forjio-internal endpoint).
huudis.user.enabled.v1 A previously disabled user is re-enabled.

role.changed and user.removed events are reserved but not currently emitted — subscribe defensively if you need them; the audit log carries the same data for now.

See Webhooks for the envelope and retry policy.

Next

  • End users — users of your OIDC clients (different surface).
  • Access keys — long-lived service-account credentials.
  • Policies — attach IAM policies to users.