Working with HTTP APIs in Stelvio
Stelvio supports Amazon API Gateway HTTP APIs using the HttpApi component. HTTP APIs are API Gateway v2: they use the Lambda payload format 2.0, native CORS, auto-deploy stages, and regional endpoints.
Use HttpApi for new Lambda-backed HTTP endpoints unless you need a REST API v1 feature such as edge-optimized endpoints, API Gateway REST gateway responses, or non-Lambda integrations. The existing RestApi component remains available for REST APIs.
Creating an HTTP API
Create an API in stlv_app.py, then add routes before accessing API properties:
from stelvio.aws.dynamo_db import DynamoTable
from stelvio.aws.api_gateway import HttpApi
users = DynamoTable("users", fields={"id": "string"}, partition_key="id")
api = HttpApi("users-api", cors=True)
api.route("GET", "/users", "functions/users.list")
api.route("POST", "/users", "functions/users.create", links=[users])
api.route(["GET", "DELETE"], "/users/{id}", "functions/users.detail")
This creates an HTTP API, an auto-deploy stage, a CloudWatch access-log group, Lambda integrations, routes, Lambda invoke permissions, and any custom domain resources you configure.
Add routes and authorizers first
Add all routes and authorizers before accessing .resources, api.url, api.api_id, api.arn, or api.execution_arn. Resource access creates the Pulumi resources, after which route and authorizer changes are rejected.
Configuration
You can pass options directly or use HttpApiConfig:
from stelvio.aws.api_gateway import HttpApi, HttpApiConfig
# Keyword options
api = HttpApi(
"users-api",
domain_name="api.example.com",
cors=True,
access_log_retention_days=30,
)
# Or a config object
api = HttpApi(
"users-api",
config=HttpApiConfig(
stage_name="$default",
cors=True,
),
)
| Option | Default | Description |
|---|---|---|
domain_name |
None |
Custom domain owned by this API, such as api.example.com. Requires a DNS provider in the Stelvio app. |
domain |
None |
Shared HttpApiDomain component. Use this when multiple HTTP APIs share one domain. |
api_mapping_key |
None |
Base path on a custom domain, such as v1 or admin. Requires domain_name or domain. |
stage_name |
"$default" |
Stage name. Use "$default" for no stage path in the execute-api URL. Custom names may contain letters, numbers, _, and -. |
cors |
None |
True, CorsConfig, or a dictionary. See CORS. |
disable_execute_api_endpoint |
False |
Disable the default execute-api endpoint. Requires a custom domain. |
access_log_retention_days |
30 |
CloudWatch access-log retention in days. Use None to keep logs indefinitely. |
URLs
api.url returns the public base URL as a Pulumi Output[str].
| Configuration | URL form |
|---|---|
No domain, $default stage |
https://{api_id}.execute-api.{region}.amazonaws.com |
| No domain, custom stage | https://{api_id}.execute-api.{region}.amazonaws.com/{stage_name} |
domain_name="api.example.com" |
https://api.example.com |
Domain with api_mapping_key="v2" |
https://api.example.com/v2 |
Defining Routes
Routes use the same basic shape as the REST API component:
api.route(http_method, path, handler)
HTTP methods are case-insensitive and can be a single method, a list of methods, or "ANY":
api.route("GET", "/users", "functions/users.list")
api.route(["POST", "PUT"], "/users", "functions/users.write")
api.route("ANY", "/files/{proxy+}", "functions/files.dispatch")
api.route("*", "$default", "functions/fallback.handler")
Paths must start with /, except for the special $default route. $default can only be used with "ANY" or "*".
Lambda Handlers
The route handler accepts the same forms as other Stelvio Lambda integrations:
from stelvio.aws.function import Function, FunctionConfig
# Handler path string
api.route("GET", "/users", "functions/users.list")
# FunctionConfig
api.route(
"POST",
"/users",
FunctionConfig(
handler="functions/users.create",
memory=512,
timeout=20,
),
)
# Dictionary
api.route(
"PATCH",
"/users/{id}",
{"handler": "functions/users.update", "memory": 512},
)
# Existing Function instance
users_fn = Function("users-handler", handler="functions/users.handler")
api.route("GET", "/users", users_fn)
api.route("POST", "/users", users_fn)
You can also pass Lambda options directly when the handler is a string:
api.route(
"POST",
"/orders",
"functions/orders.create",
memory=512,
timeout=20,
links=[users],
)
30 second timeout limit
HTTP APIs cap Lambda proxy integrations at 30 seconds. Stelvio validates route Lambda timeouts and raises an error if a route uses timeout > 30.
Lambda Event Format
HTTP APIs use Lambda payload format 2.0. This differs from the REST API component's v1 event shape.
# functions/users.py
import json
def list(event, context):
method = event["requestContext"]["http"]["method"]
path = event["rawPath"]
return {
"statusCode": 200,
"headers": {"content-type": "application/json"},
"body": json.dumps({"method": method, "path": path}),
}
If you migrate from RestApi, update handlers that read v1 fields such as event["httpMethod"] or event["pathParameters"] directly.
Migrating Route Auth From RestApi
HTTP API authorizers use JWT terminology for Cognito-backed scopes. If existing REST API code used cognito_scopes=, switch to jwt_scopes= on HttpApi routes:
api.route(
"GET",
"/profile",
"functions/profile.get",
auth=cognito_auth,
jwt_scopes=["profile:read"],
)
Stelvio still accepts cognito_scopes= for compatibility and emits a deprecation warning, but new HTTP API routes should use jwt_scopes=.
Authorization
Routes are public by default. You can protect individual routes with auth=, or set api.default_auth to protect routes unless they opt out.
api = HttpApi("users-api")
authorizer = api.add_lambda_authorizer(
"session-auth",
"functions/auth.authorize",
identity_sources="$request.header.Authorization",
)
api.default_auth = authorizer
api.route("GET", "/me", "functions/me.get")
api.route("GET", "/health", "functions/health.get", auth=False)
api.route("POST", "/internal/jobs", "functions/jobs.create", auth="IAM")
Route auth value |
Result |
|---|---|
omitted / None |
Use api.default_auth. If no default is set, the route is public. |
False |
Public route, even when default_auth is set. |
"IAM" |
AWS IAM authorization. Clients must sign requests with SigV4. |
| Authorizer object | Use that Lambda or JWT authorizer for this route. |
Lambda Authorizers
HTTP APIs have one Lambda authorizer type: REQUEST. Stelvio exposes it as add_lambda_authorizer.
auth = api.add_lambda_authorizer(
"api-key-auth",
"functions/auth.api_key",
identity_sources=[
"$request.header.X-API-Key",
"$request.querystring.tenant",
],
ttl=300,
simple_response=True,
)
api.route("GET", "/reports", "functions/reports.list", auth=auth)
With simple_response=True, the authorizer returns the HTTP API simple response format:
# functions/auth.py
def api_key(event, context):
api_key = event["headers"].get("x-api-key")
return {
"isAuthorized": api_key == "expected-key",
"context": {"tenant": "demo"},
}
Set simple_response=False if your authorizer returns an IAM policy response.
JWT and Cognito Authorizers
Use add_jwt_authorizer for generic OIDC providers:
jwt_auth = api.add_jwt_authorizer(
"oidc",
issuer="https://auth.example.com/",
audiences=["api-client-id"],
)
api.route(
"GET",
"/account",
"functions/account.get",
auth=jwt_auth,
jwt_scopes=["account:read"],
)
Use add_cognito_authorizer with Stelvio Cognito components:
from stelvio.aws.cognito import UserPool
users = UserPool("users", usernames=["email"])
web_client = users.add_client("web")
cognito_auth = api.add_cognito_authorizer(
"cognito",
user_pool=users,
audiences=[web_client],
)
api.route(
"GET",
"/profile",
"functions/profile.get",
auth=cognito_auth,
jwt_scopes=["profile:read"],
)
Identity source syntax
HTTP APIs use v2 identity sources such as $request.header.Authorization. Stelvio can rewrite common REST API v1 sources like method.request.header.Authorization, but new code should use the v2 form directly.
CORS
HTTP APIs use native API-level CORS. Stelvio does not create synthetic OPTIONS routes and does not inject CORS response helpers into your Lambda functions.
from stelvio.aws.cors import CorsConfig
from stelvio.aws.api_gateway import HttpApi
# Permissive defaults
api = HttpApi("public-api", cors=True)
# Explicit configuration
api = HttpApi(
"app-api",
cors=CorsConfig(
allow_origins=[
"https://app.example.com",
"https://admin.example.com",
],
allow_methods=["GET", "POST", "PATCH", "DELETE"],
allow_headers=["content-type", "authorization"],
allow_credentials=True,
max_age=3600,
),
)
When cors=True, Stelvio uses allow_origins="*", allow_methods="*", and allow_headers="*". If you set allow_credentials=True, list explicit origins instead of "*".
Custom Domains
For one API on one domain, pass domain_name directly:
from stelvio.aws.api_gateway import HttpApi
api = HttpApi(
"public-api",
domain_name="api.example.com",
cors=True,
)
As described in the DNS guide, custom domains require a DNS provider on the Stelvio app. Stelvio creates the ACM certificate, validates it with DNS, creates the API Gateway v2 domain, and publishes the DNS record.
For multiple HTTP APIs on one domain, create a shared HttpApiDomain and give each API a distinct mapping key:
from stelvio.aws.api_gateway import HttpApi, HttpApiDomain
domain = HttpApiDomain("public-domain", domain_name="api.example.com")
public_api = HttpApi("public-api", domain=domain)
admin_api = HttpApi("admin-api", domain=domain, api_mapping_key="admin")
partner_api = HttpApi("partner-api", domain=domain, api_mapping_key="partners/v1")
This gives you:
| API | URL |
|---|---|
public_api |
https://api.example.com |
admin_api |
https://api.example.com/admin |
partner_api |
https://api.example.com/partners/v1 |
Disable execute-api for domain-only APIs
Set disable_execute_api_endpoint=True when all clients should use your custom domain. AWS will reject requests to the default execute-api hostname.
Linking
HttpApi is linkable. Linking an HTTP API to a Lambda function gives the function the API URL and execution ARN as environment variables and generated stlv_resources.py properties.
from stelvio.aws.function import Function
from stelvio.aws.api_gateway import HttpApi
api = HttpApi("billing-api")
api.route("POST", "/invoices", "functions/invoices.create")
worker = Function(
"billing-worker",
handler="functions/worker.handler",
links=[api],
)
# functions/worker.py
from stlv_resources import Resources
def handler(event, context):
api_url = Resources.billing_api.url
execution_arn = Resources.billing_api.execution_arn
| Property | Environment variable | Description |
|---|---|---|
url |
STLV_<API_NAME>_URL |
Public base URL for the API. |
execution_arn |
STLV_<API_NAME>_EXECUTION_ARN |
Execute API ARN, useful when granting execute-api:Invoke. |
Linking an HTTP API grants no IAM permissions by default. For IAM-protected routes, grant callers execute-api:Invoke for the required route ARN.
REST API vs HTTP API
The RestApi and HttpApi components are separate because API Gateway REST APIs and HTTP APIs behave differently.
| Concern | RestApi REST API |
HttpApi HTTP API |
|---|---|---|
| Import | from stelvio.aws.api_gateway import RestApi |
from stelvio.aws.api_gateway import HttpApi |
| Lambda payload | 1.0 | 2.0 |
| Stage behavior | Explicit deployment and stage, default "v1" |
Auto-deploy stage, default "$default" |
| CORS | Stelvio creates OPTIONS routes and Lambda CORS helpers |
Native API-level CORS |
| Origins | Single origin in Stelvio REST CORS | Multiple origins supported |
| Endpoints | Regional or edge-optimized | Regional only |
| Custom domains | BasePathMapping |
ApiMapping, with shareable HttpApiDomain |
| Authorizers | Token, request, Cognito, IAM | Lambda request, JWT/Cognito, IAM |
| Integration timeout | 29 seconds | 30 seconds |
Choose RestApi when you need a REST API-only feature. Choose HttpApi for native HTTP API behavior, especially payload format 2.0, multi-origin CORS, auto-deploy stages, or shared domain mappings.
Access Logs
HttpApi creates a dedicated CloudWatch log group and enables stage access logs by default. The default retention is 30 days:
# Keep logs for 90 days
api = HttpApi("audit-api", access_log_retention_days=90)
# Keep logs indefinitely
api = HttpApi("audit-api", access_log_retention_days=None)
The default log format is JSON and includes request ID, source IP, request time, method, route key, status, protocol, response length, and integration error message.
Customization
The HttpApi and HttpApiDomain components support the customize parameter to override underlying Pulumi resource properties. For an overview, see the Customization guide.
HttpApi Resource Keys
| Resource Key | Pulumi Args Type | Description |
|---|---|---|
api |
ApiArgs | The API Gateway v2 HTTP API. |
stage |
StageArgs | The auto-deploy API stage. |
log_group |
LogGroupArgs | The CloudWatch access-log group. |
api_mapping |
ApiMappingArgs | The custom-domain API mapping, when a domain is configured. |
HttpApiDomain Resource Keys
| Resource Key | Pulumi Args Type | Description |
|---|---|---|
certificate |
CertificateArgs | The ACM certificate created by AcmValidatedDomain. |
domain |
DomainNameArgs | The API Gateway v2 domain name. |
dns_record |
DNS provider record args | The DNS record pointing the domain to API Gateway. |
Example
api = HttpApi(
"users-api",
customize={
"api": {
"description": "Users HTTP API",
},
"stage": {
"route_settings": [
{
"route_key": "GET /users",
"throttling_burst_limit": 100,
"throttling_rate_limit": 50,
}
],
},
"log_group": {
"retention_in_days": 90,
},
},
)
Use route-key strings such as GET /users, ANY /files/{proxy+}, or $default when customizing stage route settings.
Best Practices
- Prefer the default
$defaultstage unless you specifically need stage names in URLs. - Use
cors=Truefor public prototypes, then restrict origins before production. - Use
HttpApiDomainwhen more than one API should live under the same domain. - Keep route Lambda timeouts at or below 30 seconds and move long-running work to queues or background functions.
- Use
auth=Falsefor public health checks whendefault_authprotects the rest of the API.
Next Steps
- Working with API Gateway - Compare the REST API component.
- Working with Lambda Functions - Learn how Lambda packaging and configuration work.
- Authentication with Cognito - Create user pools and app clients for JWT authorizers.
- Linking - Learn how links generate environment variables and permissions.
- Customization - Override underlying Pulumi resource properties.