Claude API Flask Integration Tutorial
Claude API Flask Integration Tutorial
If you're building a Python backend and want to add Claude to a Flask app, the integration is straightforward: install an HTTP client or SDK, store your API key as an environment variable, create a route that forwards the user's input to Claude, and return the response as JSON or a stream. This guide walks through each step with working code, including how to handle streaming output in a Flask route and how to avoid the common pitfalls that trip up first-time integrations (timeouts, key management, and rate limits).
The core pattern is the same whether you're calling Claude directly through Anthropic's SDK or through a proxy like SubToAPI that gives you a standard REST API key. Below we'll build a minimal Flask app, then extend it with streaming and error handling.
Setting Up the Flask Project
Start with a clean virtual environment and install Flask plus the requests library (or the official Anthropic SDK if you prefer):
python -m venv venv
source venv/bin/activate
pip install flask requests python-dotenv
Create a .env file to hold your API key — never hardcode it in source:
SUBTOAPI_KEY=sub_live_xxxxxxxxxxxxxxxx
If you're using SubToAPI instead of managing raw Anthropic credentials, your key looks like sub_live_... and works the same way across environments, which makes it easier to swap between dev and prod without touching code. See /docs/quickstart for how keys are generated.
A Minimal Flask Route
Here's a basic app that accepts a prompt and returns Claude's response:
import os
import requests
from flask import Flask, request, jsonify
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
API_KEY = os.environ["SUBTOAPI_KEY"]
API_URL = "https://api.subtoapi.app/v1/messages"
@app.route("/chat", methods=["POST"])
def chat():
data = request.get_json()
user_message = data.get("message", "")
if not user_message:
return jsonify({"error": "message is required"}), 400
response = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": user_message}],
},
timeout=30,
)
if response.status_code != 200:
return jsonify({"error": response.text}), response.status_code
result = response.json()
return jsonify({"reply": result["content"][0]["text"]})
if __name__ == "__main__":
app.run(debug=True, port=5000)
Test it with curl:
curl -X POST http://localhost:5000/chat \
-H "Content-Type: application/json" \
-d '{"message": "Explain Flask blueprints in two sentences"}'
This gives you a working endpoint, but a 30-second blocking request isn't ideal for a production app, especially for longer generations. That's where streaming comes in.
Streaming Claude's Response in Flask
Flask supports streaming responses natively through generator functions and the Response object. This lets your frontend start rendering Claude's output token by token instead of waiting for the full completion.
from flask import Response, stream_with_context
import json
@app.route("/chat/stream", methods=["POST"])
def chat_stream():
data = request.get_json()
user_message = data.get("message", "")
def generate():
with requests.post(
API_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"stream": True,
"messages": [{"role": "user", "content": user_message}],
},
stream=True,
timeout=60,
) as resp:
for line in resp.iter_lines():
if line:
yield f"{line.decode('utf-8')}\n\n"
return Response(stream_with_context(generate()), mimetype="text/event-stream")
The response comes back as server-sent events, which you can consume directly in the browser with EventSource or a fetch call that reads the response body as a stream. Full event formats and delta types are documented at /docs/streaming — worth reading before you parse events manually, since content deltas, tool-use blocks, and stop events all have distinct shapes.
Adding Tool Use (Function Calling)
If your Flask app needs Claude to call out to your own functions — looking up a database record, hitting an internal API, running a calculation — you pass tool definitions in the request body and handle the tool_use block in the response:
tools = [
{
"name": "get_order_status",
"description": "Look up the status of a customer order by ID",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
}
]
payload = {
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"tools": tools,
"messages": [{"role": "user", "content": "Where is order 4821?"}],
}
When Claude decides to call get_order_status, the response contains a tool_use content block with the arguments. Your Flask route executes the actual lookup, then sends the result back in a follow-up message with role "tool_result". The full request/response shapes for multi-turn tool conversations are in /docs/tools.
Error Handling and Production Concerns
A few things worth building in from day one:
- Timeouts: set a sane
timeouton your HTTP calls and catchrequests.exceptions.Timeoutexplicitly, returning a clear error to the client instead of letting Flask hang. - Retries with backoff: transient 429s and 5xxs should be retried once or twice with exponential backoff before surfacing an error.
- Key storage: use environment variables or a secrets manager, never commit keys. If you're on SubToAPI, you can rotate
sub_live_...keys from the dashboard without redeploying. - Usage visibility: for team projects, it helps to know which routes or users are consuming the most tokens. SubToAPI's dashboard includes per-key usage metadata, which is often easier than instrumenting this yourself from scratch — check /pricing for how that scales across Solo, Team, and Scale plans.
- Async workers: for high-traffic Flask apps, consider running Claude calls through Celery or a background worker queue rather than blocking the request thread, especially for longer generations.
Deploying the App
Once your routes work locally, deploy with Gunicorn behind Nginx, or to a platform like Render, Fly.io, or a container on your cloud provider of choice. Make sure streaming responses aren't buffered by your reverse proxy — Nginx needs proxy_buffering off on the streaming route, and Gunicorn should run with enough workers to handle concurrent long-lived connections.
If you want to skip setting up Anthropic account billing, key rotation, and usage tracking yourself, signing up at /signup gives you a working API key in minutes and the same Messages-compatible endpoints shown above — see /docs/messages for the full request reference.
Questions
Do I need the official Anthropic Python SDK to use Flask with Claude? No. Plain HTTP requests via requests or httpx work fine, as shown above. The SDK adds convenience wrappers but isn't required for a Flask integration.
How do I handle long-running Claude requests without blocking Flask? Use streaming responses with a generator and Response(stream_with_context(...)), or offload the call to a background worker (Celery, RQ) and poll or use websockets for the result.
Can I use the same Flask code with SubToAPI instead of calling Claude directly? Yes. Swap the base URL to https://api.subtoapi.app/v1/messages and your sub_live_... key — the request and response shapes match the examples above, so no other code changes are needed.