Users
A user is a person (or service-bound identity) who can sign into the Huudis dashboard and administer your workspace. The Go SDK exposes the user-flavoured operations under client.IAM; every method takes context.Context first and a huudis.RequestAuth last for the per-call bearer override. Group membership, role assignment, and invites have their own pages. For HTTP shapes and the full field table, see API → Users.
End users — the people who sign into your product through Huudis OIDC — are a different surface; see End users.
Field on the Client
client.IAM — type *huudis.IamResource. Installed on *huudis.Client returned by NewClient; shares the underlying *http.Client, issuer, and audience. There is no per-namespace state — methods are safe to call concurrently from many goroutines.
Methods
ListUsers
Signature. func (r *IamResource) ListUsers(ctx context.Context, p ListUsersParams, auth RequestAuth) (*ListPage[IamUser], error)
Cursor-paginated. Default page size 25, max 100. The active workspace is determined by the bearer token; switch with client.Workspaces.Switch(...).
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
page, err := client.IAM.ListUsers(ctx, huudis.ListUsersParams{Limit: 50}, huudis.RequestAuth{})
if err != nil {
return err
}
for _, u := range page.Items {
state := "active"
if u.Disabled {
state = "disabled"
}
fmt.Println(u.ID, u.Email, state)
}
if page.NextCursor != "" {
next, err := client.IAM.ListUsers(ctx,
huudis.ListUsersParams{Limit: 50, Cursor: page.NextCursor},
huudis.RequestAuth{})
_ = next; _ = err
}
CreateUser
Signature. func (r *IamResource) CreateUser(ctx context.Context, in CreateUserInput, auth RequestAuth) (*IamUser, error)
Directly creates a workspace member, bypassing the email invite flow. Useful when seeding a new workspace from a script. Email is required; Name, Password, and Role are optional. Omitting Password forces the user through a password reset before first sign-in.
u, err := client.IAM.CreateUser(ctx, huudis.CreateUserInput{
Email: "alice@example.com",
Name: "Alice Tan",
Role: "admin",
}, huudis.RequestAuth{})
if err != nil {
return err
}
log.Printf("created %s", u.ID) // "usr_01HX..."
Prefer invites for humans. For real onboarding, use
client.IAM.SendInvite(...)— see Invites.CreateUseris the bypass for service principals and migration scripts.
UpdateUser
Signature. func (r *IamResource) UpdateUser(ctx context.Context, id string, patch map[string]any, auth RequestAuth) (*IamUser, error)
PATCH semantics — the patch map carries only the fields you want to change. Common updates: name, disabled. To change role, see Roles — workspace role assignment is a separate endpoint.
_, err := client.IAM.UpdateUser(ctx, "usr_01HX...", map[string]any{
"name": "Alice T. Wijaya",
}, huudis.RequestAuth{})
// Soft-disable an account.
_, _ = client.IAM.UpdateUser(ctx, "usr_01HX...", map[string]any{"disabled": true}, huudis.RequestAuth{})
DeleteUser
Signature. func (r *IamResource) DeleteUser(ctx context.Context, id string, auth RequestAuth) error
Hard-removes the user from this workspace. Their global Huudis identity stays — workspace membership is the join table. The very last owner of a workspace cannot delete themselves; you'll get *huudis.Error{Code: "state_conflict"} with Message containing cannot_remove_last_owner.
if err := client.IAM.DeleteUser(ctx, "usr_01HX...", huudis.RequestAuth{}); err != nil {
return err
}
ResetUserPassword
Signature. func (r *IamResource) ResetUserPassword(ctx context.Context, id string, auth RequestAuth) (*PasswordResetResult, error)
Sends a password-reset email. The user receives a tokenised link valid for one hour. Returns &PasswordResetResult{Sent: true} on dispatch.
res, err := client.IAM.ResetUserPassword(ctx, "usr_01HX...", huudis.RequestAuth{})
if err != nil {
return err
}
if !res.Sent {
log.Println("mailer returned non-success — check audit log")
}
Types
type IamUser struct {
ID string `json:"id"` // "usr_..."
AccountID string `json:"accountId"` // workspace
Email string `json:"email"`
Name string `json:"name,omitempty"`
EmailVerified bool `json:"emailVerified"`
MfaEnrolled bool `json:"mfaEnrolled"`
Disabled bool `json:"disabled"`
CreatedAt string `json:"createdAt"` // ISO-8601
}
type ListPage[T any] struct {
Items []T `json:"items"`
NextCursor string `json:"nextCursor,omitempty"`
}
For per-field validation rules see API → Users.
Common patterns
Find a user by email
The list endpoint has no email filter — walk pages and match client-side. For sub-thousand-member workspaces this is fine; for larger orgs, cache the lookup or maintain a side index keyed by your IdP's subject claim.
func userByEmail(ctx context.Context, c *huudis.Client, email string) (*huudis.IamUser, error) {
var cursor string
for {
page, err := c.IAM.ListUsers(ctx,
huudis.ListUsersParams{Limit: 100, Cursor: cursor},
huudis.RequestAuth{})
if err != nil {
return nil, err
}
for i := range page.Items {
if page.Items[i].Email == email {
return &page.Items[i], nil
}
}
if page.NextCursor == "" {
return nil, nil
}
cursor = page.NextCursor
}
}
Bulk-disable on offboarding
func offboard(ctx context.Context, c *huudis.Client, email string) error {
workspaces, err := c.Workspaces.List(ctx, huudis.RequestAuth{})
if err != nil {
return err
}
for _, ws := range workspaces {
if _, err := c.Workspaces.Switch(ctx, ws.ID, huudis.RequestAuth{}); err != nil {
return err
}
u, err := userByEmail(ctx, c, email)
if err != nil {
return err
}
if u != nil {
_, _ = c.IAM.UpdateUser(ctx, u.ID, map[string]any{"disabled": true}, huudis.RequestAuth{})
}
}
return nil
}
Sync from your HRIS
type HrisUser struct{ Email, Name, Role string }
func syncFromHris(ctx context.Context, c *huudis.Client, active []HrisUser) error {
present := map[string]*huudis.IamUser{}
var cursor string
for {
page, err := c.IAM.ListUsers(ctx,
huudis.ListUsersParams{Limit: 100, Cursor: cursor},
huudis.RequestAuth{})
if err != nil {
return err
}
for i := range page.Items {
present[page.Items[i].Email] = &page.Items[i]
}
if page.NextCursor == "" {
break
}
cursor = page.NextCursor
}
wanted := map[string]bool{}
for _, u := range active {
wanted[u.Email] = true
if _, ok := present[u.Email]; !ok {
_, _ = c.IAM.CreateUser(ctx, huudis.CreateUserInput{
Email: u.Email, Name: u.Name, Role: u.Role,
}, huudis.RequestAuth{})
}
}
for email, u := range present {
if !wanted[email] && !u.Disabled {
_, _ = c.IAM.UpdateUser(ctx, u.ID, map[string]any{"disabled": true}, huudis.RequestAuth{})
}
}
return nil
}
Errors
Code |
Status |
Cause |
|---|---|---|
validation_error |
400 | Bad email format, role doesn't exist, password too weak. |
conflict |
409 | Email already a member of this workspace. |
not_found |
404 | User ID doesn't exist or lives in another workspace. |
state_conflict |
409 | Tried to delete the last owner; tried to disable yourself. |
forbidden |
403 | Caller isn't an owner or admin. |
Branch with errors.As:
var he *huudis.Error
if errors.As(err, &he) {
switch he.Code {
case "conflict": // duplicate email
case "not_found": // missing user
case "validation_error":
log.Printf("bad input: %s", he.Message)
}
}
Next
- Invites — the email-driven onboarding flow.
- Groups — bundle users for policy attachment.
- Roles — assignable workspace permissions.
- API → Users — HTTP-level reference.