Skip to content
PantherWatchSwitch to dark theme
Back

Developers

Everything PantherWatch shows about Georgia State courses comes from a small JSON API. It’s public: no key, no sign-in. If you’re building a script or a browser extension, use it instead of scraping the results pages. To connect an AI assistant, use the MCP connector.

Connect an AI assistant (MCP)

PantherWatch is a remote MCP server. Any MCP-capable assistant or agent can connect, for example Claude, ChatGPT or Cursor. Once connected, it searches classes and reads a student’s tracked classes and schedule, and, if the student allows it, tracks sections and edits the schedule for them. This is the preferred way for an assistant to use PantherWatch.

Students: the AI Assistant page has step-by-step setup for Claude, ChatGPT and Cursor. This page is the reference for developers.

Connector URL
https://www.pantherwatch.app/api/mcp
  1. In the assistant’s settings, add a custom connector (MCP server) and paste the URL. In Claude, for example: Customize → Connectors → + Add → Add custom connector.
  2. Sign in to PantherWatch if asked, then select Allow. Students can untick the write permissions there.
  3. Manage or disconnect it anytime in Settings → Connected apps.
ScopeAllows
readSearch courses; read tracked classes and schedule. Always granted.
watches:writeStart and stop seat alerts. The student can leave it off.
schedule:writeAdd and remove schedule entries. The student can leave it off.
ToolScopeWhat it does
list_termsreadTerms in GoSolar, registrable ones first.
search_subjectsreadSubject codes matching a word ("psych" → PSYC).
find_coursesreadCourse name or nickname to subject and course number from the GSU catalog ("calculus 2" → MATH 2212), with a confidence and the catalog description.
search_sectionsreadLive sections of a subject or course at one level (the level given, else the student's saved campus, else bachelor's): seats, waitlist, meetings, delivery mode.
get_section_detailsreadGrade history, RateMyProfessors rating and syllabus for one section.
get_course_requirementsreadPrerequisites, corequisites, registration restrictions and catalog description of one section.
get_syllabus_summaryreadAn AI summary of one section's posted syllabus: grading weights, exams, attendance, textbook, workload.
list_watchesreadThe student's tracked sections, optionally with live seats.
list_schedulereadThe student's PantherWatch schedule, by term.
check_conflictsreadTime clashes between CRNs, from live meeting times.
add_watch / remove_watchwatches:writeStart or stop a seat alert (email when a seat opens).
add_to_schedule / remove_from_scheduleschedule:writeEdit the schedule; adding reports any clashes.

For client developers: Streamable HTTP, OAuth 2.1 per the MCP authorization spec. Discovery starts at /.well-known/oauth-protected-resource/api/mcp (also at the root location) and /.well-known/oauth-authorization-server. Clients identify themselves with a Client ID Metadata Document URL or register at /api/oauth/register; PKCE (S256) is required, redirect URIs must be https, localhost or an app’s own scheme (like cursor:), access tokens last an hour and refresh tokens rotate. At the token endpoint, metadata-document clients authenticate with none or private_key_jwt (RS256, ES256 or PS256, keys from their jwks_uri). The server allows about 30 tool calls a minute per student.

Overview

  • Base URL: https://www.pantherwatch.app. All responses are JSON (application/json).
  • The GET endpoints send Access-Control-Allow-Origin: *, so browser code on any site can call them. They answer OPTIONS preflights too.
  • A typical flow: list terms, then search a subject (optionally one course number) for that term, then fetch grades and ratings for the sections you care about.
  • Term codes are YYYYMM: 01 spring, 05 summer, 08 fall (202701 is Spring 2027). Read them from /api/courses/terms rather than guessing.

Fair use

Free for personal and student use. Be gentle: at most 1 request per second. Searches go straight to GSU’s registration system (GoSolar), and ratings to RateMyProfessors, so a burst from one script slows things down for every student using the site. Cache what you can, fetch meta only for the sections you will actually show, and don’t poll seat counts in a loop (sign in and use Track instead; PantherWatch emails you when a seat opens).

PantherWatch is not affiliated with Georgia State University. Data can be incomplete or out of date; GoSolar is the source of truth for registration.

List terms

GET/api/courses/terms

Terms you can register for come first (newest first), then view-only past terms with viewOnly: true. No parameters.

Request
curl https://www.pantherwatch.app/api/courses/terms
Response 200
{
  "terms": [
    { "code": "202701", "description": "Spring Semester 2027", "viewOnly": false },
    { "code": "202608", "description": "Fall Semester 2026", "viewOnly": false },
    { "code": "202605", "description": "Summer Semester 2026 (View Only)", "viewOnly": true },
    …
  ]
}

Find subjects

GET/api/courses/subjects?term&q

