Docs
Pricing Log in
Documentation

Quizzes

A quiz is a set of graded, choice-only questions. Managing a quiz — including reading attempts — requires your API key.

Scoring is exact-match, all-or-nothing per question — there is no partial credit. A question can have more than one correct choice (select-all-that-apply); it only counts toward the score if the submitted set of choices exactly matches the correct set. Selecting only some of the correct choices, or a correct choice plus an extra wrong one, both score that question as incorrect.

A quiz attempt's answers embed is_correct and the exact choices selected, which is effectively the answer key, so attempt results are never public — only submitting an attempt is. See Quiz Attempts.

MethodEndpointDescription
POST/quizzesCreate a quiz
GET/quizzesList all of your quizzes
GET/quizzes/identifier/{identifier}Get one of your quizzes by its identifier
GET/quizzes/{quiz}Get one of your quizzes by ID
PATCH/quizzes/{quiz}Update a quiz (PUT also accepted)
DELETE/quizzes/{quiz}Delete a quiz (also deletes its questions, choices, attempts, and answers)
POST /quizzes API key

Create a quiz

FieldTypeRules
identifierstringOptional, same rules as Poll's identifier — unique per account. Generated from title if omitted.
titlestringRequired, max 255
descriptionstringOptional
metadataobjectOptional, freeform — same as Poll's metadata
site_idintegerOptional, same rules as Poll's site_id
requires_captchabooleanOptional, same rules as Poll's requires_captcha — see Quiz Attempts.
is_closedbooleanOptional, same rules as Poll's is_closed — see Quiz Attempts.
response_limitintegerOptional, nullable, minimum 1, same rules as Poll's response_limit.
questionsarrayRequired, at least 1
questions[].promptstringRequired
questions[].choicesarrayRequired, at least 2, labels must be unique within that question
questions[].choices[].labelstringRequired, max 255
questions[].choices[].is_correctbooleanRequired — at least one choice per question must be true
REQUEST
curl -X POST https://qnaapi.com/api/v1/quizzes \
  -H "X-API-Key: qna_..." \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "js-basics",
    "title": "JavaScript Basics",
    "questions": [
      {
        "prompt": "Which are primitive types in JS?",
        "choices": [
          { "label": "string", "is_correct": true },
          { "label": "object", "is_correct": false },
          { "label": "number", "is_correct": true }
        ]
      },
      {
        "prompt": "What is 2 + 2?",
        "choices": [
          { "label": "3", "is_correct": false },
          { "label": "4", "is_correct": true }
        ]
      }
    ]
  }'
201 CREATED
{
  "data": {
    "id": 1,
    "identifier": "js-basics",
    "title": "JavaScript Basics",
    "description": null,
    "metadata": null,
    "site_id": null,
    "requires_captcha": false,
    "is_closed": false,
    "response_limit": null,
    "is_accepting_responses": true,
    "questions": [
      {
        "id": 1,
        "prompt": "Which are primitive types in JS?",
        "position": 0,
        "choices": [
          { "id": 1, "label": "string", "is_correct": true },
          { "id": 2, "label": "object", "is_correct": false },
          { "id": 3, "label": "number", "is_correct": true }
        ]
      },
      {
        "id": 2,
        "prompt": "What is 2 + 2?",
        "position": 1,
        "choices": [
          { "id": 4, "label": "3", "is_correct": false },
          { "id": 5, "label": "4", "is_correct": true }
        ]
      }
    ],
    "created_at": "2026-08-15T01:25:24.000000Z",
    "updated_at": "2026-08-15T01:25:24.000000Z"
  }
}
is_accepting_responses is read-only and computed on every response — false whenever is_closed is true or the quiz's attempt count has reached response_limit. Toggle is_closed/response_limit instead of trying to set it directly.
GET /quizzes, /quizzes/{quiz}, /quizzes/identifier/{identifier} API key

List / get by ID / get by identifier

Same shape as the equivalent Poll endpoints, scoped to your own quizzes.

REQUEST
curl -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/quizzes
PATCH /quizzes/{quiz} API key

Update a quiz

Every field is optional. questions (with their choices) can be replaced using the same shape and validation rules as create — but only while the quiz has zero attempts. site_id, requires_captcha, is_closed, and response_limit can be changed at any time.

REQUEST
curl -X PATCH https://qnaapi.com/api/v1/quizzes/1 \
  -H "X-API-Key: qna_..." \
  -H "Content-Type: application/json" \
  -d '{ "title": "JavaScript Fundamentals" }'
Once someone has attempted a quiz, sending questions in an update is rejected (422), for the same reason Poll options lock after the first vote: changing them would silently invalidate existing scores. Delete and recreate the quiz if the questions need to change after that point.
DELETE /quizzes/{quiz} API key

Delete a quiz

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

204 No Content on success. Cascades — questions, choices, attempts, and answers are all removed too.