How to Build a Serverless Claude API Backend
Building a serverless Claude API backend means wrapping Claude requests in a stateless function — AWS Lambda, Cloudflare Workers, Vercel Edge Functions, or similar — so you get automatic scaling, no server management, and pay-per-invocation pricing instead of a always-on process waiting for traffic.
The core pattern is simple: your function receives a request from your frontend, forwards it to Claude (or to a proxy like SubToAPI) with the right headers and parameters, handles streaming if needed, and returns the response. The hard parts are the things that don't show up in a "hello world" example: key management, timeout limits on streaming responses, retry logic, and keeping per-request costs visible. This article walks through each of those.
Why Serverless Fits Claude Workloads
Claude requests are bursty by nature — a user fires off a message, waits a few seconds, then goes quiet. That's exactly the traffic pattern serverless platforms are built for: you don't pay for idle compute, and you don't need to provision for peak concurrency in advance.
The tradeoffs are real too:
- Cold starts add latency to the first request after inactivity, which matters more for synchronous responses than streaming ones.
- Execution time limits — Lambda caps at 15 minutes, Vercel Edge Functions and Cloudflare Workers have their own ceilings — can clip long streaming responses if you're not careful.
- No persistent connections, so anything requiring websockets or long-lived state needs an external store.
For most API wrapper use cases (chat features, content generation, summarization endpoints), these tradeoffs are worth it.
Choosing a Runtime
Three common choices:
AWS Lambda — mature, integrates with API Gateway, good for teams already on AWS. Streaming support via Lambda response streaming is available but adds setup complexity.
Cloudflare Workers — fast cold starts, native streaming support, runs close to users geographically. Good default choice if you're starting fresh.
Vercel Edge Functions / Next.js Route Handlers — best if your frontend is already on Vercel. Streaming works naturally with the Fetch API's ReadableStream.
All three can call Claude the same way: a fetch call with the right auth header, piping the response back to the client.
Basic Serverless Function Structure
Here's a minimal Cloudflare Worker that proxies a chat request:
export default {
async fetch(request, env) {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const { messages } = await request.json();
const upstream = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
messages
})
});
return new Response(upstream.body, {
status: upstream.status,
headers: { "Content-Type": "application/json" }
});
}
};
This pattern — frontend calls your function, your function calls the Claude API endpoint, response flows back — keeps your API key off the client entirely. That's non-negotiable: never ship a Claude or SubToAPI key in frontend JavaScript. The serverless function exists specifically to keep that key server-side.
Handling Streaming Within Execution Limits
Streaming is where serverless gets tricky. Each platform handles it differently, but the general approach is to forward the upstream stream directly rather than buffering it in memory:
export default {
async fetch(request, env) {
const { messages } = await request.json();
const upstream = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
stream: true,
messages
})
});
return new Response(upstream.body, {
headers: { "Content-Type": "text/event-stream" }
});
}
};
Piping upstream.body directly avoids loading the full response into memory and avoids hitting function timeout before the stream even starts. See the streaming docs for the exact event format if you're parsing server-sent events on the client.
If your platform's execution timeout is shorter than your typical Claude response time (long completions, heavy tool use), consider splitting work: return a job ID immediately, run generation in a queue-backed worker, and let the client poll or subscribe for the result.
Key Management Without a Server
Serverless functions still need somewhere to store secrets. Each platform has an equivalent of environment variables bound at deploy time — Cloudflare's env bindings, Lambda's environment variables plus Secrets Manager for rotation, Vercel's project environment variables. Store your Claude or SubToAPI key there, never in code or in a committed .env file.
If you're running the same backend across staging and production, or across multiple apps, generate separate keys per environment so you can revoke one without affecting the others. SubToAPI issues scoped sub_live_ keys per application from the dashboard, which makes this straightforward — no need to build your own key rotation system. Sign up and grab a key at /signup, or check /docs/quickstart for the first-request walkthrough.
Error Handling and Retries
Serverless functions should assume the upstream call can fail — timeouts, rate limits, transient network errors. A basic retry with backoff for 429s and 5xx responses covers most cases:
async function callClaude(body, apiKey, attempt = 0) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
if ((res.status === 429 || res.status >= 500) && attempt < 3) {
await new Promise(r => setTimeout(r, 2 ** attempt * 500));
return callClaude(body, apiKey, attempt + 1);
}
return res;
}
Keep retries bounded — serverless functions have their own timeout, so an unbounded retry loop can eat your execution budget without ever returning to the client.
Tracking Cost Per Function Invocation
One blind spot with serverless Claude backends: it's easy to lose track of how much each function invocation actually costs once traffic is spread across dozens of Lambda or Worker deployments. Centralizing your Claude access behind an API key gateway rather than individual raw API keys per function gives you a single place to see usage and spend, rather than reconstructing it from scattered cloud provider logs. SubToAPI's dashboard shows usage metadata per request regardless of which function or region called it — useful once you have more than one serverless endpoint hitting Claude. See /pricing for plan details if you're scaling this across a team.
Questions
Does serverless add noticeable latency to Claude requests? Cold starts can add 50–300ms on platforms like Lambda, but warm invocations and edge platforms like Cloudflare Workers add negligible overhead compared to Claude's own response time.
Can I use serverless functions for streaming responses? Yes — most platforms support forwarding a ReadableStream from the upstream fetch directly to the client, as long as you don't buffer the full response first.
Do I need my own backend if I use SubToAPI? You still need a thin serverless function to hold your API key and forward requests, but SubToAPI removes the need to build key management, usage tracking, and multi-provider handling yourself — see /docs/messages for the request format.