ParameterRequiredDescription
termYes6-digit term code.
qNoText to match against the code or name (up to 64 characters). Blank returns the first 50 subjects.

Returns at most 50 subjects whose code or description contains q.

Request
curl "https://www.pantherwatch.app/api/courses/subjects?term=202701&q=comp"
Response 200
{
  "subjects": [
    { "code": "APCP", "description": "APPLIED COMPOSITION" },
    { "code": "CIS", "description": "COMPUTER INFORMATION SYSTEMS" },
    { "code": "CSC", "description": "COMPUTER SCIENCE" }
  ]
}

Find a course by name

GET/api/courses/catalog?q&level&limit

ParameterRequiredDescription
qYesA course name, nickname or code, 2–120 characters: calculus 2, intro to programming, data structures, CSC 1301.
levelNoUS undergraduate (bachelor’s), UA courses taught at Perimeter College (associate), GS graduate, LW law. Leave it out for all levels.
limitNoCourses to return, 1–20. Default 5.
  • Searches GSU’s course catalog (courses, not sections), copied nightly from GoSolar’s course catalog for the terms open for registration. Whether a course runs in a term is Search sections’ job.
  • courses are ranked by score (0–1): a course code in q or a common nickname scores 1, title words up to 0.95, description words lower. confidence is high when the first course is a clear winner, ambiguous when several fit (ask the user which one), none when nothing matched. matchedBy says how the first course matched: code, nickname, title or description.
  • title is the catalog’s full title; description is the catalog text (prerequisites first, when there are any). level is PantherWatch’s reading of the course number: 5000 and up is graduate, courses Perimeter College owns are associate. Graduate courses are often open to undergraduates too.
Request
curl "https://www.pantherwatch.app/api/courses/catalog?q=calculus%202&level=US"
Response 200 (trimmed)
{
  "query": "calculus 2",
  "level": "US",
  "confidence": "high",
  "matchedBy": "nickname",
  "matched": "calculus 2",
  "courses": [
    {
      "subject": "MATH",
      "courseNumber": "2212",
      "title": "Calculus of One Variable II",
      "description": "Prerequisite: Math 2201 or Math 211 with a C or higher.\nApplications and techniques of integration; transcendental and inverse trigonometric functions; …",
      "credits": "4",
      "level": "US",
      "score": 1
    },
    {
      "subject": "MATH",
      "courseNumber": "2202",
      "title": "Calculus for the Life Sciences II",
      "credits": "4",
      "level": "US",
      "score": 0.6
    },
    …
  ]
}

GET/api/courses/search?term&subject&courseNumber&level

ParameterRequiredDescription
termYes6-digit term code.
subjectYesOne subject code, 2–8 letters (case-insensitive), e.g. CSC.
courseNumberNo1–8 letters or digits, e.g. 1301 or 1301L. Leave it out to get the whole subject.
levelNoGoSolar level: US bachelor’s (undergraduate, Atlanta campus and Online), UA associate (Perimeter College), GS graduate. Leave it out for all levels. The site’s search form and the assistant default to US.
  • Returns every matching section in one response: no paging, no 12-per-page limit. One subject per call; for several subjects, make one call each (about a second apart).
  • Each section is GoSolar’s own record, passed through. Useful fields: seatsAvailable (can be negative when over-enrolled; when crossList is set the section shares a seat pool, and only min(seatsAvailable, crossListAvailable) seats are takeable), waitCount, waitCapacity, creditHourLow/creditHourHigh (a range for variable-credit courses, else creditHourLow or creditHours), faculty[].displayName (“Last, First”; primaryIndicator marks the main instructor), and instructionalMethodDescription plus sectionAttributes for the delivery mode (“Fully Distance 95% tech”, “Hybrid/Partially Online”).
  • Meeting days are booleans on meetingsFaculty[].meetingTime (monday … sunday). beginTime/endTime are 24-hour HHMM strings, null when the time is TBA or the class is asynchronous online. A section can have several meeting rows (lecture + lab, or different date ranges).
  • Seat counts are live, so this endpoint is never cached: every call is a fresh GoSolar search (about a second for one course, longer for a whole large subject).
