Claude API Django App Integration Guide
Integrating the Claude API into a Django app means adding a service layer that calls an HTTPS endpoint from your views or background tasks, storing the API key safely in your settings, and handling streaming or long-running requests without blocking Django's request/response cycle. The core pattern is simple: a small client wrapper, a view that calls it, and proper handling of timeouts, retries and async requests if you need streaming in a WSGI-based app.
This guide walks through a practical Django integration — project structure, environment configuration, a reusable service class, a view example, and the specific gotchas that come up with Django's synchronous request model (streaming, timeouts, Celery for long jobs). It also shows how to simplify the whole thing by going through SubToAPI, which turns your Claude access into a standard HTTPS API with its own key management, so you don't have to build retry logic, usage tracking and team access controls yourself.
Project setup
Start by keeping API credentials out of your codebase. Add them to your environment and read them through Django settings:
# settings.py
import os
CLAUDE_API_KEY = os.environ.get("CLAUDE_API_KEY")
CLAUDE_API_BASE = os.environ.get("CLAUDE_API_BASE", "https://api.anthropic.com/v1")
If you're using SubToAPI instead of calling Anthropic directly, the same pattern applies — just point the base URL and key at your SubToAPI application key:
SUBTOAPI_KEY = os.environ.get("SUBTOAPI_KEY")
SUBTOAPI_BASE = "https://api.subtoapi.app/v1"
This is useful if you want a single dashboard across multiple Django services, per-app keys for a staging vs production split, and usage metadata without writing your own logging.
A reusable service layer
Don't call the API directly from views. Wrap it in a service module so you can swap providers, add retries, and unit-test the logic independently of Django's request cycle.
# myapp/services/claude_client.py
import requests
from django.conf import settings
class ClaudeClient:
def __init__(self, api_key=None, base_url=None):
self.api_key = api_key or settings.CLAUDE_API_KEY
self.base_url = base_url or settings.CLAUDE_API_BASE
def send_message(self, messages, model="claude-3-5-sonnet-20241022", max_tokens=1024):
response = requests.post(
f"{self.base_url}/messages",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
json={
"model": model,
"max_tokens": max_tokens,
"messages": messages,
},
timeout=30,
)
response.raise_for_status()
return response.json()
Keep timeout explicit. Django's default WSGI worker will happily hang on a slow upstream call and tie up a worker thread, which affects everyone else hitting your app at the same time.
Calling it from a view
# myapp/views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import json
from .services.claude_client import ClaudeClient
@csrf_exempt
def ask_claude(request):
if request.method != "POST":
return JsonResponse({"error": "POST required"}, status=405)
body = json.loads(request.body)
user_message = body.get("message", "")
client = ClaudeClient()
try:
result = client.send_message(
messages=[{"role": "user", "content": user_message}]
)
except Exception as e:
return JsonResponse({"error": str(e)}, status=502)
return JsonResponse(result)
This is enough for a basic prototype: a POST endpoint, a service call, a JSON response. In production you'll want to add input validation, rate limiting, and authentication on this endpoint since every call costs tokens.
Handling streaming in Django
Django's synchronous views don't naturally support server-sent streaming the way an async framework does, but StreamingHttpResponse works fine for proxying a streamed API response to the browser:
from django.http import StreamingHttpResponse
import requests
def stream_claude(request):
def event_stream():
with requests.post(
f"{settings.CLAUDE_API_BASE}/messages",
headers={"Authorization": f"Bearer {settings.CLAUDE_API_KEY}"},
json={"model": "claude-3-5-sonnet-20241022", "stream": True, "messages": [...]},
stream=True,
timeout=60,
) as resp:
for chunk in resp.iter_lines():
if chunk:
yield chunk + b"\n"
return StreamingHttpResponse(event_stream(), content_type="text/event-stream")
}
If you use Django Channels or ASGI deployment, you can do this more cleanly with an async view and httpx.AsyncClient, which avoids blocking a worker thread for the full duration of the stream. SubToAPI's streaming docs cover the equivalent SSE format if you're routing through its API — the response shape is the same whether you call Anthropic directly or go through SubToAPI's sub_live_ keys.
Offloading long requests to Celery
For requests that might take a while — long documents, multi-step tool use, batch processing — don't do it synchronously inside a view. Push it to a Celery task and return a job ID:
# tasks.py
from celery import shared_task
from .services.claude_client import ClaudeClient
@shared_task
def process_with_claude(message_text):
client = ClaudeClient()
return client.send_message(messages=[{"role": "user", "content": message_text}])
The view enqueues the task and returns immediately; a separate endpoint or websocket notifies the client when the result is ready. This keeps your Django workers free and avoids request timeouts on slow model calls.
Tool use and structured outputs
If your Django app needs Claude to call functions — looking up a database record, triggering a workflow — you define tool schemas in the request payload and parse tool_use blocks from the response in your service layer. This logic belongs in the ClaudeClient class, not scattered across views, so you can test it with mocked responses. See /docs/tools for the request/response shape if you're using SubToAPI's tool-use pass-through.
Where SubToAPI fits
If you're building a Django app for a team rather than a personal project, the pain points are usually: managing API keys per environment, tracking usage per user or per feature, and giving teammates access without sharing a single raw key. SubToAPI handles this by issuing scoped sub_live_ keys per application, giving you usage metadata out of the box, and letting you manage seats under Solo (€9), Team (€19/seat) or Scale (€49/seat) plans. You can wire it into the ClaudeClient class above by just changing the base URL and key — the request/response format matches what's documented at /docs/messages. Start with a free trial at /signup or check the quickstart for a five-minute setup.
Questions
Do I need an async Django setup to use the Claude API? No. Standard synchronous Django views work fine for request/response calls. Async or Channels only helps if you want non-blocking streaming without tying up a WSGI worker for the duration of the response.
Where should I store the API key in a Django project? In environment variables, read via os.environ into settings.py, never hardcoded or committed to version control. Use separate keys for staging and production.
How do I avoid Django request timeouts on slow Claude responses? Set an explicit timeout on your HTTP client, and for genuinely long operations use Celery to process the request asynchronously instead of blocking inside the view.