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.
- Open any gradescope.com page while logged in, and open DevTools (F12).
- Go to the Network tab and reload the page.
- Click the first
www.gradescope.comrequest. Under Request Headers, copy the whole value of Cookie. - Save it to
~/.config/gscope/cookie, thenchmod 600that 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 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.
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 |
| Outline editing, creating assignments, uploading scans | planned |
| Applying grades from a reviewed file | planned |
gscope-cli[pset]: outlines and rubrics from pset LaTeX problems |
reserved, not built |
Related work
- nyuoss/gradescope-api
(
gradescopeapion 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.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gscope_cli-0.1.0.tar.gz | 19.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gscope_cli-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.5 kB
Release files / gscope_cli-0.1.0.tar.gz
| Download URL | gscope_cli-0.1.0.tar.gz |
|---|---|
| Size | 19.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
87573828383be272921eda3067dae63023236408c9da6694dcf41ca407c83a22
|
|
BLAKE2b-256 checksum How to use checksums |
31d1d291073a99026e3f9047ac0dc4b424c7fa4998cd5f36089d6b74cf08e852
|
| 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.1.0-py3-none-any.whl
| Download URL | gscope_cli-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b5878570b91c8cc9d695f9604652eee0c3c517f2a50d5ba0ffba490718007737
|
|
BLAKE2b-256 checksum How to use checksums |
19bcb403b4504de6bb47e62e213615778b0dbc62aeaa6ef823710e1b99f9388e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|