quiz-taking
9 endpoints.
/api/quizzes/assigned Bearer tokenList my assigned quizzes
Every published quiz assigned to the caller — individually, or through any unexpired grant of an assigned role — with where they stand on each: the current and next scheduled sittings they may sit (a roster sitting counts only for those on its roster), attempts used, the open attempt to resume, and the latest result once results are visible. Needs no permission grant: being assigned is the authorization.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The caller's assigned quizzes, newest first. | AssignedQuizList |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/quizzes/{quiz_id}/attempts Bearer tokenStart (or resume) an attempt
Starts an attempt of an assigned, published quiz that is takeable right now — inside an open sitting for live quizzes (one open to every assignee, or a roster sitting the caller is on), any time for async ones. The paper is drawn per the quiz's selection mode, persisted, and served with the answers stripped. An attempt already in progress is returned instead of starting another (200, not 201). Refused outside a sitting, or once max_attempts is used up — counted within the current sitting for live quizzes, per quiz for async. Needs no permission grant: being assigned is the authorization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| quiz_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The attempt already in progress, resumed. | QuizAttemptDetail |
| 201 | The freshly started attempt and its paper. | QuizAttemptDetail |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No quiz assigned to the caller has that id or slug. | ErrorResponse |
| 409 | No sitting is open, no attempts remain, or the paper is empty. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/quiz-attempts/{attempt_id}/questions/{question_id}/pdf Bearer tokenGet a download URL for a presented question's PDF
Issues a short-lived presigned GET for the PDF of a pdf question this attempt presented — while sitting the attempt and in answer review alike. Readable by the taker themselves, and by holders of quizzes:read over the quiz. Carries no answers.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| attempt_id | path | string (uuid) | yes | UUID id of the attempt. |
| question_id | path | string (uuid) | yes | UUID id of the question. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Where to fetch the PDF from, for the next five minutes. | DownloadTicket |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible attempt has that id, the question is not on its paper, or it has no PDF. | ErrorResponse |
| 500 | The download URL could not be signed. | ErrorResponse |
| 503 | File storage is not configured on this deployment. | ErrorResponse |
/api/quiz-attempts/{attempt_id}/review.csv Bearer tokenDownload an attempt's answers as CSV
The attempt's graded paper as one CSV file, one row per presented question in presentation order, with the columns `attempt_id`, `quiz_id`, `quiz_name`, `user_id`, `user_display_name`, `attempt_number`, `status`, `started_at`, `submitted_at`, `auto_submitted`, `score_percentage`, `passed`, `position`, `question_id`, `kind`, `prompt`, `response`, `correct_answer`, `is_correct`, `points_awarded`, `points_possible`, `feedback`, `answered_at`. Lists in a cell are `; `-separated, alternative accepted spellings `|`-separated, matched pairs read `left → right`, and the file opens with a UTF-8 byte order mark for spreadsheets. Takers may download their own scored attempt once its results are visible and the quiz shows correct answers; holders of quizzes:read or quizzes:grade over the quiz may download any attempt, in progress or not.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| attempt_id | path | string (uuid) | yes | UUID id of the attempt. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The CSV file, served as an attachment. | — |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | Review is not (or not yet) available to the taker for this quiz. | ErrorResponse |
| 404 | No visible attempt has that id. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/quiz-attempts/{attempt_id}/review.json Bearer tokenDownload an attempt's answers as JSON
The attempt's graded paper — exactly the review endpoint's document — served as a JSON attachment. Takers may download their own scored attempt once its results are visible and the quiz shows correct answers; holders of quizzes:read or quizzes:grade over the quiz may download any attempt, in progress or not.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| attempt_id | path | string (uuid) | yes | UUID id of the attempt. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The JSON file, served as an attachment. | QuizAttemptReview |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | Review is not (or not yet) available to the taker for this quiz. | ErrorResponse |
| 404 | No visible attempt has that id. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/quiz-attempts/{attempt_id}/review Bearer tokenReview an attempt's answers
The attempt's paper with correct answers, the taker's responses and per-question grades — points awarded and possible, the marker's feedback, and the rubric where the reader may see it. Takers see it only once the attempt is scored, its results are visible, and the quiz is configured to show correct answers; holders of quizzes:read or quizzes:grade over the quiz always may, with the rubric and the machine's suggestion on manually graded questions. Questions render as they are *now* — an edit after the sitting changes the review, never the recorded grades. The same paper downloads as a file from the sibling review.csv and review.json paths.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| attempt_id | path | string (uuid) | yes | UUID id of the attempt. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The graded paper, answers included. | QuizAttemptReview |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | Review is not (or not yet) available to the taker for this quiz. | ErrorResponse |
| 404 | No visible attempt has that id. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/quiz-attempts/{attempt_id} Bearer tokenRead an attempt
The attempt and its paper, answers stripped — the taking and resume view. Readable by the taker themselves, and by holders of quizzes:read over the quiz. An attempt past its deadline is finalized (auto-submitted as-is) by this read. Held-back results are nulled for the taker until released; permission holders always see them.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| attempt_id | path | string (uuid) | yes | UUID id of the attempt. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The attempt and its paper. | QuizAttemptDetail |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible attempt has that id. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/quiz-attempts/{attempt_id}/answers Bearer tokenAnswer one question
Saves (or replaces) the taker's answer to one presented question, in the shape its kind takes: response_boolean (true_false), selected_option_ids (multiple_choice takes exactly one id; multi_select the chosen ids; ordering every presented item in the taker's arrangement), response_text (short_answer), response_number (numeric), response_matches (matching; partial pair lists are fine), response_blanks (fill_in_blank; one entry per blank, empty ones allowed) or response_drawing (drawing; the canvas as an image data URL). Only the taker may answer, only while the attempt is in progress and its deadline has not passed. Answers are not graded here; grading happens at submit.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| attempt_id | path | string (uuid) | yes | UUID id of the attempt. |
| Field | Type | Required | Description |
|---|---|---|---|
| question_id | string (uuid) | yes | — |
| response_boolean | boolean | no | — |
| selected_option_ids | array of string (uuid) | no | multiple_choice: exactly one id. multi_select: the chosen ids. ordering: every presented item id, in the taker's arrangement. 1–20 items |
| response_text | string | no | short_answer: up to 500 characters. long_answer: up to 20000. 1–20000 characters |
| response_number | number | no | — |
| response_matches | array of ResponseMatch | no | 1–10 items |
| response_blanks | array of string | no | fill_in_blank: one entry per blank of the prompt, in order; leave an entry empty for a blank not yet filled, but fill at least one. 1–10 items |
| response_drawing | string | no | drawing: the taker's canvas as one image — the starting image, if any, beneath their strokes — as a base64 data URL (image/png, image/webp or image/jpeg). at most 4000000 characters · matches ^data:image\/(png|webp|jpeg);base64,[A-Za-z0-9+/]+=*$ |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The attempt with the saved answer. | QuizAttemptDetail |
| 400 | The answer does not fit the question's kind or options. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible attempt has that id, or the question is not on its paper. | ErrorResponse |
| 409 | The attempt is already submitted, or its deadline has passed. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/quiz-attempts/{attempt_id}/submit Bearer tokenSubmit an attempt
Grades the attempt server-side, scores it against the quiz's pass mark, and reports it into the taker's learning record for the quiz. Unanswered questions score zero. A paper holding a manually graded question lands in pending_grading instead: a marker awards its points and finalizes it, and an administrator releases the result. Submitting twice is safe — the second call returns the attempt as it stands. Score and pass/fail travel back only when the quiz's rules make results visible.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| attempt_id | path | string (uuid) | yes | UUID id of the attempt. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The submitted attempt; results nulled while withheld. | QuizAttempt |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible attempt has that id. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |