Rate Limiting

How verification, creation, and management routes are throttled in the IAM API token system.

API token rate limiting combines shared guards with operation-specific limiter buckets. The service applies these limiters in the public verification route and in the authenticated creation and management routes.

This page documents the apiTokens and rate_limiters.apiTokensLimiters configuration only. The generic limiter engine, strike cache, and helper utilities are documented on the Rate Limiting page.

All API token limiter responses come from guard() and consumeOrReject(). That means API token rate limiting always fails with 429 Too Many Requests, not with the 400, 401, or 403 responses documented on the endpoint pages.
This page only documents limiter behavior. Shared authentication failures, bad request payloads, and XSS ban responses still belong to the endpoint-specific docs.

API token limiter map

The API token system uses eight limiter objects plus one verification switch. Seven buckets are operation or verification limiters, and one bucket is the shared generalUnionLimiter that sits in front of most API token routes.

FlowRouteLimiter objectsSuccess reset behaviorMentioned on
VerificationGET /api/public/verifyconsumptionRateLimiter and optional generalUnionLimiterconsumptionRateLimiter is reset on success for ${req.ip}_verify, apiKey, and providedIpAddress. The optional union limiter is not reset.Verifying Tokens
Token creationPOST /api/manage/new-tokengeneralUnionLimiter, operationRateLimits.newTokenCreationLimiterresetApiUnionLimiters(req.ip!) resets only the union limiter.Creating Tokens
Token listingGET /api/manage/list-metadatageneralUnionLimiter onlyNo limiter is reset on success.Token Listing
RevocationPOST /api/manage/revokegeneralUnionLimiter, operationRateLimits.revokeTokensLimiterresetApiUnionLimiters(req.ip!) resets only the union limiter.Revocation
MetadataPOST /api/manage/metadatageneralUnionLimiter, operationRateLimits.getMetadataTokenLimiterresetApiUnionLimiters(req.ip!) resets only the union limiter.Extensive Metadata
RotationPOST /api/manage/rotategeneralUnionLimiter, operationRateLimits.rotationRateLimiterresetApiUnionLimiters(req.ip!) resets only the union limiter.Rotation
IP restriction updatesPOST /api/manage/ip-restriction-updategeneralUnionLimiter, operationRateLimits.ipRestrictionUpdateresetApiUnionLimiters(req.ip!) resets only the union limiter.IP Restriction Updates
Privilege updatesPOST /api/manage/privilege-updategeneralUnionLimiter, operationRateLimits.privilegeUpdateresetApiUnionLimiters(req.ip!) resets only the union limiter.Privileges
Token listing is the only management action without a dedicated operationRateLimits bucket. The controller goes straight from generalUnionLimiter to getAllValidTokensList(userId).

Failed rate-limit responses

The first limiter rejection comes from consumeOrReject(). It catches the underlying limiter error, computes retry as Math.ceil(err.msBeforeNext / 1000), and sends the standard API token rate-limit response:

response.http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: 60

{
    "error": "Too many requests",
    "retry": 60
}

The API token controllers then pass that same key back through guard() on later requests. guard() stores consecutive limiter failures in an action-specific cache and escalates the key into limiter.block() when the strike threshold is reached.

For API token routes, generalUnionLimiter and consumptionRateLimiter use maxBans = 1. That means the first actual limiter rejection adds the key to the permanent block cache after the numeric 429 response is sent. The operation-specific limiters use maxBans = 2, so the second consecutive rejection creates the permanent block.

After that cache entry exists, guard() rejects the request before consuming new points and reuses the cached expiry marker. Because the API token controllers do not pass a custom seconds value to guard(), that cached marker becomes permanent:

response.http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: permanent

{
    "error": "Too many requests",
    "retry": "permanent"
}

This is why API token rate-limit failures stay in the 429 family even after the key has been escalated into a long-lived block.

How each limiter is used

Each limiter has a different job in the API token subsystem. Some protect only failed verification attempts, some protect a single dashboard action, and the union limiter acts as the shared front gate.

rate_limiters.apiTokensLimiters.consumptionRateLimiter

This limiter only appears in verifyApiTokenController. The controller consumes it when the verification request is already failing, including the missing x-api-key branch, the missing req.ip branch, invalid privilege schema input, and any failed verifyApiKey result.

On successful verification, the controller resets this limiter for three keys: ${req.ip}_verify, the provided raw apiKey, and the resolved providedIpAddress. That makes it a failure-only limiter for Verifying Tokens.

rate_limiters.apiTokensLimiters.generalUnionLimiter

This limiter is the shared burst-plus-slow gate for API token routes. It is always used for token creation and token management, and it is only used for verification when apiTokens.rateLimitOnSuccessfulRequest is true.

Most API token routes key this limiter by req.ip!. Token listing uses the dedicated key ${req.ip}_list-metadata, and verification uses ${req.ip}_verify. Successful creation, revocation, metadata, rotation, IP updates, and privilege updates call resetApiUnionLimiters(req.ip!), but token listing and verification do not reset this limiter.