Request
curl "https://www.pantherwatch.app/api/courses/search?term=202701&subject=CSC&courseNumber=1301&level=US"
Response 200 (one section shown, trimmed)
{
  "sections": [
    {
      "term": "202701",
      "termDesc": "Spring Semester 2027",
      "courseReferenceNumber": "19010",
      "subject": "CSC",
      "subjectDescription": "COMPUTER SCIENCE",
      "courseNumber": "1301",
      "sequenceNumber": "002",
      "courseTitle": "PRINCIPLES OF COMPUTER SCI I",
      "scheduleTypeDescription": "Lecture",
      "campusDescription": "Atlanta",
      "creditHours": null,
      "creditHourLow": 3,
      "creditHourHigh": null,
      "maximumEnrollment": 120,
      "enrollment": 22,
      "seatsAvailable": 98,
      "waitCapacity": 99,
      "waitCount": 0,
      "waitAvailable": 99,
      "openSection": true,
      "faculty": [
        {
          "displayName": "Saghaeiannejad Esfahani, Sayed Hossein (Hossein) ",
          "primaryIndicator": true,
          …
        }
      ],
      "meetingsFaculty": [
        {
          "meetingTime": {
            "beginTime": "1100",
            "endTime": "1215",
            "monday": true, "tuesday": false, "wednesday": true, "thursday": false,
            "friday": false, "saturday": false, "sunday": false,
            "buildingDescription": "Langdale Hall",
            "room": "700",
            "startDate": "01/11/2027",
            "endDate": "05/04/2027",
            "meetingTypeDescription": "Class",
            …
          },
          …
        }
      ],
      "sectionAttributes": [
        { "code": "FMTH", "description": "Area F Mathematics Electives", … },
        { "code": "LF2F", "description": "Face to Face - Instruction", … },
        …
      ],
      "instructionalMethodDescription": "*Tech enhanced classrm inst re",
      …
    },
    …
  ]
}

Grades, ratings and syllabus

GET/api/courses/meta?term&subject&courseNumber&crn&instructor

ParameterRequiredDescription
termYes6-digit term code.
subjectYesSubject code.
courseNumberYesCourse number.
crnYes5-digit CRN (used for the syllabus lookup).
instructorNoThe primary instructor’s faculty[].displayName from the search response, as-is. Without it, rating is null and only course-wide grades come back.
  • grades: GSU grade history for the course over recent terms. overall is course-wide, professors has one entry per instructor, and instructorDistribution is the queried instructor’s own history when instructorHasTaught is true. gpa is enrollment-weighted; dwfPercent counts D, F, WF and W. hasData: false means no history (often a new course).
  • rating: RateMyProfessors summary. found: false means no match, which is “unknown”, not “bad”. avgRating and avgDifficulty are out of 5.
  • syllabus: a public PDF when the department has posted one.
  • Each part is null if its lookup fails; a failed lookup never turns the whole response into an error.
Request
curl -G https://www.pantherwatch.app/api/courses/meta \
  --data-urlencode term=202701 --data-urlencode subject=CSC \
  --data-urlencode courseNumber=2720 --data-urlencode crn=12992 \
  --data-urlencode "instructor=Islam, S M Towhidul (Towhid)"
Response 200 (trimmed)
{
  "grades": {
    "subject": "CSC",
    "courseNumber": "2720",
    "hasData": true,
    "termsIncluded": ["202605", "202601", "202508", "202505", …],
    "overall": { "professor": null, "gpa": 3.08, "dwfPercent": 12, "total": 2077, "sectionsCount": 99, … },
    "professors": [ … ],
    "instructorQueried": "Islam, S M Towhidul (Towhid)",
    "instructorHasTaught": true,
    "instructorDistribution": {
      "professor": "Islam, S M Towhidul",
      "gpa": 2.65,
      "dwfPercent": 16.1,
      "total": 790,
      "withdrawCount": 32,
      "sectionsCount": 36,
      "termsTaught": ["202601", "202508", "202505", "202501", "202408"],
      "gradeCounts": { "A+": 38, "A": 76, "A-": 64, "B+": 74, "B": 104, "B-": 83,
                       "C+": 60, "C": 163, "C-": 0, "D": 60, "F": 35, "WF": 0 }
    }
  },
  "rating": {
    "professorQueried": "Islam, S M Towhidul (Towhid)",
    "found": true,
    "matchedName": "S M Towhidul Islam",
    "department": "Computer Science",
    "profileUrl": "https://www.ratemyprofessors.com/professor/2921056",
    "avgRating": 3.7,
    "avgDifficulty": 2.8,
    "numRatings": 13,
    "wouldTakeAgainPercent": 76.9231,
    "distribution": { "r1": 2, "r2": 1, "r3": 2, "r4": 2, "r5": 6 },
    "topTags": [],
    …
  },
  "syllabus": { "url": "https://cdn.gsu.edu/static/syllabi-public/files/0021772031792993727494.pdf" }
}

Prerequisites and restrictions

GET/api/courses/requirements?term&crn

ParameterRequiredDescription
termYes6-digit term code.
crnYes5-digit CRN of the section.
  • prerequisites is null when there are none. text is GoSolar’s wording; tree nests and/or groups of course, test and other (e.g. student attributes) nodes; items lists every course once.
  • restrictions: the student must match one value of each mustBe group and none of the mustNotBe ones. description is the catalog text, or null.
