Skip to content

IAM API

niksis02 edited this page Sep 4, 2026 · 1 revision

This page covers the public IAM API exposed by the standalone IAM service (the versitygw iam process). It documents where this API departs from AWS IAM and STS — parameter shapes, response elements and CLI usage that behave the same way are AWS's documentation to give, not this page's. For an overview of what the service is, see Standalone IAM; for how to start it and wire it to an S3 gateway, see Standalone IAM Setup.

The service implements 41 actions: 39 IAM actions and 2 STS actions. Everything else in the AWS IAM and STS surface is absent — see What is not supported.


Endpoint basics

The public IAM API is an AWS Query API served on the address given by the global --port flag, which defaults to :7070. There is no separate STS port or hostname: both STS actions are served from this same endpoint and are told apart by their Version value.

Property Value
HTTP methods GET and POST only — anything else falls to the unknown-operation handler
Parameter location URL query string, or an application/x-www-form-urlencoded POST body
Precedence a parameter present in both the query string and the form body resolves to the query string value
Request path ignored — any path works as long as Action is present; / is the convention
Version (IAM actions) 2010-05-08, required and matched exactly
Version (STS actions) 2011-06-15, required and matched exactly
Authentication AWS Signature Version 4
SigV4 service name iam for the 39 IAM actions, sts for GetCallerIdentity
SigV4 region fixed us-east-1; any other credential scope is rejected with SignatureDoesNotMatch / Credential should be scoped to a valid region.
Response namespace https://iam.amazonaws.com/doc/2010-05-08/, or https://sts.amazonaws.com/doc/2011-06-15/ for the two STS actions
Content-Type of every response application/xml
Request ID header x-amzn-RequestId on every response

The standard aws iam and aws sts command groups work against this endpoint unmodified:

aws iam list-users --region us-east-1 --endpoint-url http://127.0.0.1:7070
  • The region must be us-east-1. If your shell already exports another region for S3 work, pass --region us-east-1 explicitly. The gateway's own --region flag does not change the IAM service's signing region — see Standalone IAM Setup.
  • The AWS CLI sends Query-API calls as POST with a form-encoded body, which sidesteps the 8 KiB request-head limit that bites long values placed in a GET query string. See Routing edge cases.
  • Root credentials bypass identity-policy evaluation entirely. Everything an IAM user does through this API is checked against that user's inline policies — see Identity Policies.

Signing

  • The clock-skew window is a fixed ±15 minutes in either direction. Outside it: SignatureDoesNotMatch with Signature expired: … or Signature not yet current: ….
  • Query-string (presigned) SigV4 is accepted, but unlike S3, X-Amz-Expires is neither required nor validated — only the ±15-minute freshness window applies.
  • X-Amz-Content-Sha256: UNSIGNED-PAYLOAD and the STREAMING-* values are not honored. The service always hashes the real body, so declaring them yields SignatureDoesNotMatch. This affects hand-rolled clients and signing proxies only.
  • The SignatureDoesNotMatch response carries no StringToSign / CanonicalRequest / HeadersNotSigned elements the way the S3 data plane does. --log-level unsafe is the only way to see that material — see Debugging.
  • AssumeRoleWithWebIdentity is routed without the SigV4 middleware and takes no signature at all.
  • x-amzn-RequestId is set on every response, including the root 302 redirect and the unknown-operation 404. An inbound client-supplied value is ignored and never echoed. The same UUID is the <RequestId> in the body and the join key into the vgw-iam access log.

Action index

Every action, with the iam: action string it needs and the resource ARN it is authorized against. That mapping is what AWS's documentation cannot tell you; the parameters and responses are AWS's own. Root callers skip authorization entirely.

