Skip to content

API Reference

benjamindobbs edited this page Oct 1, 2026 · 8 revisions

API Reference

All routes are JSON. Mounted in server/index.js. Two auth gates:

  • requireAuth — student. Authorization: Bearer <session-or-google-token>; sets req.userKey. Bypass: DEV_USER.
  • requireTeacher — teacher. Same header shape (the "Copy Token" value, or a raw Google access token from an @hartfordschools.org account); sets req.teacherKey. Bypass: DEV_TEACHER.

Dates are YYYY-MM-DD. Timestamps in responses are epoch-ms.


/api/auth

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.

/api/kenken

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).

/api/measurement

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).

/api/projection

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.

/api/sat and /api/sat-math

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.

/api/questions

Method Path Auth Query Returns
GET /suspended student ?subject=math|english array of suspended question_ids (reported by 2+ students or teacher-suppressed).

/api/student

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.

/api/teacher

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.

/api/wbl

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.

/api/how

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}] }.

/api/it

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.

Clone this wiki locally