Lesson 09: API Routers — Overview
"RESTful APIs: The language of the web."
Two Routers, One Prefix
Both routers mount at the same /agents prefix:
# main.py
app.include_router(agents.router) # prefix="/agents", tags=["Agents"]
app.include_router(documents.router) # prefix="/agents", tags=["Documents"]FastAPI happily merges routers sharing a prefix — the OpenAPI docs (/docs) group them by tags instead, which is why Swagger shows an "Agents" section and a separate "Documents" section even though every URL starts with /agents.
The Full Endpoint Count
20 endpoints total across the two files — not 18, a number an earlier draft of this course used. Corrected count:
backend/app/routers/agents.py— 15 endpointsbackend/app/routers/documents.py— 5 endpoints
| File | Method | Path | Purpose |
|---|---|---|---|
| agents.py | POST | /agents/ | Create agent |
| agents.py | GET | /agents/ | List all agents |
| agents.py | GET | /agents/{id} | Get single agent |
| agents.py | POST | /agents/{id}/chat | Chat with agent |
| agents.py | GET | /agents/{id}/runs | Chat run history |
| agents.py | POST | /agents/{id}/email/analyze | Analyze one email |
| agents.py | GET | /agents/{id}/tasks | List tasks for agent |
| agents.py | PUT | /tasks/{id}/status | Update a task's status |
| agents.py | GET | /agents/{id}/tool-calls | List LLM call logs |
| agents.py | POST | /agents/{id}/workflows/email | Run multi-agent email workflow |
| agents.py | GET | /agents/{id}/workflow-runs | List workflow run history |
| agents.py | GET | /workflow-runs/{id}/steps | Steps for one workflow run |
| agents.py | GET | /agents/{id}/dashboard | Aggregated metrics |
| agents.py | GET | /agents/{id}/messages | Paginated message history |
| agents.py | GET | /agents/{id}/stats | ⚠️ Broken — see Lesson 09a |
| documents.py | POST | /agents/{id}/documents/upload | Upload a .txt document |
| documents.py | GET | /agents/{id}/documents | List documents for agent |
| documents.py | GET | /agents/{id}/chunks | List chunks for agent |
| documents.py | POST | /agents/{id}/documents/search | Raw semantic search |
| documents.py | POST | /agents/{id}/documents/ask | Corrective-RAG question answering |
For the line-by-line walkthrough of each, go to the dedicated lessons:
👉 Lesson 09a: Agents Router Deep Dive — all 15 endpoints, plus the 7 module-level helper functions. 👉 Lesson 09b: Documents Router Deep Dive — all 5 endpoints, the upload pipeline, and RAG entry point.
The Recurring Pattern
Every single endpoint in both files follows the same shape:
@router.post("/{agent_id}/chat", response_model=schemas.ChatResponse)
def chat_with_agent(agent_id: int, chat_data: schemas.ChatRequest, db: Session = Depends(get_db)):
agent = db.query(models.Agent).filter(models.Agent.id == agent_id).first()
if agent is None:
raise HTTPException(status_code=404, detail="Agent not found")
# ... call a service function ...
# ... persist the result ...
return result- Validate input — Pydantic does this automatically before the function body even runs, via the
chat_data: schemas.ChatRequestparameter. - Check existence — a manual
.filter(...).first()+if ... is None: raise HTTPException(404, ...). This is repeated in every single endpoint that takes anagent_id— 18 of the 20 endpoints. There is no shared dependency for this (see Lesson 10 for why that's a missed opportunity). - Call a service function — the router never talks to OpenAI, ChromaDB, or builds prompts itself; it delegates to
services/. - Persist and return —
db.add(...),db.commit(),db.refresh(...), then return an ORM object or a dict that FastAPI serializes throughresponse_model.
Endpoints Without a response_model
Two endpoints skip the response_model= argument entirely: POST /agents/{id}/email/analyze and (implicitly, since it declares one but the body doesn't match it) the broken /stats endpoint. Skipping response_model means FastAPI returns whatever dict the function produces, unvalidated and undocumented in the OpenAPI schema — a smaller version of the same problem the /stats endpoint has in a worse form.
Key Patterns
✅ Validate input with Pydantic
✅ Check existence (404 if not found)
✅ Call service functions — routers never own business logic
✅ Log a ToolCall for anything that talks to an LLM
✅ Save to database, then return
Next Steps
👉 Lesson 09a: Agents Router Deep Dive →
Module 4 · API & Business Logic