Claude API FastAPI Backend Example in Python
If you're building a Python backend that talks to Claude, FastAPI is a natural fit: async support, Pydantic validation, and minimal boilerplate. This article walks through a working Claude API FastAPI backend example — a single service that accepts chat requests from a frontend, forwards them to Claude, and returns both plain and streamed responses.
The core pattern is always the same: FastAPI handles HTTP routing and validation, an async HTTP client calls the Claude-compatible endpoint, and you shape the response (JSON or Server-Sent Events) for your frontend. Everything below works whether you call Anthropic directly or route through a proxy like SubToAPI — the only thing that changes is the base URL and key.
Project setup
Install the dependencies:
pip install fastapi uvicorn httpx python-dotenv
Project structure:
app/
main.py
claude_client.py
models.py
.env
Store your credentials in .env:
CLAUDE_API_KEY=sk-ant-your-key-here
CLAUDE_BASE_URL=https://api.anthropic.com/v1
Defining request models
Use Pydantic to validate incoming requests before they ever reach Claude. This catches malformed payloads early and gives you clean type hints throughout the app.
# app/models.py
from pydantic import BaseModel
from typing import List, Optional
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
messages: List[Message]
model: str = "claude-3-5-sonnet-20241022"
max_tokens: int = 1024
system: Optional[str] = None
A thin Claude client
Wrap the HTTP call in its own module so routes stay small and the client can be reused or mocked in tests.
# app/claude_client.py
import os
import httpx
CLAUDE_BASE_URL = os.getenv("CLAUDE_BASE_URL", "https://api.anthropic.com/v1")
CLAUDE_API_KEY = os.getenv("CLAUDE_API_KEY")
async def send_message(payload: dict) -> dict:
headers = {
"x-api-key": CLAUDE_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
async with httpx.AsyncClient(timeout=60) as client:
response = await client.post(
f"{CLAUDE_BASE_URL}/messages",
headers=headers,
json=payload,
)
response.raise_for_status()
return response.json()
The FastAPI route
# app/main.py
from fastapi import FastAPI, HTTPException
from app.models import ChatRequest
from app.claude_client import send_message
import httpx
app = FastAPI()
@app.post("/chat")
async def chat(request: ChatRequest):
payload = {
"model": request.model,
"max_tokens": request.max_tokens,
"messages": [m.dict() for m in request.messages],
}
if request.system:
payload["system"] = request.system
try:
result = await send_message(payload)
except httpx.HTTPStatusError as exc:
raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
except httpx.TimeoutException:
raise HTTPException(status_code=504, detail="Claude request timed out")
return result
Run it with:
uvicorn app.main:app --reload
Test it:
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Explain FastAPI in one sentence"}]}'
Adding streaming
For chat UIs, users expect tokens as they're generated instead of waiting for the full response. FastAPI supports this with StreamingResponse, forwarding Claude's SSE stream directly to the client.
from fastapi.responses import StreamingResponse
import httpx, json, os
CLAUDE_BASE_URL = os.getenv("CLAUDE_BASE_URL")
CLAUDE_API_KEY = os.getenv("CLAUDE_API_KEY")
async def stream_claude(payload: dict):
headers = {
"x-api-key": CLAUDE_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
payload["stream"] = True
async with httpx.AsyncClient(timeout=None) as client:
async with client.stream(
"POST", f"{CLAUDE_BASE_URL}/messages", headers=headers, json=payload
) as response:
async for line in response.aiter_lines():
if line:
yield f"{line}\n\n"
@app.post("/chat/stream")
async def chat_stream(request: ChatRequest):
payload = {
"model": request.model,
"max_tokens": request.max_tokens,
"messages": [m.dict() for m in request.messages],
}
return StreamingResponse(stream_claude(payload), media_type="text/event-stream")
The frontend can then consume this with EventSource or a fetch reader, same pattern you'd use for any SSE endpoint.
Error handling and timeouts
Three things break production Claude backends more than anything else:
- No timeout handling — a hung connection blocks a worker indefinitely. Always set an explicit
timeouton your HTTP client. - Swallowed error bodies — Claude returns useful error detail in the response body. Pass it through to your own API consumers instead of a generic 500.
- No retry on transient failures — 429s and 503s are common under load. A simple retry with exponential backoff on
httpx.HTTPStatusErrorfor 429/503 status codes goes a long way.
Using SubToAPI instead of calling Anthropic directly
If you don't want to manage raw Anthropic keys, rate limit handling, and per-team usage tracking yourself, you can point the exact same FastAPI code at SubToAPI instead. It turns your Claude access into a standard HTTPS API with sub_live_... keys, so the client module above barely changes:
CLAUDE_BASE_URL = "https://api.subtoapi.app/v1"
CLAUDE_API_KEY = os.getenv("SUBTOAPI_KEY")
Everything else — routes, Pydantic models, streaming logic — stays identical, because the request/response shape follows the same Messages format. See the quickstart, messages docs, and streaming docs for the exact payloads. SubToAPI also adds team seats and usage metadata per key, which is useful once more than one service or developer is calling Claude through your backend. Plans start at €9/month with a free trial at signup; full pricing is on the pricing page.
Wrapping up
A Claude API FastAPI backend doesn't need much: a Pydantic model for validation, a thin async client for the HTTP call, one route for plain responses, and one for streaming. From there, add retries, logging, and timeouts as your traffic grows. Whether you call Anthropic directly or route through a managed layer like SubToAPI, the FastAPI code itself stays the same — only the base URL and key change.
Questions
Does FastAPI require any special SDK to call Claude? No. A standard async HTTP client like httpx is enough — you don't need Anthropic's Python SDK unless you want its convenience methods and built-in retries.
How do I handle long-running Claude requests without blocking FastAPI? Use async def routes with an async HTTP client (httpx.AsyncClient) so requests don't block the event loop, and set explicit timeouts so stuck connections don't tie up workers.
Can I use the same FastAPI code with SubToAPI instead of Anthropic directly? Yes. Change the base URL to https://api.subtoapi.app/v1 and use a sub_live_... key — the request and response format follows the Messages API, so routes and models don't need rewrites. See /docs for details.