REST API Reference¶
The API is built on Django REST Framework and lives in the api/ package. All endpoints return JSON and require an authenticated session.
Source: api/viewsets.py, api/serializers.py, rota/urls.py.
Architecture¶
| Aspect | Detail |
|---|---|
| Framework | Django REST Framework |
| Authentication | SessionAuthentication -- cookie-based Django sessions |
| Permissions | IsAuthenticated on all endpoints (global default) |
| Schema | DRF Spectacular (OpenAPI 3.0) at /api/schema/ |
| Throttling | Global burst: 20/min (anonymous); per-view rates below |
| URL prefix | /api/ (unversioned), /api/v1/, /api/v2/ |
Throttling Tiers¶
| Tier | Rate | Used by |
|---|---|---|
standard |
100/hour | CRUD ViewSets (clinicians, shifts, leave, alerts, working terms) |
expensive_calculation |
10/hour | Statistics, forecast, workload, availability, bulk calculations |
burst (default) |
20/min | All anonymous requests |
Cross-cutting Concerns¶
All ViewSets inherit from APIRequestLoggingMixin (api/mixins.py), which logs:
- Request method, path, user, and query params
- Response status code and duration
- Errors with stack traces
- Slow requests exceeding 1.0 second threshold
Authentication¶
The API uses Django's built-in session authentication via django-allauth. There is no token or API key system.
Requirements for every request:
- A valid Django session cookie (obtained by logging in through
/accounts/login/) - A CSRF token header for unsafe methods (POST, PATCH, DELETE)
| Concern | Detail |
|---|---|
| Login URL | /accounts/login/ (django-allauth) |
| CSRF header | X-CSRFToken |
| CSRF cookie | csrftoken |
| CSRF cookie SameSite | Lax |
| Login redirect | / |
Include the CSRF token in the X-CSRFToken header for any write request:
API Versioning¶
Unversioned (/api/)¶
Deprecated — redirects to v1. Registered via DefaultRouter in rota/urls.py. Includes all resource ViewSets plus standalone endpoints for rota generation, rebalancing, statistics, and availability. Use /api/v1/ for new integrations.
v1 (/api/v1/)¶
Registered in api/v1/urls.py. Contains:
ClinicianViewSetV1-- full CRUD plusstatsandactiveactions (same shape as unversioned)StatisticsViewSetV1--list,forecast,workload(wraps responses withversionkey)
v1 responses include a version field and slightly different envelope structures.
v2 (/api/v2/)¶
Placeholder. api/v2/urls.py registers no ViewSets yet. The v2 router and URLconf exist for forward compatibility.
Endpoints¶
Clinicians¶
Managed by ClinicianViewSet (router-registered at /api/clinicians/).
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/clinicians/ |
List all clinicians. Supports ?search=<name> and ?ordering=<field>. |
Any user |
| POST | /api/clinicians/ |
Create a clinician. | Any user |
| GET | /api/clinicians/{id}/ |
Retrieve a single clinician. | Any user |
| PATCH | /api/clinicians/{id}/ |
Partial update a clinician. | Any user |
| PUT | /api/clinicians/{id}/ |
Full update a clinician. | Any user |
| DELETE | /api/clinicians/{id}/ |
Delete a clinician. | Any user |
| GET | /api/clinicians/active/ |
List only clinicians where active=True. |
Any user |
| GET | /api/clinicians/{id}/stats/ |
Return shift counts, deficiency, and leave balance for one clinician. | Any user |
Clinician serializer fields: id, name, active, created_at, updated_at.
Clinician stats response shape:
{
"clinician_id": 1,
"clinician_name": "Dr. Smith",
"current_deficiency": 0.5,
"completed_shifts": 12,
"scheduled_shifts": 8,
"total_shifts": 20,
"leave_balance_remaining": 6.0,
"as_of_date": "2024-06-01"
}
Working Terms¶
Managed by WorkingTermViewSet (router-registered at /api/working-terms/). Optionally nested under clinicians.
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/working-terms/ |
List all working terms. Ordered by -start_date. |
Any user |
| POST | /api/working-terms/ |
Create a working term. | Any user |
| GET | /api/working-terms/{id}/ |
Retrieve a working term. | Any user |
| PATCH | /api/working-terms/{id}/ |
Partial update. | Any user |
| PUT | /api/working-terms/{id}/ |
Full update. | Any user |
| DELETE | /api/working-terms/{id}/ |
Delete a working term. | Any user |
| GET | /api/clinicians/{pk}/working-terms/ |
List terms for a specific clinician (nested route). | Any user |
Serializer fields: id, clinician, clinician_name, type, start_date, end_date, percentage, fixed_working_days, fixed_half_days, cannot_work_days, must_work_days, annual_leave_entitlement, participates_in_duty, created_at, updated_at.
Shifts¶
Managed by ShiftViewSet (router-registered at /api/shifts/).
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/shifts/ |
List shifts. Supports ?date_from= and ?date_to= filters. Ordered by date, clinician. |
Any user |
| POST | /api/shifts/ |
Create a shift. | Any user |
| GET | /api/shifts/{id}/ |
Retrieve a shift. | Any user |
| PATCH | /api/shifts/{id}/ |
Partial update. | Any user |
| PUT | /api/shifts/{id}/ |
Full update. | Any user |
| DELETE | /api/shifts/{id}/ |
Delete a shift. | Any user |
| PATCH | /api/shifts/{id}/pin/ |
Pin a shift (sets is_pinned=True). Prevents automatic rebalancing. |
Any user |
| PATCH | /api/shifts/{id}/unpin/ |
Unpin a shift (sets is_pinned=False). Allows automatic rebalancing. |
Any user |
Serializer fields: id, clinician, clinician_name, date, type, duration, status, is_pinned, counts_as_worked, counts_toward_minimum, created_by, created_at, modified_by, modified_at.
Pin/unpin -- The pin and unpin actions set modified_by to the authenticated user's username.
Leave Requests¶
Managed by LeaveRequestViewSet (router-registered at /api/leave-requests/).
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/leave-requests/ |
List all leave requests. Ordered by -requested_at. |
Any user |
| POST | /api/leave-requests/ |
Create a leave request. Triggers LeaveRequestProcessor on save. |
Any user |
| GET | /api/leave-requests/{id}/ |
Retrieve a leave request. | Any user |
| PATCH | /api/leave-requests/{id}/ |
Partial update. | Any user |
| PUT | /api/leave-requests/{id}/ |
Full update. | Any user |
| DELETE | /api/leave-requests/{id}/ |
Delete a leave request. | Any user |
| GET | /api/leave-requests/mine/ |
List leave requests for a specific clinician. Pass ?clinician_id=. |
Any user |
| POST | /api/leave-requests/{id}/cancel/ |
Cancel an approved leave request. Delegates to LeaveRequestProcessor.cancel_request(). |
Any user |
Serializer fields: id, clinician, clinician_name, type, start_date, end_date, status, requested_at, processed_at, processed_by, denial_reason, affected_shift_count.
Create side-effect: perform_create calls LeaveRequestProcessor.process_request() which validates against leave quota and updates shift records.
Alerts¶
Managed by AlertViewSet (router-registered at /api/alerts/).
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/alerts/ |
List alerts. Returns active only by default. Pass ?active=false to include all. Ordered by -date, -created_at. |
Any user |
| POST | /api/alerts/ |
Create an alert. | Any user |
| GET | /api/alerts/{id}/ |
Retrieve an alert. | Any user |
| PATCH | /api/alerts/{id}/ |
Partial update. | Any user |
| PUT | /api/alerts/{id}/ |
Full update. | Any user |
| DELETE | /api/alerts/{id}/ |
Delete an alert. | Any user |
| POST | /api/alerts/{id}/resolve/ |
Resolve an alert (status becomes RESOLVED). |
Any user |
| POST | /api/alerts/{id}/dismiss/ |
Dismiss an alert (status becomes DISMISSED). |
Any user |
Serializer fields: id, type, type_label, severity, date, message, details, status, created_at, resolved_at.
Default filtering: The get_queryset method filters to status='ACTIVE' unless ?active=false is passed.
Calculations¶
Managed by CalculationViewSet (router-registered at /api/calculations/).
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/calculations/ |
List available calculation endpoints. | Any user |
| POST | /api/calculations/bulk-leave-quota/ |
Calculate leave quota for multiple clinicians in one request. | Any user |
Bulk leave quota request:
Maximum batch size: 100 clinician IDs. All IDs must correspond to active clinicians.
Bulk leave quota response:
{
"results": [
{
"clinician_id": 1,
"clinician_name": "Dr. Smith",
"has_entitlement": true,
"entitlement": "8.00",
"remaining": "5.50",
"unit": "weeks",
"adjustments": "0.00",
"percentage_used": 31
}
],
"total_count": 3,
"success_count": 3,
"error_count": 0,
"errors": []
}
Individual clinician errors are reported in the errors array without failing the entire batch.
Rota Generation¶
Standalone endpoint (not router-registered).
| Method | URL | Description | Auth |
|---|---|---|---|
| POST | /api/rota/generate/ |
DEPRECATED -- returns 501 Not Implemented. | Any user |
This endpoint has been superseded by the phase-based rota generation system accessed through the admin interface (/admin/rota/) or the management command python manage.py generate_rota --phase <1|3|4|6|all>.
Rebalancing¶
Standalone endpoint (not router-registered).
| Method | URL | Description | Auth |
|---|---|---|---|
| POST | /api/rebalance/ |
DEPRECATED -- returns 501 Not Implemented. | Any user |
The rebalancing service depended on the replaced RotaGenerator class. A future reimplementation is planned.
Statistics¶
Standalone endpoints (not router-registered).
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/statistics/ |
List available statistics sub-endpoints. | Any user |
| GET | /api/statistics/forecast/ |
Staffing forecast for a date range. | Any user |
| GET | /api/statistics/workload/ |
Workload balance report for a reporting period. | Any user |
Forecast query parameters:
| Param | Required | Default | Description |
|---|---|---|---|
start_date |
No | Today | ISO date (YYYY-MM-DD) |
end_date |
No | start_date + 4 weeks | ISO date (YYYY-MM-DD) |
weeks |
No | 4 | Number of weeks (used if end_date omitted) |
Forecast response shape:
[
{
"date": "2024-06-03",
"day_of_week": "Monday",
"minimum_required": 2,
"scheduled_count": 3,
"pending_leave": 1,
"status": "above"
}
]
status values: "above", "at", "below" -- comparing scheduled_count against minimum_required.
Workload query parameters:
| Param | Required | Default | Description |
|---|---|---|---|
reporting_period |
No | "Period 1" |
Period name to report on |
as_of_date |
No | Today | ISO date for the report snapshot |
Workload response shape:
Availability¶
Standalone endpoint (not router-registered).
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/availability/ |
Availability heatmap (doctor count per day) for a date range. | Any user |
Query parameters:
| Param | Required | Default | Description |
|---|---|---|---|
start_date |
No | Today | ISO date |
end_date |
No | start_date + 4 weeks | ISO date |
Response shape:
Calendar Data (Frontend)¶
This is a frontend-specific JSON endpoint (not DRF), registered in frontend/urls.py.
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /api/calendar-data/ |
Calendar shifts, leave, and alerts grouped by date. Used by the rota calendar view. | Any user |
Query parameters: start_date (required), end_date (required), clinician_id (optional).
Response keys: shiftsByDate, ownShiftsByDate, today, alertsByDate (admin only), annualLeaveByDate, requestedOffByDate, coronersLeaveByDate, studyLeaveByDate, ownAnnualLeaveByDate.
Leave Booking (Frontend)¶
Frontend-specific JSON endpoints for the leave booking flow.
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /leave-booking/calendar-data/ |
Capacity and availability data for the leave booking calendar. | Any user |
| POST | /leave-booking/submit/ |
Submit a leave booking request. Accepts JSON body with leave_type and dates array. |
Any user |
v1 Endpoints¶
| Method | URL | Description |
|---|---|---|
| GET | /api/v1/clinicians/ |
List clinicians (v1: no pagination). |
| POST | /api/v1/clinicians/ |
Create clinician. |
| GET | /api/v1/clinicians/{id}/ |
Retrieve clinician. |
| GET | /api/v1/clinicians/active/ |
Active clinicians only. |
| GET | /api/v1/clinicians/{id}/stats/ |
Clinician statistics (same shape as unversioned). |
| GET | /api/v1/statistics/ |
List available statistics endpoints. |
| GET | /api/v1/statistics/forecast/ |
Staffing forecast (response wrapped in {version, start_date, end_date, results}). |
| GET | /api/v1/statistics/workload/ |
Workload report (response wrapped with version key). |
v1 forecast and workload responses include a "version": "v1" field in the response envelope. The unversioned equivalents do not.
Request/Response Formats¶
Content Types¶
- Request body:
application/json(DRF parsers) - Response body:
application/json - CSRF token:
X-CSRFTokenheader
Common Patterns¶
Listing endpoints return arrays directly (no pagination wrapper in the current configuration):
[
{"id": 1, "name": "Dr. Smith", "active": true, ...},
{"id": 2, "name": "Dr. Jones", "active": true, ...}
]
Detail endpoints return a single object:
Creation returns the created object with HTTP 201:
Error Handling¶
DRF's default exception handling applies. All errors return JSON.
Validation Errors (400)¶
Not Authenticated (401)¶
Not Found (404)¶
Throttled (429)¶
Deprecated Endpoints (501)¶
The rota generation and rebalancing endpoints return 501 with:
Standard HTTP Status Codes¶
| Code | Meaning |
|---|---|
| 200 | Success (read, update, action) |
| 201 | Created (POST to list endpoint) |
| 204 | No content (DELETE) |
| 400 | Validation error |
| 401 | Not authenticated |
| 403 | Permission denied |
| 404 | Not found |
| 429 | Throttled |
| 500 | Server error |
| 501 | Deprecated / not implemented |