I tried out the rate limiting feature added to AgentCore Gateway

I tried out the rate limiting feature added to AgentCore Gateway

I tried out the rate limiting feature added to Amazon Bedrock AgentCore Gateway!
2026.08.13

This page has been translated by machine translation. View original

Introduction

Hello, I'm Kamino from the Consulting Division, a huge fan of cheese naan.

On August 6, 2026, new features including Rate Limiting were added to Amazon Bedrock AgentCore Gateway!

https://aws.amazon.com/jp/about-aws/whats-new/2026/08/temporal-policies-agentcore/

It feels like a feature truly worthy of the Gateway name has been added! Let's give it a try right away!

Prerequisites

The environment used for verification is as follows.

Item Details
Region ap-northeast-1 (Tokyo)
boto3 / botocore 1.43.67
Python / Package Manager 3.12 / uv
Gateway AgentCore Gateway (MCP, AWS_IAM authentication)
Target Inference Target (Bedrock Mantle connector)

First, let me briefly introduce what the rate limiting feature does.

Rate Limiting

This is a feature that groups traffic passing through the Gateway by units called "dimensions" and allows you to set an allowed rate for each group.
Here is a rough overview of its characteristics.

  • The grouping method is determined by dimension keys.
    • In addition to target name (targetName) and model ID (qualifiedModelId), you can also specify JWT claims like $.context.jwt.sub to split by calling user.
  • Rates for each group are determined by entries.
    • In addition to the number of requests, you can also limit token count and concurrent connections for inference targets.
  • Setting the rate to 0 allows you to block specific callers.
  • When multiple rate limits are configured, they are evaluated with AND logic, and only requests that pass all of them are executed.

The intended use cases seem to be backend protection, per-user quotas, and inference cost caps (TPM control). Indeed, it seems quite useful for inference targets connected to LLMs!

The official documentation is here.

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-rate-limits.html

Now that we have an overview of what it is, let's try it out!

Preparing the Verification Gateway

For verification, I'll create a Gateway with one Inference Target using the Bedrock Mantle connector. This is a feature that allows the Gateway to serve as an entry point for LLM APIs, and it's introduced in detail in the article below.

https://dev.classmethod.jp/articles/agentcore-gateway-inference-target/

Creating the IAM Role

First, create the service role for the Gateway. Allow bedrock-agentcore.amazonaws.com in the trust policy, and attach Bedrock invocation and Mantle permissions to the permission policy.

create_iam_role.sh
# Trust policy
aws iam create-role \
  --role-name rate-limit-demo-gateway-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
      "Action": "sts:AssumeRole",
      "Condition": {"StringEquals": {"aws:SourceAccount": "<ACCOUNT_ID>"}}
    }]
  }'

# Permission policy
aws iam put-role-policy \
  --role-name rate-limit-demo-gateway-role \
  --policy-name inference-permissions \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "bedrock:InvokeModel",
          "bedrock:InvokeModelWithResponseStream"
        ],
        "Resource": "*"
      },
      {
        "Effect": "Allow",
        "Action": "bedrock-mantle:*",
        "Resource": "arn:aws:bedrock-mantle:ap-northeast-1:<ACCOUNT_ID>:project/*"
      }
    ]
  }'

Creating the Gateway and Inference Target

Next, create the Gateway and the inference target.

setup.py
import boto3

client = boto3.client("bedrock-agentcore-control", region_name="ap-northeast-1")

# Create Gateway
gateway = client.create_gateway(
    name="rate-limit-demo-gateway",
    roleArn="arn:aws:iam::<ACCOUNT_ID>:role/rate-limit-demo-gateway-role",
    protocolType="MCP",
    authorizerType="AWS_IAM",
)
print(gateway["gatewayId"], gateway["gatewayUrl"])

# Register Bedrock Mantle connector inference target
client.create_gateway_target(
    gatewayIdentifier=gateway["gatewayId"],
    name="bedrock-mantle",
    credentialProviderConfigurations=[
        {"credentialProviderType": "GATEWAY_IAM_ROLE"}
    ],
    targetConfiguration={
        "inference": {"connector": {"source": {"connectorId": "bedrock-mantle"}}}
    },
)

