Claude API Integration with Python Flask Apps
Integrating the Claude API into a Python Flask application means setting up an Anthropic client, creating a route that forwards requests to Claude, and returning the response (or a stream) to your frontend. The core work is small — a client instance, a /chat endpoint, and some error handling — but doing it in a way that's production-ready involves a few extra steps most tutorials skip: timeouts, streaming, key management, and rate-limit handling.
This guide walks through a working Flask integration from scratch, then covers the parts that matter once you move past a prototype: concurrent requests, retries, and how to avoid rebuilding infrastructure you don't need to.
Setting up the project
Install Flask and the Anthropic Python SDK:
pip install flask anthropic python-dotenv
Store your key in an environment variable rather than hardcoding it:
# .env
ANTHROPIC_API_KEY=sk-ant-...
# app.py
import os
from flask import Flask, request, jsonify
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
This is the minimum you need before writing any routes.
A basic chat endpoint
A single route that accepts a user message and returns Claude's reply:
@app.route("/api/chat", methods=["POST"])
def chat():
data = request.get_json()
message = data.get("message", "")
if not message:
return jsonify({"error": "message is required"}), 400
try:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": message}],
)
return jsonify({"reply": response.content[0].text})
except Exception as e:
return jsonify({"error": str(e)}), 502
if __name__ == "__main__":
app.run(debug=True, port=5000)
Test it with curl:
curl -X POST http://localhost:5000/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "Summarize this ticket in one sentence."}'
This works, but it's blocking, has no retry logic, and sends the entire response at once — fine for internal tools, not great for anything user-facing.
Streaming responses from Flask
For chat UIs, users expect tokens to appear as they're generated. Flask supports this with a generator and Response object:
from flask import Response, stream_with_context
@app.route("/api/chat/stream", methods=["POST"])
def chat_stream():
data = request.get_json()
message = data.get("message", "")
def generate():
with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": message}],
) as stream:
for text in stream.text_stream:
yield text
return Response(stream_with_context(generate()), mimetype="text/plain")
On the frontend, consume this with fetch and a ReadableStream reader, or point an EventSource-style client at it if you switch to server-sent events instead of plain text chunks.
Handling conversation history
Claude's API is stateless — each request needs the full message history. In a Flask app, store history per session or per user ID (in a database, Redis, or session cookie for prototypes) and pass it in on every call:
@app.route("/api/chat", methods=["POST"])
def chat_with_history():
data = request.get_json()
history = data.get("history", [])
message = data.get("message", "")
history.append({"role": "user", "content": message})
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=history,
)
reply = response.content[0].text
history.append({"role": "assistant", "content": reply})
return jsonify({"reply": reply, "history": history})
Keep an eye on token count as history grows — truncate or summarize older turns before context length becomes a cost or latency problem.
Error handling and rate limits
Claude's API returns rate-limit and overload errors that your Flask app needs to handle gracefully rather than surfacing a raw 500 to users:
import time
from anthropic import RateLimitError, APIStatusError
def call_claude_with_retry(messages, retries=3):
for attempt in range(retries):
try:
return client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=messages,
)
except RateLimitError:
time.sleep(2 ** attempt)
except APIStatusError as e:
if e.status_code >= 500 and attempt < retries - 1:
time.sleep(2 ** attempt)
else:
raise
raise Exception("Claude API unavailable after retries")
Wrap this in your route and return a clear error response when retries exhaust, instead of letting Flask's default 500 page leak through.
Skipping the infrastructure work
Everything above — key storage, streaming, retries, rate-limit handling — is work you'll repeat on every project that talks to Claude. If you're building multiple Flask apps or a product that needs to expose Claude to end users with usage tracking per client, it's often faster to put SubToAPI in front of your existing Claude access instead of reimplementing this layer each time.
SubToAPI turns your Claude access into a standard HTTPS API with its own sub_live_... keys, so your Flask app calls one predictable endpoint instead of managing SDK versions, retries, and streaming plumbing directly:
import requests
response = requests.post(
"https://api.subtoapi.app/v1/messages",
headers={"Authorization": f"Bearer {os.environ['SUBTOAPI_KEY']}"},
json={
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Summarize this ticket."}],
},
)
print(response.json())
This is useful specifically when your Flask app is one of several services calling Claude, or when you need per-key usage visibility without building a billing and metering system yourself. Full request formats are in the docs and quickstart guide, including streaming setup in the streaming docs.
Deploying the Flask app
Once the integration works locally, deploy it like any other Flask app — Gunicorn behind Nginx, a container on Fly.io or Render, or a managed platform. The only Claude-specific consideration is making sure your ANTHROPIC_API_KEY (or SUBTOAPI_KEY) is set as an environment variable in your deployment platform, never committed to source control, and that your timeout settings account for longer Claude responses — 30 seconds is a safer default than Flask's implicit short timeouts for streaming endpoints.
Questions
Do I need the official Anthropic SDK, or can I use plain HTTP requests in Flask? Either works. The SDK handles retries, streaming helpers, and type validation for you, but requests or httpx calling the REST endpoint directly is fine for simple integrations.
How do I handle multiple users chatting with Claude at the same time in Flask? Flask's default dev server is single-threaded; use Gunicorn with multiple workers (or an async framework) in production, and keep per-user conversation history in a database or Redis rather than global variables.
Is streaming required for a Flask + Claude integration? No. Streaming improves perceived speed for chat UIs, but a simple request/response endpoint is fine for batch tasks like summarization, classification, or backend automation where there's no live typing effect to show.