Skip to main content

gscope

An unofficial, instructor-side Gradescope client for Python and the command line. It reads courses, assignments, outlines and rubrics, and pushes rubric items from a spec file, so a rubric written once in a document goes up to every version of a quiz without retyping.

pip install gscope-cli

The package installs the gscope command and the gscope Python module.

Unofficial. Gradescope has no public API. gscope makes the same requests Gradescope's own web pages make, using your logged-in session. Gradescope can change those routes without notice, so every write is a dry run until you pass --apply, and is read back and checked afterwards. Use it on courses you teach, at a human pace, and read Gradescope's terms of service.

Logging in

gscope uses the session cookie from a browser where you are already logged in. That works with single sign-on (school) accounts, which have no Gradescope password.

  1. Open any gradescope.com page while logged in, and open DevTools (F12).
  2. Go to the Network tab and reload the page.
  3. Click the first www.gradescope.com request. Under Request Headers, copy the whole value of Cookie.
  4. Save it to ~/.config/gscope/cookie, then chmod 600 that file.

gscope looks for the cookie in, in order:

  • --cookie FILE
  • $GSCOPE_COOKIE, holding the header itself
  • $GSCOPE_COOKIE_FILE
  • ~/.config/gscope/cookie

Logging out of Gradescope in the browser ends the session, and the cookie stops working.

Accounts with a Gradescope password can also use gscope.Session.login(email, password) from Python.

Command line

gscope courses                         # id and name of every course
gscope assignments COURSE              # points, submissions, % graded
gscope questions COURSE ASSIGNMENT     # question tree, points, scoring type
gscope rubric show COURSE ASSIGNMENT   # rubric items, as signed points
gscope rubric push SPEC [--map MAP] [--apply]
gscope rubric to-json RUBRIC.md --map MAP [-o SPEC.json]
gscope assignment create COURSE TITLE TEMPLATE.pdf [--apply]
gscope assignment delete COURSE ASSIGNMENT --confirm TITLE --apply
gscope outline show COURSE ASSIGNMENT
gscope outline guess TEMPLATE.pdf --parts "i:7,ii:7,iii:6" [-o OUTLINE.json]
gscope outline push COURSE ASSIGNMENT OUTLINE.json [--replace] [--apply]
gscope scans list COURSE ASSIGNMENT
gscope scans upload COURSE ASSIGNMENT SCAN.pdf... [--apply]
gscope submissions COURSE ASSIGNMENT   # who is matched, who is not
gscope setup SETUP.json [--steps create,outline,scoring,rubric,scans] [--apply]
gscope roster COURSE [-o roster.csv]
gscope scores COURSE ASSIGNMENT [-o scores.csv]

The ids are the numbers in Gradescope URLs: /courses/COURSE/assignments/ASSIGNMENT. gscope questions lists the question ids.

Pushing a rubric

rubric push creates the items on each question, in order. It refuses any question that already has rubric items, or that is a group of parts, so it cannot make duplicates. Without --apply it only prints what it would do.

A spec is JSON:

{"course_id": 123,
 "questions": [
   {"assignment_id": 456, "question_id": 789, "label": "A Q1",
    "items": [{"points": 0,  "description": "Correct."},
              {"points": -8, "description": "Denominator missing or wrong."}]}]}

Or it is a markdown rubric document plus a small map. In the document, ## headings group the questions (a leading "Version " is dropped), and ### headings name them by their first word. Rubric items are list lines with the points in bold:

## Version A
### Q1: Bayes rule (20 pts)
- **0**: Correct.
- **-8**: Denominator missing or wrong.

The map says where each section goes, as [assignment id, question id]:

{"course_id": 123, "A": {"Q1": [456, 789]}, "B": {"Q1": [457, 790]}}

Sections the map doesn't mention, such as TA notes, are ignored.

Setting up a scanned quiz

gscope setup takes one JSON file and does everything a scanned quiz needs before grading starts: it creates an "Exam / Quiz" assignment per template, saves each outline, sets the scoring type, pushes the rubric, uploads the scans, and reports which submissions still need matching to a student.