Family Action Authorized against
User CRUD CreateUser iam:CreateUser on the ARN the request would build from UserName + Path (* when UserName is absent)
DeleteUser iam:DeleteUser on the stored user ARN
GetUser iam:GetUser on the named user's stored ARN, else the caller's own ARN
ListUsers iam:ListUsers on *
UpdateUser iam:UpdateUser on the current ARN, and again on the destination ARN when NewUserName or NewPath is supplied — both must Allow
User tagging TagUser iam:TagUser on the stored user ARN
UntagUser iam:UntagUser on the stored user ARN
ListUserTags iam:ListUserTags on the stored user ARN
Access keys CreateAccessKey iam:CreateAccessKey on the named user's stored ARN, else the caller's own ARN
UpdateAccessKey iam:UpdateAccessKey on the named user's stored ARN, else the caller's own ARN
DeleteAccessKey iam:DeleteAccessKey on the named user's stored ARN, else the caller's own ARN
GetAccessKeyLastUsed iam:GetAccessKeyLastUsed on the owning user's ARN, resolved from the key
ListAccessKeys iam:ListAccessKeys on the named user's stored ARN, else the caller's own ARN
User inline policies PutUserPolicy iam:PutUserPolicy on the stored user ARN
GetUserPolicy iam:GetUserPolicy on the stored user ARN
DeleteUserPolicy iam:DeleteUserPolicy on the stored user ARN
ListUserPolicies iam:ListUserPolicies on the stored user ARN
Role CRUD CreateRole iam:CreateRole on the ARN the request would build from RoleName + Path (* when RoleName is absent)
GetRole iam:GetRole on the stored role ARN
ListRoles iam:ListRoles on *
DeleteRole iam:DeleteRole on the stored role ARN
UpdateAssumeRolePolicy iam:UpdateAssumeRolePolicy on the stored role ARN
Role tagging TagRole iam:TagRole on the stored role ARN
UntagRole iam:UntagRole on the stored role ARN
ListRoleTags iam:ListRoleTags on the stored role ARN
Role inline policies PutRolePolicy iam:PutRolePolicy on the stored role ARN
GetRolePolicy iam:GetRolePolicy on the stored role ARN
DeleteRolePolicy iam:DeleteRolePolicy on the stored role ARN
ListRolePolicies iam:ListRolePolicies on the stored role ARN
OIDC providers CreateOpenIDConnectProvider iam:CreateOpenIDConnectProvider on the ARN built from the validated Url
GetOpenIDConnectProvider iam:GetOpenIDConnectProvider on the raw OpenIDConnectProviderArn parameter, as supplied
ListOpenIDConnectProviders iam:ListOpenIDConnectProviders on *
DeleteOpenIDConnectProvider iam:DeleteOpenIDConnectProvider on the raw ARN parameter
AddClientIDToOpenIDConnectProvider iam:AddClientIDToOpenIDConnectProvider on the raw ARN parameter
RemoveClientIDFromOpenIDConnectProvider iam:RemoveClientIDFromOpenIDConnectProvider on the raw ARN parameter
UpdateOpenIDConnectProviderThumbprint iam:UpdateOpenIDConnectProviderThumbprint on the raw ARN parameter
OIDC provider tagging TagOpenIDConnectProvider iam:TagOpenIDConnectProvider on the raw ARN parameter
UntagOpenIDConnectProvider iam:UntagOpenIDConnectProvider on the raw ARN parameter
ListOpenIDConnectProviderTags iam:ListOpenIDConnectProviderTags on the raw ARN parameter
STS AssumeRoleWithWebIdentity Nothing — routed without the SigV4 middleware and without the identity-policy middleware; access is governed entirely by the role's trust policy
GetCallerIdentity Authentication only (credential scope sts); no identity-policy check

ListUsers, ListRoles and ListOpenIDConnectProviders authorize against * and have no resource-level permissions: a policy that allows iam:ListUsers on a narrower ARN never matches, and one that allows it on * returns every user regardless of path.

The OIDC provider and STS actions are documented end to end — URL rules, thumbprints, client IDs, trust policies, session credentials and their failure modes — in STS & Web Identity. OIDC is the only federation provider type here.


Access keys

  • UserName is optional on CreateAccessKey, UpdateAccessKey, DeleteAccessKey and ListAccessKeys. When entirely absent it resolves to the IAM user whose access key signed the request, matching AWS. GetAccessKeyLastUsed takes no UserName at all.
  • Only an entirely absent parameter is inferred. Sending UserName with an empty value is still ValidationError (400) The specified value for userName is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_-.
  • Inference needs a caller that is an IAM user. An assumed-role session, and the gateway's root credential — a configured key rather than a stored IAM user, owning no access keys this API can manage — both get ValidationError (400) Must specify userName when calling with non-User credentials, the same message GetUser returns for an omitted UserName. Either must pass UserName explicitly.
  • The inferred scope is strictly the caller's own user. Naming another user's AccessKeyId on UpdateAccessKey or DeleteAccessKey returns NoSuchEntity (404) The Access Key with id <id> cannot be found and leaves that key untouched, and an inferred ListAccessKeys returns only the caller's own keys. Because all four resolve their authorization resource through the same caller-or-named-user rule as GetUser, a policy scoped to the caller's own user ARN authorizes the omitted-UserName form.
  • Under standalone IAM, ACL grantees on the S3 gateway are access key IDs, so deleting or rotating a key orphans every ACL grant that named it — see Standalone IAM. Bucket-policy principals are ARNs and survive key rotation, but deleting the user or role a policy names makes that ARN stop resolving, so the same document can no longer be re-stored by PutBucketPolicy — see Bucket Policies.

