HTTP APIs
Build HTTP APIs with API Gateway, routes, integrations, auth, throttling, stages, and custom domains.
Mental model
API Gateway is a thin, configurable layer between the internet and your backend. It handles TLS, auth, routing, throttling, and request validation, so your Lambda doesn't have to. Every error code API Gateway returns (502, 504, 403) has a specific root cause. Knowing what causes each one is the skill this lab builds.
Prerequisites
IAM and the Two Permission Boundaries
Goal
Establish the two IAM concepts before touching any API. Mixing them up is the #1 source of silent failures in API Gateway.
Estimated time: 30 min
The two IAM concepts
What's happening here
API Gateway involves two completely separate IAM concepts that are easy to confuse:
- Execution role: the IAM role your Lambda function assumes when it runs. Controls what AWS services Lambda can call (DynamoDB, S3, SQS, etc.). Trust principal:
lambda.amazonaws.com. - Resource policy: a policy attached directly to your Lambda function that controls *who can invoke it*. When API Gateway calls Lambda, it needs permission here. Without it, Lambda silently rejects the invocation and API Gateway returns a 500 or 502 with no useful detail.
The two-question model:
- What can Lambda call? → execution role
- Who can call Lambda? → resource policy
When you wire a route to Lambda via the AWS Console, AWS adds the resource policy automatically. When you do it via CLI (as in this lab), you must add it manually. Forgetting this is the single most common source of silent 502 errors in API Gateway.
export AWS_REGION=ap-south-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Create Lambda execution role
cat > /tmp/lambda-trust.json << 'EOF'
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}
EOF
aws iam create-role \
--role-name apigw-lab-lambda-role \
--assume-role-policy-document file:///tmp/lambda-trust.json
aws iam attach-role-policy \
--role-name apigw-lab-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
export LAMBDA_ROLE_ARN=$(aws iam get-role \
--role-name apigw-lab-lambda-role \
--query 'Role.Arn' --output text)
echo "Lambda execution role: $LAMBDA_ROLE_ARN"
sleep 10 # IAM propagationCreate a base Lambda function
# Lambda that echoes the full event it receives
mkdir -p /tmp/apigw-lab
cat > /tmp/apigw-lab/index.mjs << 'EOF'
export const handler = async (event) => {
console.log('Event:', JSON.stringify(event, null, 2));
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Hello from Lambda',
method: event.requestContext?.http?.method,
path: event.rawPath,
pathParams: event.pathParameters,
queryParams: event.queryStringParameters,
body: event.body
})
};
};
EOF
cd /tmp/apigw-lab && zip function.zip index.mjs
export LAMBDA_ARN=$(aws lambda create-function \
--function-name apigw-lab-handler \
--runtime nodejs20.x \
--handler index.handler \
--role $LAMBDA_ROLE_ARN \
--zip-file fileb://function.zip \
--region $AWS_REGION \
--query 'FunctionArn' --output text)
echo "Lambda ARN: $LAMBDA_ARN"Checkpoint: aws lambda get-function --function-name apigw-lab-handler --region $AWS_REGION --query 'Configuration.State' returns Active.
Break It
Break 1: Resource policy missing, silent 502
# Create a minimal HTTP API wired to Lambda -- WITHOUT adding the resource policy
export API_ID=$(aws apigatewayv2 create-api \
--name apigw-lab-test \
--protocol-type HTTP \
--region $AWS_REGION \
--query 'ApiId' --output text)
# Create integration (Lambda proxy)
export INTEGRATION_ID=$(aws apigatewayv2 create-integration \
--api-id $API_ID \
--integration-type AWS_PROXY \
--integration-uri $LAMBDA_ARN \
--payload-format-version 2.0 \
--region $AWS_REGION \
--query 'IntegrationId' --output text)
# Create route
aws apigatewayv2 create-route \
--api-id $API_ID \
--route-key 'GET /test' \
--target "integrations/$INTEGRATION_ID" \
--region $AWS_REGION
# Create stage with auto-deploy
aws apigatewayv2 create-stage \
--api-id $API_ID \
--stage-name '$default' \
--auto-deploy \
--region $AWS_REGION
export API_URL="https://$API_ID.execute-api.$AWS_REGION.amazonaws.com"
echo "API URL: $API_URL"
# Call the endpoint WITHOUT resource policy
curl -s $API_URL/test{"message":"Internal Server Error"} (a 500. Lambda was never invoked. Check CloudWatch logs for apigw-lab-handler) no new log stream. The error is in API Gateway's internal logs, not Lambda's. No helpful message tells you the resource policy is missing.
# Now add the resource policy and retry
aws lambda add-permission \
--function-name apigw-lab-handler \
--statement-id apigw-lab-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$AWS_REGION:$ACCOUNT_ID:$API_ID/*/*/test" \
--region $AWS_REGION
sleep 3
curl -s $API_URL/test | jqnow returns a 200 with the Lambda response. The only change was adding the resource policy. Same API, same Lambda, same code, different result.
# Clean up the test API
aws apigatewayv2 delete-api --api-id $API_ID --region $AWS_REGIONBreak 2: Execution role missing CloudWatch Logs
# Detach the basic execution role
aws iam detach-role-policy \
--role-name apigw-lab-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Invoke Lambda directly
aws lambda invoke \
--function-name apigw-lab-handler \
--payload '{}' \
--cli-binary-format raw-in-base64-out \
--region $AWS_REGION /tmp/out.json && cat /tmp/out.json
# Check CloudWatch -- no log group, no streams
aws logs describe-log-groups \
--log-group-name-prefix /aws/lambda/apigw-lab-handler \
--region $AWS_REGIONLambda returns a 200 (it ran), but CloudWatch has no log group. Debugging a Lambda without logs is extremely difficult. Re-attach the policy before continuing.
aws iam attach-role-policy \
--role-name apigw-lab-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
sleep 5Your First HTTP API
Goal
Create a production-shaped HTTP API via CLI. Understand routes, integrations, auto-deploy stages, and the Lambda event/response contract. Ship a working endpoint.