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