Guide: Add an Endpoint
Worked example: GET /agents/{id}/tasks/summary — a task-count summary endpoint — built backend-first, then exposed to the browser. Adapt the steps; keep the contracts.
Step 1 — Define the response schema
class TaskSummaryResponse(BaseModel):
agent_id: int
total: int
open: int
in_progress: int
completed: intRequest bodies (for POST/PUT) get an XRequest schema the same way. Conventions: Schemas.
Step 2 — Write the handler with the house pattern
@router.get("/{agent_id}/tasks/summary", response_model=schemas.TaskSummaryResponse)
def get_task_summary(agent_id: int, 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") # guard first
tasks = db.query(models.Task).filter(models.Task.agent_id == agent_id).all()
return {
"agent_id": agent_id,
"total": len(tasks),
"open": len([t for t in tasks if t.status == "open"]),
"in_progress": len([t for t in tasks if t.status == "in_progress"]),
"completed": len([t for t in tasks if t.status == "completed"]),
}Checklist by endpoint kind:
| If your endpoint… | Then also… |
|---|---|
| executes a tool/LLM | gate with agent_has_tool (403) and log_tool_call success and failure paths (contract) |
| calls an LLM | put the call in a service, time it, attach cost via estimate_openai_cost (conventions) |
| is a multi-stage pipeline | create_workflow_run first, log_workflow_step per stage (engine) |
| writes rows | one db.commit() at the end; db.refresh() anything whose generated fields you return |
Step 3 — Verify against the live server
Hot reload picks the change up; exercise it via Swagger UI (http://localhost:8001/docs) or:
curl http://localhost:8001/agents/1/tasks/summary
curl http://localhost:8001/agents/999/tasks/summary # → 404, confirm the guardStep 4 — Expose to the browser (only if the UI needs it)
Server components can fetch the backend directly (reads pattern). For client-side calls, add the proxy route:
import { NextResponse } from "next/server";
const API_BASE_URL = process.env.AGENTOPS_API_BASE_URL ?? "http://localhost:8001";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params; // Next 15: params is a Promise
try {
const response = await fetch(`${API_BASE_URL}/agents/${id}/tasks/summary`, {
cache: "no-store",
});
const text = await response.text();
let data: unknown = null;
try { data = text ? JSON.parse(text) : null; }
catch { data = { detail: text || "Unexpected backend response" }; }
return NextResponse.json(data, { status: response.status });
} catch {
return NextResponse.json(
{ detail: `Unable to reach ${API_BASE_URL}/agents/${id}/tasks/summary` },
{ status: 502 },
);
}
}Same skeleton as the existing proxy — defensive parse, exact status relay, 502 on unreachable (API Proxy). Mirror the response type in the consuming component and run npm run typecheck.
Step 5 — Document it
Add the route to the API Reference section it belongs to, and update the relevant module page (Routers: Agents). Docs build must stay warning-free.
Related pages
- Coding Standards — the pattern in full
- Add a Tool — when the endpoint should be tool-gated