SIS API Documentation
Base URL: http://localhost:8000/api/v1/
This documentation covers all REST API endpoints for the Student Information System (SIS).
The machine-readable inventory
openapi.yaml is an OpenAPI 3.0.3 description of every registered route — 1,397 operations across 902 paths — with the permission node and access level guarding each one.
It is generated from the router, not written by hand:
cd sis_backendgo
go test ./internal/router/ -run TestEmitOpenAPI -v -args -openapi-out=<repo>/docs/public/openapi.yamlThe pages below carry what the router cannot know — request and response body shapes, worked examples, and the reasoning behind an endpoint. The spec carries what prose keeps getting wrong: what exists, and what guards it. When the two disagree, the spec is right about the route table and the page is right about the payload.
Reading x-authz
Each operation records kind (gated, selfservice, public, custom), and for a gated route the node and level required. x-router-pattern carries the pattern exactly as registered, which differs from the path key in the two places where the router spells the same wildcard two ways.
To see the same inventory as a table instead:
go test ./internal/router/ -run TestDumpRoutes -v # grouped text
go test ./internal/router/ -run TestDumpRoutes -v -args -format=mdTable of Contents
Core
| Domain | Description | Documentation |
|---|---|---|
| Authentication | Login, logout, sessions, user management | /auth/ |
| Core | Lookup values, currencies, settings, countries, email templates | /core/ |
People & Roles
| Domain | Description | Documentation |
|---|---|---|
| People | Person records, names, addresses, phones, emails, photos | /people/ |
| Roles | Students, teachers, advisors, cohorts, student lists | /roles/ |
| Authorization | Roles, permissions, impersonation, dashboard tiles | /authorization/ |
| Proxy Access | Proxy access, invitations, student data access | /proxy/ |
| Communications | Staff-to-student email: compose, sent mail, sender policy | /communications/ |
Academics
| Domain | Description | Documentation |
|---|---|---|
| Academic | Years, terms, courses, sections, programs, requirements, materials | /academic/ |
| Registration | Enrollments, transactions, holds, planning, waitlists | /registration/ |
| Grading | Grades, GPA calculations, transcripts | /grading/ |
| Degree Audit | Degree progress, waivers, transfer credits, graduation | /audit/ |
| Test Scores | Test types, scores, policies, superscoring | /testscores/ |
Administration
| Domain | Description | Documentation |
|---|---|---|
| Organization | Campuses, buildings, rooms, departments, colleges, course codes | /organization/ |
| Billing | Tuition billing, fees, payment plans, refund schedules, transactions | /billing/ |
| Financial Aid | Retired — endpoints return 403 | /financial-aid/ |
| Form Builder | Dynamic application forms, templates, responses | /formbuilder/ |
| Workflow | Registration workflows, automation rules | /workflow/ |
| Workflow Engine | Generic configurable approval workflows | /workflow-engine/ |
| LMS | LMS connectors, instances, shells, sync, CSV export | /lms/ |
| Reporting | IPEDS reports, student reports, grade reports | /reporting/ |
Admissions
The admissions domain was removed in August 2026 — a different system is taking it over. Its database schema is untouched, but there are no /admissions/ endpoints. Applicant records are still served by Roles at /roles/applicants.
Authentication
All API endpoints (except where noted) require a valid session. The API uses cookie-based session authentication, not Bearer tokens.
Logging In
# Login to get a session
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email": "user@example.com", "password": "yourpassword"}'Response: The server sets three cookies:
sis_session(HttpOnly) - Opaque session IDsis_csrf- CSRF token for state-changing requestssis_logged_in- Flag for JavaScript to check auth state
{
"success": true,
"data": {
"user": {
"auth_user_id": 1,
"email": "user@example.com",
"person_id": "550e8400-e29b-41d4-a716-446655440000",
"is_superuser": false,
"is_staff": true
},
"csrf_token": "<csrf_token>"
}
}The user object is abbreviated here; Authentication has the full shape. Its roles labels are not authorization: the roles a user holds and is acting as come from GET /authorization/user-roles/my_roles.
Making Authenticated Requests
Include the session cookie with each request:
curl -X GET http://localhost:8000/api/v1/academic/courses \
-b cookies.txtCSRF Protection
State-changing requests (POST, PUT, PATCH, DELETE) require a CSRF token:
# Read the sis_csrf cookie value, then send it as a header
curl -X POST http://localhost:8000/api/v1/academic/courses \
-b cookies.txt \
-H "X-CSRF-Token: <csrf_token_from_cookie>" \
-H "Content-Type: application/json" \
-d '{"course_code": "CS101", "title": "Intro to CS"}'Common Patterns
Pagination
List endpoints return paginated results:
{
"count": 245,
"page": 1,
"page_size": 20,
"total_pages": 13,
"results": [...]
}Query Parameters:
page- Page number (default: 1)page_size- Results per page (default: 20)
Filtering
Most list endpoints support filtering by specific fields:
# Filter by department
GET /api/v1/academic/courses?department_id=5
# Filter by status
GET /api/v1/academic/courses?status_id=1
# Multiple filters
GET /api/v1/academic/courses?department_id=5&is_repeatable=trueSearching
Use the search parameter for text search across multiple fields:
GET /api/v1/academic/courses?search=calculusOrdering
Use the ordering parameter to sort results:
# Ascending
GET /api/v1/academic/courses?ordering=course_code
# Descending (prefix with -)
GET /api/v1/academic/courses?ordering=-created_at
# Multiple fields
GET /api/v1/academic/courses?ordering=department,-course_codePartial updates and edit conflicts
A PATCH writes only the fields present in the body. A field you leave out is left alone; a field sent as null is cleared. Two people editing different fields of one record therefore both land.
To be protected against overwriting someone else's change to the same field, send the values you originally loaded for the fields you are changing under _expected:
curl -X PATCH http://localhost:8000/api/v1/people/persons/550e8400-e29b-41d4-a716-446655440000 -b cookies.txt -H "X-CSRF-Token: <csrf_token_from_cookie>" -H "Content-Type: application/json" -d '{
"gender_id": 3,
"deceased_date": null,
"_expected": { "gender_id": 2, "deceased_date": "2020-01-01" }
}'The write happens only if every field named in _expected still holds that value. If any has changed since you loaded it, nothing is written and the response is 409, naming each field that moved and what it holds now:
{
"error": {
"code": "edit_conflict",
"message": "This record was changed by someone else while you were editing it",
"message_key": "errors.editConflict",
"conflicts": [
{ "field": "gender_id", "current": "4" }
]
}
}current is the stored value as text, or null when the field is empty. Reload, decide, and send again. An expected blank (null or "") matches a field that is either NULL or empty. Without _expected the write is unconditional, so existing scripts keep working.
These endpoints follow the partial-write rule and accept _expected:
| Resource | Endpoint |
|---|---|
| Person | PATCH /people/persons/{personID} |
| Person names, addresses, phones, emails | PATCH /people/person-names/{nameID} and the matching person-addresses, person-phones, person-emails routes; the caller's own /people/me/addresses, /me/phones, /me/emails |
| Student | PATCH /roles/students/{studentID} |
| Enrollment | PATCH /registration/enrollments/{enrollmentID} |
| User account | PATCH /auth/users/{userID} |
| Grade definition | PATCH /academic/grades/{gradeID} |
| Billing method, method config, payment plan | PATCH /billing/methods/{methodID}, /billing/method-configs/{configID}, /billing/payment-plans/{planID} |
| Graduation record | PATCH /audit/graduation-records/{recordID} |
Other update endpoints do not accept _expected yet. They still load the row, apply the request and write every column back, so a concurrent edit to the same record by someone else can be silently undone.
Response Format
Success Response
Single resource:
{
"course_id": 1,
"course_code": "CS101",
"title": "Introduction to Computer Science",
...
}List endpoint:
{
"count": 100,
"page": 1,
"page_size": 20,
"total_pages": 5,
"results": [...]
}Error Response
{
"error": {
"code": "not_found",
"message": "Course not found"
}
}Validation errors:
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": {
"course_code": "This field is required",
"title": "Must be at most 200 characters"
}
}
}HTTP Status Codes
| Code | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created successfully |
204 No Content | Resource deleted successfully |
400 Bad Request | Invalid request data or business rule violation |
401 Unauthorized | Missing or invalid session |
403 Forbidden | Insufficient permissions |
404 Not Found | Resource not found |
409 Conflict | Duplicate, or edit_conflict: a field changed since you loaded it (see Partial updates) |
429 Too Many Requests | Rate limit exceeded (auth endpoints) |
500 Internal Server Error | Server error |
Lookup Values
The system uses individual lookup tables (lookup_*) for reference data (statuses, types, etc.). Each lookup type has its own table (e.g., lookup_program_status, lookup_course_status). These are accessed via the Core API.
Common Lookup Types
| Type Code | Description |
|---|---|
PROGRAM_STATUS | Program statuses (Active, Inactive, etc.) |
COURSE_STATUS | Course statuses |
BLOCK_TYPE | Requirement block types |
REQUISITE_TYPE | Course requisite types (Prerequisite, Corequisite) |
TERM_TYPE | Academic term types (Fall, Spring, Summer) |
NAME_TYPE | Person name types (Legal, Preferred, etc.) |
ADDRESS_TYPE | Address types (Home, Mailing, etc.) |
PHONE_TYPE | Phone types (Mobile, Home, Work) |
EMAIL_TYPE | Email types (Personal, Work, Institutional) |
Fetching Lookups
# Get all lookup types with values
GET /api/v1/core/lookups
# Get values for a specific type
GET /api/v1/core/lookups/by-type/TERM_TYPETypeScript Types
TypeScript interfaces for all API responses are available in:
sis_frontend/src/api/types/index.ts