Building an AI gateway to Amazon Bedrock with Amazon API Gateway
What matters here is the control plane, not the model call itself. Putting Amazon API Gateway in front of Amazon Bedrock turns raw model access into a governed enterprise service boundary, which is useful when different teams, tenants, or applications need different rules without changing client code. The architectureโs value comes from centralizing authorization, quota enforcement, and request inspection while keeping the client interface familiar to AWS SDK users.
The key design trade-off is abstraction versus fidelity. The Lambda integration forwards the original request and signs it on the server side, which preserves compatibility with current and future Bedrock APIs. That reduces maintenance when new capabilities appear, but it also means the gateway becomes a critical translation layer that must remain reliable, observable, and tightly versioned. Any mismatch in headers, payload shape, or endpoint routing can surface as hard-to-diagnose failures across every dependent application.
Operationally, the interesting part is how deployment discipline changes once the gateway becomes shared infrastructure. CloudFormation makes the pattern repeatable, but API Gateway still requires explicit deployment to activate configuration updates, so infrastructure changes and runtime activation are separate steps. That matters for teams running canary releases, authorizer updates, or endpoint changes, because release management now spans IaC, gateway configuration, and stage promotion.
For platform teams, this pattern is also a cost and resilience lever. Throttling, caching, WAF, and response streaming are not just feature add-ons; they are mechanisms for controlling burst load, protecting downstream model consumption, and shaping user experience. In practice, the gateway becomes the policy enforcement point for AI services, which makes it a natural place to balance security, latency, and maintainability.
Architecture of the AI gateway
The reference architecture gives you granular control over LLM access using fully managed AWS services. It is transparent to client applications and seamlessly integrates into existing enterprise environments.
Figure 1. Reference architecture of the AI gateway. The solution consists of four core components:
- Amazon Route 53 (optional) manages custom domain routing, allowing clients to access the gateway through a company-specific endpoint instead of the default Amazon API Gateway domain.
- Amazon API Gateway serves as the entry point for the requests and provides capabilities like authorization, request throttling, and lifecycle management.
- AWS Lambda authorizer handles request authorization, which in the Dynatrace implementation involves validating JWT tokens with existing authentication systems. For your specific implementation, you can implement your own authorization logic in a Lambda authorizer, integrate with Amazon Cognito user pools, or use other API Gateway authorization mechanisms.
- Lambda integration is a dynamic request forwarder that signs incoming requests with AWS credentials and routes them to the appropriate Amazon Bedrock endpoints. The function preserves the original request details, including the API action and parameters, to support current or future Amazon Bedrock APIs without code changes. The complete implementation is available in the integration Lambda function.
- Amazon Bedrock provides access to foundation models and AI capabilities.
- Captures the original request with its details (headers, body, and parameters).
- Applies AWS Signature Version 4 authentication.
- Forwards the request to the correct Amazon Bedrock service endpoint.
Deploying with AWS CloudFormation
This walkthrough will deploy a private AI gateway with authorization disabled for initial testing. Youโll create the core infrastructure (API Gateway, Lambda functions, and VPC endpoints) and then test basic functionality before optionally adding security features. The quickest way to deploy this solution is with AWS CloudFormation:- Sign in as an administrator to the AWS Management Console and use the navigation bar to select your desired AWS Region for deployment.
- Choose the following Launch Stack button:
- In theย Quick create stackย page, configure the key parameters as follows. For complete parameter descriptions, see theย documentation.
| Parameter | Description | Choose value | Why |
|---|---|---|---|
| EndpointType | API Gateway endpoint accessibility (PRIVATE or REGIONAL) | PRIVATE | Secure internal access only |
| EnableAuthorizer | Enable Lambda Authorizer for API Gateway | false | Start without auth for simpler testing |
| CustomDomain | Custom domain name for API Gateway | (leave empty) | Use default domain initially |
| HostedZoneId | Route 53 Hosted Zone ID for custom domain SSL validation | (leave empty) | Not needed with default domain |
- Select the capability I acknowledge that AWS CloudFormation might create IAM resources.
- Leave all other configurations at their default values and chooseย Create Stack.
- In the stack page, wait until the Status of the stack transitions to CREATE_COMPLETE.
- Choose Outputs and copy the values for GatewayUrl, VpcId, and ApiId โ youโll use these to test your gateway later.
Testing the deployment
Your gateway is now running privately inside its VPC, but that means you canโt reach it from the outside. Youโll now create an AWS CloudShell environment inside the VPC to test the gateway:- Open the CloudShell console page, choose the + icon and then choose Create VPC environment.
- On the Create a VPC environment page, configure:
- Name: for example, AIGatewayTest
- Virtual Private Cloud (VPC): the VpcId you copied earlier
- Subnet: any available subnet
- Security group: the default VPC security group
- Choose Create to create your VPC environment.
cat > boto3_client_factory.py << 'EOF'
import boto3
from botocore import UNSIGNED
from botocore.client import BaseClient
from botocore.config import Config
class Boto3ClientFactory:
"""Utility class for creating boto3 clients."""
@classmethod
def create(cls, service_name: str, endpoint_url: str, jwt_token: str = None) -> BaseClient:
"""Create a boto3 client instance usable with the sign-and-forward Lambda.
Parameters
----------
service_name : str
The service name to be used when initiating boto3.client.
endpoint_url: str
URL pointing to API gateway invoke endpoint.
jwt_token: str, optional
JWT token to include in Authorization header for all requests.
If provided, adds 'Authorization: Bearer {jwt_token}' header.
"""
generic_client = boto3.client(
service_name = service_name,
endpoint_url = endpoint_url,
# do not sign the request at the client side; authentication is done
# in API gateway
config = Config(signature_version = UNSIGNED),
# non-None Region_name needs to be passed due to validation logic
# it is NOT used if we pass endpoint_url above
region_name="",
)
def add_client_headers(model, params, **kwargs):
"""Hook to add custom headers each request."""
headers = params["headers"]
# the header "aws-endpoint-prefix" is used by the Lambda integration
headers["aws-endpoint-prefix"] = model.service_model.endpoint_prefix
# Add Authorization header if jwt_token is provided
if jwt_token:
headers["Authorization"] = f"Bearer {jwt_token}"
# register the hook to add custom headers before each call
generic_client.meta.events.register("before-call.*.*", add_client_headers)
return generic_client
EOF
# Replace with your actual Gateway URL from CloudFormation Outputs (used in all examples)
export GATEWAY_URL="https://your-api-id.execute-api.region.amazonaws.com/v1"
# Replace with one of your pre-existing Amazon Bedrock Knowledge Bases ID (optional, only needed for knowledge base example)
export KB_ID="your-kb-id"
cat > test_converse_stream.py << 'EOF'
import os
from boto3_client_factory import Boto3ClientFactory
import json
# Get configuration from environment variables
api_gateway_url = os.environ['GATEWAY_URL']
# Create client for Bedrock Runtime (model inference)
bedrock_runtime_client = Boto3ClientFactory.create(
service_name = "bedrock-runtime",
endpoint_url = api_gateway_url
)
response = bedrock_runtime_client.converse_stream(
modelId = 'global.anthropic.claude-haiku-4-5-20251001-v1:0',
messages = [{"role": "user", "content": [{"text": "Who invented the airplane?"}]}]
)
print("Model Response:")
# Stream the response as it arrives
for event in response['stream']:
if 'contentBlockDelta' in event:
delta = event['contentBlockDelta']['delta']
if 'text' in delta:
print(delta['text'], end='', flush=True) # Print each chunk immediately
elif 'messageStop' in event:
print("\n") # End of stream
break
EOF
python test_converse_stream.py
cat > test_knowledge_base.py << 'EOF'
import os
from boto3_client_factory import Boto3ClientFactory
# Get configuration from environment variables
api_gateway_url = os.environ['GATEWAY_URL']
knowledge_base_id = os.environ['KB_ID']
# Create client for Bedrock Agent Runtime (knowledge bases)
bedrock_kb_client = Boto3ClientFactory.create(
service_name = "bedrock-agent-runtime",
endpoint_url = api_gateway_url
)
response = bedrock_kb_client.retrieve(
knowledgeBaseId = knowledge_base_id,
retrievalQuery = {'text': 'Who invented the airplane?'},
retrievalConfiguration = {
'vectorSearchConfiguration': {
'numberOfResults': 5
}
}
)
print("Knowledge base retrieval results:")
print(response)
EOF
python test_knowledge_base.py
Configuring authorization
After testing the basic functionality, you can now enable authorization by updating your deployed stack with custom authorization logic. For example, to implement JWT validation in a Lambda authorizer:- Open the CloudFormation template file with your favorite text editor and replace the following placeholder code in the Lambda authorizer with your own authorization logic (see examples):
def lambda_handler(event, context):
try:
# Placeholder for JWT validation
# Implement your authorization logic
token = event['authorizationToken']
# By default, deny all requests
return generate_policy('user', 'Deny', event['methodArn'])
except Exception as e:
return generate_policy('user', 'Deny', event['methodArn'])
- Update the CloudFormation stack to enable your new authorization logic:
- Go back to theย CloudFormation console
- Select yourย bedrock-llm-gatewayย stack, chooseย Update stack, and choose Make a direct update
- Chooseย Replace existing template, upload your modified template file, and choose Next
- In the parameters section, changeย EnableAuthorizerย fromย falseย toย true and choose Next
- Select the capability I acknowledge that AWS CloudFormation might create IAM resources.
- Chooseย Nextย and chooseย Submit
- Once the CloudFormation stack update is complete, deploy your changes to the API Gateway. API Gateway requires an explicit deployment step to activate configuration changes. While CloudFormation updates the API Gateway resources, the changes wonโt be active until you create a new deployment to push them to the stage.
- Navigate to theย API Gateway console
- Choose your API (find it using theย ApiIdย from your CloudFormation outputs)
- Chooseย Deploy API
- For Stage selectย v1, and chooseย Deploy
- Test your authorization by going back to CloudShell and running this example. This example passes a JWT token to test the authorization โ replace it with your actual token or other authorization parameters you configured in the Lambda authorizer.
cat > test_with_auth.py << 'EOF'
import os
from boto3_client_factory import Boto3ClientFactory
# Get configuration from environment variables
api_gateway_url = os.environ['GATEWAY_URL']
# Replace "your-jwt-token" with your actual JWT token
jwt_token = "your-jwt-token"
bedrock_runtime_client_with_jwt = Boto3ClientFactory.create(
service_name = "bedrock-runtime",
endpoint_url = api_gateway_url,
jwt_token = jwt_token
)
response = bedrock_runtime_client_with_jwt.converse_stream(
modelId = 'global.anthropic.claude-haiku-4-5-20251001-v1:0',
messages = [{"role": "user", "content": [{"text": "Who invented the airplane?"}]}]
)
print("Model Response:")
# Stream the response as it arrives
for event in response['stream']:
if 'contentBlockDelta' in event:
delta = event['contentBlockDelta']['delta']
if 'text' in delta:
print(delta['text'], end='', flush=True) # Print each chunk immediately
elif 'messageStop' in event:
print("\n") # End of stream
break
EOF
python test_with_auth.py
Enhancement options for the AI gateway
The solution can be enhanced using additional API Gateway capabilities. Here are some examples:- Rate limiting and throttling: Control request rates using usage plans and API keys. This is especially important in multi-tenant SaaS applications to avoid noisy neighbor problems. For examples of throttling scenarios, see theย throttling documentation.
- Private or edge-optimized endpoints: Configure endpoint types to optimize for internal access or global performance.
- Lifecycle management and canary releases: Manage multiple API versions and implement gradual rollouts with stage variables and canary deployments.
- WAF integration: Add AWS WAF rules to help protect from common exploits.
- Prompt and response caching:ย Implement caching strategies to reduce costs and improve response times for frequently requested prompts usingย API Gateway caching.
- Content filtering:ย In addition to the safeguards offered byย Amazon Bedrock Guardrails, add custom filtering in the Lambda integration layer to screen for sensitive content such as personally identifiable information (PII).
Conclusion
The AI gateway pattern demonstrated in this post provides a scalable way to manage access to foundation models and agent tools through Amazon Bedrock. Initially developed and implemented by Dynatrace to serve their global user base, this pattern has proven its effectiveness at enterprise-scale. By using the Amazon API Gateway enterprise features organizations can implement necessary controls while maintaining the benefits of serverless architecture. To start using this solution today, follow the walkthrough in this blog post or check out our GitHub repository. To learn more about the services used in this solution, explore the Amazon API Gateway features page, or visit the documentation for Amazon API Gateway and Amazon Bedrock. To learn how this solution streams foundation model responses, see the documentation for the new Amazon API Gateway response streaming capability.About the authors
Enjoyed this article? Sign up for our newsletter to receive regular insights and stay connected.
