The grading policy is one JSON object per course: a list of graders
(assignment types) and the grade cutoffs. Subsections reference a grader by
its type (/iblai-api-studio-subsection graderType), and each grader's
weight decides its share of the final grade. Replace the whole object or
patch one grader by index.
Before you start
- Preflight:
node .claude/skills/iblai-api-studio-auth/scripts/studio-login.mjs --checkprints twooks; otherwise run/iblai-api-studio-authfirst — do not attempt the calls below. - A course key from
/iblai-api-studio-course-create. - Set the policy before creating graded subsections (
graderTypemust name an existing type) and before the first publish; decide the pass mark and letter grades with the user.
Auth & conventions
- Base URL:
$STUDIO_URL; Studio session cookies fromstudio.env(its values are single-quoted — bash strips the quotes; any other parser must strip them too, or Studio answers 500:/iblai-api-studio-auth). - Snippet:
set -a; . ./studio.env; set +a S=(-s -b "studio_session_id=$STUDIO_SESSION; csrftoken=$STUDIO_CSRF" -H "X-CSRFToken: $STUDIO_CSRF" \ -H "Origin: $STUDIO_URL" -H "Referer: $STUDIO_URL/" -H "Accept: application/json" -H "Content-Type: application/json") COURSE="course-v1:<org>+<number>+<run>"(raw in the path).- Weights are percentages that should sum to 100; Studio does not enforce it — check before writing.
- Changing a grader's
typeorphans subsections that reference the old name (they become ungraded) — rename with care.
Reads
- GET
/settings/grading/{COURSE}→{ "graders": [ { "id": 0, "type": "Homework", "min_count": 4, "drop_count": 1, "short_label": "HW", "weight": 40.0 }, { "id": 1, "type": "Final Exam", "min_count": 1, "drop_count": 0, "short_label": "Final", "weight": 60.0 } ], "grade_cutoffs": { "Pass": 0.6 }, "grace_period": null, "minimum_grade_credit": 0.8 }grace_periodisnullwhen zero, otherwise{ "hours", "minutes" }. - GET
/settings/grading/{COURSE}/{index}→ one grader object (index = position ingraders, 0-based). - The outline root (
GET /xblock/outline/<course root>, see/iblai-api-studio-section) lists the current type names incourse_graders.
Writes
- POST
/settings/grading/{COURSE}— replace the whole policy (send every grader you want to keep):
→ the stored policy in the read shape. Fields:{ "graders": [ { "id": 0, "type": "Homework", "min_count": 4, "drop_count": 1, "short_label": "HW", "weight": 40 }, { "id": 1, "type": "Final Exam", "min_count": 1, "drop_count": 0, "short_label": "Final", "weight": 60 } ], "grade_cutoffs": { "A": 0.85, "B": 0.7, "Pass": 0.5 }, "grace_period": { "hours": 0, "minutes": 30 }, "minimum_grade_credit": 0.8 }type(name shown to users, unique);min_count(how many of this type the course will have — used to compute the average, missing ones count as 0);drop_count(lowest scores ignored);short_label(progress-page abbreviation);weight(percent of final grade).grade_cutoffs: letter → minimum fraction. Pass/fail courses use exactly{ "Pass": 0.5 }; letter grades useA,B,C, … with noPass(the lowest letter is the pass mark). Values descending, all between 0 and 1. - POST
/settings/grading/{COURSE}/{index}— create or update one grader (index = its position; use the next free index to append):
→ the grader as stored.{ "id": 1, "type": "Lab", "min_count": 6, "drop_count": 2, "short_label": "Lab", "weight": 20 } - DELETE
/settings/grading/{COURSE}/{index}→ removes that grader. Confirm with the user first (subsections of that type become ungraded).
Example
set -a; . ./studio.env; set +a
S=(-s -b "studio_session_id=$STUDIO_SESSION; csrftoken=$STUDIO_CSRF" -H "X-CSRFToken: $STUDIO_CSRF" \
-H "Origin: $STUDIO_URL" -H "Referer: $STUDIO_URL/" -H "Accept: application/json" -H "Content-Type: application/json")
COURSE="course-v1:acme+DATA101+2026-T1"
curl "${S[@]}" -X POST "$STUDIO_URL/settings/grading/$COURSE" -d '{
"graders": [
{"id":0,"type":"Homework","min_count":6,"drop_count":1,"short_label":"HW","weight":40},
{"id":1,"type":"Project","min_count":1,"drop_count":0,"short_label":"Proj","weight":30},
{"id":2,"type":"Final Exam","min_count":1,"drop_count":0,"short_label":"Final","weight":30}],
"grade_cutoffs": {"Pass": 0.6}, "grace_period": {"hours": 0, "minutes": 0}, "minimum_grade_credit": 0.8}' \
| python3 -c 'import sys,json; p=json.load(sys.stdin); print([(g["type"], g["weight"]) for g in p["graders"]], p["grade_cutoffs"])'
Notes
- New courses ship with four default graders (Homework 15 / Lab 15 / Midterm Exam 30 / Final Exam 40) and
{"Pass": 0.5}; replace them rather than appending. - A grader with
min_countlarger than the number of subsections of that type drags the average down (missing assignments score 0) — setmin_countto the real number of assignments. weightfor a type with no graded subsections still counts (as 0) — remove unused types.- What users see:
GET $LMS_URL/api/course_home/progress/{COURSE}→grading_policy.assignment_policies(weights as fractions) andgrade_range(/iblai-api-studio-lms).