-
Notifications
You must be signed in to change notification settings - Fork 315
IAM API
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.
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-1explicitly. The gateway's own--regionflag does not change the IAM service's signing region — see Standalone IAM Setup. - The AWS CLI sends Query-API calls as
POSTwith 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.
- The clock-skew window is a fixed ±15 minutes in either direction. Outside it:
SignatureDoesNotMatchwithSignature expired: …orSignature not yet current: …. - Query-string (presigned) SigV4 is accepted, but unlike S3,
X-Amz-Expiresis neither required nor validated — only the ±15-minute freshness window applies. -
X-Amz-Content-Sha256: UNSIGNED-PAYLOADand theSTREAMING-*values are not honored. The service always hashes the real body, so declaring them yieldsSignatureDoesNotMatch. This affects hand-rolled clients and signing proxies only. - The
SignatureDoesNotMatchresponse carries noStringToSign/CanonicalRequest/HeadersNotSignedelements the way the S3 data plane does.--log-level unsafeis the only way to see that material — see Debugging. -
AssumeRoleWithWebIdentityis routed without the SigV4 middleware and takes no signature at all. -
x-amzn-RequestIdis 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 thevgw-iamaccess log.
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.
-
UserNameis optional onCreateAccessKey,UpdateAccessKey,DeleteAccessKeyandListAccessKeys. When entirely absent it resolves to the IAM user whose access key signed the request, matching AWS.GetAccessKeyLastUsedtakes noUserNameat all. - Only an entirely absent parameter is inferred. Sending
UserNamewith an empty value is stillValidationError(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 messageGetUserreturns for an omittedUserName. Either must passUserNameexplicitly. - The inferred scope is strictly the caller's own user. Naming another user's
AccessKeyIdonUpdateAccessKeyorDeleteAccessKeyreturnsNoSuchEntity(404)The Access Key with id <id> cannot be foundand leaves that key untouched, and an inferredListAccessKeysreturns only the caller's own keys. Because all four resolve their authorization resource through the same caller-or-named-user rule asGetUser, a policy scoped to the caller's own user ARN authorizes the omitted-UserNameform. - 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 | 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 |
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).
-
ServiceNameisiam,stsors3, naming the plane the key was last used on. A role's record has no service dimension at all —RoleLastUsedcarries onlyLastUsedDateandRegion. -
Regionisus-east-1for 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'sRoleLastUsedinstead of any key record, andAssumeRoleWithWebIdentityalone 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
RoleIdand its ARN to match what the session recorded atAssumeRoleWithWebIdentitytime. So a session of a deleted role authenticates andGetCallerIdentitystill 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
DeleteObjectsbatch 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→s3change 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 withus-east-1keeps 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: aGetRoleorGetAccessKeyLastUsedissued 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.
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.
-
Markerappears in a result only whenIsTruncatedis true. - Tag markers on users and roles are matched case-insensitively, so a marker of
ENVresumes after a tag stored asenv. On an OIDC provider the marker must match a key byte-for-byte.
Tagging is the subtlest corner of this API, because two different case rules and two different quotas are in play.
| 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.
| 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.
- Members are read as
Tags.member.<N>.Key/Tags.member.<N>.Value(andTagKeys.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
KeyandValuemust 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 isTags.member.1.Key, but the reported field istags.1.member.key. - A tag value may be the empty string; the
Valueparameter must still be present. - Duplicate keys within one
Tagsrequest are rejected. Duplicate keys within oneTagKeysrequest 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, andiam:ResourceTag/<key>/aws:ResourceTag/<key>when the role is the resource of an IAM action. See Policy Conditions.
| 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 — withAccessDenied(403) in the OIDC provider case, notValidationError. -
One partition. Only
awsis accepted in policy resource ARNs;aws-cn,aws-us-govand theaws-iso*partitions are rejected.
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.
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.
<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:
-
DeleteUserchecks 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 thenCannot delete entity, must delete access keys first. -
UpdateUserfetches the user before anything else, so a missing user reportsNoSuchEntityrather than a validation error on the new values. A rename rewrites the access-key index, so existing keys keep working under the new name. -
PutUserPolicyruns the policy grammar check only after the user is confirmed to exist: an invalid document for a nonexistent user reportsNoSuchEntity, notMalformedPolicyDocument. -
CreateRolevalidates the trust document's grammar before its 2048-byte size, so a document that is both malformed and oversized reportsMalformedPolicyDocument, notLimitExceeded/Cannot exceed quota for ACLSizePerRole: 2048. - The four role-policy actions report a missing
RoleNameorPolicyNameasValidationError, neverMissingParameter; a missing role policy reportsThe 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.
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.
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.
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.
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.
Home · Quickstart · Configuration · Operations · Testing · Developer Guide · GitHub · Discussions · Issues
Apache 2.0 · @versitysoftware · LinkedIn · X · Facebook · Instagram
- Home
- Key Features
- User Guide
- Getting Started
- Networking and Deployment
- Access Control / IAM
- Features
- Backends
- Compatibility
- Operations
- Metrics
- Admin APIs
- Logging
- S3 RDMA
- Developer Guide
- Articles