Documentation
Quiz Attempts
Submitting an attempt is public — no API key required. Reading attempt results back is developer-only, since answers embed the correct choices.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /quizzes/{quiz}/attempts | public | Submit an attempt |
| GET | /quizzes/{quiz}/attempts | API key | List all attempts on a quiz |
| GET | /quizzes/{quiz}/attempts/identifier/{taker_identifier} | API key | Find attempt(s) by a taker identifier |
| GET | /quizzes/{quiz}/attempts/{attempt} | API key | Get a single attempt |
POST
/quizzes/{quiz}/attempts
Public
Submit an attempt
answers must contain exactly one entry per question belonging to the quiz (no missing, extra, or duplicate question_ids), and each choice_ids value must belong to that entry's own question_id.
| Field | Type | Rules |
|---|---|---|
| taker_identifier | string | Required, max 255 — a string you choose to represent one of your end-users; one attempt per identifier per quiz |
| answers | array | Required — one entry per question in the quiz |
| answers[].question_id | integer | Required, must belong to this quiz |
| answers[].choice_ids | array of integers | Required, at least 1 — the choice(s) selected for that question |
| captcha_token | string | Required only if this quiz has requires_captcha: true (see Quizzes) — a token from your embedded Cloudflare Turnstile widget, verified server-side before the attempt is accepted. |
curl -X POST https://qnaapi.com/api/v1/quizzes/1/attempts \
-H "Content-Type: application/json" \
-d '{
"taker_identifier": "student-42",
"answers": [
{ "question_id": 1, "choice_ids": [1, 3] },
{ "question_id": 2, "choice_ids": [5] }
]
}'{
"data": {
"id": 1,
"quiz_id": 1,
"taker_identifier": "student-42",
"score": 2,
"total": 2,
"percentage": 100,
"answers": [
{ "quiz_question_id": 1, "choice_ids": [1, 3], "is_correct": true },
{ "quiz_question_id": 2, "choice_ids": [5], "is_correct": true }
],
"created_at": "2026-08-15T01:25:32.000000Z"
}
}This is the only time a taker sees their is_correct breakdown, since attempt reads require your API key. If taker_identifier has already submitted an attempt on this quiz, the response is 409 Conflict. Rate-limited to 30 requests/minute/IP.
If the quiz has
requires_captcha: true, a missing, invalid, or Cloudflare-rejected captcha_token returns 422 Unprocessable Entity with a validation error on captcha_token. See Error Responses.
If the quiz is closed —
is_closed: true, or its response_limit has been reached — an attempt is rejected with 422 Unprocessable Entity and a validation error on closed. Check is_accepting_responses on the quiz (see Quizzes) before showing your quiz to avoid this.
GET
/quizzes/{quiz}/attempts, .../attempts/{attempt}, .../attempts/identifier/{taker_identifier}
API key
Read attempt results
Requires your API key, scoped to your own quizzes — same shape as the submission response above, as a single object or a list.
curl -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/quizzes/1/attempts