Skip to content

Grading API ​

Base URL: /api/v1/grading

Manage grade entry, GPA calculations, and transcripts.

Endpoints Overview ​

MethodEndpointDescription
GET/term-gpaCalculate term GPA
GET/cumulative-gpaCalculate cumulative GPA
GET/transcriptGet student transcript
POST/transcript/officialIssue an official transcript (logged)
GET/rosterGet grade roster for a section
GET/roster/anonymousAnonymous grading sheet (student numbers only)
GET/roster/exportRoster and photos as a ZIP, for a seating chart
GET/roster/terms, /roster/sectionsPickers for the roster screen
POST/submitSubmit final or midterm grades in bulk
POST/recalculateRecalculate student GPA
GET/grade-summaryGet grade distribution summary

Term GPA ​

GET /api/v1/grading/term-gpa

Calculate GPA for a specific term.

Query Parameters ​

ParameterTypeRequiredDescription
student_idintegerYesStudent ID
term_idintegerYesTerm ID

Example Request ​

bash
curl -X GET "http://localhost:8000/api/v1/grading/term-gpa?student_id=1001&term_id=8" \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "student_id": 1001,
  "term_id": 8,
  "term_code": "FA2024",
  "gpa": 3.67,
  "credits_attempted": 15.0,
  "credits_earned": 15.0,
  "quality_points": 55.05,
  "courses": [
    {
      "course_code": "MATH 101",
      "course_title": "College Algebra",
      "credits": 3.0,
      "grade": "A",
      "quality_points": 12.0,
      "counts_toward_gpa": true
    },
    {
      "course_code": "CS 101",
      "course_title": "Intro to Programming",
      "credits": 4.0,
      "grade": "A-",
      "quality_points": 14.8,
      "counts_toward_gpa": true
    }
  ]
}

TypeScript Types ​

typescript
interface TermGPA {
  student_id: number
  term_id: number
  term_code: string
  gpa: number
  credits_attempted: number
  credits_earned: number
  quality_points: number
  courses: GradedCourse[]
}

interface GradedCourse {
  course_code: string
  course_title: string
  credits: number
  grade: string
  quality_points: number
  counts_toward_gpa: boolean
}

Cumulative GPA ​

GET /api/v1/grading/cumulative-gpa

Calculate overall cumulative GPA across all terms.

Query Parameters ​

ParameterTypeRequiredDescription
student_idintegerYesStudent ID

Example Request ​

bash
curl -X GET "http://localhost:8000/api/v1/grading/cumulative-gpa?student_id=1001" \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "student_id": 1001,
  "cumulative_gpa": 3.45,
  "credits_attempted": 48.0,
  "credits_earned": 45.0,
  "quality_points": 165.6,
  "terms": [
    {
      "term_id": 8,
      "term_code": "FA2024",
      "term_gpa": 3.67,
      "credits_attempted": 15.0,
      "credits_earned": 15.0
    },
    {
      "term_id": 7,
      "term_code": "SU2024",
      "term_gpa": 3.25,
      "credits_attempted": 6.0,
      "credits_earned": 6.0
    }
  ]
}

Transcript ​

GET /api/v1/grading/transcript

Get full academic transcript for a student. This is the on-screen copy; printed, it is unofficial and carries an UNOFFICIAL watermark. Transcript holds do not withhold it.

Query Parameters ​

ParameterTypeRequiredDescription
student_idintegerYesStudent ID
include_in_progressbooleanNoInclude current term enrollments

Example Request ​