Identifier and ARN formats

Identifier Format
UserId / RoleId AIDA / AROA plus 17 characters from the base32 alphabet ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
Access key id AKIA + 17 for a long-term key, ASIA + 17 for a session key
Secret access key 40 characters, standard base64 of 30 random bytes — may contain + and /, never carries = padding
User and role ARNs arn:aws:iam::000000000000:user<path><userName>, arn:aws:iam::000000000000:role<path><roleName>
OIDC provider ARN arn:aws:iam::000000000000:oidc-provider/<url-without-scheme>
CreateDate UTC, truncated to the second

Last-used tracking

GetAccessKeyLastUsed and a role's RoleLastUsed share one mechanism: the record is written as identity-policy evaluation is entered, on both the IAM/STS plane (as the request authenticates) and the S3 plane (as a gateway backed by this IAM service authorizes the request).

  • ServiceName is iam, sts or s3, naming the plane the key was last used on. A role's record has no service dimension at all — RoleLastUsed carries only LastUsedDate and Region.
  • Region is us-east-1 for IAM/STS calls and the gateway's configured region for S3 requests — not the region the S3 request was signed for, because a request's credential scope is unverified at the point the record is written. Real AWS reports the region each request was actually signed for.
  • Root credentials never update key last-used (root is not an IAM user). A session (ASIA…) credential updates its role's RoleLastUsed instead of any key record, and AssumeRoleWithWebIdentity alone is not a use of the role — only a request authenticated as one of its sessions counts.
  • A use is attributed only while the session's role is still the same role the session was minted against: the service re-loads the role by name and requires both its RoleId and its ARN to match what the session recorded at AssumeRoleWithWebIdentity time. So a session of a deleted role authenticates and GetCallerIdentity still answers, but records no use anywhere; and a role deleted and recreated under the same name reports as never used, however many pre-existing sessions of the old role keep making requests. This is the same gate that governs identity-policy inheritance (STS & Web Identity), and it applies identically on both planes.
  • A request denied by an identity policy is still recorded, on either plane, because the record is written before the decision is computed. Real AWS counts denied requests too. Two S3 cases are not recorded, because neither reaches identity-policy evaluation: an anonymous request to a public bucket — the public-access path is never entered for a request carrying credentials, so an authenticated caller's request against a public bucket is recorded — and a request denied by a bucket policy, which for a DeleteObjects batch holds only when the policy denies every key in the group, since a batch with any surviving key still makes the round trip. Root and admin-role callers never reach it either. Real AWS records both cases.
  • Repeat use is coalesced to at most one write a minute. For an access key the coalescing is keyed on service and region, so an iam→s3 change writes through immediately; a role has no service dimension, so only a region change does — a role used on the IAM/STS plane and then, within the same minute, against an S3 gateway configured with us-east-1 keeps the earlier timestamp.
  • Under Vault-backed storage (versitygw iam --vault-*) both writes are dispatched to a detached background goroutine with a 5-second timeout and report success to the request immediately, so the record is eventually consistent: a GetRole or GetAccessKeyLastUsed issued straight after the triggering request can still report the previous value, or nothing at all if the background write failed. The file-backed (--dir) store writes synchronously under the store lock before the request completes, so read-after-write holds there.

A never-used key renders ServiceName and Region as the literal string N/A and omits LastUsedDate entirely. A never-used role carries the empty <RoleLastUsed></RoleLastUsed> element, which the AWS CLI renders as "RoleLastUsed": {}; ListRoles omits the element entirely, used or not.


Pagination

Eight actions accept MaxItems and Marker. ListOpenIDConnectProviders is the one list action with neither, and its response has no IsTruncated.

Action Sorted by
ListUsers UserName, ascending
ListRoles RoleName, ascending
ListAccessKeys AccessKeyId, ascending
ListUserPolicies, ListRolePolicies policy name, ascending
ListUserTags, ListRoleTags, ListOpenIDConnectProviderTags tag key, ascending
ListOpenIDConnectProviders ARN, ascending — unpaginated

