← Blog

Claude API Flask App Integration Example

2026-11-09 · 5 min read · SubToAPI Team

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:

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.

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 →