Run boto3 with the latest version specified using uv.

Execution command
uv run --with 'boto3>=1.43.67' setup.py

Setting a TPM Limit

Let's try limiting token consumption to 300 tokens per minute using the per-model dimension (qualifiedModelId).

create_rate_limit.py
import boto3

client = boto3.client("bedrock-agentcore-control", region_name="ap-northeast-1")

response = client.create_gateway_rate_limit(
    gatewayIdentifier="rate-limit-demo-gateway-xxxx",
    dimensionKeys=["qualifiedModelId"],
    description="Per-model TPM limit for demo",
    entries=[
        {
            "dimensions": {"qualifiedModelId": "*"},
            "tokens": [{"rate": 300, "period": "minute"}],
        }
    ],
)
print(response["rateLimitId"])
Execution command
uv run --with 'boto3>=1.43.67' create_rate_limit.py

This time, we're specifying the token count.

Since we're using a wildcard (*), this becomes a limit common to all models, but by specifying a model ID explicitly, you can differentiate settings like "tighten Opus but loosen Haiku."

It takes a little time to take effect, so wait a few minutes before testing.

Triggering HTTP Status Code 429

With the limit in effect, let's call Claude Haiku 4.5 via the Gateway's /inference/v1/messages endpoint. Firing off requests that consume approximately 210 tokens each (21 input + 191 output) in rapid succession...

Execution result
--- call 1: HTTP 200 (Success)
--- call 2: HTTP 200 (Success)
--- call 3: HTTP 200 (Success)
--- call 4: HTTP 429
{"type":"error","error":{"type":"rate_limit_error","message":"Token rate limit exceeded",
 "limitKey":"0bqftbvw7w","metric":"tokens","retryAfter":0.2}}

It stopped with HTTP 429!

The response tells us which metric was exceeded (tokens), which rate limit was hit (limitKey), and how many seconds to wait (retryAfter). Since the error format matches the Anthropic Messages API standard rate_limit_error, the built-in retry mechanisms of the Anthropic SDK and OpenAI SDK can respond to it as-is.

It appears that the 300 tokens/minute setting operates as a token bucket that replenishes 5 tokens per second, and even right after a 429, the next request succeeded after a short wait.

Since it doesn't behave as a strict window of exactly 300 per minute, it may be better to think of it as throttling rather than strict counting.

requests and tokens Can Be Used Together

You can specify both token count (tokens) and request count (requests) in a single entry simultaneously.

In this case, they are evaluated with AND logic, and a 429 is returned as soon as either limit is reached. If you want to cap both the number of calls with RPM and the cost with TPM, you can express it like this.

update_rate_limit.py(entries section)
entries=[
    {
        "dimensions": {"qualifiedModelId": "*"},
        "requests": [{"rate": 2, "period": "minute"}],
        "tokens": [{"rate": 300, "period": "minute"}],
    }
]

Let's test this too. This time, firing small requests that consume almost no tokens (approximately 32 tokens per call) in rapid succession to hit the RPM before reaching the TPM...

Execution result
--- call 6: HTTP 200 (Success)
--- call 7: HTTP 429
{"type":"error","error":{"type":"rate_limit_error","message":"Rate limit exceeded",
 "limitKey":"0bqftbvw7w","metric":"requests","retryAfter":30.0}}

This time the metric is requests!

The total token consumption is around 200, which hasn't reached the TPM (300), so the request count limit triggered first. Even with the same rate limit, you can tell from the error which limit was hit. The retryAfter: 30.0 also seems consistent with the behavior of 2 calls/minute = replenishment every 30 seconds.

Setting Different Limits per Model

Since multiple entries can be written, you can set different limits for each model.

As a test, I set only Haiku to rate 0 (= complete block) while leaving all other models at 100 calls/minute via wildcard.

update_rate_limit.py(entries section)
entries=[
    {
        "dimensions": {"qualifiedModelId": "anthropic.claude-haiku-4-5"},
        "requests": [{"rate": 0, "period": "minute"}],
    },
    {
        "dimensions": {"qualifiedModelId": "*"},
        "requests": [{"rate": 100, "period": "minute"}],
    },
]
Execution result
haiku    : HTTP 429  {"metric":"requests","retryAfter":60.0}
deepseek : HTTP 200
deepseek : HTTP 200
deepseek : HTTP 200

While Haiku is blocked from the very first call, DeepSeek successfully processed requests at the same time without any issues!

This seems useful for differentiating settings like strictly limiting expensive models only, or temporarily stopping a specific model!

MCP Targets Can Be Limited the Same Way

This time I tested with an inference target, but you can apply the same mechanism to MCP targets that expose tools like Lambda. Use targetName as the dimension and limit the number of requests with requests.

create_rate_limit.py(For MCP targets)
response = client.create_gateway_rate_limit(
    gatewayIdentifier="my-gateway-xxxx",
    dimensionKeys=["targetName"],
    description="Per-target RPM limit",
    entries=[
        {
            "dimensions": {"targetName": "SupportTarget"},
            "requests": [{"rate": 3, "period": "minute"}],
        },
        {
            "dimensions": {"targetName": "*"},
            "requests": [{"rate": 100, "period": "minute"}],
        },
    ],
)

The differences from inference targets are that tokens cannot be used (since there's no concept of tokens) and that targetName is used as the dimension. Incidentally, specifying toolName allows you to narrow it down to the tool level.

When I actually limited a Lambda mock tool to 3 calls/minute and fired requests in rapid succession, I confirmed that HTTP 429 was returned in the same way!

I get the impression it's convenient when you want to control the rate limit of tool executions from the outside!

Points to Note

Here are some points I personally found worth noting, summarized as bullet points.

  • Rate limiting is fail-open by default.
    • If there is a problem on the limiting service side or if the dimension cannot be resolved, the request will succeed. It seems the correct positioning is to use it as traffic flow control rather than as a security boundary.
  • Requests may momentarily exceed the configured rate.
    • For token limits, the input token count is estimated before execution, and the actual consumption is recorded after the response is returned. During the brief period until the response is fully returned, the consumption is not yet finalized, so rapid-fire requests can temporarily exceed the limit. This is likely why up to the 4th call (approximately 840 tokens worth) went through in our verification with a 300 tokens/minute setting.
      • For use cases where you cannot afford even 1 token of overage (such as confirmed billing control), please use it in combination with a backend-side check.
    • The same thing happens with request counts. When firing requests in rapid succession from a state where no calls had been made for a while against a 2 calls/minute limit, 14 consecutive calls went through. Even with a low rate setting, the accumulated tokens in the bucket can all be consumed at once, so it is recommended to observe for at least a few minutes before making a judgment.
  • Gateways already have limits managed by AWS (service quotas) by default.
    • Rate limiting is a feature to further restrict within those limits, so setting a value larger than the quota has no effect. What is actually applied is the smaller of the value you set and the service quota.
  • Rate limiting is executed before the policy evaluation of AgentCore Policy.
    • Requests that hit the limit are rejected with 429 before proceeding to policy evaluation, so please use this as a reference when troubleshooting when using both Policy and rate limiting together.

The fail-open behavior and the mechanism for recording token limit consumption are described in the best practices documentation, and the service quota values are in the quota documentation.

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-rate-limits-best-practices.html

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/bedrock-agentcore-limits.html

Additionally, the official blog introduces a more advanced design example of segmenting users by JWT claims (Basic / Advanced / Beta) and setting different limits for each tier. This is a useful reference when considering full-fledged design for multi-tenant scenarios.

https://aws.amazon.com/blogs/machine-learning/configure-rate-limits-for-ai-traffic-on-agentcore-gateway/

Conclusion

Rate limiting has been implemented and can now be achieved with just the gateway configuration! It's really starting to feel more and more like a true gateway...!!!

The per-model TPM limiting I tested this time I believe is a useful feature for setting an upper limit on LLM costs, and by using JWT claim-based dimensions, it can also be applied to per-user quotas in multi-tenant SaaS.

I'm also currently verifying the Temporal Policies announced at the same time, so I'll write a separate article on that soon. The Gateway is increasingly becoming a place where agent behavior is governed with greater depth!

I hope this article was helpful in some way.
Thank you for reading all the way to the end!

Share this article

AWSのお困り事はクラスメソッドへ