Skip to main content
Developer TutorialPython

Building Agent-Ready APIs with Django and Flask

The companion to our Next.js tutorial, built for Python developers. Eight copy-paste code blocks that take a Django REST Framework or Flask API from invisible to agent-ready. Structured errors, OpenAPI spec, health endpoint, agent-card.json, llms.txt, CORS, and Bearer auth — everything an AI agent needs to discover and use your API.

AH
AgentHermes Research
April 15, 202614 min read

Why a Python-Specific Guide Matters

Python powers a significant portion of production APIs. Django REST Framework alone is used by companies from Instagram to Mozilla, and Flask is the go-to for lightweight microservices. Yet most agent readiness tutorials assume Node.js or Next.js. Python developers are left translating concepts into their framework's patterns.

This guide provides framework-native code for both Django and Flask. Every step shows the Django way and the Flask way side by side. You do not need to adapt from JavaScript examples — the code below drops directly into your Python project.

8
steps to agent-ready
60+
target score
2
frameworks covered
~2h
implementation time

8 Steps to an Agent-Ready Python API

Each step targets specific dimensions of the Agent Readiness Score. Implement all 8 for maximum coverage. Each code block is production-ready — copy, paste, adjust the names, and deploy.

1

Structured JSON Error Responses (Django REST Framework)

Replace Django's default HTML error pages with structured JSON that agents can parse. DRF has a custom exception handler hook. Override it to always return { error, message, code, request_id }. This is the single highest-impact change for agent readiness.

Score impact: D2 API Quality (15%) + D6 Data Quality (10%)

# myproject/exceptions.py
import uuid
from rest_framework.views import exception_handler
from rest_framework.response import Response

def agent_exception_handler(exc, context):
    response = exception_handler(exc, context)
    if response is not None:
        response.data = {
            "error": response.data.get("detail", "Unknown error"),
            "code": response.status_code,
            "message": str(exc),
            "request_id": str(uuid.uuid4()),
        }
    else:
        response = Response(
            {
                "error": "internal_error",
                "code": 500,
                "message": "An unexpected error occurred",
                "request_id": str(uuid.uuid4()),
            },
            status=500,
        )
    return response

# settings.py
REST_FRAMEWORK = {
    "EXCEPTION_HANDLER": "myproject.exceptions.agent_exception_handler",
}
2

Structured JSON Error Responses (Flask)

Flask uses error handlers registered on the app. Register handlers for 400, 404, 405, 422, and 500 to return structured JSON. Without these, Flask returns HTML error pages that agents cannot parse.

Score impact: D2 API Quality (15%) + D6 Data Quality (10%)

# app.py
import uuid
from flask import Flask, jsonify

app = Flask(__name__)

@app.errorhandler(400)
@app.errorhandler(404)
@app.errorhandler(405)
@app.errorhandler(422)
@app.errorhandler(500)
def handle_error(error):
    return jsonify({
        "error": getattr(error, "name", "Unknown error"),
        "code": getattr(error, "code", 500),
        "message": getattr(error, "description", "An error occurred"),
        "request_id": str(uuid.uuid4()),
    }), getattr(error, "code", 500)
3

OpenAPI Spec with drf-spectacular (Django)

drf-spectacular auto-generates an OpenAPI 3.0 spec from your DRF views, serializers, and type annotations. Agents use this spec to discover every endpoint, parameter, and response shape without documentation. This is the discovery layer that makes your API machine-readable.

Score impact: D1 Discovery (12%) + D3 Onboarding (8%)

# Install: pip install drf-spectacular
# settings.py
INSTALLED_APPS = [
    ...
    "drf_spectacular",
]

REST_FRAMEWORK = {
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

SPECTACULAR_SETTINGS = {
    "TITLE": "My App API",
    "DESCRIPTION": "Agent-ready API for My App",
    "VERSION": "1.0.0",
    "SERVE_INCLUDE_SCHEMA": False,
}

# urls.py
from drf_spectacular.views import (
    SpectacularAPIView,
    SpectacularSwaggerView,
)

urlpatterns = [
    path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
    path("api/docs/", SpectacularSwaggerView.as_view(), name="docs"),
]
4

OpenAPI Spec with flask-smorest (Flask)

flask-smorest (formerly flask-rest-api) generates OpenAPI specs from your Flask views and marshmallow schemas. It decorates Blueprint routes with schema information that gets compiled into a machine-readable spec at /api/openapi.json.

Score impact: D1 Discovery (12%) + D3 Onboarding (8%)

# Install: pip install flask-smorest marshmallow
from flask import Flask
from flask_smorest import Api, Blueprint, abort
from marshmallow import Schema, fields

app = Flask(__name__)
app.config["API_TITLE"] = "My App API"
app.config["API_VERSION"] = "1.0.0"
app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/api"
app.config["OPENAPI_JSON_PATH"] = "openapi.json"

api = Api(app)

class ItemSchema(Schema):
    id = fields.Int(dump_only=True)
    name = fields.Str(required=True)
    price = fields.Float(required=True)

blp = Blueprint("items", __name__, url_prefix="/api/items")

@blp.route("/")
@blp.response(200, ItemSchema(many=True))
def get_items():
    """List all items with pricing."""
    return Item.query.all()

api.register_blueprint(blp)
5

/health Endpoint

A /health endpoint tells agents whether your service is operational. Both Django and Flask make this trivial. Return { status, timestamp, version } at minimum. Agents check this before attempting any other call, and monitoring services use it for uptime tracking. This directly impacts D8 Reliability (13% weight).

Score impact: D8 Reliability (13%)

# Django: health/views.py
from django.http import JsonResponse
from django.utils import timezone

def health_check(request):
    return JsonResponse({
        "status": "healthy",
        "timestamp": timezone.now().isoformat(),
        "version": "1.0.0",
        "service": "my-app",
    })

# urls.py
urlpatterns = [
    path("health", health_check, name="health"),
]

# ---

# Flask: app.py
from datetime import datetime, timezone

@app.route("/health")
def health():
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "version": "1.0.0",
        "service": "my-app",
    })
6

agent-card.json as a Static File

Serve agent-card.json at /.well-known/agent-card.json. This is the discovery file that AI agents look for first. In Django, use the static files system or a dedicated view. In Flask, serve it from the static folder or a route. The file describes your API capabilities, authentication requirements, and contact information.

Score impact: D9 Agent Experience (10%)

# Django: serve via URL route
# views.py
import json
from django.http import JsonResponse

AGENT_CARD = {
    "name": "My App",
    "description": "What your app does, in one sentence",
    "url": "https://myapp.com",
    "version": "1.0.0",
    "capabilities": {
        "rest": {
            "openapi": "https://myapp.com/api/schema/"
        }
    },
    "authentication": {
        "type": "bearer",
        "token_url": "https://myapp.com/api/auth/token"
    },
    "contact": {
        "email": "api@myapp.com",
        "docs": "https://myapp.com/api/docs/"
    }
}

def agent_card(request):
    return JsonResponse(AGENT_CARD)

# urls.py
urlpatterns = [
    path(".well-known/agent-card.json", agent_card),
]

# ---

# Flask: serve from route
@app.route("/.well-known/agent-card.json")
def agent_card():
    return jsonify({
        "name": "My App",
        "description": "What your app does",
        "url": "https://myapp.com",
        "version": "1.0.0",
        "capabilities": {
            "rest": {"openapi": "https://myapp.com/api/openapi.json"}
        },
        "authentication": {"type": "bearer"},
        "contact": {"email": "api@myapp.com"}
    })
7

llms.txt Route

Serve a plain-text file at /llms.txt that describes your service for AI models in natural language. Unlike agent-card.json (structured metadata), llms.txt is prose that helps LLMs understand what your service does, its key endpoints, and common use cases. Both Django and Flask can serve this as a plain text response.

Score impact: D9 Agent Experience (10%)

# Django: views.py
from django.http import HttpResponse

def llms_txt(request):
    content = """# My App

## What This Service Does
My App is a [description]. It helps users [value prop].

## API Access
- Base URL: https://myapp.com/api
- Authentication: Bearer token
- OpenAPI spec: https://myapp.com/api/schema/
- Rate limit: 100 requests per minute

## Key Endpoints
- GET /api/items/ - List items with pagination
- GET /api/items/{id}/ - Get item details
- POST /api/orders/ - Create an order
- GET /api/availability/ - Check availability

## Error Handling
All errors return JSON: { error, message, code, request_id }
Retry on 429 with the Retry-After header value.
"""
    return HttpResponse(
        content,
        content_type="text/plain; charset=utf-8",
    )