See Creating Tokens, Verifying Tokens, Revocation, Rotation, IP Restriction Updates, Privileges, Extensive Metadata, and Token Listing.

resetApiUnionLimiters() only deletes the burstLimiter and slowLimiter keys inside generalUnionLimiter. It does not reset the operation-specific limiters or the verification failure limiter.

rate_limiters.apiTokensLimiters.operationRateLimits.newTokenCreationLimiter

This limiter protects POST /api/manage/new-token with the composite key ${req.ip}_${userId}. It is the dedicated quota bucket for token creation and is not reset on success, so it always relies on its own duration window.

See Creating Tokens.

rate_limiters.apiTokensLimiters.operationRateLimits.revokeTokensLimiter

This limiter protects POST /api/manage/revoke with the composite key ${req.ip}_${userId}. It only applies after schema validation succeeds and the request has already passed generalUnionLimiter.

See Revocation.

rate_limiters.apiTokensLimiters.operationRateLimits.getMetadataTokenLimiter

This limiter protects POST /api/manage/metadata with the composite key ${req.ip}_${userId}. It is the dedicated high-throughput bucket for metadata reads, which is why its defaults are much looser than the other management operation limiters.

See Extensive Metadata.

rate_limiters.apiTokensLimiters.operationRateLimits.rotationRateLimiter

This limiter protects POST /api/manage/rotate with the composite key ${req.ip}_${userId}. It sits in front of a destructive replace action, so its default block duration is longer than the IP and privilege update buckets.

See Rotation.

rate_limiters.apiTokensLimiters.operationRateLimits.ipRestrictionUpdate

This limiter protects POST /api/manage/ip-restriction-update with the composite key ${req.ip}_${userId}. It is dedicated to network whitelist changes and uses a shorter block window than revoke or rotate.

See IP Restriction Updates.

rate_limiters.apiTokensLimiters.operationRateLimits.privilegeUpdate

This limiter protects POST /api/manage/privilege-update with the composite key ${req.ip}_${userId}. It is dedicated to privilege changes and uses the same default timings as the IP restriction update bucket.

See Privileges.

Configuration reference

All API token limiter settings live under apiTokens and rate_limiters.apiTokensLimiters in the object passed to configuration().

Verification switch

Verification has one related switch outside the rate_limiters object:

Verification success switch

  • Option: apiTokens.rateLimitOnSuccessfulRequest
  • Description: Enables generalUnionLimiter in verifyApiTokenController before any token validation happens. When it stays false, verification only uses consumptionRateLimiter.
  • Default: false

Rate limiter defaults

These are the built-in defaults from buildLimiters() when you do not provide custom limiter values in configuration.

Verification failure limiter

  • Limiter name: rate_limiters.apiTokensLimiters.consumptionRateLimiter
  • Description: Verification failure limiter. It consumes points only in failed verification branches.
  • Default behavior: Allows 10 failed verification attempts per minute, then blocks for 1 hour.

Shared union limiter

  • Limiter name: rate_limiters.apiTokensLimiters.generalUnionLimiter
  • Description: Shared burst-plus-slow front gate for creation and management, and optional gate for successful verification.
  • Default behavior: The burst limiter allows 1 request per second, then blocks for 15 minutes. The slow limiter allows 50 requests per minute, then blocks for 1 hour.

Token creation limiter

  • Limiter name: rate_limiters.apiTokensLimiters.operationRateLimits.newTokenCreationLimiter
  • Description: Dedicated limiter for POST /api/manage/new-token.
  • Default behavior: Allows 5 token creations per 10 minutes, then blocks for 1 hour.

Revocation limiter

  • Limiter name: rate_limiters.apiTokensLimiters.operationRateLimits.revokeTokensLimiter
  • Description: Dedicated limiter for POST /api/manage/revoke.
  • Default behavior: Allows 5 revocations per 10 minutes, then blocks for 2 hours.

Metadata limiter

  • Limiter name: rate_limiters.apiTokensLimiters.operationRateLimits.getMetadataTokenLimiter
  • Description: Dedicated limiter for POST /api/manage/metadata.
  • Default behavior: Allows 20 metadata requests per 2 seconds, then blocks for 30 minutes.

Rotation limiter

  • Limiter name: rate_limiters.apiTokensLimiters.operationRateLimits.rotationRateLimiter
  • Description: Dedicated limiter for POST /api/manage/rotate.
  • Default behavior: Allows 5 rotations per 10 minutes, then blocks for 2 hours.

IP restriction update limiter

  • Limiter name: rate_limiters.apiTokensLimiters.operationRateLimits.ipRestrictionUpdate
  • Description: Dedicated limiter for POST /api/manage/ip-restriction-update.
  • Default behavior: Allows 5 IP restriction updates per 10 minutes, then blocks for 30 minutes.

Privilege update limiter

  • Limiter name: rate_limiters.apiTokensLimiters.operationRateLimits.privilegeUpdate
  • Description: Dedicated limiter for POST /api/manage/privilege-update.
  • Default behavior: Allows 5 privilege updates per 10 minutes, then blocks for 30 minutes.
Logo