Claude API Flask App Integration Example
If you're building a Flask backend and want to add Claude for chat, summarization, or structured data extraction, the integration is straightforward: you send a POST request to a messages endpoint with your API key, a model name, and a list of messages, then parse the response. This article walks through a complete working example, from a minimal /chat route to streaming responses and handling errors properly.
The short answer is: install the Anthropic SDK (or use requests), create a client with your API key, wrap the call in a Flask route, and return the response as JSON. Below is the full pattern, plus the adjustments you need for production use — timeouts, retries, and streaming — which is where most "simple tutorial" code falls apart.
Minimal Flask + Claude Setup
Start with a basic Flask app and the official anthropic Python package.
pip install flask anthropic python-dotenv
# 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"])
@app.route("/chat", methods=["POST"])
def chat():
data = request.get_json()
user_message = data.get("message", "")
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": user_message}]
)
return jsonify({
"reply": response.content[0].text,
"usage": response.usage.model_dump() if response.usage else None
})
if __name__ == "__main__":
app.run(port=5000, debug=True)
This works, but it has three problems you'll hit quickly in a real app: no conversation history, no error handling, and a blocking request that ties up a worker thread for the full duration of the generation.
Adding Conversation History
Claude is stateless — you resend the full message history on every request. A simple way to handle this in Flask is to store history per session or conversation ID.
from flask import session
@app.route("/chat", methods=["POST"])
def chat():
data = request.get_json()
conversation = session.get("history", [])
conversation.append({"role": "user", "content": data["message"]})
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=conversation
)
reply_text = response.content[0].text
conversation.append({"role": "assistant", "content": reply_text})
session["history"] = conversation
return jsonify({"reply": reply_text})
For production apps with multiple users, store history in Redis or a database keyed by user ID instead of Flask's session cookie, which has size limits and isn't suitable for long conversations.
Handling Errors Properly
Anthropic's API returns specific error types — rate limits, overloaded errors, invalid requests — and your Flask app should distinguish between them instead of returning a generic 500.
from anthropic import APIStatusError, APIConnectionError, RateLimitError
@app.route("/chat", methods=["POST"])
def chat():
data = request.get_json()
message = data.get("message")
if not message:
return jsonify({"error": "message field is required"}), 400
try:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": message}]
)
except RateLimitError:
return jsonify({"error": "rate limited, retry shortly"}), 429
except APIConnectionError:
return jsonify({"error": "could not reach Claude API"}), 503
except APIStatusError as e:
return jsonify({"error": str(e)}), e.status_code
return jsonify({"reply": response.content[0].text})
This matters more than it looks — without it, a transient Anthropic-side overload turns into an unhandled exception and a 500 error with a stack trace leaking into your logs or, worse, your response body.
Streaming Responses from Flask
Flask can stream Claude's response token-by-token using a generator and Response with text/event-stream or chunked transfer. This avoids making users wait for the full completion.
from flask import Response, stream_with_context
@app.route("/chat/stream", methods=["POST"])
def chat_stream():
message = request.get_json().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")
If you're serving this with a standard WSGI server (not an async one), be aware that streaming holds a worker for the full duration of the response. For moderate traffic that's fine; for high concurrency, consider gevent workers or moving to an ASGI-based framework for the streaming route specifically.
Running Claude Behind an API Layer Instead
If your Flask app is one of several services calling Claude — a web app, a mobile backend, an internal script — managing the Anthropic key, rate limits, and usage tracking separately in each one gets messy fast. An alternative is to put SubToAPI in front: it turns your Claude access into an HTTPS API with its own sub_live_... keys, so your Flask app calls a single, consistent endpoint instead of juggling raw Anthropic credentials per service.
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": message}]
}
)
This is useful if you want per-service API keys, centralized usage metadata across a team, or streaming and tool use without re-implementing retry logic in every Flask app you deploy. See the quickstart and messages docs for the full request/response shape, and pricing for plan details — there's a free trial at signup.
Deployment Notes
A few things that trip people up when moving from flask run to production:
- Timeouts: Claude generations can take several seconds for long outputs. Make sure your gunicorn/uwsgi timeout and any reverse proxy (nginx, load balancer) timeout are set higher than the default 30s for non-streaming routes.
- Environment variables: Never hardcode API keys. Use
python-dotenvlocally and your platform's secret manager in production. - Concurrency: Synchronous Flask with sync workers blocks one worker per in-flight Claude request. Size your worker count accordingly, or use async-capable deployment if you expect high concurrent chat traffic.
questions
Can I use Flask's built-in dev server for a Claude-backed app in production? No. Use gunicorn or uwsgi behind a reverse proxy. The dev server isn't designed for concurrent requests or the longer response times typical of LLM calls.
Do I need the Anthropic SDK, or can I just use requests? Either works. The SDK handles retries, streaming helpers, and typed responses for you; raw requests calls work fine too and give you more control over timeouts and error parsing.
How do I maintain conversation context across multiple Flask requests? Store the message history server-side (Redis, a database, or a session store) keyed by user or conversation ID, and resend the full history with each new Claude API call, since the API itself is stateless.