Reporting API
Base URL: /api/v1/reporting
Institutional reporting including IPEDS compliance reports, student reports, and grade reports.
Endpoints Overview
IPEDS Reports
| Method | Endpoint | Description |
|---|---|---|
| GET | /ipeds/available_surveys | List available IPEDS surveys |
| GET | /ipeds/data_quality | Get IPEDS data quality report |
| GET | /ipeds/filter_options | Terms, award years and cohort years the surveys can be run for |
| GET | /ipeds/fall_enrollment | Get fall enrollment report |
| GET | /ipeds/completions | Get completions report |
| GET | /ipeds/graduation_rates | Get graduation rates report |
All Students Report
| Method | Endpoint | Description |
|---|---|---|
| GET | /all-students/filter_options | Get available filter options |
| GET | /all-students/summary | Get student summary statistics |
| GET | /all-students/students | Get filtered student list |
| GET | /all-students/unique_count | Get unique student count |
All Grades Report
| Method | Endpoint | Description |
|---|---|---|
| GET | /all-grades/filter_options | Get available filter options |
| GET | /all-grades/summary | Get grade summary statistics |
| GET | /all-grades/enrollments | Get filtered grade/enrollment list |
| GET | /all-grades/unique_count | Get unique enrollment count |
Grade Summary
| Method | Endpoint | Description |
|---|---|---|
| GET | /grade-summary/filter_options | List filter options (periods with grades, active programs) |
| GET | /grade-summary/enrollments | Get grade summary by enrollment |
Midterm Grades
| Method | Endpoint | Description |
|---|---|---|
| GET | /midterm-grades/filter_options | Started terms (newest first), grade letters (best first) and majors, within the caller's scope |
| GET | /midterm-grades/enrollments | One row per enrollment with its midterm grade |
IPEDS Reports
IPEDS (Integrated Postsecondary Education Data System) reports provide federally required institutional data.
They read the vw_ipeds_* views that migration 290 creates; a database without them answers 503 with a message naming the migration. Every IPEDS route is wrapped in requireUnrestricted: a REPORTS.IPEDS grant below ALL is refused. How each dimension is derived is in the admin guide.
The three survey endpoints share two query parameters:
| Parameter | Description |
|---|---|
summary | true reads the _summary view (grouped counts) instead of one row per student |
format | csv streams a download instead of JSON |
The JSON response is { "success": true, "count": n, "data": [...] } plus the filter echoed back.
Available Surveys
GET /api/v1/reporting/ipeds/available_surveysList all available IPEDS survey components and their reporting status.
Example Request
curl -X GET http://localhost:8000/api/v1/reporting/ipeds/available_surveys \
-b cookies.txtData Quality
GET /api/v1/reporting/ipeds/data_qualityCounts the gaps in the inputs the IPEDS views read. Each issue is { field, table, missing_count, severity, description }, and only checks with a non-zero count are returned: unknown residency and missing admit status among recent students (HIGH), open degree programs with students but no CIP code (CRITICAL), recent term enrollments with neither a load nor attempted credits (MEDIUM), and students missing gender (MEDIUM). A check that fails to run is logged and omitted rather than reported as passing.
Filter Options
GET /api/v1/reporting/ipeds/filter_optionsWhat each survey can be run for, read from the data: terms (every term with enrollments, newest first, each with term_code, term_name, start_date and is_fall), default_term (the most recent fall term that has started), award_years (from vw_ipeds_completions) and cohort_years (from the federal FED- cohorts' names).
Example Request
curl -X GET http://localhost:8000/api/v1/reporting/ipeds/data_quality \
-b cookies.txtFall Enrollment
GET /api/v1/reporting/ipeds/fall_enrollmentGet the IPEDS Fall Enrollment survey data: one row per student enrolled in the term, or with summary=true the headcount by level, student type, full-time status, gender and race/ethnicity. Filter with term_code; any term may be passed, not only a fall one.
Example Request
curl -X GET http://localhost:8000/api/v1/reporting/ipeds/fall_enrollment \
-b cookies.txtCompletions
GET /api/v1/reporting/ipeds/completionsGet the IPEDS Completions survey data: one row per conferred award, or with summary=true the count by award year, CIP code, award level, gender and race/ethnicity. Filter with year, the IPEDS award year (July 1 to June 30, named by the year it ends in).
Example Request
curl -X GET http://localhost:8000/api/v1/reporting/ipeds/completions \
-b cookies.txtGraduation Rates
GET /api/v1/reporting/ipeds/graduation_ratesGet the IPEDS Graduation Rates survey data: one row per member of a federal (FED-) cohort, with graduated_100_time, graduated_150_time and transferred_out (always null -- no transfer-out data exists), or with summary=true the cohort size and graduate counts. Filter with cohort_year.
Example Request
curl -X GET http://localhost:8000/api/v1/reporting/ipeds/graduation_rates \
-b cookies.txtAll Students Report
A configurable report that provides a comprehensive view of all student records with filtering and summary statistics.
Filter Options
GET /api/v1/reporting/all-students/filter_optionsGet available filter options for the all students report. Returns lists of terms, statuses, campuses, programs, and other filterable fields.
Example Request
curl -X GET http://localhost:8000/api/v1/reporting/all-students/filter_options \
-b cookies.txtSummary
GET /api/v1/reporting/all-students/summaryThe pivot table: one row per major (or minor), one column per academic year (or term), each cell a COUNT(DISTINCT student_id).
Majors and minors fan out — a student is counted under every one they held during the period (migration 105) — so the cells in a column sum well above the number of students in it. That is why the totals are separate counts rather than sums: row.total, column_totals[] and grand_total are each their own COUNT(DISTINCT student_id), computed as extra GROUPING SETS in the same query as the cells (migration 173). Do not add cells up.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
group_by | string | year (default) or term — what the columns are |
row_type | string | major (default) or minor — what the rows are |
academic_years | string | Comma-separated academic_year_id list |
terms | string | Comma-separated term_id list. Only applied when group_by=term; the by-year views carry no term_id |
campuses | string | Comma-separated campus_id list |
majors | string | Comma-separated major_id list; applied only when row_type=major |
minors | string | Comma-separated minor_id list; applied only when row_type=minor |
include_totals | bool | false omits the three total grouping sets |
Unrecognised group_by / row_type values fall back to year / major; the response echoes the pair that actually ran.
Response
{
"success": true,
"scope": "ALL",
"group_by": "year",
"row_type": "major",
"columns": [{ "id": 2921, "code": "2024-2025", "name": "2024-2025" }],
"rows": [{
"id": 10845, "code": "ND.PHAR", "name": "Pre-Pharmacy",
"cells": [{ "column_id": 2921, "column_code": "2024-2025", "count": 6 }],
"total": 7
}],
"column_totals": [{ "column_id": 2921, "column_code": "2024-2025", "count": 1376 }],
"grand_total": 2037,
"include_totals": true
}Example Request
curl -X GET "http://localhost:8000/api/v1/reporting/all-students/summary?group_by=year&row_type=major&academic_years=2921,2980" -b cookies.txtStudents
GET /api/v1/reporting/all-students/studentsThe drill-down behind one number in the pivot: the students it counted, one row each. count equals the pivot number the parameters describe — a cell, a row total, a column total or the grand total — provided the same period and program sets are passed.
Query Parameters
Every filter takes a plural (set) form and a singular form; the plural wins when both are present. A cell drill-down passes one of each; a row total passes every displayed period; a column total passes the report's program filter; the grand total passes both.
| Parameter | Type | Description |
|---|---|---|
academic_year_ids | string | Comma-separated academic_year_id list |
term_ids | string | Comma-separated term_id list |
campus_ids | string | Comma-separated campus_id list |
major_ids | string | Comma-separated major_id list; 0 is the "No Declared Major" bucket |
minor_ids | string | Comma-separated minor_id list; 0 is the "No Declared Minor" bucket |
academic_year_id, term_id, campus_id, major_id, minor_id | integer | Singular equivalents |
fields | string | Comma-separated column list; honoured by format=csv |
format | string | csv streams a download instead of JSON |
Passing a term set scopes the point-in-time window to terms; otherwise a year set scopes it to academic years. Passing neither leaves the window unbounded, which counts a major's whole history and will not match any pivot number.
Returned fields
student_number, first_name, last_name, preferred_first_name, preferred_email, primary_email, date_of_birth, gender_display, ethnicity_display, religion_display, country_of_birth, citizenship_country, races, major_codes, major_names, minor_codes, minor_names, all_major_codes, all_major_names, all_minor_codes, all_minor_names, majors_minors_combined, academic_status, class_standing, start_term, and -- for a caller holding REPORTS.ALL_STUDENTS.GPA -- overall_gpa, plus term_gpa when exactly one term is requested (migration 275) — echoed as available_fields. Without that node the GPA fields are never selected, so they are absent from both the JSON and the CSV, and cannot be sorted on.
academic_status is the latest status on file, or on a single-term request the status as of the term's end date. GPA and class standing are read from the cached per-level figures, for the academic level carrying the most attempted credits in the requested periods (falling back to the all-levels figures on the student row). start_term is Colleague's declared start term (STA_START_TERM) for that same level, as the term name; it sorts by the term's start date. It is not the first term the student enrolled in. person_id also comes back, for linking to the person record; it is not an exportable column.
major_codes / major_names / minor_codes / minor_names list what the student held during the periods requested, so they agree with the row that was clicked. The all_* fields are instead the student's currently held programs (end_date IS NULL) — which is how a double major stays visible when you have drilled into only one of them — with the primary major first. majors_minors_combined pairs the two current code lists as majors / minors.
These last five reproduce vw_student_all_majors, vw_student_all_minors and vw_student_races in the handler rather than reading them through vw_student_report_detail. Joining that view made Postgres compute all three aggregates for every student in the institution to answer a few hundred rows, at 13.6M planner cost, which exhausted the postgres container's /dev/shm on production and failed the request with SQLSTATE 53100. The duplication is held to the views by internal/domain/reporting/parity_live_test.go.
Example Request
# the Pre-Pharmacy row total across two academic years
curl -X GET "http://localhost:8000/api/v1/reporting/all-students/students?academic_year_ids=2921,2980&major_ids=10845" -b cookies.txtUnique Count
GET /api/v1/reporting/all-students/unique_countA single COUNT(DISTINCT student_id) for one filter combination. The report no longer uses it — /summary returns every total it needs — but it remains for ad hoc counting.
Query Parameters
Takes the plural forms only: academic_year_ids, term_ids, campus_ids, major_ids, minor_ids (comma-separated ids; 0 is the "No Declared" bucket). Singular spellings are ignored here.
Example Request
curl -X GET "http://localhost:8000/api/v1/reporting/all-students/unique_count?academic_year_ids=2921,2980" -b cookies.txtAll Grades Report
A configurable report that provides a view of all grade and enrollment records with filtering and summary statistics.
Filter Options
GET /api/v1/reporting/all-grades/filter_optionsGet available filter options for the all grades report. Returns lists of terms, courses, sections, grade values, and other filterable fields.
Example Request
curl -X GET http://localhost:8000/api/v1/reporting/all-grades/filter_options \
-b cookies.txtSummary
GET /api/v1/reporting/all-grades/summaryGet summary statistics for grades matching the current filters. Includes grade distribution, average GPA, and counts by course or section.
Query Parameters
Accepts the same filter parameters as the enrollments endpoint.
Example Request
curl -X GET "http://localhost:8000/api/v1/reporting/all-grades/summary?term_id=8" \
-b cookies.txtEnrollments
GET /api/v1/reporting/all-grades/enrollmentsGet a filtered and paginated list of enrollment/grade records for the report.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
term_id | integer | Filter by term |
course_id | integer | Filter by course |
section_id | integer | Filter by section |
grade_id | integer | Filter by grade |
search | string | Search in student name, course code |
ordering | string | Sort field |
page | integer | Page number |
page_size | integer | Results per page |
Example Request
curl -X GET "http://localhost:8000/api/v1/reporting/all-grades/enrollments?term_id=8&page=1&page_size=25" \
-b cookies.txtUnique Count
GET /api/v1/reporting/all-grades/unique_countGet the count of unique enrollment records matching the current filters.
Query Parameters
Accepts the same filter parameters as the enrollments endpoint.
Example Request
curl -X GET "http://localhost:8000/api/v1/reporting/all-grades/unique_count?term_id=8" \
-b cookies.txtWhat these reports count
Both grade reports read records.vw_grade_enrollment_base, which holds the enrollments that are part of a student's academic record. Three kinds are never in it:
- Dropped enrollments (
DROPPED). - Cancelled enrollments (
CANCELLED) — Colleague'sSTC_CURRENT_STATUS = 'C'. These were imported asCOMPLETEDuntil migration 243 and appeared as ungraded rows in both reports. - Enrollments carrying Colleague grade
X, whose legend is "Used for classes deleted". The same fact, recorded on the grade instead of the status.
They remain visible in the student record at GET /registration/enrollments/by_student; they are excluded from reporting, not deleted.
Withdrawn enrollments are the one case you can choose. They are excluded by default — which is what these reports have always done — and include_withdrawn=true puts them back. The flag must be passed identically to a pivot and to its drill-down, or a cell will not match the list behind it.
| Parameter | Type | Description |
|---|---|---|
include_withdrawn | boolean | true to count WITHDRAWN enrollments. Default false. Accepted by /all-grades/summary, /all-grades/enrollments, /all-grades/unique_count, /grade-summary/enrollments and /grading/grade-summary |
Grade Summary
The pivot itself is served by the grading domain at GET /api/v1/grading/grade-summary (group_by=term|year, term_ids=, academic_year_ids=). The two endpoints here back its pickers and its drill-down.
Filter Options
GET /api/v1/reporting/grade-summary/filter_optionsEvery picker on the report in one unpaginated response. The report used to build these from the paginated /academic list endpoints, which return 20 rows in ascending order by default -- so the year picker offered the 20 oldest academic years (the 1950s) and the major picker 20 of 441.
academic_years and terms are restricted to periods that actually carry grades, using the same predicate as /academic/terms/latest_active; majors and minors to programs that have not ended. Under a UNIT-scoped grant in the students lens, the program lists narrow to the caller's own programs.
| Parameter | Type | Description |
|---|---|---|
unit_view | string | courses (default) or students; only meaningful for UNIT-scoped grants |
Returns { success, scope, unit_view, academic_years, terms, majors, minors }, where each option is {id, code, name?} and a term also carries academic_year_id.
Enrollments by Grade
GET /api/v1/reporting/grade-summary/enrollmentsThe drill-down behind a clicked number: the enrollments making up one cell, one row total, one column total, or the grand total.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
term_ids | string | Comma-separated term ids -- the set of periods the number spans |
academic_year_ids | string | Comma-separated academic year ids, for a year-grouped pivot |
term_id | integer | Single-term form, accepted for one-cell links |
academic_year_id | integer | Single-year form |
letter_grade | string | Filter to one grade; omit for every grade |
major_id | integer | Filter by declared major (0 = no declared major) |
minor_id | integer | Filter by currently-held minor |
include_withdrawn | boolean | true to include WITHDRAWN enrollments; default false. Must match the pivot |
fields | string | Comma-separated columns to return |
format | string | csv to download instead of JSON |
Pass the plural forms for a total that spans several periods. Omitting the period entirely means every period in the database, which is rarely what a total covers.
Example Request
curl -X GET "http://localhost:8000/api/v1/reporting/grade-summary/enrollments?term_ids=8,9&letter_grade=A" \
-b cookies.txtMidterm Grades
Midterm grades by term, for registrars, deans, advisors and faculty (migration 292). Gated on REPORTS.MIDTERM_GRADES, and unlike every other report it honours personal scopes: ALL, UNIT (with the same unit_view=courses|students lens as All Grades), ADVISEES (the caller's current advisees) and OWN_SECTIONS (sections the caller teaches). Its population matches All Grades: dropped and cancelled enrollments never, withdrawn ones only with include_withdrawn=true.
Enrollments
GET /api/v1/reporting/midterm-grades/enrollmentsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
term_id | string | Comma-separated term ids |
grades | string | Comma-separated letter grades to include (the screen presets C- and below). Letters match across grading scales |
include_ungraded | boolean | Also list enrollments with no midterm grade. With no grades, lists every enrollment; with neither, every enrollment that has a midterm grade |
include_withdrawn | boolean | Include withdrawn enrollments |
major_ids / major_id | string | Filter by major |
course_id, teacher_id | integer | Filter by course or instructor |
page, page_size, with_count, ordering | Paging and sort, as for the other drill-downs | |
format | string | csv streams every matching row |
Returned fields
enrollment_id, student_id, student_number, first_name, last_name, preferred_email, major_codes, major_names, course_code, course_title, section_code, term_code, term_name, teacher_name, midterm_grade, midterm_grade_date, midterm_grade_source (SIS or ODS), final_grade, enrollment_status_code. person_id also comes back for linking, but is not an exportable column. The response carries the applied scope.
Complete endpoint reference
Every route this domain registers, with the permission node that gates it. Generated from the route table, so it is complete by construction — the sections above explain behaviour, this one guarantees nothing is missing.
Paths are relative to /api/v1/reporting. A node in italics is an exemption rather than a grant: selfservice acts only on the caller's own record, public needs no session at all, and custom carries its own wrapper.
| Method | Endpoint | Permission node | Description |
|---|---|---|---|
| GET | /all-grades/enrollments | REPORTS.ALL_GRADES | List enrollments |
| GET | /all-grades/filter_options | REPORTS.ALL_GRADES | List filter options |
| GET | /all-grades/summary | REPORTS.ALL_GRADES | Get summary |
| GET | /all-grades/unique_count | REPORTS.ALL_GRADES | Get unique count |
| GET | /all-students/filter_options | REPORTS.ALL_STUDENTS | List filter options |
| GET | /all-students/students | REPORTS.ALL_STUDENTS | List students |
| GET | /all-students/summary | REPORTS.ALL_STUDENTS | Get summary |
| GET | /all-students/unique_count | REPORTS.ALL_STUDENTS | Get unique count |
| GET | /grade-summary/enrollments | REPORTS.GRADE_SUMMARY | List enrollments |
| GET | /grade-summary/filter_options | REPORTS.GRADE_SUMMARY | List filter options |
| GET | /ipeds/available_surveys | REPORTS.IPEDS | List available surveys |
| GET | /ipeds/completions | REPORTS.IPEDS | List completions |
| GET | /ipeds/data_quality | REPORTS.IPEDS | Get data quality |
| GET | /ipeds/fall_enrollment | REPORTS.IPEDS | Get fall enrollment |
| GET | /ipeds/filter_options | REPORTS.IPEDS | List IPEDS terms, award years and cohort years |
| GET | /ipeds/graduation_rates | REPORTS.IPEDS | List graduation rates |
| GET | /midterm-grades/enrollments | REPORTS.MIDTERM_GRADES | List midterm grades |
| GET | /midterm-grades/filter_options | REPORTS.MIDTERM_GRADES | List midterm report filter options |