Groups
A group bundles users so you can attach a policy once instead of once per member. "Engineers", "Finance", "On-call" — typical workspace shapes. Groups have no implicit permissions; they're a join target for policy attachments. The Go SDK exposes the group methods on client.IAM. For the HTTP surface see API → Groups; for the policy model see Concepts → Authorization.
Field on the Client
client.IAM — the group methods below sit alongside the user methods. No Update — group names and descriptions are immutable. Rename by creating fresh, copying members, re-attaching policies, deleting the old.
Methods
ListGroups
Signature. func (r *IamResource) ListGroups(ctx context.Context, auth RequestAuth) ([]IamGroup, error)
Lists every group in the workspace. MemberCount is included for each row — cheap on the server; you don't need to walk members to count.
groups, _ := client.IAM.ListGroups(ctx, huudis.RequestAuth{})
for _, g := range groups {
fmt.Println(g.ID, g.Name, g.MemberCount, "members")
}
GetGroup
Signature. func (r *IamResource) GetGroup(ctx context.Context, id string, auth RequestAuth) (*IamGroup, error)
Fetches one group by ID. Response shape is identical to a ListGroups row — Huudis doesn't include the member list on this call.
eng, err := client.IAM.GetGroup(ctx, "grp_01HX...", huudis.RequestAuth{})
CreateGroup
Signature. func (r *IamResource) CreateGroup(ctx context.Context, in CreateGroupInput, auth RequestAuth) (*IamGroup, error)
Creates an empty group. Name must be unique inside the workspace (case-insensitive).
eng, err := client.IAM.CreateGroup(ctx, huudis.CreateGroupInput{
Name: "Engineering",
Description: "Read/write on all infra resources",
}, huudis.RequestAuth{})
if err != nil {
return err
}
log.Println(eng.ID)
DeleteGroup
Signature. func (r *IamResource) DeleteGroup(ctx context.Context, id string, auth RequestAuth) error
Deletes the group. Any policy attachments referencing the group ARN are deleted at the same time (cascade). Members aren't touched — they keep their direct policy attachments.
_ = client.IAM.DeleteGroup(ctx, "grp_01HX...", huudis.RequestAuth{})
AddGroupMember
Signature. func (r *IamResource) AddGroupMember(ctx context.Context, groupID, userID string, auth RequestAuth) error
Adds a user to the group. Idempotent — re-adding an existing member returns 200, not 409.
_ = client.IAM.AddGroupMember(ctx, "grp_01HX...", "usr_01HY...", huudis.RequestAuth{})
RemoveGroupMember
Signature. func (r *IamResource) RemoveGroupMember(ctx context.Context, groupID, userID string, auth RequestAuth) error
Removes a user. Idempotent — removing a non-member is a no-op, not an error.
_ = client.IAM.RemoveGroupMember(ctx, "grp_01HX...", "usr_01HY...", huudis.RequestAuth{})
Types
type IamGroup struct {
ID string `json:"id"` // "grp_..."
AccountID string `json:"accountId"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
MemberCount int `json:"memberCount,omitempty"`
CreatedAt string `json:"createdAt"` // ISO-8601
}
For the group ARN shape and per-field rules see API → Groups.
Common patterns
Bootstrap a new workspace's groups
The "every workspace should have these" baseline:
var baseline = []huudis.CreateGroupInput{
{Name: "Owners", Description: "Full admin"},
{Name: "Engineering", Description: "Read/write on infra"},
{Name: "Finance", Description: "Read on billing only"},
{Name: "Read-only", Description: "List everything, mutate nothing"},
}
func bootstrapGroups(ctx context.Context, c *huudis.Client) error {
existing, err := c.IAM.ListGroups(ctx, huudis.RequestAuth{})
if err != nil {
return err
}
have := map[string]bool{}
for _, g := range existing {
have[strings.ToLower(g.Name)] = true
}
for _, g := range baseline {
if !have[strings.ToLower(g.Name)] {
if _, err := c.IAM.CreateGroup(ctx, g, huudis.RequestAuth{}); err != nil {
return err
}
}
}
return nil
}
Add a user to a set of groups
The SDK doesn't batch — just loop. Concurrency-safe because the membership writes are idempotent:
func addToGroups(ctx context.Context, c *huudis.Client, userID string, groupIDs []string) error {
for _, g := range groupIDs {
if err := c.IAM.AddGroupMember(ctx, g, userID, huudis.RequestAuth{}); err != nil {
return err
}
}
return nil
}
Sync membership from your IdP groups
If the upstream IdP carries group claims in the OIDC payload, reconcile on every sign-in:
func syncMembership(ctx context.Context, c *huudis.Client, userID string, idpGroupNames []string) error {
groups, err := c.IAM.ListGroups(ctx, huudis.RequestAuth{})
if err != nil {
return err
}
wanted := map[string]bool{}
for _, n := range idpGroupNames {
wanted[strings.ToLower(n)] = true
}
for _, g := range groups {
if wanted[strings.ToLower(g.Name)] {
_ = c.IAM.AddGroupMember(ctx, g.ID, userID, huudis.RequestAuth{})
} else {
_ = c.IAM.RemoveGroupMember(ctx, g.ID, userID, huudis.RequestAuth{})
}
}
return nil
}
The idempotent semantics make this safe to re-run on every sign-in without tracking diffs.
Attach a policy to a group
Combine with the Policies and policy-attachment surfaces:
policy, _ := client.IAM.CreatePolicy(ctx, huudis.CreatePolicyInput{
Name: "EngineeringReadWriteInfra",
Document: map[string]any{ /* JSON policy */ },
}, huudis.RequestAuth{})
_, _ = client.IAM.AttachPolicy(ctx, huudis.AttachPolicyInput{
PolicyID: policy.ID,
PrincipalArn: fmt.Sprintf("arn:huudis:iam::%s:group/Engineering", accountID),
}, huudis.RequestAuth{})
Errors
Code |
Status |
Cause |
|---|---|---|
validation_error |
400 | Name empty or too long, description too long. |
conflict |
409 | Group name already used in this workspace. |
not_found |
404 | Group ID doesn't exist; user ID doesn't exist (on AddGroupMember). |
forbidden |
403 | Caller isn't an admin/owner. |
Next
- Users — the members groups bundle.
- Policies — what gets attached to groups.
- API → Groups — HTTP-level reference.