bash
curl -X GET "http://localhost:8000/api/v1/grading/transcript?student_id=1001&include_in_progress=true" \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "student": {
    "student_id": 1001,
    "student_number": "STU001234",
    "name": "John Robert Smith",
    "date_of_birth": "2000-05-15",
    "program": "Bachelor of Science in Computer Science",
    "major": "Computer Science",
    "minor": null,
    "initial_term": "FA2024",
    "expected_graduation": "2028-05-15"
  },
  "cumulative": {
    "gpa": 3.45,
    "credits_attempted": 48.0,
    "credits_earned": 45.0,
    "quality_points": 165.6
  },
  "terms": [
    {
      "term_id": 8,
      "term_code": "FA2024",
      "term_name": "Fall 2024",
      "gpa": 3.67,
      "credits_attempted": 15.0,
      "credits_earned": 15.0,
      "courses": [
        {
          "course_code": "MATH 101",
          "course_title": "College Algebra",
          "credits": 3.0,
          "grade": "A",
          "grade_date": "2024-12-20",
          "quality_points": 12.0
        },
        {
          "course_code": "CS 101",
          "course_title": "Intro to Programming",
          "credits": 4.0,
          "grade": "A-",
          "grade_date": "2024-12-20",
          "quality_points": 14.8
        },
        {
          "course_code": "ENGL 101",
          "course_title": "English Composition",
          "credits": 3.0,
          "grade": "B+",
          "grade_date": "2024-12-20",
          "quality_points": 9.9
        }
      ]
    }
  ],
  "transfer_credits": [],
  "notes": [],
  "generated_at": "2024-12-21T10:00:00Z"
}

A graduate's response also carries graduation_records, the degrees conferred: graduation_date, degree_name, degree_code, major_name (omitted when the major did not resolve), graduation_type, title, title_html, honors_display, with_honors and other_honors. A double major has one entry per major, because each is its own Colleague credential (migration 301; see Audit API).

TypeScript Types ​

typescript
interface Transcript {
  student: TranscriptStudent
  cumulative: CumulativeGPA
  terms: TranscriptTerm[]
  transfer_credits: TransferCredit[]
  notes: string[]
  generated_at: string
}

interface TranscriptTerm {
  term_id: number
  term_code: string
  term_name: string
  gpa: number
  credits_attempted: number
  credits_earned: number
  courses: TranscriptCourse[]
}

interface TranscriptCourse {
  course_code: string
  course_title: string
  credits: number
  grade: string
  grade_date: string | null
  quality_points: number
}

Official Transcript ​

POST /api/v1/grading/transcript/official

Body: {"student_id": 1001}. Gated on GRADING.TRANSCRIPT.OFFICIAL (migration 306), held initially by REGISTRAR and SUPER_ADMIN; superusers pass through the guard's bypass.

  • Refused with 403 while the student has an active hold whose type has prevents_transcript (e.g. LCTRA, LCTUI, FATRA). Holds withhold only the official transcript.
  • Each successful call writes a row to records.transcript_issuances (student, issuing user, impersonator if any, time).
  • Returns the same body as GET /transcript, plus official: true, issuance_id and issued_at. The transcript screen prints exactly this response, without the watermark, with the issue date and issuance number in the header.

Grade Roster ​

GET /api/v1/grading/roster

The students holding a seat in a section (every enrollment not dropped, withdrawn or cancelled), for grade entry. The caller must be able to see the section: faculty reach their own sections, deans their units, the registrar everything.

Query Parameters ​

ParameterTypeRequiredDescription
section_idintegerYesSection ID

Example Request ​

bash
curl -X GET "http://localhost:8000/api/v1/grading/roster?section_id=512"   -b cookies.txt

Success Response (200 OK) ​

An array, one entry per student:

json
[
  {
    "enrollment_id": 1,
    "student_id": 1001,
    "person_id": "6f1c...",
    "student_name": "John Smith",
    "student_number": "1234567",
    "email": "jsmith@example.edu",
    "major_names": "BS Biology",
    "enrollment_date": "2026-09-01T00:00:00Z",
    "current_grade_id": null,
    "midterm_grade_id": 14,
    "midterm_grade": "C-",
    "midterm_grade_date": "2026-10-20T00:00:00Z",
    "credit_hours": 4,
    "is_audit": false,
    "is_pass_fail": false,
    "status_display": "Enrolled",
    "has_photo": true
  }
]

The pickers that lead to a roster are GET /roster/terms and GET /roster/sections?term_id=. Each section carries its roster count (the same predicate as this endpoint) and how many are graded or incomplete; they deliberately do not use course_sections.current_enrollment, which reads 0 for a term whose enrollments have completed.


Anonymous Grading Sheet ​

GET /api/v1/grading/roster/anonymous?section_id=512

