Roles
A role is a temporarily-assumable bundle of permissions. Where a group extends a user's standing permissions, a role is something a user (or service account) assumes explicitly via POST /v1/authz/assume-role and gets short-lived credentials for.
This is Huudis's analog of AWS STS. Use roles when:
- A service needs scoped, short-lived credentials rather than a long-lived access key (e.g. a daily cron that should only have
huudis:audit:readfor one hour). - You want cross-principal delegation: service account
svc_Ais trusted to assume roleBillingReader, regardless of whethersvc_Aitself holds the underlying policy. - You want a clean audit trail: every
AssumeRolecall generates an audit-log entry with the assumed credential'saccessKeyId, and every call made with that credential is taggable back to the original principal.
All endpoints require a bearer admin JWT — see Authentication.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/iam/roles |
Create a role |
GET |
/v1/iam/roles |
List roles in the active workspace |
GET |
/v1/iam/roles/:id |
Retrieve one role |
DELETE |
/v1/iam/roles/:id |
Delete a role |
To assume a role and get session credentials, see authz.
Create a role
POST /v1/iam/roles
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string (1–120) | yes | Role name, unique per workspace. Convention: PascalCase. |
description |
string (≤500) | no | Operator note. |
trustPolicy |
object | yes | Who is allowed to assume this role. See Trust policy below. |
maxSessionDurationSec |
integer (900–43200) | no | Max duration the resulting credentials can last. Default 3600 (1 hour). Hard cap 43200 (12 hours). |
Response — 201 Created. The full role.
List roles
GET /v1/iam/roles
{
"data": [
{
"id": "rol_01KPG…",
"accountId": "acc_01KPG…",
"name": "BillingReader",
"description": "Read invoices for daily ETL job.",
"trustPolicy": { … },
"maxSessionDurationSec": 3600,
"createdAt": "2026-04-01T00:00:00.000Z"
}
]
}
Retrieve a role
GET /v1/iam/roles/:id
Returns the role's full trust policy. Use this when previewing who can assume it before making a change.
Delete a role
DELETE /v1/iam/roles/:id
Hard-deletes. Active assumed-role sessions issued by this role keep working until they expire — deletion does not retroactively revoke them. Use POST /v1/iam/assumed-sessions/:id/revoke to kill live sessions explicitly.
204 No Content.
Trust policy
The trust policy says who is allowed to assume the role. It's a regular policy document with Principal blocks — the inverse of a permission policy.
{
"Version": "2026-01-01",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"User": ["usr_01KPG30SPWNKDQ9G40NET6QKA2"],
"ServiceAccount": ["svc_01KPG30TZK…"]
},
"Action": "sts:AssumeRole"
}
]
}
Recognised Principal keys:
| Key | Matches | Example |
|---|---|---|
User |
Workspace member by usr_… |
["usr_01KPG…"] |
ServiceAccount |
Service account by svc_… |
["svc_01KPG…"] |
Role |
Another role by rol_… (role chaining) |
["rol_01KPG…"] |
Group |
IAM group by grp_… |
["grp_01KPG…"] |
* |
Wildcard. Use the value "*", e.g. { "*": "*" }. |
Action is always sts:AssumeRole. You can omit it — an empty Action matches every operation against the role.
Explicit deny wins in trust policies, just like permission policies.
What the assumed credentials look like
When POST /v1/authz/assume-role succeeds, it returns:
{
"credentials": {
"accessKeyId": "ASIA0123456789ABCDEF",
"secretAccessKey": "BASE64_SECRET_HERE",
"sessionToken": "BASE64_TOKEN_HERE",
"expiresAt": "2026-05-13T00:30:00.000Z"
},
"role": {
"id": "rol_01KPG…",
"name": "BillingReader",
"arn": "forjio:huudis::acc_01KPG…:role/BillingReader"
},
"sessionId": "ars_01KPG…"
}
The accessKeyId always starts with ASIA (Assumed-role STS-Identity Authentication) to distinguish it from long-lived access keys (AKIA…). Use the trio (accessKeyId, secretAccessKey, sessionToken) to sign requests — see API authentication for the recipe.
Effective permissions of an assumed role
When a session is acting under a role, the effective permissions are exactly the policies attached to that role (via Attachments). The underlying principal's own permissions don't bleed in — that's the whole point. A user with full admin can assume a BillingReader role and find themselves locked out of everything except billing reads for the next hour.
Session lifecycle
- Issued by
POST /v1/authz/assume-role. - Listed at
GET /v1/iam/assumed-sessions. - Auto-expires at
expiresAt. After that, the credentials silently stop working. - Can be revoked early via
POST /v1/iam/assumed-sessions/:id/revoke.
Events
No events for role CRUD or assume-role. Audit-log entries (iam.assume_role, iam.role.created, iam.role.deleted, assumed_role_session_revoked) cover everything.
Next
- Assumed sessions — live STS sessions and how to revoke them.
- Authz — the AssumeRole endpoint itself.
- Policies — the permission policies you attach to a role.