Skip to content

Question bank ​

The question bank ("My Questions" in the teacher console) holds reusable questions on their own, separate from any quiz. Storing a question creates no quiz and uses no AI credits.

OperationEndpointScope
List questionsGET /api/v1/question-bank/questionsapi:quiz:read
Add questionsPOST /api/v1/question-bank/questionsapi:quiz:write
Get a questionGET /api/v1/question-bank/questions/{questionId}api:quiz:read
Update a questionPATCH /api/v1/question-bank/questions/{questionId}api:quiz:write
Archive a questionDELETE /api/v1/question-bank/questions/{questionId}api:quiz:write

The bank uses the quiz scopes: a key that can read or write quizzes can read or write the bank.

Where bank questions come from ​

Besides the questions you add here, the bank fills itself: whenever a quiz is saved — through /api/v1/quizzes, in the console, or through the Claude connector — each question of a storable type is added to the bank (a question identical to one already there is not added twice). Those rows are what status: "needs_review" is for — a question that was read from a quiz but did not pass the bank's validation.

So after creating or updating a quiz you do not need to post its questions here; doing so stores them a second time, because POST on this page does not deduplicate.

A quiz holds its own copy of its questions, so editing or archiving a bank question never changes a quiz. See Questions, Quizzes, and Collections.

The question object ​

A question is written and returned in one shape:

FieldTypeNotes
question_typestringOne of vocab_mcq, cloze_mcq, reading_mcq, subject_mcq, true_false, ordering.
promptstringThe question text.
optionsstring[]Every type except ordering: 2–6 unique choices. true_false takes exactly 2 and defaults to ["True", "False"].
answer_indexnumber0-based index of the correct option.
ordered_itemsstring[]ordering only: 2–8 items in their correct order.
passagestringRequired for reading_mcq.
explanation, hint, instructionstringOptional.
image_urlstringOptional absolute http(s) URL.

matching, short_answer, and fill_in_blank questions cannot be stored in the bank yet; the API answers 400 and names the supported types. Put those in a quiz with POST /api/v1/quizzes.

List questions ​

GET /api/v1/question-bank/questions?search=chai&limit=20
QueryNotes
searchText to find in prompts, options, items, explanations, and passages.
question_typeOne of the types above.
target_languageLanguage code, e.g. vi.
tagAn exact tag.
statusverified or needs_review (see below).
set_idOnly questions in this question set.
limit1–100, default 20.
cursornext_cursor from the previous page.

Questions come newest first as summaries (no options or answers). Paging is by cursor because the bank reports no total: pass next_cursor back as cursor until it is null. Archived questions are never listed.

json
{
  "data": [
    { "id": "…", "revision": 1, "question_type": "vocab_mcq", "target_language": "vi",
      "status": "verified", "tags": ["food"], "prompt": "What is 'Chai'?",
      "created_at": "…", "updated_at": "…" }
  ],
  "next_cursor": null,
  "limit": 20
}

status is needs_review for a question that was copied from one of your quizzes but did not pass the bank's validation. Saving valid content with PATCH makes it verified.

Add questions ​

POST /api/v1/question-bank/questions
FieldTypeRequiredNotes
target_languagestringYesLanguage code shared by every question in the call.
questionsarrayYes1–50 question objects.
tagsstring[]NoUp to 50 tags, applied to every question in the call.

Every question is validated before any is saved, so a rejected call stores nothing. The bank does not look for duplicates: sending the same question twice stores it twice.

json
{
  "target_language": "vi",
  "tags": ["restaurant"],
  "questions": [
    { "question_type": "vocab_mcq", "prompt": "What is 'Chai'?",
      "options": ["Bottle", "Can", "Plate"], "answer_index": 0 },
    { "question_type": "ordering", "prompt": "Put the words in order.",
      "ordered_items": ["Cho", "anh", "một", "tô", "phở"] }
  ]
}

Returns 201 with { "data": [ …summaries… ] }.

Get a question ​

GET /api/v1/question-bank/questions/{questionId}

Returns the summary fields plus question, the full question object, and revision, which PATCH and DELETE use as a guard.

Update a question ​

PATCH /api/v1/question-bank/questions/{questionId}
FieldTypeNotes
questionobjectThe complete replacement question. Saved as a new revision.
expected_revisionnumberThe revision you read. Required with question; optional with tags alone. Whenever it is given and no longer matches, the call answers 409.
target_languagestringOptional; defaults to the stored language.
tagsstring[]Replaces the tag list. Send [] to clear it.

Send question, tags, or both. Earlier revisions are kept, and quizzes that were already built from the question keep the revision they used. If the question changed after you read it, the call answers 409; read it again and retry.

Archive a question ​

DELETE /api/v1/question-bank/questions/{questionId}?expected_revision=3

Archiving is the bank's only way to remove a question, and it cannot be undone. Quizzes already built from the question are not affected. expected_revision is optional; when given, the call answers 409 if the question changed after you read it. Returns { "data": { "id": "…", "archived": true } }. Archiving the same id again answers 404.

Errors ​

StatuscodeMeaning
400—The body is not question-shaped (error: "Invalid input" with details).
400INVALID_INPUTA question failed validation; error names the question and the field.
403FEATURE_DISABLEDThe question bank is not enabled for this account.
404NOT_FOUNDNo such question in your bank, or it was archived.
409REVISION_CONFLICTexpected_revision no longer matches.

TechTrans Lab