The roster with identity stripped, for blind grading. Same students as the roster above, by student number only, ordered by student number -- never by name. A student without a student number cannot be identified on a sheet and is left off. Same node and section check as the roster.

json
{
  "section_id": 512,
  "course_code": "MATH 101",
  "course_title": "College Algebra",
  "section_code": "01",
  "term_name": "Fall Quarter 2026",
  "entries": [ { "student_number": "1234567" }, { "student_number": "1234570" } ]
}

There is deliberately no name, email, person_id, student_id or photo field.


Roster Export for a Seating Chart ​

GET /api/v1/grading/roster/export?section_id=512

Returns application/zip (roster-{course}-{section}-{date}.zip, Cache-Control: no-store), built in memory, containing:

  • roster.csv (UTF-8 with a BOM, so Excel reads accented names): last_name, first_name, preferred_name, student_number, email, majors, photo_file
  • photos/Last_First_Number.jpg, one display-size JPEG per student with an approved photo

The students are the roster's. A student with a FERPA opt-out stays in the CSV with no photo; a student whose photo is missing has an empty photo_file. Authority is the section check, not PEOPLE.VIEW: photos are read through the photo service, not the HTTP photo route. Each export is logged with the counts of photos written, withheld and missing.


Bulk Grade Submission ​

POST /api/v1/grading/submit

Submit final or midterm grades for several enrollments in one section.

Request Body ​

FieldTypeRequiredDescription
section_idintegerYesSection ID
gradesarrayYesArray of grade entries
grades[].enrollment_idintegerYesEnrollment ID; must belong to the section
grades[].grade_idinteger or nullYesGrade ID from the section's grading scale. null clears a midterm grade; a final grade is always required
grades[].notesstringNoNote recorded with the change
is_midtermbooleanNotrue writes the midterm grade instead of the final grade

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/grading/submit   -b cookies.txt   -H "Content-Type: application/json"   -d '{
    "section_id": 512,
    "is_midterm": false,
    "grades": [
      { "enrollment_id": 1, "grade_id": 1 },
      { "enrollment_id": 2, "grade_id": 2 }
    ]
  }'

Success Response (200 OK) ​

json
{
  "total_submitted": 2,
  "total_succeeded": 2,
  "total_failed": 0
}

A grade that fails is reported in errors (one message per failure) and counted in total_failed; the others still save.

Rules ​

RuleFinal gradeMidterm grade
Grade must be on the section's grading scale (section, else course, else the level's default)YesYes
WindowRefused after the term's grades_due_date unless the caller is a superuser or holds GRADING.RELEASERefused unless MIDTERM_GRADES_ENABLED is on, and outside the term's midterm_grade_start_date..midterm_grade_end_date
EnrollmentAny on the rosterOnly students holding a seat; not dropped or withdrawn
GPARecalculated afterwardsNot affected

A midterm grade written here is marked midterm_grade_source = 'SIS' (migration 292), and so is a midterm cleared here. The nightly Colleague import writes midterm grades only where the source is empty or ODS, so a grade entered or cleared in the SIS is never overwritten.


Recalculate GPA ​

POST /api/v1/grading/recalculate

Force recalculation of a student's GPA.

Request Body ​

FieldTypeRequiredDescription
student_idintegerYesStudent ID
term_idintegerNoRecalculate specific term only

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/grading/recalculate \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "student_id": 1001,
    "term_id": 8
  }'

Success Response (200 OK) ​

json
{
  "success": true,
  "data": {
    "student_id": 1001,
    "previous_cumulative_gpa": 3.42,
    "new_cumulative_gpa": 3.45,
    "changed": true,
    "recalculated_at": "2024-12-21T10:30:00Z"
  }
}

Grade Summary ​

GET /api/v1/grading/grade-summary

The pivot behind the Summary Grade Report: grade distribution across a set of periods, one column per term or per academic year. (This is not a per-section summary; the documentation here used to describe a section_id parameter that the endpoint has never accepted.)

