Claude API AWS Lambda Integration Example
Running Claude behind an AWS Lambda function is one of the most common ways teams add AI features to an existing serverless backend without standing up a new server. This article walks through a working Claude API AWS Lambda integration example in Node.js, covers the gotchas specific to Lambda (timeouts, payload limits, no native streaming), and shows how to deploy it.
The short answer: you call the Claude Messages endpoint with a standard HTTPS POST from inside your handler, store the API key in an environment variable or Secrets Manager, set a function timeout that accounts for generation time, and avoid chunked streaming responses unless you're using a Lambda Function URL with response streaming enabled. Everything below expands on that.
Why Lambda + Claude is a common pattern
Lambda is a good fit for Claude calls when:
- You already have an API Gateway or Function URL backend and want to add an AI endpoint without a new service.
- The workload is request/response (summarize, classify, extract, generate) rather than long-running chat sessions.
- You want per-invocation isolation and automatic scaling without managing a pool of servers.
It's a poor fit if you need long-lived streaming to a browser over several minutes, or very high invocation volume where cold starts and concurrency limits start to matter — in those cases a small always-on container is often simpler.
Prerequisites
- An AWS account with permissions to create Lambda functions, IAM roles, and (optionally) Secrets Manager secrets.
- An API key for Claude. You can use a key issued directly against the model provider, or a
sub_live_...key from SubToAPI if you want a single HTTPS endpoint with built-in usage metadata and team key management — see /docs/quickstart. - Node.js 18+ runtime (native
fetch, no extra HTTP client needed).
Storing the API key safely
Never hardcode the key in your function code. Two common options:
- Lambda environment variable, encrypted with a KMS key (default AWS managed key is fine for most teams).
- AWS Secrets Manager, fetched once per cold start and cached in memory for the life of the execution environment.
For most small-to-medium workloads, an encrypted environment variable is simpler and avoids an extra API call per cold start:
aws lambda update-function-configuration \
--function-name claude-handler \
--environment "Variables={CLAUDE_API_KEY=sk-ant-xxxxx}"
The Lambda handler
Here's a minimal handler that accepts a prompt via the event body and returns Claude's response. This example calls the model provider directly:
export const handler = async (event) => {
const body = JSON.parse(event.body || "{}");
const prompt = body.prompt || "Say hello.";
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": process.env.CLAUDE_API_KEY,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
}),
});
const data = await response.json();
return {
statusCode: response.status,
headers: { "content-type": "application/json" },
body: JSON.stringify(data),
};
};
If you'd rather manage one API surface instead of juggling provider-specific headers and versioning, swap the URL and auth header for SubToAPI's endpoint — the rest of the handler stays identical:
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"content-type": "application/json",
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
}),
});
Full request/response shape and parameters are documented at /docs/messages.
Timeout and payload considerations
Lambda has hard limits that matter when calling an LLM:
- Function timeout: default is 3 seconds, way too short. For non-streaming Claude calls, set the timeout to 30–60 seconds depending on expected output length and
max_tokens. Large completions take longer to generate. - Payload size: synchronous invocations (API Gateway REST/HTTP API) cap response bodies around 6 MB for REST, 10 MB for Function URLs. Long completions with large
max_tokenscan approach this if you're also returning the full conversation history — truncate what you echo back. - Memory: Lambda memory also scales CPU allocation. 256–512 MB is usually enough since the function itself does almost no CPU work; it's mostly waiting on the HTTPS call.
aws lambda update-function-configuration \
--function-name claude-handler \
--timeout 60 \
--memory-size 512
Streaming in Lambda: what actually works
Classic API Gateway (REST or HTTP API) does not support streaming responses — it buffers the full Lambda response before returning it to the client. If you need token-by-token output, you have two realistic options:
- Lambda Function URLs with response streaming (
InvokeMode: RESPONSE_STREAM), which lets you pipe chunks as they arrive from Claude's streaming endpoint directly to the client. - Skip Lambda for the streaming leg and have the browser connect directly to a streaming-capable endpoint (SubToAPI supports Server-Sent Events — see /docs/streaming) while Lambda handles everything else: auth, logging, pre/post-processing.
For most CRUD-style AI features (classify this, summarize that, extract these fields), non-streaming is fine and dramatically simpler to debug in Lambda. Reserve streaming for interactive chat UIs.
Deploying it
A minimal AWS SAM template wires the function to an HTTP API:
Resources:
ClaudeFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs18.x
Timeout: 60
MemorySize: 512
Environment:
Variables:
CLAUDE_API_KEY: !Ref ClaudeApiKey
Events:
Api:
Type: HttpApi
Properties:
Path: /chat
Method: POST
Deploy with sam build && sam deploy --guided. If you're using SubToAPI keys instead, just swap the environment variable name and the fetch target — no infrastructure changes needed.
Error handling and retries
Wrap the fetch call in a try/catch and check response.status explicitly — rate limits and transient 5xx errors are common enough in production that you should retry once with a short backoff before failing the invocation. Log the request ID from the response headers so you can correlate failures with provider-side logs. If you're routing through SubToAPI, the dashboard usage view gives you per-key error breakdowns without extra logging code — see /docs for setup details.
Questions
Does Lambda support streaming Claude responses to the client? Only via Function URLs with RESPONSE_STREAM invoke mode. Standard API Gateway integrations buffer the entire response, so streaming chat UIs usually bypass Lambda for that leg.
What timeout should I set for a Claude Lambda function? 30–60 seconds for typical completions. Base it on your max_tokens setting — larger outputs take proportionally longer to generate.
Should I call the provider API directly or through a gateway like SubToAPI? Calling directly works fine for a single key and simple use. A gateway makes sense once you need multiple keys, usage metadata per team/project, or want to avoid managing provider auth headers yourself — see /pricing and /signup to try it.