Replace Question
PUT /v1/nextexams/questions/{id}
Replace a question
A question is saved whole: options, answer key and tags you leave out
are removed. Read it, change what you need, send it back. A body with no
options key is refused with 422 OPTIONS_REQUIRED while the question
has options — send [] to remove them deliberately.
Option ids survive a save: send each option's id back and answers already
given to it keep grading. A change that would alter how existing answers
are graded is refused with 409 ANSWERED_NEEDS_VERSION unless the body
carries new_version. Send expected_version to be refused with
409 STALE if someone saved in between.
Scope questions:write; the role needs questions.edit.
Request
PUT https://your-site.example/api/index.php/v1/nextexams/questions/{id}
Parameters
| Name | Type | In | Required | Description |
|---|---|---|---|---|
id |
integer |
path |
Yes | The record's id. (min 1) |
Request body
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
title |
string |
No | |
question_type |
string |
No | |
stem |
string |
No | |
explanation |
string |
No | |
subject_id |
integer |
No | |
difficulty |
integer |
No | (min 1, max 5) |
default_points |
number |
No | |
shuffle_options |
boolean|integer |
No | |
published |
boolean|integer |
No | |
options |
object[] |
No | |
options.id |
integer |
No | Send it back to keep the option (and the answers given to it). |
options.option_text |
string |
No | |
options.is_correct |
boolean|integer |
No | |
options.match_key |
string|null |
No | |
options.feedback |
string|null |
No | |
answer_key |
object |
No | |
tags |
array |
No | Tag ids, tag titles, or {id, title} objects; a new title creates the tag. |
owner_exam_id |
integer |
No | Create the question private to this exam. Needs exams:write too. |
expected_version |
integer |
No | Refuse with 409 STALE if the stored version differs. |
new_version |
object |
No | Save as a new version, swapped into the listed exams. |
new_version.exam_ids |
integer[] |
No |
{
"title": "string",
"question_type": "string",
"stem": "string",
"explanation": "string",
"subject_id": 1,
"difficulty": 1,
"default_points": 1.5,
"shuffle_options": true,
"published": true,
"options": [
{
"id": 1,
"option_text": "string",
"is_correct": true,
"match_key": "string",
"feedback": "string"
}
],
"answer_key": {},
"tags": [
"string"
],
"owner_exam_id": 1,
"expected_version": 1,
"new_version": {
"exam_ids": [
1
]
}
}
Responses
200 — Saved.
{
"data": {
"id": 1
}
}
| Field | Type | Meaning |
|---|---|---|
data.id |
integer |
Errors
| Status | Code | When |
|---|---|---|
400 |
BAD_REQUEST |
The body failed validation, or is not a JSON object (INVALID_JSON). |
401 |
INVALID_TOKEN |
No token (MISSING_TOKEN), or one that is wrong, expired, revoked, or whose owner can no longer sign in (INVALID_TOKEN). |
403 |
MISSING_SCOPE |
The token is valid but this request is not allowed, and code says why: MISSING_SCOPE (the token lacks the scope), FORBIDDEN (the owner's role does not allow it), ACCESS_DENIED (the owner cannot manage Next Exams), API_DISABLED (the API Tokens app is off), APP_DISABLED (the feature's app is off — meta.app names it), ACCOUNT_UNUSABLE (the owner is blocked or must reset their password), DEMO_LOCKED (a demo site). |
404 |
NOT_FOUND |
No record has this id. |
409 |
The save or delete would break something that depends on the record: QUESTION_IN_USE, ANSWERED_NEEDS_VERSION, TYPE_LOCKED, STALE. meta carries the details (for example the exams that use a question). |
|
422 |
The request is well formed but would lose data (OPTIONS_REQUIRED). |
Examples
curl
curl -s -X PUT "https://your-site.example/api/index.php/v1/nextexams/questions/1" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"string","question_type":"string","stem":"string","explanation":"string","subject_id":1,"difficulty":1,"default_points":1.5,"shuffle_options":true,"published":true,"options":[{"id":1,"option_text":"string","is_correct":true,"match_key":"string","feedback":"string"}],"answer_key":{},"tags":["string"],"owner_exam_id":1,"expected_version":1,"new_version":{"exam_ids":[1]}}'
PHP
<?php
$ch = curl_init('https://your-site.example/api/index.php/v1/nextexams/questions/1');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{"title":"string","question_type":"string","stem":"string","explanation":"string","subject_id":1,"difficulty":1,"default_points":1.5,"shuffle_options":true,"published":true,"options":[{"id":1,"option_text":"string","is_correct":true,"match_key":"string","feedback":"string"}],"answer_key":{},"tags":["string"],"owner_exam_id":1,"expected_version":1,"new_version":{"exam_ids":[1]}}',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
JavaScript
const API_TOKEN = process.env.API_TOKEN;
const response = await fetch("https://your-site.example/api/index.php/v1/nextexams/questions/1", {
method: 'PUT',
headers: {
"Authorization": `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "string",
"question_type": "string",
"stem": "string",
"explanation": "string",
"subject_id": 1,
"difficulty": 1,
"default_points": 1.5,
"shuffle_options": true,
"published": true,
"options": [
{
"id": 1,
"option_text": "string",
"is_correct": true,
"match_key": "string",
"feedback": "string"
}
],
"answer_key": {},
"tags": [
"string"
],
"owner_exam_id": 1,
"expected_version": 1,
"new_version": {
"exam_ids": [
1
]
}
}),
});
const data = await response.json();