How to Add Claude API to an Existing Python Backend
Adding Claude to a Python backend you already have in production is mostly a matter of isolating the AI call behind a small service layer, handling the network and rate-limit edge cases properly, and not leaking your API key into client-facing code. You don't need to rewrite your app or adopt a new framework — Claude slots into Flask, FastAPI, or Django the same way any third-party HTTP API does, as long as you treat it as a dependency with its own timeout, retry, and error-handling rules.
The short version: install the Anthropic Python SDK (or call the REST endpoint directly with requests/httpx), store your API key as an environment variable, wrap the call in a dedicated function or service class, and expose it through whatever route or endpoint your app already uses to talk to the frontend. The rest of this guide walks through each step and the decisions that actually matter — sync vs. async, streaming vs. blocking responses, and how to keep the integration maintainable as it grows.
Step 1: Install and configure the client
If you're using the official SDK:
pip install anthropic
Store the key as an environment variable rather than hardcoding it:
export ANTHROPIC_API_KEY="sk-ant-..."
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
If you'd rather not manage Anthropic key rotation, team seats, and usage tracking yourself, you can point the same client config at a gateway like SubToAPI instead and get a sub_live_... key with per-team usage metadata and streaming support out of the box — see the quickstart for setup.
Step 2: Isolate the call in a service layer
Don't scatter client.messages.create() calls across your route handlers. Wrap it once:
# services/claude.py
from anthropic import Anthropic
import os
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
def ask_claude(prompt: str, system: str = None, max_tokens: int = 1024) -> str:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=max_tokens,
system=system,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
This gives you one place to change models, add retries, or swap the underlying provider later without touching every route that uses Claude.
Step 3: Wire it into your existing routes
Flask:
from flask import Flask, request, jsonify
from services.claude import ask_claude
app = Flask(__name__)
@app.route("/api/ask", methods=["POST"])
def ask():
prompt = request.json.get("prompt")
if not prompt:
return jsonify({"error": "prompt required"}), 400
try:
reply = ask_claude(prompt)
return jsonify({"reply": reply})
except Exception as e:
return jsonify({"error": str(e)}), 502
FastAPI:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from services.claude import ask_claude
app = FastAPI()
class AskRequest(BaseModel):
prompt: str
@app.post("/api/ask")
async def ask(req: AskRequest):
try:
return {"reply": ask_claude(req.prompt)}
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
Django follows the same pattern: a view function that calls your services/claude.py helper and returns a JsonResponse.
The key point is that Claude integration is just another outbound HTTP dependency — treat it with the same discipline you'd apply to a payments API or a third-party email service.
Step 4: Handle latency and streaming
Claude responses for anything beyond short answers can take several seconds. If your backend serves a UI that waits on the full response, users will perceive it as slow even if the model itself is fast. Two options:
- Blocking with a sensible timeout — fine for background jobs, batch processing, or internal tools.
- Streaming — forward tokens as they arrive so the frontend can render incrementally. In FastAPI this means using
StreamingResponseand the SDK's streaming mode; see streaming docs for the request/response shape if you're routing through a gateway instead of calling Anthropic directly.
from fastapi.responses import StreamingResponse
@app.post("/api/ask-stream")
async def ask_stream(req: AskRequest):
def generate():
with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": req.prompt}],
) as stream:
for text in stream.text_stream:
yield text
return StreamingResponse(generate(), media_type="text/plain")
Step 5: Handle failures explicitly
Add Claude to an existing backend, and now your uptime partly depends on a third party. Plan for it:
- Set explicit connect/read timeouts — don't let a slow Claude call hang your worker pool.
- Catch rate-limit (429) and overload (529) responses separately and retry with backoff instead of failing immediately.
- Log token usage per request if cost tracking matters to your product — this becomes important fast once multiple features call the model.
- Keep the Claude-calling code async-safe if your framework is async (FastAPI, Django async views) so one slow AI call doesn't block other requests on the same worker.
If you're adding Claude access for multiple internal teams or need per-key usage breakdowns without building that tracking yourself, a hosted layer like SubToAPI handles key issuance, per-seat usage, and tool-use metadata — worth checking the pricing page if that's a near-term need rather than something you want to build and maintain.
Step 6: Add tool use if your backend needs it to take actions
If the integration needs Claude to call functions in your backend (look up a record, run a calculation, hit another internal API), define tools in the request and handle the tool_use content block in your response parsing. This works the same whether you're calling Anthropic directly or through a gateway — see tool use docs for the schema. Keep tool execution in your service layer too, not in the route handler, so the logic is testable independent of the HTTP framework.
questions
Do I need to change my backend architecture to add Claude? No. Treat the Claude API like any outbound HTTP dependency — a service function, an environment variable for the key, and normal timeout/retry handling. No framework change is required.
Should I call Claude synchronously or use streaming? Use blocking calls for background jobs or short responses. Use streaming when a user is waiting on the output in real time — it reduces perceived latency significantly for longer generations. See docs/streaming for the request format.
How do I avoid exposing my API key to the frontend? Keep all Claude calls server-side. The frontend should only ever talk to your own backend routes (/api/ask, etc.), which hold the key as an environment variable and proxy the request — never ship the key in client-side JavaScript or mobile app code.