IAM roles and STS AssumeRole¶
DotID models roles the same way AWS IAM does: a role is an assumable identity in an account with a trust policy (who may assume it) and attached permission policies (what it may do). A caller assumes a role through STS AssumeRole and receives short-lived credentials that act with the role’s permissions in the role’s account. This is the mechanism behind cross-account access.
Authentication¶
Role management and STS are self-managed by the account admin. Sign requests with an account service-user access key using FGAI-HMAC-SHA256 (see [CLI & service authentication](https://docs.flexgalaxy.ai/dev/dotid/cli-authentication/)); a bearer JWT is also accepted. Your credential is pinned to one account — you manage roles only in that account.
Base URL: https://api.flexgalaxy.ai (production) or http://api.dev.fgai.test (local dev).
Roles¶
Method |
Path |
Purpose |
|---|---|---|
|
|
Create a role |
|
|
List roles |
|
|
Get a role |
|
|
Update trust policy / description / max session |
|
|
Delete a role |
|
|
Attach a permission policy |
|
|
List attached policies |
|
|
Detach a policy |
Create a role¶
The trust_policy is required and answers “who may assume me”. name is required;
description, path, and max_session_duration (seconds) are optional.
Field names on the wire are
snake_case. Every DotID request and response body usessnake_case(trust_policy,role_frn,access_key_id, …). A body spelled in camelCase binds nothing and the API answers400with"role_frn: must not be blank"— a message that reads like a missing field when the real cause is the spelling.
{
"name": "DeviceReader",
"description": "Read-only device access, assumable by the ops tooling account.",
"max_session_duration": 3600,
"trust_policy": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Account": ["acc-0a1b2c3d4e5f"] },
"Action": "sts:AssumeRole"
}
]
}
}
The response is the created role, including its id:
{
"id": "8b2f0a3e-1c4d-4e6f-9a01-2b3c4d5e6f70",
"account_id": "11111111-1111-1111-1111-111111111111",
"name": "DeviceReader",
"trust_policy": { "…": "…" },
"max_session_duration": 3600,
"version": 1,
"created_at": "2026-07-07T09:00:00Z"
}
Trust policy¶
Each statement admits a caller when Effect is Allow, Action includes
sts:AssumeRole, the Principal block matches the caller, and every Condition
is satisfied. Evaluation is fail-closed: with no matching Allow the caller is
denied, and a matching Deny always wins.
Principal accepts:
Form |
Meaning |
|---|---|
|
Any caller. |
|
Any principal in that account. |
|
A specific principal, by [FRN](https://docs.flexgalaxy.ai/dev/dotid/frn-format/). |
|
A whole account (account-form FRN). |
Optionally require a shared secret with an sts:ExternalId condition:
{
"Effect": "Allow",
"Principal": { "Account": ["acc-0a1b2c3d4e5f"] },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "shared-secret-xyz" } }
}
Attach a permission policy¶
Attach one of your account’s IAM policies by id to grant the role what it may do:
curl -fsS -X POST \
"https://api.flexgalaxy.ai/identity/v1/accounts/$ACCOUNT_ID/roles/$ROLE_ID/policies" \
-H "Content-Type: application/json" \
-d '{ "policy_id": "a1b2c3d4-…" }'
# + FGAI-HMAC-SHA256 signing headers
STS AssumeRole¶
Exchange your credential for temporary credentials scoped to a role.
Method |
Path |
Purpose |
|---|---|---|
|
|
Assume a role → temporary credentials |
Request¶
role_frn and role_session_name are required; duration_seconds and external_id
are optional. Pass external_id when the target role’s trust policy requires one.
{
"role_frn": "frn:acc-target00acct:iam:role/DeviceReader",
"role_session_name": "ops-tooling-run-42",
"duration_seconds": 3600,
"external_id": "shared-secret-xyz"
}
duration_seconds is clamped to [900, role.max_session_duration].
Response¶
{
"assumed_role_frn": "frn:acc-target00acct:iam:role/DeviceReader",
"account_id": "22222222-2222-2222-2222-222222222222",
"role_session_name": "ops-tooling-run-42",
"access_key_id": "ASIA…",
"secret_access_key": "…",
"session_token": "…",
"expiration": "2026-07-07T10:00:00Z"
}
The returned credentials are temporary (ASIA… key id). Use them to sign
subsequent requests with FGAI-HMAC-SHA256, adding the session token as the
X-FGAI-Security-Token header. Requests signed with these credentials act in the
role’s account with the role’s permissions — so authorization decisions for the
session resolve from the role’s attached policies (see
Check authorization (PDP)).
Failures¶
A missing or invalid signature returns
401.If the role does not exist or its trust policy denies the caller, the call returns
403— the two cases are indistinguishable, so a caller cannot probe for roles in another account.
Assume a role from a workload identity (CI, machine persona)¶
A workload identity holds no key at all — it authenticates by presenting its
IdP-issued OIDC token to the token exchange and receiving a
short-lived DotID-signed bearer. A bearer is not a CLI command credential, so a machine
persona that needs to transact takes one more step: it assumes a role and receives the
same temporary ASIA… credential a human gets from Identity Center.
The route is a workload twin of the one above, not the same one:
Method |
Path |
Caller |
|---|---|---|
|
|
An access key (FGAI-HMAC) or a console/federated JWT |
|
|
A workload token from the exchange, as |
Sending a workload token to the first route returns 401 invalid_token with
unknown_realm: token-exchange — that is “wrong door”, not “denied”. The request and
response bodies are identical on both routes.
One-time setup by the account admin¶
Grant the workload identity the STS audience. Attach an IAM policy allowing
iam:ExchangeTokenon the STS service resource, so the workload can mint a token the STS lane accepts:{ "Version": "2024-01-01", "Statement": [ { "Sid": "ExchangeForSts", "Effect": "Allow", "Action": "iam:ExchangeToken", "Resource": "frn::iam:service/dotid-sts" } ] }
This grants nothing but the right to ask. The audience puts the workload’s token on the STS lane; which roles it may assume is still decided, per role, by the trust policy below.
Name the workload in the role’s trust policy. A workload identity’s principal FRN uses the
workload-identityresource type — it is not auser, and a trust policy written for one can never be satisfied by the other:{ "Effect": "Allow", "Principal": { "FGAI": ["frn:acc-0a1b2c3d4e5f:iam:workload-identity/<workload-identity-id>"] }, "Action": "sts:AssumeRole" }
Attach the role’s permissions as managed policies through
POST /identity/v1/accounts/{accountId}/roles/{roleId}/policies. An assumed session resolves the role’s attached policies; an inline role policy is not read.
The call¶
# 1. exchange the CI OIDC token for a workload token on the STS lane
ACCESS_TOKEN=$(curl -fsS -X POST https://api.flexgalaxy.ai/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
--data-urlencode "subject_token=$OIDC_TOKEN" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \
--data-urlencode "audience=dotid-sts" | jq -r .access_token)
# 2. assume the role — no signing, the bearer IS the credential here
curl -fsS -X POST https://api.flexgalaxy.ai/identity/v1/workload/sts/assume-role \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "role_frn": "frn:acc-0a1b2c3d4e5f:iam:role/Publisher",
"role_session_name": "ci-run-42" }'
The response is the same ASIA… credential shape as above. From here the workload signs
requests with FGAI-HMAC-SHA256 exactly like any other principal — including through the
fgai CLI:
fgai configure --name ci-publisher \
--account-id "$ACCOUNT_ID" \
--access-key-id "$ACCESS_KEY_ID" \
--secret-access-key "$SECRET_ACCESS_KEY" \
--session-token "$SESSION_TOKEN" \
--expiration "$EXPIRATION"
Record --expiration: fgai auth status then reports why a credential started
returning 401 instead of the command failing with no explanation.
Telling the failures apart¶
Status |
Meaning |
|---|---|
|
Wrong route — you sent a workload token to |
|
Either the token is not on the STS lane (missing the |
|
The body was spelled in camelCase. The wire is |
Revoke a live session at any time with POST /identity/v1/sts/revoke-session
({"access_key_id": "ASIA…"}); the credential stops working within about half a minute.
→ Manage workforce users¶
A common use of an assumed role is headless workforce (Identity Center) user
management: attach a managed policy granting identitycenter:User:* to a role,
assume it, and manage users in the organization Identity Center directory with the
temporary credentials — no interactive login. See the worked recipe in
Manage workforce (Identity Center) users headlessly (M2M).
See also¶
[FRN format](https://docs.flexgalaxy.ai/dev/dotid/frn-format/)
[Hierarchical access](https://docs.flexgalaxy.ai/dev/dotid/hierarchical-access/)