The drill-down behind each number is GET /reporting/grade-summary/enrollments. The two are separate implementations over the same population -- the pivot aggregates the raw tables, the drill-down reads vw_grade_enrollment_base -- and grade_summary_parity_live_test.go exists to keep them equal. Any filter added to one must be added to the other.

What it counts ​

Enrollments that are part of the academic record. Dropped and cancelled ones are not: DROPPED, CANCELLED, and rows carrying Colleague grade X (legend "Used for classes deleted") are all excluded. Withdrawn enrollments are excluded by default and can be asked for.

Query Parameters ​

ParameterTypeDescription
group_bystringterm (default) or year -- the column axis
term_idsstringComma-separated term ids. Send with group_by=term
academic_year_idsstringComma-separated academic year ids. Send with group_by=year
major_idintegerFilter to students holding this major
minor_idintegerFilter to students holding this minor
include_withdrawnbooleantrue to count WITHDRAWN enrollments; default false. Pass the same value to the drill-down or a cell will not match the list behind it
unit_viewstringcourses (default) or students; only meaningful for UNIT-scoped grants

Send term_ids or academic_year_ids, not both -- together they silently AND.

Example Request ​

bash
curl -X GET "http://localhost:8000/api/v1/grading/grade-summary?group_by=term&term_ids=8,9" \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "columns": [
    { "id": 8, "code": "24/WI", "name": "Winter 2024" },
    { "id": 9, "code": "24/SP", "name": "Spring 2024" }
  ],
  "grades": [
    { "letter_grade": "A", "display_name": "A", "grading_system": "Standard" },
    { "letter_grade": "(none)", "display_name": "Not Graded", "grading_system": "" }
  ],
  "data": {
    "A": { "24/WI": 412, "24/SP": 388 },
    "(none)": { "24/WI": 7, "24/SP": 51 }
  },
  "column_totals": { "24/WI": 419, "24/SP": 439 },
  "grand_total": 858,
  "group_by": "term"
}

(none) is the bucket for an enrollment with no grade row. It is a real enrollment, not a missing one, and the report counts it.


GPA Calculation Rules ​

Quality Points ​

Quality points are calculated as: credits × grade_quality_points

GradeQuality Points
A4.0
A-3.7
B+3.3
B3.0
B-2.7
C+2.3
C2.0
C-1.7
D+1.3
D1.0
F0.0

GPA Formula ​

GPA = Total Quality Points / Total GPA Credits

Non-GPA Grades ​

These grades do not affect GPA:

GradeDescription
WWithdrawal
IIncomplete
PPass (Pass/Fail)
NPNo Pass (Pass/Fail)
AUAudit
TRTransfer Credit

Repeat Policy ​

When a course is repeated:

  • Only the highest grade counts toward GPA
  • Both attempts appear on transcript
  • Previous attempt marked as "Repeated"



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/grading. 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.

MethodEndpointPermission nodeDescription
GET/academic-levelsGRADING.GPAList academic levels
GET/cumulative-gpaGRADING.GPAGet cumulative GPA
GET/grade-changesGRADING.GRADE_HISTORYList grade changes
GET/grade-summaryREPORTS.GRADE_SUMMARYGet grade summary
POST/recalculateGRADING.GPARecalculate
POST/releaseGRADING.RELEASERelease grades
POST/release/bulkGRADING.RELEASERelease all pending grades
GET/release/historyGRADING.RELEASEList grade releases
GET/release/pendingGRADING.RELEASEList grades awaiting release
GET/rosterGRADING.ROSTERGet roster
GET/roster/anonymousGRADING.ROSTERGet the anonymous grading sheet (student IDs only)
GET/roster/exportGRADING.ROSTERDownload the roster with photos as a ZIP
GET/roster/sectionsGRADING.ROSTERList gradeable sections for the roster picker
GET/roster/termsGRADING.ROSTERList terms for the roster picker
GET/student-termsGRADING.GPAList the terms a student has grades in
POST/submitGRADING.ROSTERSubmit
GET/term-gpaGRADING.GPAGet term GPA
GET/transcriptGRADING.TRANSCRIPTGet transcript
POST/transcript/officialGRADING.TRANSCRIPT.OFFICIAL (VIEW)Issue an official transcript

Student Information System Documentation