MaxItems defaults to 100 with a ceiling of 1000; an out-of-range value reports ValidationError naming the bound. A non-integer MaxItems returns MalformedInput (400) with no <Message> element.

Marker rules:

  • The marker is not opaque. It is literally the last item's sort key from the previous page: a UserName, RoleName, AccessKeyId, policy name or tag key. This diverges from AWS, whose markers are opaque tokens.
  • The marker is never validated. A marker naming no element yields an empty page, not an error.
  • Marker appears in a result only when IsTruncated is true.
  • Tag markers on users and roles are matched case-insensitively, so a marker of ENV resumes after a tag stored as env. On an OIDC provider the marker must match a key byte-for-byte.

Tagging semantics

Tagging is the subtlest corner of this API, because two different case rules and two different quotas are in play.

Case sensitivity

Resource Key comparison Duplicate-key error message
User case-insensitive Duplicate tag keys found. Please note that Tag keys are case insensitive.
Role case-insensitive Duplicate tag keys found. Please note that Tag keys are case insensitive.
OIDC provider exact Duplicate tag keys found.

Keys are always stored case-preserving; what changes is how they are matched. On a user or role, env and ENV are the same tag: tagging ENV=x on a resource that already has env=y replaces the existing tag in place — keeping its position but adopting the new casing — and UntagUser --tag-keys ENV removes a tag stored as env. On an OIDC provider they are two independent tags, both counting separately toward the 50-tag cap.

The two quotas

Quota Scope Error
MaxTagMembersPerRequest = 50 members in one request ValidationError (400) — Value at 'tags' failed to satisfy constraint: Member must have length less than or equal to 50 for a Tags request; the same sentence against 'tagKeys' for a TagKeys request (UntagUser, UntagRole, UntagOpenIDConnectProvider)
MaxTagsPerResource = 50 tags stored on one resource LimitExceeded (409) — The number of tags has reached the maximum limit.

The per-resource cap is checked against the merged result, not the request, so replacing an existing tag on a resource already holding 50 tags succeeds — the incoming key takes over the existing slot and the total stays at 50.

Parsing rules

  • Members are read as Tags.member.<N>.Key / Tags.member.<N>.Value (and TagKeys.member.<N>) starting at N = 1 and incrementing. The scan stops at the first index where nothing is present, so a gap in the numbering silently truncates the list.
  • Both Key and Value must be present for a tag member. Half a member is an error: Value at 'tags.<N>.member.value' failed to satisfy constraint: Member must not be null. Note the naming asymmetry — the request parameter is Tags.member.1.Key, but the reported field is tags.1.member.key.
  • A tag value may be the empty string; the Value parameter must still be present.
  • Duplicate keys within one Tags request are rejected. Duplicate keys within one TagKeys request are accepted, because removal is idempotent.
  • Tag validation runs before the resource lookup, so a malformed tag on a nonexistent user reports the tag error rather than NoSuchEntity.
  • On UntagUser, every key-shape failure collapses into a single combined message rather than a per-index one.
  • Role tags and user tags are entirely independent namespaces: tagging a role has no effect on a user of the same name.
  • Role tags feed the aws:PrincipalTag/<key> condition keys for sessions created from that role, and iam:ResourceTag/<key> / aws:ResourceTag/<key> when the role is the resource of an IAM action. See Policy Conditions.

Quotas and limits