Request
curl "https://www.pantherwatch.app/api/courses/requirements?term=202701&crn=19010"
Response 200 (trimmed)
{
  "term": "202701",
  "crn": "19010",
  "description": "Principles of Computer Science I.\nPrerequisite: Only for STEM majors and minors. …",
  "prerequisites": {
    "text": "(Course or Test: MATHEMATICS 1111 to 1113 Minimum Grade of C May be taken concurrently.) or …",
    "rule": "Prereq",
    "tree": {
      "type": "or",
      "children": [
        { "type": "course", "subject": "MATH", "subjectDescription": "MATHEMATICS", "courseNumber": "1111",
          "courseNumberTo": "1113", "minimumGrade": "C", "concurrent": true },
        …,
        { "type": "and", "children": [ … ] }
      ]
    },
    "items": [ { "subject": "MATH", "courseNumber": "1111", "courseNumberTo": "1113", "minimumGrade": "C", … }, … ]
  },
  "corequisites": [
    { "subject": "CSC", "subjectDescription": "COMPUTER SCIENCE", "courseNumber": "1301L",
      "title": "PRINCIPLES COMPUTER SCI LAB I" }
  ],
  "restrictions": {
    "mustBe": [
      { "kind": "major", "label": "Fields of Study (Major, Minor or Concentration)",
        "values": ["Computer Science (CSC)", "Data Science (DSCI)", …] }
    ],
    "mustNotBe": [
      { "kind": "level", "label": "Levels", "values": ["Undergrad Associate Semester (UA)"] },
      …
    ],
    "specialApprovals": [],
    "other": []
  }
}

Batch meta (same-origin only)

POST/api/courses/meta/batch

What the results page uses to load grades and ratings for a whole page of cards at once. Body: { "items": [{ term, subject, courseNumber, crn, instructor? }, …] } (1–50 items); response: { "items": [...] } in the same order, each shaped like /api/courses/meta. It only accepts requests from pantherwatch.app pages (cross-origin calls get 403 forbidden) and sends no CORS headers. From your own code, call GET /api/courses/meta once per section instead.

Errors

Errors share one shape. 400 invalid_request means a parameter is missing or malformed (the message names it). 502 upstream_unavailable means GoSolar didn’t answer; wait a minute and retry. 500 internal_error is a bug on our side.

Response 400
{ "error": { "code": "invalid_request", "message": "term: term must be a 6-digit term code" } }

Caching

EndpointCache-Control
/api/courses/termspublic, s-maxage=300, stale-while-revalidate=600
/api/courses/subjectspublic, s-maxage=3600, stale-while-revalidate=86400
/api/courses/catalogpublic, s-maxage=3600
/api/courses/requirementspublic, s-maxage=3600, stale-while-revalidate=86400
/api/courses/metapublic, s-maxage=600, stale-while-revalidate=3600
/api/courses/search, /api/courses/meta/batchprivate, no-store (live seat counts)

The s-maxage values apply at our CDN, which strips them, so your client sees Cache-Control: public. Repeating a cached request is cheap; a search always reaches GoSolar. Grade history changes once a term and ratings rarely, so cache meta on your side for at least a day.

Reading the web pages

If you must read the rendered pages, don’t parse the text or the styling. Each card’s seat panel and meeting list carry stable data attributes: data-days lists meeting days in M T W R F S U order (Monday–Sunday, empty for no set days), and data-start/data-end are 24-hour HHMM (absent when TBA). data-seats-available is the takeable count: for a cross-listed section, the smaller of its own seats and the shared pool’s. The Rate My Professors pill has data-rmp (average out of 5) and the grade line data-gpa, with data-gpa-scope saying whether it is the professor’s own history for the course (instructor) or course-wide (course). Either is absent when there is no data.

Rendered card (excerpt)
<div data-crn="19010" data-subject="CSC" data-course-number="1301"
     data-term="202701" data-seats-available="98" data-wait-count="0">…</div>
<ul data-crn="19010" …>
  <li data-days="MW" data-start="1100" data-end="1215">…</li>
</ul>
<span data-rmp="3.7">…</span>
<div data-gpa="2.65" data-gpa-scope="instructor">…</div>

Results pages are not for crawlers (robots.txt disallows them): each one is a live GoSolar search.

AI assistants

The MCP connector is the way to connect an assistant: any MCP-capable assistant can use it with no setup beyond pasting the URL, and it can act on the student’s account. Agents that can’t use connectors can call the public API above directly. Signed-in students can also chat with PantherWatch’s own assistant on the AI Assistant page; it uses the same tools as the connector and has no API of its own.

  • /llms.txt (a short site summary for language models)