Welcome to the Jupiterp API, a free and open-source API to get detailed course data for the University of Maryland. Currently, the API is in pre-release phase and is unstable; expect breaking changes, but the information in these docs should be correct and up-to-date.
For any questions or bugs, please contact admin@jupiterp.com.
Feel free to view or contribute to the project on GitHub.
Everything is served under /v1. Use it for new work.
/v0 still works and is not going away. Every read endpoint documented here
answers on both prefixes, from the same handlers, returning the same bytes —
/v0/courses and /v1/courses are the same endpoint. Existing clients,
including @jupiterp/jupiterp
1.x, need no change and there is no migration deadline.
The two prefixes are held together by a parity check that compares every read endpoint across both, so they cannot quietly drift apart.
| path | description | link |
|---|---|---|
/v1/ |
Base endpoint | jump |
/v1/courses |
Get a list of courses with full course info | jump |
/v1/courses/minified |
Get a list of courses with just the code and title for each | jump |
/v1/courses/withSections |
Get a list of courses, including section data for each course | jump |
/v1/sections |
Get a list of sections for courses | jump |
/v1/instructors |
Get a list of instructors and their ratings | jump |
/v1/instructors/active |
Get a list of instructors actively teaching a course | jump |
/v1/deptList |
Get a list of 4-letter department codes | jump |
/v1/grades |
Get grade distributions for individual sections | jump |
/v1/grades/summary |
Get grade distributions aggregated by course, term, or instructor | jump |
/v1/grades/terms |
Get the terms for which grade data is available | jump |
/v1/This is the base endpoint for the Jupiterp API. It will simply return a HTTP StatusOK with some text to indicate that the Jupiterp API is online.
/v1/coursesGets a list of courses that match the given query parameters. This endpoint does not return section information; for section info, use the sections endpoint listed below.
| param | description | example |
|---|---|---|
courseCodes (optional) |
A string of one or multiple comma-separated course codes to fetch course data for; cannot set both courseCodes and prefix. |
courseCodes=CMSC132,MATH141 |
prefix (optional) |
The course prefix to match records to; for instance, CMSC1 would match all CMSC1XX courses (like CMSC131 and CMSC132); cannot set both courseCodes and prefix. |
prefix=CMSC1 |
number (optional) |
The course number to search for across multiple departments; for instance, 433 would match to courses like AOSC433, AREC433, etc. |
number=433 |
genEds (optional) |
A string of one or multiple comma-separated Gen-Eds to filter for; if multiple Gen-Eds are included, the API will return courses that satisfy all listed Gen-Eds. | genEds=DVUP,DSSP |
credits (optional) |
A string of equalities/inequalities to filter courses by how many credits they have. For courses with a range of possible credit values, filters by the minimum number of credits. Possible equality/inequality expressions are: eq, lte, lt, gt, gte, neq (for equal to, less than or equal to, less than, etc.). For multiple conditions, use multiple credits arguments. |
credits=gt.1&credits=lt.5 |
limit (optional) |
Maximum number of course records to return; defaults to 100, maximum of 500. | limit=10 |
offset (optional) |
How many records to skip when returning courses; defaults to 0 | offset=10 |
sortBy (optional) |
A comma-separated list of which columns to sort by when returning; can be sorted in ascending (.asc) or descending (.desc) order. |
sortBy=name.asc,min_credits.desc |
| field | type | description |
|---|---|---|
course_code |
string | The unique course code for a course, which consists of a four-letter department code, a three digit course identifier, and optional letters at the end. |
name |
string | The name of a course, providing very brief information about the subject of the course. |
min_credits |
int | The minimum number of credits this course is worth. For courses that do not have a range of possible credit values, this field is simply the number of credits the course is worth. All courses have a non-null min_credits field. |
max_credits |
int or null | The maximum number of credits a course is worth, if the course has a range of possible credit values. For courses that have a set credit value, this field will be null. |
gen_eds |
string[] or null | A list of four-letter codes for the Gen-Ed requirements this course satisfies (ex. DSSP, DVUP). |
conditions |
string[] or null | A list of additionall conditions listed for this course. This consists of things like prerequisites, corequisites, or additional information. |
description |
string or null | A detailed description of the course. Some courses do not have a description, especially independent research courses. |
Request: GET http://api.jupiterp.com/v1/courses?courseCodes=CMSC131,MATH141
Response:
[
{
"course_code": "CMSC131",
"name": "Object-Oriented Programming I",
"min_credits": 4,
"max_credits": null,
"gen_eds": null,
"conditions": [
"Corequisite: MATH140. ",
"Credit only granted for: CMSC131, CMSC133 or CMSC141."
],
"description": "Introduction to programming and computer science.
Emphasizes understanding and implementation of applications using
object-oriented techniques. Develops skills such as program design and
testing as well as implementation of programs using a graphical IDE.
Programming done in Java."
},
{
"course_code": "MATH141",
"name": "Calculus II",
"min_credits": 4,
"max_credits": null,
"gen_eds": null,
"conditions": [
"Prerequisite: Minimum grade of C- in MATH140."
],
"description": "Continuation of MATH140, including techniques of
integration, improper integrals, applications of integration (such as
volumes, work, arc length, moments), inverse functions, exponential and
logarithmic functions, sequences and series."
}
]
Request: GET http://api.jupiterp.com/v1/courses?genEds=DVUP,DSSP&limit=2&sortBy=courseCode.asc
Response:
[
{
"course_code": "AAST351",
"name": "Asian Americans and Media",
"min_credits": 3,
"max_credits": null,
"gen_eds": [
"DSSP",
"DVUP"
],
"conditions": [
"Credit only granted for: AAST351, AAST398M or AAST398N. ",
"Formerly: AAST398M, AAST398N."
],
"description": "From yellow peril invaders to model minority allies, Asian
Americans have crafted their own dynamic cultural expressions in a number
of media from film, television, and music to fashion, sports, and food that
reveal and contest the contradictions of the U.S. nation-state. Asian
American culture also uniquely sits at the nexus of immigration flows and
digital technologies, providing a transnational lens to view the US place
in the world. This advanced course, then, will introduce students to the
study and practice of Asian American culture as multiple , hybrid, and
heterogeneous. It will do so through three sections: section one will
introduce students to classical, cultural, and media concepts as well as
relevant keywords outlined by Asian American Studies scholars; section two
will review the work of Asian American cultural theorists; section three
will focus on analyses of particular Asian American cultural productions.
In doing so, students will gain an understanding of the shifting and
interlocking tensions among the local, the national, and the global that
form the cultural geographies of Asian America."
},
{
"course_code": "AMST320",
"name": "(Dis)ability in American Film",
"min_credits": 3,
"max_credits": null,
"gen_eds": [
"DSHU",
"DSSP",
"DVUP"
],
"conditions": [
"Credit only granted for: AMST320 or AMST328X. ",
"Formerly: AMST328X."
],
"description": "Explores the connection between film and disability
through an analysis of independent and mainstream American films in various
film genres. Specifically, we will consider how these film representations
reflect and/or challenge the shifting social perspectives of disability
over the 20th and 21st centuries. Beginning with the presentation of
disability as theatrical spectacle in the traveling sideshow and early
cinema, we will work our way through film history to develop an
understanding of our society's complicated relationship with disability."
}
]
/v1/courses/minifiedGets a minified list of courses that satisfy the given parameters. Takes the same parameters as the /v1/courses endpoint, but returns only the course code and title.
Same as the parameters for /v1/courses; see here.
| field | type | description |
|---|---|---|
course_code |
string | The unique course code for a course, which consists of a four-letter department code, a three digit course identifier, and optional letters at the end. |
name |
string | The name of a course, providing very brief information about the subject of the course. |
Request: GET http://api.jupiterp.com/v1/courses/minified?prefix=ASTR4&sortBy=name.asc
Response:
[
{
"course_code": "ASTR422",
"name": "Cosmology"
},
{
"course_code": "ASTR421",
"name": "Galaxies"
},
{
"course_code": "ASTR498",
"name": "Special Problems in Astronomy"
}
]
/v1/courses/withSectionsGets a list of full courses data and associated sections data. Each returned course also contains a (potentially-empty) list of sections for that course.
| param | description | example |
|---|---|---|
courseCodes (optional) |
A string of one or multiple comma-separated course codes to fetch course data for; cannot set both courseCodes and prefix. |
courseCodes=CMSC132,MATH141 |
prefix (optional) |
The course prefix to match records to; for instance, CMSC1 would match all CMSC1XX courses (like CMSC131 and CMSC132); cannot set both courseCodes and prefix. |
prefix=CMSC1 |
number (optional) |
The course number to search for across multiple departments; for instance, 433 would match to courses like AOSC433, AREC433, etc. |
number=433 |
genEds (optional) |
A string of one or multiple comma-separated Gen-Eds to filter for; if multiple Gen-Eds are included, the API will return courses that satisfy all listed Gen-Eds. | genEds=DVUP,DSSP |
credits (optional) |
A string of equalities/inequalities to filter courses by how many credits they have. For courses with a range of possible credit values, filters by the minimum number of credits. Possible equality/inequality expressions are: eq, lte, lt, gt, gte, neq (for equal to, less than or equal to, less than, etc.). For multiple conditions, use multiple credits arguments. |
credits=gt.1&credits=lt.5 |
totalClassSize (optional) |
A string of equalities/inequalities to filter by the total number of seats in a section. Possible expressions are: eq, lte, lt, gt, gte, neq (for equal to, less than or equal to, less than, etc.). For multiple conditions, use multiple totalClassSize arguments. |
totalClassSize=gt.40&totalClassSize=le.50 |
onlyOpen (optional) |
If set to true, only returns sections with more than zero open seats. | onlyOpen=true |
instructor (optional) |
Return only sections that have the given instructor in the instructors field. This field is case-sensitive. |
instructor=Darryll%20Pines |
limit (optional) |
Maximum number of course records to return; defaults to 100, maximum of 500. | limit=10 |
offset (optional) |
How many records to skip when returning courses; defaults to 0 | offset=10 |
sortBy (optional) |
A comma-separated list of which columns to sort by when returning; can be sorted in ascending (.asc) or descending (.desc) order. |
sortBy=name.asc,min_credits.desc |
| field | type | description |
|---|---|---|
course_code |
string | The unique course code for a course, which consists of a four-letter department code, a three digit course identifier, and optional letters at the end. |
name |
string | The name of a course, providing very brief information about the subject of the course. |
min_credits |
int | The minimum number of credits this course is worth. For courses that do not have a range of possible credit values, this field is simply the number of credits the course is worth. All courses have a non-null min_credits field. |
max_credits |
int or null | The maximum number of credits a course is worth, if the course has a range of possible credit values. For courses that have a set credit value, this field will be null. |
gen_eds |
string[] or null | A list of four-letter codes for the Gen-Ed requirements this course satisfies (ex. DSSP, DVUP). |
conditions |
string[] or null | A list of additionall conditions listed for this course. This consists of things like prerequisites, corequisites, or additional information. |
description |
string or null | A detailed description of the course. Some courses do not have a description, especially independent research courses. |
sections |
Section[] | A list of Sections. A Section consists of the fields described in the output of /v1/sections (see here) |
Request: GET http://api.jupiterp.com/v1/courses/withSections?courseCodes=CMSC433
Response:
[
{
"course_code": "CMSC433",
"name": "Programming Language Technologies and Paradigms",
"min_credits": 3,
"max_credits": null,
"gen_eds": null,
"conditions": [
"Prerequisite: Minimum grade of C- in CMSC330; or must be in the
(Computer Science (Doctoral), Computer Science (Master's)) program. ",
"Restriction: Permission of CMNS-Computer Science department."
],
"description": "Programming language technologies (e.g., object-oriented
programming), their implementations and use in software design and
implementation.",
"sections": [
{
"holdfile": 0,
"meetings": [
"TuTh-11:00am-12:15pm-CSI-1115"
],
"sec_code": "0101",
"waitlist": 3,
"open_seats": 0,
"course_code": "CMSC433",
"instructors": [
"Anwar Mamat"
],
"total_seats": 140
},
{
"holdfile": null,
"meetings": [
"TuTh-3:30pm-4:45pm-IRB-0318"
],
"sec_code": "0201",
"waitlist": 0,
"open_seats": 7,
"course_code": "CMSC433",
"instructors": [
"Anwar Mamat"
],
"total_seats": 50
}
]
}
]
/v1/sectionsGet sections for specific courses, or for all courses that match a course code prefix. Note that some courses don't have any sections; for example, most independent research courses, like ASTR498, will not return any sections.
| param | description | example |
|---|---|---|
courseCodes (optional) |
A string of one or multiple comma-separated course codes to fetch course data for; cannot set both courseCodes and prefix. |
courseCodes=CMSC132,MATH141 |
prefix (optional) |
The course prefix to match records to; for instance, CMSC1 would match all CMSC1XX courses (like CMSC131 and CMSC132); cannot set both courseCodes and prefix. |
prefix=CMSC1 |
totalClassSize (optional) |
A string of equalities/inequalities to filter by the total number of seats in a section. Possible expressions are: eq, lte, lt, gt, gte, neq (for equal to, less than or equal to, less than, etc.). For multiple conditions, use multiple totalClassSize arguments. |
totalClassSize=gt.40&totalClassSize=le.50 |
onlyOpen (optional) |
If set to true, only returns sections with more than zero open seats. | onlyOpen=true |
instructor (optional) |
Return only sections that have the given instructor in the instructors field. This field is case-sensitive. |
instructor=Darryll%20Pines |
limit (optional) |
Maximum number of course records to return; defaults to 100, maximum of 500. | limit=10 |
offset (optional) |
How many records to skip when returning courses; defaults to 0 | offset=10 |
sortBy (optional) |
A comma-separated list of which columns to sort by when returning; can be sorted in ascending (.asc) or descending (.desc) order. |
sortBy=name.asc,min_credits.desc |
| field | type | description |
|---|---|---|
course_code |
string | The unique course code for a course, which consists of a four-letter department code, a three digit course identifier, and optional letters at the end. |
sec_code |
string | The code (usually 4 numbers, but sometimes includes letters) for a section. The sec_code is unique within a given course, but a section may have the same sec_code as a section with a different course_code. |
instructors |
string[] | A list of the names of instructors teaching a course. Instructor may not be an actual name; for instance, it could be "Instructor: TBA". |
meetings |
string[] | A list of meeting times and places for the section. Meeting strings have different formats depending on the class format:
|
open_seats |
int | The number of available seats for this section. |
total_seats |
int | The total number of seats in this section. |
waitlist |
int | How many people are on the waitlist for this section. |
holdfile |
int or null | The number of people on the holdfile for this section, if a holdfile exists. |
Request: GET http://api.jupiterp.com/v1/sections?courseCodes=CMSC433
Response:
[
{
"course_code": "CMSC433",
"sec_code": "0101",
"instructors": [
"Anwar Mamat"
],
"meetings": [
"TuTh-11:00am-12:15pm-CSI-1115"
],
"open_seats": 0,
"total_seats": 140,
"waitlist": 7,
"holdfile": 0
},
{
"course_code": "CMSC433",
"sec_code": "0201",
"instructors": [
"Anwar Mamat"
],
"meetings": [
"TuTh-3:30pm-4:45pm-IRB-0318"
],
"open_seats": 15,
"total_seats": 50,
"waitlist": 0,
"holdfile": null
}
]
/v1/instructorsGet a list of all instructors and their average ratings, including instructors not actively teaching any courses.
| param | description | example |
|---|---|---|
instructorNames (optional) |
A comma-separated list of instructor names to get results for. Cannot set both instructorNames and instructorSlugs. |
instructorNames=Testudo%20Terrapin,Darryll%20Pines |
instructorSlugs (optional) |
A comma-separated list of instructor slugs to get results for; slugs are the internal identifier used to distinguish an instructor and are unique to each instructor. Cannot set both instructorNames and instructorSlugs. |
instructorSlugs=shane-walsh,darryll-pines |
nameSearch (optional) |
Case-insensitive substring match on instructor name. Matched against a normalized form of the name, so accents and punctuation are ignored on both sides: obrien matches "O'Brien" and jose matches "José". |
nameSearch=walsh |
activeOnly (optional) |
If true, only returns instructors currently teaching at least one section. | activeOnly=true |
count (optional) |
If true, the total number of matching records is returned in the Content-Range response header (0-49/4812). Costs an extra aggregate over the filtered set, so it is off by default. |
count=true |
ratings (optional) |
A string of equalities/inequalities to filter instructors by their average rating on PlanetTerp. Possible equality/inequality expressions are: eq, lte, lt, gt, gte, neq (for equal to, less than or equal to, less than, etc.). For multiple conditions, use multiple ratings arguments. | ratings=gt.3.14&ratings=lt.5 |
limit (optional) |
The number of results to return. Defaults to 100, maximum of 500. | limit=10 |
offset (optional) |
How many records to skip when returning results; defaults to 0 | offset=5 |
sortBy (optional) |
A comma-separated list of which columns to sort by when returning; can be sorted in ascending (.asc) or descending (.desc) order. |
sortBy=average_rating.asc,name.desc |
| field | type | description |
|---|---|---|
slug |
string | The internal string used to identify an individual instructor, unique to that instructor. See PlanetTerp API spec for more info. |
name |
string | The instructor's name as listed on PlanetTerp |
average_rating |
float | The average rating given to that professor from reviews on PlanetTerp |
Request: GET http://api.jupiterp.com/v1/instructors?ratings=gt.4.5&ratings=lt.5&limit=5&sortBy=average_rating.desc,name.desc
Response:
[
{
"slug": "gramlich_meredith",
"name": "Meredith Gramlich",
"average_rating": 4.9667
},
{
"slug": "cropper",
"name": "Maureen Cropper",
"average_rating": 4.9474
},
{
"slug": "gruber_sean",
"name": "Sean Gruber",
"average_rating": 4.9398
},
{
"slug": "o’brien",
"name": "Terrence O’Brien",
"average_rating": 4.9375
},
{
"slug": "zomback",
"name": "Jenna Zomback",
"average_rating": 4.9355
}
]
/v1/instructors/activeGet all instructors that are currently teaching a course, as listed on Testudo.
Same as /v1/instructors; see here.
Same as /v1/instructors; see here.
Request: GET http://api.jupiterp.com/v1/instructors/active?limit=5
Response:
[
{
"slug": "abadi_daniel",
"name": "Daniel Abadi",
"average_rating": 3.122
},
{
"slug": "abasi",
"name": "Ali Abasi",
"average_rating": null
},
{
"slug": "abbasi",
"name": "Hossein Abbasi",
"average_rating": 3.7791
},
{
"slug": "abdul-alim",
"name": "Jamaal Abdul-Alim",
"average_rating": 3.25
},
{
"slug": "abioye",
"name": "Victor Abioye",
"average_rating": null
}
]
/v1/deptListGet a list of 4-letter department codes.
None
| field | type | description |
|---|---|---|
dept_code |
string | A unique 4-letter department code |
name |
string | The name of the department |
/v1/gradesGets grade distributions for individual course sections, as released by the University Registrar under the Maryland Public Information Act. Data covers fall and spring terms from Fall 2010 through Spring 2026; winter and summer terms were not released.
For counts aggregated across sections, terms, or instructors, use the summary endpoint below.
| param | description | example |
|---|---|---|
courseCodes (optional) |
A string of one or multiple comma-separated course codes; cannot be combined with prefix or number. |
courseCodes=CMSC132,MATH141 |
prefix (optional) |
The course prefix to match records to; for instance, CMSC1 would match all CMSC1XX courses. |
prefix=CMSC1 |
number (optional) |
The course number to search for across multiple departments. | number=433 |
term (optional) |
A string of equalities/inequalities to filter by term code. Possible expressions are: eq, lte, lt, gt, gte, neq, and in for a specific set. For multiple conditions, use multiple term arguments. |
term=gte.202008 or term=in.(202408,202501) |
instructor (optional) |
Return only sections taught by the given instructor, written in "First Last" order. This field is case-sensitive. | instructor=Larry%20Herman |
instructorSource (optional) |
A comma-separated list of instructor_source values to include. Defaults to all. Use reported,lead to exclude attributions carried across lecture groups. |
instructorSource=reported |
gpa (optional) |
A string of equalities/inequalities to filter by computed GPA. | gpa=gte.3.5 |
graded (optional) |
A string of equalities/inequalities to filter by how many students received a letter grade. Useful for excluding sections too small to draw conclusions from. | graded=gte.30 |
limit (optional) |
Maximum number of records to return; defaults to 100, maximum of 500. | limit=10 |
offset (optional) |
How many records to skip when returning results; defaults to 0. | offset=10 |
sortBy (optional) |
A comma-separated list of which columns to sort by, ascending (.asc) or descending (.desc). |
sortBy=term.desc,sec_code.asc |
| field | type | description |
|---|---|---|
term |
int | Six-digit term code: the four-digit year followed by the month the term begins (01 spring, 08 fall). Fall 2024 is 202408. |
course_code |
string | The course code, matching course_code elsewhere in this API. A course that has since been retired will have grade records but no entry in /v1/courses. |
sec_code |
string | The section code, matching sec_code on /v1/sections. |
instructor |
string or null | The instructor exactly as the Registrar printed them, in "Last, First Middle" order. Null where the release left the field blank. |
instructor_name |
string or null | The effective instructor in "First Last" order, suitable for matching against /v1/instructors. May be populated where instructor is null; see instructor_source. |
instructor_source |
string or null | How instructor_name was determined. reported means the Registrar named them on this row. lead means the name was carried from the lead section of the same lecture, which is how the release records discussion and lab sections. course means it was carried from a different lecture group or a differently-coded offering, and is materially less reliable. Null where no section of the course was named. |
total |
int | Students enrolled, as reported. From Fall 2017 this equals the sum of the fifteen grade buckets; in earlier terms it can exceed that sum by a few students whose outcome the older report did not categorize. Prefer graded as a denominator when comparing across that boundary. |
a_plus, a, a_minus … d_minus, f |
int | Students receiving each letter grade. |
w |
int | Students who withdrew. |
other |
int | Students receiving a non-letter outcome (pass/fail, incomplete, audit, and similar). |
graded |
int | Students who received a letter grade; the denominator used for gpa. |
gpa |
number or null | Mean GPA on the UMD 4.0 scale over graded students. Withdrawals and non-letter outcomes are excluded from both the numerator and the denominator. Null where nobody received a letter grade. |
Request: GET http://api.jupiterp.com/v1/grades?courseCodes=CMSC132&term=eq.202408&limit=2
Response:
[
{
"term": 202408,
"course_code": "CMSC132",
"sec_code": "0101",
"instructor": "Herman, Larry",
"instructor_name": "Larry Herman",
"instructor_source": "reported",
"total": 32,
"a_plus": 0,
"a": 1,
"a_minus": 4,
"graded": 30,
"gpa": 2.583
},
{
"term": 202408,
"course_code": "CMSC132",
"sec_code": "0102",
"instructor": null,
"instructor_name": "Larry Herman",
"instructor_source": "lead",
"total": 34,
"a_plus": 2,
"a": 7,
"a_minus": 5,
"graded": 34,
"gpa": 3.118
}
]
Note the second record: the release lists the instructor once against the lecture and leaves the discussion sections blank, so instructor is null while instructor_name carries the lecturer's name and instructor_source records that it was inferred.
/v1/grades/summaryGets grade distributions with the individual sections summed together. This is usually the endpoint you want: groupBy=course answers "how hard is this course", groupBy=term answers "has it changed", groupBy=instructor answers "who should I take it with", and groupBy=instructorOverall answers "how does this professor grade in general".
Note that instructorOverall and instructorTerm aggregate across every course, so they take no course filter; passing courseCodes, prefix, or number with them returns 400 rather than silently ignoring the filter.
| param | description | example |
|---|---|---|
groupBy (optional) |
One of course (default), term, instructor, instructorOverall, or instructorTerm. course returns one record per course across every term on file; term one record per course per term; instructor one record per course per instructor; instructorOverall one record per instructor across every course they have taught; instructorTerm one record per instructor per term. |
groupBy=instructorOverall |
includeCarried (optional) |
Only meaningful with groupBy=instructor. When true, also counts sections whose instructor was carried across lecture groups (instructor_source of course). Wider coverage, lower confidence. Defaults to false. |
includeCarried=true |
courseCodes (optional) |
A string of one or multiple comma-separated course codes; cannot be combined with prefix or number. |
courseCodes=CMSC132 |
prefix (optional) |
The course prefix to match records to. | prefix=CMSC3 |
number (optional) |
The course number to search for across multiple departments. | number=433 |
term (optional) |
Equalities/inequalities to filter by term code. Only valid with groupBy=term or groupBy=instructorTerm; the other groupings aggregate across every term on file and will reject this parameter rather than ignore it. |
term=gte.202008 |
instructor (optional) |
Return only the given instructor, in "First Last" order. Case-sensitive, exact. Prefer instructorSlug: the same professor is spelled several different ways across the registrar's grade files, Testudo, and PlanetTerp, so an exact name match silently returns nothing for a large share of instructors. Requires an instructor grouping. |
instructor=Anwar%20Mamat |
instructorSlug (optional) |
Return only the given instructor, by Jupiterp slug. This resolves through instructor identity rather than string equality, so it cannot miss because of a middle name or an accent. Requires an instructor grouping. | instructorSlug=shane-walsh |
instructorId (optional) |
Return only the given instructor, by numeric id. Requires an instructor grouping. | instructorId=4711 |
gpa (optional) |
Equalities/inequalities to filter by the aggregated GPA. | gpa=gte.3.0 |
minStudents (optional) |
Exclude groups with fewer than this many students who received a letter grade. Applied to graded, not total: before Fall 2017 the registrar's total includes students whose outcome was never categorized, so it is not comparable across eras, while graded is also the GPA denominator. |
minStudents=100 |
count (optional) |
If true, the total number of matching records is returned in the Content-Range response header. |
count=true |
limit (optional) |
Maximum number of records to return; defaults to 100, maximum of 500. | limit=10 |
offset (optional) |
How many records to skip; defaults to 0. | offset=10 |
sortBy (optional) |
A comma-separated list of which columns to sort by. | sortBy=gpa.desc |
All groupings return the summed grade buckets (a_plus through other), total, graded, and gpa, defined exactly as on /v1/grades. In addition:
| field | type | description |
|---|---|---|
course_code |
string | The course these counts are for. |
term |
int | Only present when groupBy=term. |
instructor |
string | Present on the instructor groupings; the instructor's canonical display name. |
instructor_id |
int | Present on the instructor groupings; the Jupiterp instructor id. |
instructor_slug |
string | Present on the instructor groupings; the Jupiterp slug, which is the professor page URL segment. |
course_count |
int | Only present when groupBy=instructorOverall or instructorTerm; how many distinct courses are represented. |
section_count |
int | How many individual sections were summed. |
term_count |
int | How many distinct terms are represented. Not present when groupBy=term. |
first_term, last_term |
int | The earliest and latest term represented. Not present when groupBy=term. |
Request: GET http://api.jupiterp.com/v1/grades/summary?courseCodes=CMSC351
Response:
[
{
"course_code": "CMSC351",
"section_count": 97,
"term_count": 32,
"first_term": 201008,
"last_term": 202601,
"total": 14969,
"graded": 13346,
"a_plus": 450,
"a": 1578,
"a_minus": 1153,
"b_plus": 1313,
"b": 2169,
"b_minus": 1441,
"c_plus": 1263,
"c": 1583,
"c_minus": 1002,
"d_plus": 185,
"d": 846,
"d_minus": 70,
"f": 293,
"w": 738,
"other": 791,
"gpa": 2.699
}
]
Request: GET http://api.jupiterp.com/v1/grades/summary?groupBy=instructor&courseCodes=CMSC330&minStudents=1000&sortBy=gpa.desc
Response:
[
{
"course_code": "CMSC330",
"instructor": "Michael W. Hicks",
"section_count": 37,
"term_count": 7,
"first_term": 201301,
"last_term": 202101,
"total": 1205,
"graded": 1076,
"gpa": 3.123
},
{
"course_code": "CMSC330",
"instructor": "Roger D. Eastman",
"section_count": 39,
"term_count": 5,
"first_term": 201808,
"last_term": 202108,
"total": 1258,
"graded": 1045,
"gpa": 3.047
}
]
/v1/grades/termsGets every term for which grade data has been loaded, newest first. Takes no parameters. Useful for discovering coverage before querying, since the released data covers fall and spring only.
| field | type | description |
|---|---|---|
term |
int | Six-digit term code. |
section_count |
int | Sections with grade data in this term. |
course_count |
int | Distinct courses with grade data in this term. |
total |
int | Students enrolled across every section. |
graded |
int | Students who received a letter grade. |
gpa |
number | Mean GPA across the whole university for the term. |
Request: GET http://api.jupiterp.com/v1/grades/terms
Response:
[
{
"term": 202601,
"section_count": 6543,
"course_count": 3213,
"total": 168366,
"graded": 161397,
"gpa": 3.488
},
{
"term": 202508,
"section_count": 7056,
"course_count": 3263,
"total": 186608,
"graded": 176931,
"gpa": 3.503
}
]
The endpoints below write, and they are governed differently from the read
endpoints above even though both are served under /v1. Writes need things
reads do not: an origin allowlist, authentication, rate limiting, and a captcha.
The read endpoints stay permissive, unauthenticated, and cacheable.
That difference is worth stating plainly, because it is the one thing the shared
prefix hides. Reads accept requests from any origin. Writes accept them only
from an allowlist (V1_ALLOWED_ORIGINS). A browser on an unrelated domain can
call GET /v1/courses and will be refused by POST /v1/reviews.
Reviews are pre-moderated. Nothing submitted here is publicly visible until a moderator approves it, and that is true whether the decision is made by a person or by the automated triage.
| path | method | description |
|---|---|---|
/v1/reviews |
GET | Approved reviews for a professor |
/v1/reviews |
POST | Submit a review |
/v1/reviews/verify/:token |
GET | Confirm an emailed link |
/v1/reviews/:id |
DELETE | Withdraw (manage key) |
/v1/reviews/:id/report |
POST | Report a published review |
/v1/admin/reviews |
GET | Moderation queue (admin key) |
/v1/admin/reviews/:id |
PUT | Approve, reject, or escalate |
/v1/admin/reports |
GET | Open reports (admin key) |
/v1/admin/sweep |
POST | Scheduled maintenance (admin key). Answers 200 when every step succeeded and 207 with a failures object when any did not — alert on non-200. |
GET /v1/reviewsApproved reviews only, newest first. Served from a database view that cannot express an unapproved row and does not contain the submitter's identity columns at all.
| parameter | description | example |
|---|---|---|
instructorSlug (required) |
Whose reviews to return. | instructorSlug=shane-walsh |
courseCode (optional) |
Restrict to one course. | courseCode=CMSC132 |
limit, offset (optional) |
Paging; defaults 25 and 0. | limit=50 |
The total is returned in the Content-Range header.
POST /v1/reviews{
"instructor_slug": "shane-walsh",
"course_code": "CMSC132",
"term": 202508,
"rating": 4.5,
"expected_grade": "A-",
"title": "Genuinely excellent lecturer",
"body": "…",
"email": "student@terpmail.umd.edu",
"captcha_token": "0.abc…"
}
rating is a decimal between 1 and 5 on a half step — 4.5 is valid,
4.3 is not. email must be a terpmail.umd.edu or umd.edu address; it is
stored only as a peppered hash, is never displayed, and is never shown to the
professor. course_code and term are optional, and term must be a Fall or
Spring term, because the grade dataset covers only those.
Responds 202 Accepted with {"status":"verification_sent"}.
The response is identical whether or not that address has already reviewed this professor. A distinguishable "you have already reviewed this" would turn the endpoint into an oracle for "did person X review professor Y", which is the privacy property the hashing exists to provide.
Rate limited to 5 per hour per IP, 3 per day per address, and 20 per hour per professor across all submitters. The last one is what catches a coordinated run on a single professor, which the per-person limits do nothing about.
GET /v1/reviews/verify/:tokenConfirms the emailed link, moves the review to pending, and returns the
manage key once:
{ "status": "verified", "manage_key": "…", "message": "…" }
Idempotent: a second visit returns already_verified rather than an error,
because mail clients prefetch links and people double-click.
The manage key is also emailed. It cannot be recovered — there is deliberately no way to link it back to a person.
DELETE /v1/reviews/:idAuthorization: Bearer <manage key>.
A withdrawal is a soft delete — the row remains so the one-review-per-person rule still holds, but the content is actually nulled.
There is no edit endpoint. A published review is final text: the only way to change what a review says is to withdraw it and write another. Editing after approval is a way to get innocuous text past a moderator and then replace it, and re-queueing every edit for moderation solves that at the cost of a flow where a reviewer can silently republish. Withdrawal carries no such hole, so it is the one the reviewer keeps.
PUT /v1/admin/reviews/:id{
"action": "approve",
"reason": "…",
"confidence": 0.93,
"categories": [],
"policy_version": "2026-08-14",
"model": "gemini-2.0-flash-001"
}
Two callers with different keys: a human moderator with the admin key, and the automated triage with a narrowly scoped callback key that authorises this one route. Which one acted is recorded on every decision.
Idempotent — asking for the state a review is already in is a success, not a
second audit entry. State-guarded — only pending and escalated reviews are
decidable, and a late retry against a review a human already actioned returns
409 rather than overturning it.
Every call writes an audit row. While shadow mode is on, an automated decision
is recorded with applied: false and the review is escalated to a human
instead.
| status | meaning |
|---|---|
400 |
Validation failed; the message names the field |
401 |
Missing or wrong key |
404 |
No such professor or review |
409 |
Already decided by someone else |
410 |
Verification link expired |
429 |
Rate limited |