Docs
Pricing Log in
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.

MethodEndpointAuthDescription
POST/quizzes/{quiz}/attemptspublicSubmit an attempt
GET/quizzes/{quiz}/attemptsAPI keyList all attempts on a quiz
GET/quizzes/{quiz}/attempts/identifier/{taker_identifier}API keyFind attempt(s) by a taker identifier
GET/quizzes/{quiz}/attempts/{attempt}API keyGet 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.

FieldTypeRules
taker_identifierstringRequired, max 255 — a string you choose to represent one of your end-users; one attempt per identifier per quiz
answersarrayRequired — one entry per question in the quiz
answers[].question_idintegerRequired, must belong to this quiz
answers[].choice_idsarray of integersRequired, at least 1 — the choice(s) selected for that question
captcha_tokenstringRequired 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.
REQUEST — NO API KEY
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] }
    ]
  }'
201 CREATED
{
  "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.

REQUEST
curl -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/quizzes/1/attempts