A fully validated API with Pydantic models for every endpoint, custom error responses with clear messages, and input sanitization that prevents the most common API failures.
Pydantic Models: Your First Line of Defense
Pydantic models define what data your API accepts. FastAPI uses them to automatically validate incoming requests and return clear errors when data is wrong — before your code even runs.
from pydantic import BaseModel, Field, validator
from typing import Literal
class TextRequest(BaseModel):
text: str = Field(
min_length=1,
max_length=10000,
description="The text to process"
)
tone: Literal["formal", "casual", "technical"] = "formal"
@validator("text")
def strip_whitespace(cls, v):
stripped = v.strip()
if not stripped:
raise ValueError("text cannot be only whitespace")
return stripped
class APIResponse(BaseModel):
result: str
model: str
tokens_used: intHTTP Status Codes and Error Responses
Every HTTP response has a status code. 200 means success. 400 means the caller made a mistake. 500 means something broke on your end. Using the right codes makes your API usable by other developers.
from fastapi import FastAPI, HTTPException, status
import anthropic
app = FastAPI()
client = anthropic.Anthropic()
@app.post("/analyze", status_code=status.HTTP_200_OK)
def analyze(req: TextRequest):
try:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": req.text}]
)
return {"result": response.content[0].text}
except anthropic.AuthenticationError:
raise HTTPException(
status_code=503,
detail="AI service configuration error"
)
except anthropic.RateLimitError:
raise HTTPException(
status_code=429,
detail="Rate limit exceeded. Try again in a moment."
)API Keys and Authentication
Your API needs its own authentication — callers need to prove who they are before using it. The simplest approach: API key in the request header.
from fastapi import Header, HTTPException
import os
VALID_API_KEYS = {os.getenv("APP_API_KEY")}
def verify_api_key(x_api_key: str = Header(...)):
if x_api_key not in VALID_API_KEYS:
raise HTTPException(
status_code=401,
detail="Invalid API key"
)
# Use as dependency on protected routes:
# @app.post("/analyze", dependencies=[Depends(verify_api_key)])What You Learned Today
- How Pydantic models validate input before your handler code runs
- The key HTTP status codes: 200 (ok), 400 (bad request), 401 (unauthorized), 429 (rate limit), 500 (server error)
- How to catch specific Anthropic API errors and return appropriate status codes
- How to add API key authentication using FastAPI's dependency injection
Go Further on Your Own
- Add a custom exception handler that catches all unhandled exceptions and returns a clean 500 error without leaking stack traces
- Add request ID tracking: generate a UUID per request and include it in every response. This is essential for debugging production issues.
- Add rate limiting: allow a maximum of 10 requests per minute per API key using a simple in-memory dictionary (for production you'd use Redis)
Nice work. Keep going.
Day 3 is ready when you are.
Continue to Day 3Want live instruction and hands-on projects? Join the AI bootcamp — 3 days, 5 cities.