Limit Value
User name on CreateUser and NewUserName 64 characters
User name on every lookup, and every PolicyName 128 characters
Role name (creation and lookup) 64 characters
Inline policy bytes per user, aggregate 2048
Inline policy bytes per role, aggregate 10240
Live sessions per role 1000, then Throttling (400) Rate exceeded.
Request head (headers + GET query string) 8 KiB
Request body 4 MiB (fiber's default)
Response XML body 4 MiB

Every other bound matches AWS's published IAM and STS quotas. The inline-policy quotas are an aggregate across all of an entity's inline policies, with the policy being replaced excluded from the total; Identity Policies explains that arithmetic and how to claw back space.

The STS DurationSeconds default is always 3600, regardless of the role's own MaxSessionDuration. Raising a role's MaxSessionDuration raises the ceiling, not the default — a caller that wants a longer session must ask for it explicitly.

Two further structural limits are not numeric but behave like quotas:

  • One account. The account ID is hard-coded as 000000000000 (twelve zeros). Any ARN naming another account is rejected — with AccessDenied (403) in the OIDC provider case, not ValidationError.
  • One partition. Only aws is accepted in policy resource ARNs; aws-cn, aws-us-gov and the aws-iso* partitions are rejected.

What is not supported

The action map holds exactly 41 entries. Everything below is absent from it, and from the type and storage layers too — these are not stubs, they are features that do not exist.

Area Missing actions
Managed policies CreatePolicy, AttachUserPolicy, ListAttachedRolePolicies and every other managed-policy, policy-version and attachment action
Groups CreateGroup, AddUserToGroup, PutGroupPolicy and the rest of the group surface
Permission boundaries PutUserPermissionsBoundary, PutRolePermissionsBoundary and their delete forms
Instance profiles CreateInstanceProfile, AddRoleToInstanceProfile, ListInstanceProfiles and the rest, tagging included
SAML providers CreateSAMLProvider, GetSAMLProvider, ListSAMLProviders and the rest, tagging included
MFA devices CreateVirtualMFADevice, EnableMFADevice, ListMFADevices and the rest
Service-linked roles CreateServiceLinkedRole, DeleteServiceLinkedRole, GetServiceLinkedRoleDeletionStatus
Login profiles and passwords CreateLoginProfile, ChangePassword, UpdateAccountPasswordPolicy and the rest
Account settings and reports ListAccountAliases, GetAccountSummary, GetAccountAuthorizationDetails, GetCredentialReport, SimulatePrincipalPolicy, SimulateCustomPolicy and the rest
Certificates and service credentials UploadServerCertificate, UploadSSHPublicKey, CreateServiceSpecificCredential and their siblings
Role mutation There is no UpdateRole and no UpdateRoleDescription — a role's Description and MaxSessionDuration are immutable after creation
STS AssumeRole, AssumeRoleWithSAML, AssumeRoot, GetSessionToken, GetFederationToken, GetAccessKeyInfo, DecodeAuthorizationMessage

AssumeRoleWithWebIdentity is the only way to obtain temporary credentials, and GetCallerIdentity is the only other STS action.

What the caller actually sees

The two families fail in visibly different ways, and the STS one is confusing enough to be worth memorizing.

An unsupported IAM action fails the action lookup and reports it plainly — InvalidAction / Could not find operation CreatePolicy for version 2010-05-08. ListGroups, AttachUserPolicy and every other absent IAM action produce the same shape. The response is rendered under the AWS Fault namespace http://webservices.amazon.com/AWSFault/2005-15-09 rather than the IAM namespace, which some strict XML clients will notice.

An unsupported STS action fails much earlier, and the error looks unrelated:

$ aws sts get-session-token --endpoint-url http://127.0.0.1:7070
An error occurred (SignatureDoesNotMatch) when calling the GetSessionToken operation:
Credential should be scoped to correct service: 'iam'.

Important

SignatureDoesNotMatch here does not mean your credentials are wrong. Only AssumeRoleWithWebIdentity and GetCallerIdentity are registered as STS actions; every other action name — including every real STS action — falls through to the IAM pipeline, which authenticates with credential-scope service iam before the action is ever looked up. Your SDK signed the request with scope sts, so the scope check fails first and you never reach the InvalidAction path. HTTP status is 400.

InvalidAction for an STS action only appears if the request happens to be signed with scope iam, in which case Version=2011-06-15 fails the version check instead: Could not find operation AssumeRole for version 2011-06-15.


Behavioral quirks

<Tags></Tags> is always emitted. The Tags element is present on every user and role response — CreateUser, GetUser, ListUsers, CreateRole, GetRole, ListRoles — even for an entity with no tags at all, so aws iam list-users always shows "Tags": []. The same holds for ClientIDList, ThumbprintList and Tags on the OIDC provider responses.

<member><Path>/</Path><UserName>bob</UserName><UserId>AIDAQIMNSZ65T75KIWTEQ</UserId><Arn>arn:aws:iam::000000000000:user/bob</Arn><CreateDate>2026-08-28T11:44:02Z</CreateDate><Tags></Tags></member>

Some deletes are idempotent and some are not. UntagUser, UntagRole, UntagOpenIDConnectProvider, AddClientIDToOpenIDConnectProvider and RemoveClientIDFromOpenIDConnectProvider all succeed as no-ops. DeleteAccessKey, DeleteUserPolicy, DeleteRolePolicy and DeleteOpenIDConnectProvider all return NoSuchEntity on a second call — and for DeleteOpenIDConnectProvider that contradicts AWS's own published behavior.

InvalidClientTokenId is deliberately vague. An unknown access key, an unknown session and a wrong session token all collapse to the same 403. Distinguishing them requires --log-level debug on the service — see Debugging.

Which error you see depends on validation order. Several actions check things in an order that decides which of two plausible errors is reported:

  • DeleteUser checks inline policies first, then access keys, so clearing a user that has both takes two rounds: DeleteConflict: Cannot delete entity, must delete policies first. and then Cannot delete entity, must delete access keys first.
  • UpdateUser fetches the user before anything else, so a missing user reports NoSuchEntity rather than a validation error on the new values. A rename rewrites the access-key index, so existing keys keep working under the new name.
  • PutUserPolicy runs the policy grammar check only after the user is confirmed to exist: an invalid document for a nonexistent user reports NoSuchEntity, not MalformedPolicyDocument.
  • CreateRole validates the trust document's grammar before its 2048-byte size, so a document that is both malformed and oversized reports MalformedPolicyDocument, not LimitExceeded / Cannot exceed quota for ACLSizePerRole: 2048.
  • The four role-policy actions report a missing RoleName or PolicyName as ValidationError, never MissingParameter; a missing role policy reports The role policy with name <policyName> cannot be found.

Names are case-insensitively unique but case-preserving. alice and ALICE cannot both exist, and the same holds for roles; the casing you created with is the casing you get back. A case-only self-rename (alice → ALICE) is allowed. The role EntityAlreadyExists message echoes the casing you submitted, not the stored one, and GetUserPolicy likewise echoes the UserName casing you supplied rather than the canonical stored one.

ListRoles omits RoleLastUsed. It includes AssumeRolePolicyDocument (percent-encoded) but drops RoleLastUsed from every entry — a list/get asymmetry. UpdateAssumeRolePolicy is the only way to mutate an existing role; it is a full replacement, never a merge, and confirms the role exists before parsing the grammar.

DeleteRole does not check for active sessions. A role with live assumed-role sessions can be deleted. Those sessions keep authenticating — GetCallerIdentity still answers for them — but they lose every permission the role granted.


Routing edge cases

Unknown paths return 404. Any request that is not a GET or POST carrying an Action parameter, and is not the bare root path, falls to the unknown-operation handler: HTTP 404 with the literal body <UnknownOperationException/>, no XML declaration and no namespace. This is what a PUT or DELETE gets, and what a HEAD or POST on the health-check path gets — the health endpoint is GET-only.

/ with no Action redirects. A request to the bare root path with no Action parameter returns HTTP 302 with Location: https://www.versity.com/products/versitygw/ and an empty body. It still carries an x-amzn-RequestId.

The Version check is strict and runs first. The version is compared before the action is looked up, so a valid action with the wrong version and an unknown action both produce InvalidAction. A missing Version renders the literal sentinel in the message: Could not find operation ListUsers for version NO_VERSION_SPECIFIED.

The request head is capped at 8 KiB — and an oversized one fails badly. The 8 KiB ReadBufferSize covers headers plus the GET query string. The IAM service's error handler has no branch for the "request header fields too large" condition, so an oversized head falls through to a generic HTTP 500 InternalFailure. The server prints [INTERNAL ERROR]: Request Header Fields Too Large to stderr and — because the rejection happens before routing — no line appears in the vgw-iam access log at all. The request is invisible in the request log.

Warning

Send long values as POST form fields, never in a GET query string. A WebIdentityToken (up to 20000 characters) or a large PolicyDocument will blow the 8 KiB head limit and surface as an unexplained 500. The AWS CLI and the AWS SDKs already POST Query-API calls with a form-encoded body, so this only affects hand-built curl requests and proxies.

The request body is capped at 4 MiB, fiber's default. The response XML body is capped at 4 MiB as well; exceeding it logs XML encoded body len … exceeds max len … and returns InternalFailure (500).

A malformed Content-Length is a bare 400. It is handled before the normal error path and returns HTTP 400 with no body at all, plus a [DEBUG]: failed to parse Content-Length line on the server.

Concurrency limiting looks like throttling. The --max-requests limiter (default 100000) is a limit on in-flight requests, not a rate. When it is saturated, excess requests get Throttling / HTTP 400 / Rate exceeded. immediately, with no Retry-After and no backoff hint.


Error catalog

Every error code the IAM service can return, with its HTTP status.

Code HTTP When it occurs
InvalidAction 400 Unknown action name, or a Version that does not match the action's API version. Rendered under the AWS Fault namespace, not the IAM one.
ValidationError 400 Length, charset, enum, range and missing-value failures on request parameters — the workhorse error. Also Must specify userName when calling with non-User credentials, returned by GetUser and the four access-key actions when the caller has no IAM user of its own.
MissingParameter 400 UserName on DeleteUser/UpdateUser; RoleName on GetRole/DeleteRole; AccessKeyId; Status. Everything else uses ValidationError.
InvalidInput 400 Duplicate tag keys; thumbprint-list shape; some OIDC provider URL failures; the unsupported STS parameters PolicyArns and ProviderId.
MalformedInput 400 A non-integer MaxItems, MaxSessionDuration or DurationSeconds. This is the only error whose response carries no <Message> element — the element is omitted when empty.
MalformedPolicyDocument 400 An identity policy or trust policy that fails grammar validation.
InvalidRequest 400 Content-Length must be a valid integer.
Throttling 400 The concurrency limiter rejected the request, or the target role already has 1000 live sessions.
IncompleteSignature 400 A malformed SigV4 Authorization header or presign query parameter set; SigV2.
InvalidIdentityToken 400 AssumeRoleWithWebIdentity token structure, claim, audience, signature or JWKS-fetch failures.
ExpiredTokenException 400 A web identity token past its exp plus the 5-minute leeway.
OpenIdIdpCommunicationError 400 Thumbprint auto-fetch could not reach the provider — Could not connect to https://<url>.
SignatureDoesNotMatch 400 or 403 400 for a wrong credential-scope service (including every unsupported STS action) or a scope date that disagrees with the request date.
403 for an actual signature mismatch, a region other than us-east-1, a bad terminator, an unsigned Host header, or clock skew beyond ±15 minutes.
MissingAuthenticationToken 403 No authentication, or an unsupported authentication scheme, on an action that requires it.
InvalidClientTokenId 403 Unknown or inactive access key, unknown session, or wrong session token — deliberately indistinguishable from one another.
AccessDenied 403 Identity-policy denial; an OIDC provider ARN naming another account; a trust policy refusing sts:AssumeRoleWithWebIdentity.
NoSuchEntity 404 The user, role, access key, inline policy or OIDC provider does not exist.
EntityAlreadyExists 409 A user name, role name (both case-insensitive) or provider URL is already in use.
DeleteConflict 409 DeleteUser with inline policies or access keys still attached; DeleteRole with inline policies still attached.
LimitExceeded 409 Any quota: AccessKeysPerUser, tags per resource, inline policy size, ACLSizePerRole, ClientIdsPerOpenIdConnectProvider, OpenIDConnectProvidersPerAccount.
ConcurrentModificationException 409 Vault storage backend only, after three failed compare-and-swap retries. Retry the request.
InternalFailure 500 An unexpected server error, a response body over 4 MiB, or an oversized request head.

InternalFailure is the only Receiver-typed error; everything else is Sender.

Denial message shapes

AccessDenied: User: arn:aws:iam::000000000000:user/alice is not authorized to perform:
iam:CreateUser because no identity-based policy allows the iam:CreateUser action
AccessDenied: User: arn:aws:iam::000000000000:root is not authorized to perform this
action on resource: arn:aws:iam::123456789012:oidc-provider/accounts.example.com

The first is an identity-policy denial; the caller ARN is the assumed-role STS ARN for a session and the user ARN for a long-term user. The second is the single-account guard — the account segment of an OIDC provider ARN must be 000000000000.

Quota message shapes

LimitExceeded: Cannot exceed quota for AccessKeysPerUser: 2
LimitExceeded: Cannot exceed quota for ACLSizePerRole: 2048
LimitExceeded: Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100
LimitExceeded: Cannot exceed quota for OpenIDConnectProvidersPerAccount: 100
LimitExceeded: Maximum policy size of 2048 bytes exceeded for user alice
LimitExceeded: Maximum policy size of 10240 bytes exceeded for role reader
LimitExceeded: The number of tags has reached the maximum limit.

See Also

Clone this wiki locally