Skip to content

API Reference

benjamindobbs edited this page Sep 18, 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/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.
GET /next student ?class_id= { domainIdx, skill, difficulty }. Uses that class's allowed domains; no class_id ⇒ all four. History is global per student.
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=<id>|none { settings, today:{kenken,sat,sat_math}, remaining, assessment_type, class_id }. Scoped to the class when given; none ⇒ {settings:null}; omitted ⇒ legacy first-class.
GET /kenken-leaderboard student — top 10 average KenKen scores among enrolled students, me flag on the caller.
GET /scores student — { kenken:[…], sat:[…], sat_math:[…] } — all of the caller's rows.

/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? } domains: array or CSV of 0–3; a full set or empty ⇒ stored NULL (all).
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 history, 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, 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). 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 /work-events/:id, 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