{"course_id": 123456,
 "scoring": "negative",
 "assignments": [
   {"title": "quiz2a_q1", "template": "templates/quiz2a_q1.pdf",
    "questions": [{"title": "Covariance Matching", "weight": 20}],
    "scans": ["scans/quiz2a_q1_*.pdf"]},
   {"title": "quiz2a_q2", "template": "templates/quiz2a_q2.pdf",
    "questions": [{"title": "Sample Covariance", "parts": [
        {"title": "i", "weight": 7}, {"title": "ii", "weight": 7}, {"title": "iii", "weight": 6}]}]}],
 "rubric": {"doc": "quiz2_rubric.md",
            "map": {"A": {"Q1": ["quiz2a_q1", "1"], "Q2.1": ["quiz2a_q2", "1.1"]}}}}

Paths are relative to the setup file. Rubric map entries name an assignment by title and a question by its number, so the map can be written before the assignments exist. Every step is skipped once it is done, so you can rerun the same file as the quiz moves along: create the assignments now, push the rubric once it is written, and upload the scans once they exist. Without --apply, it only reports what it would do.

Outlines are guessed from the template PDF (this needs pdftotext from poppler). The name and NUID boxes come from the "Name" and "NUID:" labels, the question runs from its "Problem" header down, and each part runs from its label (i., (a), 2.) to the next one. The last part also covers page 2, so graders see work that continued on the back. Check the guess with gscope outline guess ... -o outline.json and put an explicit "outline" in the setup file when it is wrong.

Gradescope splits uploaded scans by the template's page count, and matches them to students from the name and SID boxes. gscope submissions lists the ones it could not match; fix those in Manage Submissions.

New questions start with a 0-point "Correct" item. rubric push replaces it, and the scoring step removes it from question groups (whose parts hold the rubric).

Signs

Specs write points the way a rubric reads: -8 takes off 8 and +2 adds 2. Gradescope stores a weight whose meaning depends on the question's scoring type. Under negative scoring, a deduction is a positive weight, and a negative weight adds points. gscope converts using each question's scoring type, and rubric show converts back.

Python

from gscope import Session, assignment, rubric

s = Session.from_cookie_file('~/.config/gscope/cookie')
a = assignment.load(s, course_id, assignment_id)
for q in a.walk():
    print(q.id, q.title, q.weight, q.scoring_type, len(q.items))

item = rubric.create_item(s, course_id, question_id, 'Wrong sign', weight=2)
rubric.update_items(s, course_id, question_id, {item.id: {'weight': 3}})
rubric.delete_items(s, course_id, question_id, [item.id])

Status

Area State
Courses, assignments, roster, scores done
Question tree, outline (read) done
Rubric items: create, update, delete, push spec done
Creating and deleting assignments, outline editing, uploading scans, matching done
Applying grades from a reviewed file planned
gscope-cli[pset]: outlines and rubrics from pset LaTeX problems reserved, not built
  • nyuoss/gradescope-api (gradescopeapi on PyPI) covers courses, assignments, extensions, due dates and submission upload. gscope's password login follows its form fields.
  • Yuanpeng-Li/gradescope-mcp is an MCP server for AI assistants, with rubric and grading tools. Its notes on the negative-scoring sign rule match what gscope found.

gscope differs from these by its cookie login (no password needed), the rubric push with read-back checks, and the planned course-setup features.

Development

pip install -e '.[test]'
pytest

The tests run against an in-memory fake of the Gradescope pages and routes, in tests/fake_gs.py. They need no network access and contain no real course data.

MIT licensed. Not affiliated with Gradescope or Turnitin.

Metadata

Release files for gscope-cli 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gscope-cli 0.2.0
File Size Uploaded
gscope_cli-0.2.0.tar.gz 35.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gscope-cli 0.2.0
File Interpreter ABI Platform
gscope_cli-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 67.8 kB

Release files / gscope_cli-0.2.0.tar.gz

Download URL gscope_cli-0.2.0.tar.gz
Size 35.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ddf295a0cc8001a57429c200587fc869e2b2ee8eaad62026796fd041367a558c
BLAKE2b-256 checksum
How to use checksums
96dfa58e69c8b54a7bb1a2a7e8756e5202d68dea2c34c02cd366b3f23f1bf730
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.4

Release files / gscope_cli-0.2.0-py3-none-any.whl

Download URL gscope_cli-0.2.0-py3-none-any.whl
Size 32.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3d70f53c00a284c49dc80db3cd8447548dfa267f627fd2ba0f72153b66f166b2
BLAKE2b-256 checksum
How to use checksums
54cdb22de430c57bb6bdb7ec30768c448146c459839c4fd16baeedbc8d07f656
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.4

Release history Release notifications | RSS feed

0.3.0

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page