Skip to main content

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

StatusWhen
200Success (GET, PUT, DELETE)
201Created (POST) — not always used
400Bad request / validation error
401Missing or invalid JWT
403Valid JWT, insufficient permissions
404Entity doesn't exist
422Unprocessable entity (Pydantic validation)
500Unexpected 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:

RouterFilePrefix
Lessonsapi/lessons.py/api/lessons
Examsapi/exams.py/api/exams
Laboratoriesapi/laboratories.py/api/laboratories
Dashboardapi/dashboard.py/api/dashboard
Profileapi/profile.py/api/profile
Adminapi/admin.py/api/admin
Modulesapi/modules.py/api
Auth + Containersmain.py/api