API Gateway HTTP API
HTTP API with routes, Lambda/ALB integrations, custom domain, JWT authorizers, and access logs.
Verification
Live-testedReally deployed, verified, idempotent and destroyed in a cloud sandbox.
Conformance
- Static validation (fmt · validate · tflint)
- Security scan: findings disclosed (Checkov)
- Plan tests (mocked: validation rules · outputs)
Provenance
- SHA-256 checksum
- Signature (pending)
Functional
- Live-tested — applied, verified, destroyed
Last verified 2026-06-11 · how we verify
Documentation
aws-apigateway-http — API Gateway HTTP API
An HTTP API (API Gateway v2) wired end to end: typed integrations
(Lambda-proxy or any HTTP/ALB backend, including private VPC-Link upstreams),
JWT and Lambda authorizers, routes that bind methods/paths to those backends,
a stage with throttling and structured JSON access logs, and an optional
regional custom domain (ACM-backed, TLS 1.2 floor, optional mutual TLS).
Works with Terraform and OpenTofu (>= 1.6), AWS provider >= 6.0, < 7.0.
Status: static-validated, live-test pending. Ships under live-test quarantine — validated with
tofu fmt,tofu validate, andtflint. Real apply → curl the invoke URL → destroy against an AWS account is pending a cloud sandbox. HTTP APIs are cheap and fast to test (no minimum charge, the invoke URL is the verify target), so this is first in line for the live lane.
Secure / sane defaults
- Deny-by-default authorization. Routes that name no authorizer inherit
default_authorization_type, which isAWS_IAM— unauthenticated callers are rejected unless a route is explicitly opened withauthorization_type = NONE. Flip the default toNONEonly for a genuinely public API. - CORS off by default. No CORS headers are emitted (same-origin only)
until you opt in; the module refuses the
allow_credentials+"*"origin combination that AWS rejects. - Access logs on by default, streamed as a structured JSON record (request id, source IP, route, status, latency, integration error) to a module-managed CloudWatch log group with 30-day retention (KMS optional).
- TLS 1.2 floor on the custom domain, regional endpoint type, optional mutual-TLS client authentication via an S3 truststore.
- Throttling on by default (burst/rate caps in
default_route_settings) so a single client cannot exhaust the account-wide soft limits. integration_methodis dropped automatically forAWS_PROXY, andconnection_idonly applies toVPC_LINKintegrations — invalid AWS combos are pruned for you.
Usage
module "api" {
source = "./aws-apigateway-http"
name = "orders"
# CORS: explicit origins, no wildcard
cors_configuration = {
allow_origins = ["https://app.example.com"]
allow_methods = ["GET", "POST"]
allow_headers = ["authorization", "content-type"]
max_age = 600
}
integrations = {
lambda = {
integration_type = "AWS_PROXY"
integration_uri = module.orders_fn.invoke_arn
}
legacy = {
integration_type = "HTTP_PROXY"
integration_uri = "https://legacy.internal.example.com/{proxy}"
integration_method = "ANY"
}
}
authorizers = {
cognito = {
authorizer_type = "JWT"
jwt_issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc123"
jwt_audience = ["6m...client-id"]
}
}
routes = {
"GET /healthz" = { integration_key = "lambda", authorization_type = "NONE" }
"POST /orders" = { integration_key = "lambda", authorizer_key = "cognito" }
"ANY /legacy/{proxy+}" = { integration_key = "legacy", authorizer_key = "cognito" }
}
# Custom domain (regional ACM cert in the same region as the API)
domain_name = "api.example.com"
certificate_arn = aws_acm_certificate.api.arn
tags = { Environment = "prod" }
}
# Allow API Gateway to invoke the Lambda integration (grant outside the module)
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = module.orders_fn.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${module.api.execution_arn}/*"
}
For a custom domain, point DNS at the API with a Route 53 alias:
resource "aws_route53_record" "api" {
zone_id = var.zone_id
name = "api.example.com"
type = "A"
alias {
name = module.api.domain_target_domain_name
zone_id = module.api.domain_hosted_zone_id
evaluate_target_health = false
}
}
Inputs (key)
| Name | Type | Default | Description |
|---|---|---|---|
name | string | — | API name, 1–128 chars (required) |
integrations | map(object) | {} | Backends keyed by logical name: { integration_type, integration_uri, integration_method?, payload_format_version?, connection_type?, connection_id?, timeout_milliseconds?, tls_server_name_to_verify?, request_parameters? } |
authorizers | map(object) | {} | JWT (jwt_issuer+jwt_audience) or REQUEST Lambda (authorizer_uri, …) authorizers keyed by name |
routes | map(object) | {} | Route-key => { integration_key, authorizer_key?, authorization_type?, authorization_scopes? } |
default_authorization_type | string | "AWS_IAM" | Auth for routes with no authorizer — deny-by-default; set NONE for public |
cors_configuration | object | null | CORS; null = no CORS headers |
disable_execute_api_endpoint | bool | false | Turn off the default execute-api URL (use with a custom domain) |
stage_name / auto_deploy | string / bool | "$default" / true | Stage config |
throttling_burst_limit / throttling_rate_limit | number | 5000 / 10000 | Default-route throttling (-1 = account default) |
detailed_metrics_enabled | bool | true | Per-route CloudWatch metrics |
access_logs_enabled | bool | true | Stream JSON access logs |
access_log_destination_arn | string | null | Use an existing log-group ARN instead of a managed one |
access_log_format | string | null | Override the default JSON access-log format |
log_retention_days / log_kms_key_arn | number / string | 30 / null | Managed log-group retention + encryption |
domain_name + certificate_arn | string | null | Regional custom domain + ACM cert (same region) |
domain_security_policy | string | "TLS_1_2" | Minimum TLS policy on the domain |
api_mapping_key | string | "" | Base path under the domain (empty = root) |
mutual_tls_truststore_uri | string | null | S3 PEM truststore to enable mTLS |
tags | map(string) | {} | Tags for all resources |
Outputs
api_id, api_arn, api_endpoint, execution_arn, stage_id, stage_arn,
invoke_url, integration_ids, authorizer_ids, route_ids,
access_log_group_name, access_log_group_arn, domain_name,
domain_target_domain_name, domain_hosted_zone_id.
Notes
- Lambda invoke permission is granted outside the module — add an
aws_lambda_permissionwithprincipal = "apigateway.amazonaws.com"andsource_arn = "${module.api.execution_arn}/*", otherwise calls 500. logging_level/data_trace_enabledare WebSocket-only and are intentionally absent fromdefault_route_settings; request-level visibility for HTTP APIs comes from the JSON access logs.- Custom-domain certificate must be a regional ACM cert in the API's region (HTTP APIs do not use the CloudFront us-east-1 cert that REST edge endpoints require).
- The custom domain is created but DNS is yours to wire (Route 53 alias to
domain_target_domain_name/domain_hosted_zone_id).
Requirements
- Terraform or OpenTofu
>= 1.6 hashicorp/aws>= 6.0, < 7.0
Verification
Static-validated (tofu fmt, tofu validate, tflint). Live apply/curl/destroy
testing pending cloud sandbox availability — see catalog status.
License
Commercial — LicenseRef-IaCBazaar-Commercial. © IaC Bazaar. Original work
(not derived from a third-party module).