# urls.py
urlpatterns = [
    path("llms.txt", llms_txt),
]
8

CORS and Bearer Authentication

AI agents run from different origins and authenticate with Bearer tokens. Django needs django-cors-headers for CORS and DRF TokenAuthentication for Bearer auth. Flask needs flask-cors and a token check decorator. Without CORS, agents get blocked at the preflight. Without Bearer auth, there is no standard way for agents to authenticate.

Score impact: D7 Security (12%) + D2 API Quality (15%)

# Django: pip install django-cors-headers
# settings.py
INSTALLED_APPS = [..., "corsheaders"]
MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    ...
]
CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOWED_ORIGINS = [
    "https://your-allowed-origins.com",
]
CORS_ALLOW_HEADERS = [
    "authorization", "content-type",
    "x-request-id", "accept",
]

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
    ],
}

# ---

# Flask: pip install flask-cors
from flask_cors import CORS
from functools import wraps

CORS(app, origins=["https://your-allowed-origins.com"])

def require_token(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.headers.get("Authorization", "")
        if not auth.startswith("Bearer "):
            return jsonify({
                "error": "unauthorized",
                "code": 401,
                "message": "Bearer token required",
                "request_id": str(uuid.uuid4()),
            }), 401
        token = auth.split(" ", 1)[1]
        if not validate_token(token):
            return jsonify({
                "error": "invalid_token",
                "code": 401,
                "message": "Invalid or expired token",
                "request_id": str(uuid.uuid4()),
            }), 401
        return f(*args, **kwargs)
    return decorated

Combined Score Impact

Implementing all 8 steps touches 6 of the 9 scoring dimensions. Here is how each dimension is affected and the total weight covered.

Dimension
Weight
Steps That Impact It
D1 Discovery
12%
OpenAPI spec (Steps 3-4)
D2 API Quality
15%
JSON errors (Steps 1-2), CORS + Auth (Step 8)
D3 Onboarding
8%
OpenAPI spec (Steps 3-4)
D6 Data Quality
10%
JSON errors (Steps 1-2)
D7 Security
12%
Bearer auth + CORS (Step 8)
D8 Reliability
13%
/health endpoint (Step 5)
D9 Agent Experience
10%
agent-card.json (Step 6), llms.txt (Step 7)

Total weight covered: 80%. The remaining 20% comes from D4 Pricing Transparency (5%), D5 Payment (8%), and the Agent-Native Bonus (7%). Those require business-specific implementation — pricing endpoints, payment integration, and MCP server setup. See our MCP server tutorial for the next steps.

Frequently Asked Questions

Should I use Django or Flask for an agent-ready API?

Use Django REST Framework if you need a full-featured API with authentication, permissions, serialization, and automatic admin. Use Flask if you want a lightweight API with fewer abstractions. Both can reach Gold tier (75+) agent readiness. Django gets you there faster due to built-in auth and drf-spectacular; Flask gives you more control over every response.

Do I need both agent-card.json and llms.txt?

Yes. They serve different purposes. agent-card.json is structured metadata that agents parse programmatically — it declares your endpoints, auth type, and capabilities. llms.txt is natural language that helps AI models understand your service contextually. Having both maximizes your D9 Agent Experience score (10% weight). Missing either leaves points on the table.

How does this compare to the Next.js tutorial?

The concepts are identical — JSON errors, OpenAPI, health endpoint, agent-card.json, llms.txt, CORS, and Bearer auth. The implementation differs because Python frameworks handle routing, middleware, and serialization differently than Next.js. If your team uses Python, follow this guide. If they use Node/React, follow the Next.js tutorial. Both achieve the same agent readiness improvements.

What score improvement can I expect from implementing all 8 steps?

A typical Python API with no agent infrastructure starts at 10-20/100. Implementing all 8 steps addresses D1 Discovery, D2 API Quality, D6 Data Quality, D7 Security, D8 Reliability, and D9 Agent Experience — covering over 80% of the scoring weight. Most implementations reach Silver (60+) or Gold (75+) depending on existing API maturity and documentation quality.


Test your implementation

After implementing these 8 steps, run your API through the Agent Readiness Scanner. See exactly which dimensions improved and what to tackle next.


Share this article: