{"openapi":"3.1.0","info":{"title":"welearninsync API","version":"1.0.0","description":"Read-only access to courses, sessions, class lists and results for one institution. Create API keys under Manage institution → API & webhooks. Answer keys are never available through the API."},"servers":[{"url":"/api/v1"}],"security":[{"apiKey":[]}],"paths":{"/courses":{"get":{"summary":"List courses","description":"Scope: `sessions:read`","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"after","in":"query","description":"Cursor: `next_cursor` from the previous page","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Courses","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Course"}},"next_cursor":{"type":["string","null"],"description":"Pass as `after` to get the next page; null on the last page"}}}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, wrong or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or belongs to another institution)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 requests per minute for this key — see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/courses/{courseId}/sessions":{"get":{"summary":"List class sessions of a course","description":"Scope: `sessions:read`","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"after","in":"query","description":"Cursor: `next_cursor` from the previous page","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Sessions","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Session"}},"next_cursor":{"type":["string","null"],"description":"Pass as `after` to get the next page; null on the last page"}}}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, wrong or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or belongs to another institution)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 requests per minute for this key — see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/courses/{courseId}/roster":{"get":{"summary":"Class list","description":"Scope: `roster:read`","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Students on the class list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RosterEntry"}}}}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, wrong or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or belongs to another institution)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 requests per minute for this key — see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/courses/{courseId}/gradebook":{"get":{"summary":"Attendance and participation for the whole course","description":"Scope: `results:read`","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Gradebook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gradebook"}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, wrong or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or belongs to another institution)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 requests per minute for this key — see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sessions/{sessionId}/results":{"get":{"summary":"Results of one class session","description":"Scope: `results:read`","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Session results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResults"}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, wrong or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or belongs to another institution)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 requests per minute for this key — see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"session.started":{"post":{"summary":"session.started","description":"The first question of a session was opened","parameters":[{"name":"X-LearnSync-Event","in":"header","required":true,"schema":{"type":"string","const":"session.started"}},{"name":"X-LearnSync-Delivery","in":"header","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-LearnSync-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>` — reject if t is older than 5 minutes"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["event","created_at","data"],"properties":{"event":{"type":"string","const":"session.started"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried after 1m, 5m, 30m, 2h, 6h."}}}},"session.ended":{"post":{"summary":"session.ended","description":"A session ended — fetch /sessions/{session_id}/results","parameters":[{"name":"X-LearnSync-Event","in":"header","required":true,"schema":{"type":"string","const":"session.ended"}},{"name":"X-LearnSync-Delivery","in":"header","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-LearnSync-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>` — reject if t is older than 5 minutes"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["event","created_at","data"],"properties":{"event":{"type":"string","const":"session.ended"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried after 1m, 5m, 30m, 2h, 6h."}}}},"round.opened":{"post":{"summary":"round.opened","description":"A question (or a re-vote) was opened","parameters":[{"name":"X-LearnSync-Event","in":"header","required":true,"schema":{"type":"string","const":"round.opened"}},{"name":"X-LearnSync-Delivery","in":"header","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-LearnSync-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>` — reject if t is older than 5 minutes"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["event","created_at","data"],"properties":{"event":{"type":"string","const":"round.opened"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"question_id":{"type":"string","format":"uuid"},"round_id":{"type":"string","format":"uuid"},"seq":{"type":"integer"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried after 1m, 5m, 30m, 2h, 6h."}}}},"round.closed":{"post":{"summary":"round.closed","description":"A question stopped accepting answers","parameters":[{"name":"X-LearnSync-Event","in":"header","required":true,"schema":{"type":"string","const":"round.closed"}},{"name":"X-LearnSync-Delivery","in":"header","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-LearnSync-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>` — reject if t is older than 5 minutes"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["event","created_at","data"],"properties":{"event":{"type":"string","const":"round.closed"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"question_id":{"type":"string","format":"uuid"},"round_id":{"type":"string","format":"uuid"},"seq":{"type":"integer"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried after 1m, 5m, 30m, 2h, 6h."}}}},"roster.updated":{"post":{"summary":"roster.updated","description":"A course's class list changed","parameters":[{"name":"X-LearnSync-Event","in":"header","required":true,"schema":{"type":"string","const":"roster.updated"}},{"name":"X-LearnSync-Delivery","in":"header","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-LearnSync-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>` — reject if t is older than 5 minutes"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["event","created_at","data"],"properties":{"event":{"type":"string","const":"roster.updated"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"course_id":{"type":"string","format":"uuid"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried after 1m, 5m, 30m, 2h, 6h."}}}},"webhook.test":{"post":{"summary":"webhook.test","description":"Sent when you press \"Send test\"","parameters":[{"name":"X-LearnSync-Event","in":"header","required":true,"schema":{"type":"string","const":"webhook.test"}},{"name":"X-LearnSync-Delivery","in":"header","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-LearnSync-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>` — reject if t is older than 5 minutes"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["event","created_at","data"],"properties":{"event":{"type":"string","const":"webhook.test"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"webhook_id":{"type":"string","format":"uuid"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried after 1m, 5m, 30m, 2h, 6h."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"`Authorization: Bearer lsk_live_…`"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","message","request_id"],"properties":{"type":{"enum":["invalid_api_key","insufficient_scope","not_found","rate_limited","invalid_request","server_error"]},"message":{"type":"string"},"request_id":{"type":"string","description":"Quote this when contacting support"}}}}},"Course":{"type":"object","required":["id","name","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":["string","null"]},"identity_mode":{"enum":["student_number","email","name_only"]},"term":{"type":["string","null"],"description":"Academic term, e.g. 1/2026"},"sections":{"type":"array","items":{"type":"string"},"description":"Section names of the course"},"created_at":{"type":"string","format":"date-time"},"archived":{"type":"boolean"}}},"Session":{"type":"object","required":["id","course_id","title","status"],"properties":{"id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"enum":["draft","live","ended"]},"section":{"type":["string","null"],"description":"Section this session is for (null = every section)"},"join_code":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"questions":{"type":"integer"},"participants":{"type":"integer"}}},"RosterEntry":{"type":"object","properties":{"student_number":{"type":["string","null"]},"email":{"type":["string","null"]},"name":{"type":"string"},"section":{"type":["string","null"]}}},"Student":{"type":"object","properties":{"student_number":{"type":["string","null"]},"name":{"type":["string","null"]},"email":{"type":["string","null"]},"section":{"type":["string","null"]},"on_roster":{"type":"boolean","description":"false = joined but not on the class list"}}},"SessionResults":{"type":"object","required":["session","summary","questions","students"],"properties":{"session":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string"},"questions_asked":{"type":"integer"},"questions_pending_review":{"type":"integer","description":"Written-answer questions whose results the teacher hasn't confirmed yet — not counted as right or wrong"}}},"summary":{"type":"object","properties":{"roster_size":{"type":"integer"},"present":{"type":"integer"},"present_on_roster":{"type":"integer"},"participation_rate":{"type":["number","null"],"minimum":0,"maximum":1,"description":"0–1, null when there is nothing to divide by"},"correct_rate":{"type":["number","null"],"minimum":0,"maximum":1,"description":"0–1, null when there is nothing to divide by"}}},"questions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"number":{"type":"integer"},"type":{"type":"string"},"prompt":{"type":"string"},"asked":{"type":"boolean"},"responses":{"type":"integer"},"correct_rate_first_vote":{"type":["number","null"],"minimum":0,"maximum":1,"description":"0–1, null when there is nothing to divide by"},"correct_rate_after_discussion":{"type":["number","null"],"minimum":0,"maximum":1,"description":"0–1, null when there is nothing to divide by"},"pending_review":{"type":"boolean","description":"Written answers not yet confirmed by the teacher — correct_rate_first_vote is null until then"}}}},"students":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Student"}],"properties":{"present":{"type":"boolean"},"joined_at":{"type":["string","null"]},"answered":{"type":"integer"},"correct":{"type":"integer","description":"Correct on the first vote (re-votes don't count). One student on several devices: each question counts the answer sent first"},"pending_review":{"type":"integer","description":"Answers awaiting the teacher's confirmation (written answers)"},"points":{"type":"integer"},"participation_rate":{"type":["number","null"],"minimum":0,"maximum":1,"description":"0–1, null when there is nothing to divide by"},"devices":{"type":"integer","description":"More than 1 = switched phones or a shared student ID"}}}}}},"Gradebook":{"type":"object","required":["course","sessions","students"],"properties":{"course":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":["string","null"]}}},"grade_scale":{"type":"object","description":"The course's grade formula set by the teacher (default when not configured: 10 points from participation)","properties":{"max":{"type":"number"},"basis":{"type":"string","enum":["participation","attendance","correct"]},"configured":{"type":"boolean"}}},"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"created_at":{"type":"string"},"questions_asked":{"type":"integer"}}}},"students":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Student"}],"properties":{"attended":{"type":"integer"},"attendance_rate":{"type":["number","null"],"minimum":0,"maximum":1,"description":"0–1, null when there is nothing to divide by"},"answered":{"type":"integer"},"asked":{"type":"integer"},"participation_rate":{"type":["number","null"],"minimum":0,"maximum":1,"description":"Answered ÷ asked across all sessions — missing a session counts as 0"},"correct":{"type":"integer"},"pending_review":{"type":"integer"},"points":{"type":"integer"},"grade":{"type":["number","null"],"description":"Score from grade_scale — null when nothing has been asked yet"},"sessions":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"present":{"type":"boolean"},"answered":{"type":"integer"},"correct":{"type":"integer"},"points":{"type":"integer"},"pending":{"type":"integer"}}}}}}}}}}}}