REST API Design
Base URL
/api/
No versioning prefix — breaking changes handled via new endpoints, not version bumps, for this MVP.
Authentication
All /api/ routes require a JWT Bearer token in the Authorization header.
Authorization: Bearer <JWT_TOKEN>
- Missing or invalid token →
401 Unauthorized - Valid token, insufficient role →
403 Forbidden - Public endpoints:
/api/auth/login,/api/auth/register
Response Format
FastAPI returns responses directly as JSON. No envelope wrapper.
Success (single object):
{ "id": 1, "username": "alice", "role": "student" }
Success (list):
[
{ "id": 1, "title": "Linux Basics" },
{ "id": 2, "title": "File Systems" }
]
Error (FastAPI default):
{ "detail": "Not found" }
Validation error (422):
{
"detail": [
{ "loc": ["body", "email"], "msg": "value is not a valid email address", "type": "value_error" }
]
}
HTTP Status Codes
| Status | When |
|---|---|
| 200 | Success (GET, PUT, DELETE) |
| 201 | Created (POST) — not always used |
| 400 | Bad request / validation error |
| 401 | Missing or invalid JWT |
| 403 | Valid JWT, insufficient permissions |
| 404 | Entity doesn't exist |
| 422 | Unprocessable entity (Pydantic validation) |
| 500 | Unexpected server error |
Admin Endpoint Convention
Admin endpoints use query parameters (not request bodies) for create/update operations. This accommodates FastAPI's auto-documentation and avoids body-parsing complexity for simple CRUD.
# Pattern in admin.py
@router.put("/lessons/modules/{module_id}")
async def update_module(
module_id: int,
name: Optional[str] = None,
description: Optional[str] = None,
order_index: Optional[int] = None,
):
Frontend sends as URLSearchParams:
const params = new URLSearchParams()
if (updates.name) params.append('name', updates.name)
await api.put(`/api/admin/lessons/modules/${id}?${params.toString()}`)
URL Conventions
# Auth
POST /api/auth/login
POST /api/auth/register
GET /api/auth/me
# Lessons
GET /api/lessons/modules
GET /api/lessons/themes/{theme_id}
GET /api/lessons/themes/{theme_id}/content
POST /api/lessons/themes/{theme_id}/complete
# Sandbox
GET /api/sandbox-templates
POST /api/containers/create
GET /api/containers/sandbox
GET /api/containers/{id}
POST /api/containers/{id}/start
POST /api/containers/{id}/stop
DELETE /api/containers/{id}
# Exams
GET /api/exams
GET /api/exams/{id}
POST /api/exams/{id}/start
POST /api/exams/{id}/submit
GET /api/exams/attempts/{id}/results
# Labs
GET /api/laboratories/{id}
GET /api/laboratories/section/{section_id}/laboratory
POST /api/laboratories/{id}/start
POST /api/laboratories/{id}/submit
GET /api/laboratories/attempts/{id}/results
# Dashboard
GET /api/dashboard/stats
GET /api/dashboard/activity
GET /api/dashboard/analytics
# Profile
GET /api/profile/me
PUT /api/profile/me
POST /api/profile/avatar
DELETE /api/profile/avatar
# Admin (require admin role)
GET /api/admin/stats
GET /api/admin/users
POST /api/admin/users
PUT /api/admin/users/{id}/role
DELETE /api/admin/users/{id}
# ... (see SPEC.md for full admin API)
# WebSocket (terminal)
WS /ws/terminal/{container_id}?token={jwt}
Pagination
Currently not implemented on most endpoints — will be added in v0.3.0.
Planned convention:
GET /api/admin/users?page=1&page_size=20
Response will include pagination metadata in the response or as HTTP headers (TBD).
Dates
All UTC, ISO 8601: 2026-03-01T10:30:00Z
Router Organization
Routers are organized by domain and imported in main.py:
| Router | File | Prefix |
|---|---|---|
| Lessons | api/lessons.py | /api/lessons |
| Exams | api/exams.py | /api/exams |
| Laboratories | api/laboratories.py | /api/laboratories |
| Dashboard | api/dashboard.py | /api/dashboard |
| Profile | api/profile.py | /api/profile |
| Admin | api/admin.py | /api/admin |
| Modules | api/modules.py | /api |
| Auth + Containers | main.py | /api |