Manage items
The questions of a question bank are called items. Every item is one question and carries four things:
| Property | Type | Description |
|---|---|---|
blockType | string | Which kind of block this question can be played as, e.g. SingleChoice. It has to be a block type the bank's Riddle type supports and cannot be changed once the item exists |
category | string | A free text label used to group questions, e.g. History. This is what a Riddle draws by, so keep your categories consistent |
difficulty | integer | How hard the question is, from 1 to 10. A Riddle can draw a range of it |
columns | object | The actual content of the question - see below |
Columns
An item does not have a fixed set of fields; it has columns, and which columns exist depends on its blockType. A SingleChoice quiz question, for example, has a QUESTION, at least one CORRECT_CHOICE and at least one INCORRECT_CHOICE.
columns is an object with the column names as keys. Each key holds the ordered list of that column's values, and every value is an object of exactly {"id": int|null, "value": string}:
{
"QUESTION": [
{"id": null, "value": "In which year did the Berlin Wall fall?"}
],
"CORRECT_CHOICE": [
{"id": null, "value": "1989"}
],
"INCORRECT_CHOICE": [
{"id": null, "value": "1987"},
{"id": null, "value": "1991"},
{"id": null, "value": "1993"}
]
}
The id addresses a value in later updates:
- Pass
"id": nullto add a value - an ID is assigned and returned to you. - Pass an ID the item already has to update that value. An ID that belongs to another item is rejected.
- Leave a value out to remove it.
Note: Values whose value is empty are dropped instead of stored, and duplicate IDs as well as duplicate choices within one item are rejected.
Note: Text is stored without support for 4-byte characters, so emoji cannot be used in a question bank item.
Which columns does a block type have?
/question-bank/grid/block-type-columns/{riddleType} returns every block type of a Riddle type together with its columns, so you do not have to hardcode them:
GET /question-bank/grid/block-type-columns/Quiz
{
"success": true,
"code": 200,
"data": [
{
"blockType": "SingleChoice",
"columns": {
"QUESTION": 1,
"DESCRIPTION": 0,
"CORRECT_CHOICE": 1,
"INCORRECT_CHOICE": 1,
"ANSWER_EXPLANATION_TITLE": 0,
"ANSWER_EXPLANATION_DESCRIPTION": 0
}
}
]
}
The number is how many values that column requires at minimum – 0 means the column is optional, 1 means at least one value has to be sent. Sending a column that is not listed for the block type, or fewer values than required, is answered with 400 and the error code QUESTION_BANK_UPDATE.
Add an item
/question-bank/{questionBank}/item (POST) creates a new item. All four properties are required:
| Property | Required | Type | Description |
|---|---|---|---|
blockType | yes | string | The block type of the question, e.g. SingleChoice |
category | yes | string | The category of the question, at most 255 characters |
difficulty | yes | integer | 1 - 10 |
columns | yes | object | The columns of the question, at least one |
{
"blockType": "SingleChoice",
"category": "History",
"difficulty": 4,
"columns": {
"QUESTION": [{"id": null, "value": "In which year did the Berlin Wall fall?"}],
"CORRECT_CHOICE": [{"id": null, "value": "1989"}],
"INCORRECT_CHOICE": [
{"id": null, "value": "1987"},
{"id": null, "value": "1991"}
]
}
}
The response contains the created item, with the assigned value IDs:
{
"success": true,
"code": 200,
"data": {
"id": 98765,
"questionBank": {"... the bank ..."},
"blockType": "SingleChoice",
"category": "History",
"difficulty": 4,
"columns": {
"QUESTION": [{"id": 1, "value": "In which year did the Berlin Wall fall?"}],
"CORRECT_CHOICE": [{"id": 2, "value": "1989"}],
"INCORRECT_CHOICE": [
{"id": 3, "value": "1987"},
{"id": 4, "value": "1991"}
]
},
"publishedColumns": null,
"modifiedAt": "2026-08-06 10:22:41",
"createdAt": "2026-08-06 10:22:41"
}
}
columns is the draft state, publishedColumns the state your Riddles actually draw from – it stays null until the bank is published.
Update an item
/question-bank/{questionBank}/item/{questionBankItem} (PUT) takes the same four properties, and all of them are required again – the request describes the complete item, so a column you leave out is removed.
The blockType has to stay the one the item already has; changing it is rejected with QUESTION_BANK_UPDATE.
Note: An item ID that does not exist is answered with 404 and Question bank item not found.. An item ID that exists but belongs to a different bank than the one in the URL is answered with 403 and This item does not belong to the specified question bank..
Delete an item
/question-bank/{questionBank}/item/{questionBankItem} (DELETE) removes an item from the bank. The response tells you whether the bank now has unpublished changes:
{
"success": true,
"code": 200,
"data": {
"hasChanges": true
}
}
Note: Like every other write, a deletion only reaches your Riddles once the bank is published.
List the items of a bank
/question-bank/{questionBank}/items returns the items of a bank. All parameters are optional query parameters:
| Property | Type | Description |
|---|---|---|
search | string | A search term matched against the column values, e.g. a question title or a choice |
blockType | string | Only return items of this block type |
category | string | Only return items of this category |
difficulty | string | A range formatted as min,max, e.g. 1,5 |
page | integer | The page to return, starting at 1. Omit to return every matching item at once |
pageSize | integer | Items per page, only used together with page (default 50, maximum 100) |
GET /question-bank/4711/items?blockType=SingleChoice&category=History&difficulty=3,7
Note: blockType is validated against the block types the bank's Riddle type actually supports. An unsupported value is rejected with 400, and the message names every allowed type, e.g. "blockType" must be one of "SingleChoice", "MultipleChoice" for riddle type "Quiz", got "Foo"..
Note: difficulty must have min less than or equal to max. A range the wrong way round is rejected with 400, e.g. "difficulty" range must have min <= max (valid values: min and max between 1 and 10), got "7,3"..
The response holds the count of returned items and the items themselves:
{
"success": true,
"code": 200,
"data": {
"count": 24,
"items": ["... the items ..."]
}
}
Check what a Riddle actually draws
A QuestionBank block asks the bank for questions per block type and category. To see how many published items each of those criteria currently finds, use /question-bank/riddle-items/{riddle}/{blockId} with the Riddle UUID and the ID of the QuestionBank block:
{
"success": true,
"code": 200,
"data": [
{"blockType": "SingleChoice", "category": "History", "count": 24},
{"blockType": "MultipleChoice", "category": "Sports", "count": 3}
]
}
count is a plain inventory count: it ignores the block's maxNumberOfQuestions entirely and reports every published item matching the criterion's category, difficultyRange and blockType. It is not a preview of what a draw would actually pick, and it can be (and often is) higher than what the block shows once published, since an actual draw is capped at maxNumberOfQuestions.
This is the endpoint to use when a Riddle plays fewer questions than you expected: a count below the block's maxNumberOfQuestions means the bank simply does not hold enough published items for that combination.
Note: The block ID has to belong to a QuestionBank block of that Riddle – any other block type is answered with 400.
Note: Publishing is only blocked when every criterion of every QuestionBank block in the Riddle matches zero published items. A single criterion matching too few items, or none, while another criterion in the Riddle still matches something, publishes without any warning. Use this endpoint before publishing to check inventory per criterion yourself.

