← Blog

Claude API Flask Integration Tutorial

2026-10-21 · 5 min read · SubToAPI Team

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:

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.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →