Repository navigation
API Reference
All routes are JSON. Mounted in server/index.js. Two auth gates:
-
requireAuth— student.Authorization: Bearer <session-or-google-token>; setsreq.userKey. Bypass:DEV_USER. -
requireTeacher— teacher. Same header shape (the "Copy Token" value, or a raw Google access token from an@hartfordschools.orgaccount); setsreq.teacherKey. Bypass:DEV_TEACHER.
Dates are YYYY-MM-DD. Timestamps in responses are epoch-ms.
| Method | Path | Auth | Body / Query | Returns |
|---|---|---|---|---|
| POST | /login |
— |
{ token } (Google access token) |
{ sessionToken }. Rejects non-students.hartfordschools.org unless the account is a teacher domain. |
| GET | /me |
Bearer | — |
{ user_key, email } if the token resolves, else 401/403. |
| Method | Path | Auth | Body / Query | Returns |
|---|---|---|---|---|
| POST | /score |
student | { score, size, class_id? } |
{ rank, avg }. class_id validated against active enrollment; stored NULL if absent/invalid. |
| GET | /rank |
student | — |
{ rank, avg } (rank interpolated from the student's all-time average). |
Ruler Game. The server owns the student's tier and checks every answer.
| Method | Path | Auth | Body / Query | Returns |
|---|---|---|---|---|
| GET | /status |
student | — |
{ tier, max_tier, window, attempts, accuracy, avg_score, avg_pct, streak, streak_pct }: the current tier plus stats over the rolling window within it. avg_pct is the average of each attempt's points ÷ its possible points (0–1), which is what tier movement compares. streak counts correct answers in a row in this tier; the Ruler Game has no streak rule, so it's informational there. |
| POST | /attempt |
student |
{ target_32, guess_32, time_ms, class_id? } (lengths in 1/32″) |
{ ok, correct, score, previous_tier, …status }. Returns 409 with the current status if the target isn't valid for the student's current tier, or the guess isn't on one of the tier's marks (e.g. from a stale second tab). |
Projections. The server hands out, times and grades every problem, and owns the student's tier.
| Method | Path | Auth | Body / Query | Returns |
|---|---|---|---|---|
| GET | /status |
student | — | same shape as /api/measurement/status. |
| GET | /next |
student | — | the student's open problem, or a new one at their tier: { problem_id, tier, task, code, elapsed_ms, hidden_graded, align, …status }, plus shape: { box, cubes } for a draw problem, or box and views: { front, right, top } (no shape) for a build problem. Each view is `{ size:[U,V], cells:[[u,v]…], lines:[[u1,v1,u2,v2,'v' |
| POST | /attempt |
student |
{ problem_id, answer, class_id? }. answer is { front, right, top } (views as above) for a draw problem, or { cubes: [[x,y,z]…] } for a build. |
{ ok, correct, score, per_view:{front,right,top}, aligned, floating, time_ms, previous_tier, …status }, plus shape for a build problem. 400 for a malformed answer (off the grid, not unit segments, cubes outside the box, duplicates); the problem stays open. 409 if problem_id isn't the student's open problem (already answered, e.g. in another tab) or their tier has changed. |
Identical shapes; sat = English, sat-math = Math.
| Method | Path | Auth | Body / Query | Returns |
|---|---|---|---|---|
| POST | /score |
student | { correct, domainIdx, skill?, difficulty, assessment?, class_id? } |
{ ok: true }. Logs one attempt. If it arrives less than 10 s after /next served the question, nothing is stored and the reply is { ok: true, flagged: true, min_ms: 10000 } (server/satPace.js). Each serve stamp is judged once, and a submit with no stamp is stored normally. |
| GET | /next |
student | ?class_id= |
{ domainIdx, skill, difficulty }. Uses that class's allowed domains; no class_id ⇒ all four. History is global per student. Stamps sat_serves for the too-fast check on /score. |
| GET | /session |
student | ?since=<ms>&class_id= |
Today's (or since-ms) attempts grouped domain → skill → difficulty → {total, correct}. |
| POST | /report |
student | { questionId } |
{ ok, reportCount }. 2+ distinct reporters ⇒ suspended. |
| Method | Path | Auth | Query | Returns |
|---|---|---|---|---|
| GET | /suspended |
student | ?subject=math|english |
array of suspended question_ids (reported by 2+ students or teacher-suppressed). |
| Method | Path | Auth | Query | Returns |
|---|---|---|---|---|
| GET | /classes |
student | — |
{ classes: [{ class_id, name, assessment_type, required_activity, required_*_count, sat_english_domains, sat_math_domains }] } — active enrollments only. |
| GET | /daily-progress |
student | `?class_id= | none` |
| GET | /kenken-leaderboard |
student | — | top 10 average KenKen scores among enrolled students, me flag on the caller. |
| GET | /scores |
student | — |
{ kenken:[…], sat:[…], sat_math:[…], measurement:[…], measurement_status, projection:[…], projection_status }: all of the caller's rows, plus their current Ruler Game and Projections status. |
| Method | Path | Body / Query | Notes |
|---|---|---|---|
| POST | /login |
{ token } |
Google token → { sessionToken }. @hartfordschools.org only. |
| POST | /logout |
— | invalidates the session token. |
| GET | /check |
— |
{ ok, teacherKey }. |
| GET / POST | /profile |
{ name } |
teacher display name. |
| GET / POST | /gradebook-settings |
{ assignment_max_score, completion_score_pct, no_submission_score_pct, wbl_*? } |
teacher-wide. POST is an upsert of the three core fields; WBL fields patched only if present. |
| GET | /classes |
— | list with student_count, linked_count. |
| POST | /classes |
{ name } |
create empty class. |
| POST | /classes/import-roster |
{ name, ps_section_id, students:[{student_id, student_name, ps_dcid?}] } |
create class + roster in one shot (extension). |
| POST | /classes/:id/sync-roster |
{ students:[{student_id, student_name, ps_dcid?, entry_date?}] } |
non-destructive reconcile → { added, reactivated, withdrawn, unchanged }. |
| GET | /classes/:id |
— | class + roster. |
| PATCH | /classes/:id |
{ name?, assessment_type?, sat_english_domains?, sat_math_domains?, required_activity?, required_*_count?, sat_requirement_mode?, sat_accuracy_pct?, sat_accuracy_window? } |
domains: array or CSV of 0–3; a full set or empty ⇒ stored NULL (all). Counts and sat_accuracy_window are clamped to ≥ 1. sat_requirement_mode must be correct or accuracy, and sat_accuracy_pct must be 1–100, or the request gets a 400. |
| DELETE | /classes/:id |
— | delete class + roster rows. |
| POST | /classes/:id/auto-link |
— | link roster rows where student_id matches a users.user_key → { linked }. |
| POST | /classes/:id/students |
{ students:[{student_id, student_name}] } |
bulk add. |
| PATCH | /classes/:classId/students/:studentId |
{ user_key?, enrolled_on?, exited_on? } |
link/unlink, set/clear dates (clearing exited_on also clears exit_reason). |
| DELETE | /classes/:classId/students/:studentId |
— | remove one roster row. |
| DELETE | /users/:userKey |
— | wipe an app account + all its KenKen, SAT, Ruler Game and Projections history (including tiers and any open Projections problem), unlink everywhere. Irreversible. |
| GET | /grades |
?class_id=&start=&end= |
the grade computation — graded per day, then averaged. Returns { class_id, start, end, max_score, students:[{ student_id, student_name, grade, kenken_count, sat_count, sat_math_count, measurement_count, projection_count, enrolled_on, exited_on, days_counted, days_excused, exempt?, unlinked? }] } (grade: null + exempt: true when no day in the student's enrolled slice counted — e.g. no enrollment overlap, or every day excused; kenken_count/sat_count/sat_math_count are range totals across the counted days, informational only). See Grading Algorithms. |
| GET | /data |
— | every user + every score row (with emails), plus measurement_progress and projection_progress (each student's current Ruler Game and Projections tiers). Portal "All Data" tab. |
| GET | /kenken-leaderboard |
— | top-10, no me flag. |
| GET | /reports |
?subject= |
reported questions with reporter counts. |
| DELETE | /reports/:questionId |
?subject= |
clear reports (keep question). |
| POST | /reports/:questionId/suppress |
?subject= |
permanently suppress + clear reports. |
Teacher routes gate on requireTeacher and resolve ownership up to the owning program; student
routes (/me/*) gate on requireAuth. Grouped as in server/routes/wbl.js:
1. Programs & enrollment — GET/POST /programs, PATCH/DELETE /programs/:id,
GET /programs/shareable, GET/POST /programs/:id/classes, DELETE /programs/:id/classes/:classId,
POST /programs/:id/roster/sync, GET /programs/:id/roster, /roster-by-class, /unassigned,
GET/POST /programs/:id/called-outs, DELETE /called-outs/:id,
POST /programs/:id/enrollments, PATCH /programs/:id/enrollments/:studentId,
POST /programs/:id/enrollments/roll-year.
2. Catalog — credentials — GET/POST /programs/:id/credentials, PATCH/DELETE /credentials/:id,
PATCH /credentials/:id/state (cascades to skills), PUT /credentials/:id/skills,
PATCH /credential-skills/:credentialId/:skillId/state,
POST /programs/:id/catalog/import-csv ({ rows:[{credential, skill, description?, criterion?}] }).
2b. Catalog — skills (versioned) — GET/POST /programs/:id/skills, GET /soft-skills,
GET /skills/:id/versions, POST /skills/:id/versions (?copy_current=true),
PATCH /skill-versions/:id, PUT /skill-versions/:id/criteria,
POST /skill-versions/:id/publish, POST /skill-versions/:id/retire, PUT /skills/:id/openings.
3. Config — GET/POST /programs/:id/qc-criteria, PATCH/DELETE /qc-criteria/:id,
PUT /programs/:id/phase2-prereqs, GET/PUT /programs/:id/tier-points.
4. Work events & skill checks — GET/POST /programs/:id/work-events,
GET/PATCH/DELETE /work-events/:id (delete only when empty — 409 if it has participants or exit slips),
POST /work-events/:id/participants ({ student_ids:[…] }),
DELETE /work-events/:id/participants/:pid, GET /programs/:id/skill-checks,
PUT/DELETE /programs/:id/skill-checks/:studentId/:skillId.
5. Assessment — GET /participants/:id, PUT /participants/:id/skills/:skillId,
POST /participants/:id/qc-check, PUT /participants/:id/holistic,
PATCH /transfer-claims/:id/verify, GET /transfer-claims/:id/factcheck.
6. Student-facing (requireAuth) — GET /me, /me/credentials, /me/skill-checks,
/me/work-events, /me/work-history, GET/POST /me/do-now, POST /me/exit-slip,
GET /me/exit-slips, POST /me/transfer-claims.
7. Rotation, floor, sync — GET /programs/:id/rotation, /qc-floor,
POST /exit-slips/:id/verify ({ confidence, rating } — rating 0–4 required),
POST /exit-slips/:id/void, POST /exit-slips/:id/feedback,
GET /programs/:id/exit-slips, GET /students/:studentId/credentials,
GET /sync/progress?class_id= — per-class credential/skill/work-event sync state + who's satisfied,
with lazy auto-promotion of not_started rows,
GET /sync/habits?class_id=&term_start=&term_end= — all 5 soft-skill scores per student
(transfer gated on Phase 2),
POST /sync/ids — store PS assignment IDs after a sync ({ class_id, skills?, credentials?, work_events?, habits? }),
POST /classes/:classId/attendance/import — { rows:[{student_id, date, code}] } from the extension.
The non-WBL Habits of Work Do Now / Exit Slip flow (server/routes/how.js). Teacher routes gate
on requireTeacher and resolve ownership through ownedNonWblClass() — the teacher owns the class
and it has no wbl_class_programs row (else 409 class_is_wbl). Student /me/* routes gate on
requireAuth. Details and the sync math: Habits of Work Internals.
Catalogue — GET /catalog (the hard-coded Gravitas / Dignitas / Pietas framework; either role).
Student (requireAuth) — GET /me/classes (non-WBL classes + each one's cadence),
GET /me/day?class_id=&date= (this period's Do Now / Exit Slip / evidence log),
POST /me/do-now ({ class_id, date?, skills:[{category, sub_bullet}] } — 1 category daily, 3
weekly; flat 1-category body accepted; 409 already_started),
POST /me/evidence (weekly_log only — { do_now_id, category, note, date? }, append-only,
409 week_closed),
POST /me/exit-slip ({ do_now_id, skills:[{category, goal_rating, evidence, improve}] } — one per
picked category; evidence optional in weekly_log; 409 already_submitted),
GET /me/history?class_id=.
Teacher (requireTeacher) — PATCH /classes/:classId/cadence ({ cadence, weekly_log? }),
GET /classes/:classId/exit-slips?status=&q=&week= (status=pending = not voided & not fully
rated; weekly_log non-pending views also list open weeks as { id:null, in_progress:true }),
POST /exit-slips/:id/rate ({ category, rating } or { ratings:[{category, rating, note?}] } —
1–4, upsert),
POST /exit-slips/:id/void ({ reason }), DELETE /exit-slips/:id/void (undo),
GET /sync?class_id=&week= — per-category weekly + standing aggregates for the extension,
POST /sync/ids — { class_id, assignments:[{category, kind, ps_assignment_id, ps_assignmentsection_id}] }.
The IT Help Desk tool, served on the it. subdomain. Gates: requireItStaff, requireItAdmin.
Roster import/lookup (/roster/*), asset CRUD + CSV import (/assets/*), and ticketing
(/tickets/*, /tickets/:id/notes). Not otherwise related to the education platform.