Contents
On this page
Create Question - Plugixa Exam
POST /plugixa-exam/v1/questions
Create a question of any registered type, with its options and answer key.
Scope questions:write; the token’s user needs the questions:create permission.
Request
POST https://your-site.example/wp-json/plugixa-exam/v1/questions
Request body
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
title |
string |
Yes | Plain text. Must not be blank. |
question_type |
string |
Yes | A registered type key. See List Question Types and the keys below. |
slug |
string |
No | Made from the title when empty, and made unique if it is already taken. |
subject_id |
integer |
No | Defaults to 0 (no subject). The subject is not checked to exist. |
stem |
string |
No | The question text, HTML. Tags outside the question-text allow-list are stripped. |
explanation |
string |
No | Shown after marking, HTML. Same allow-list as stem. |
difficulty |
integer |
No | 1 to 5; values outside are clamped. Defaults to 1. |
default_points |
number |
No | 0 or more, rounded to 2 decimals. Defaults to 1. |
options |
object[] |
For some types | The answer options. Required, with at least 2 non-blank options, for types that use an option list (mcq_single, mcq_multiple, and module types that declare it). Ignored entirely for types without options. See Options. |
answer_key |
object |
For some types | Marking rules. The shape depends on the type: see Answer key by type. |
shuffle_options |
boolean |
No | Defaults to true. Only an explicitly empty value (false, 0, "") turns it off. |
published |
boolean |
No | Defaults to false: a question created without it is unpublished. |
approval_status |
string |
No | draft or approved. Any other value is saved as approved. |
rubric |
object[] |
No | Marking criteria, each {key, label, max_points, description}. label is required, or the row is dropped. key is made from the label when empty. max_points is 0 or more. An empty list is saved as null. |
render_hint |
object |
No | A flat object of display hints. Every value is saved as plain text. |
tag_ids |
integer[] |
No | With the Tags app on: the question’s tags. |
custom_fields |
object |
No | PRO With the Custom fields app on: values for your custom fields, by field name. |
The question starts at version 1, and that first version is saved to its history.
Note.
publisheddefaults tofalse. A question you create without sendingpublished: truedoes not appear on the site until you publish it.
{
"title": "Capital of France",
"question_type": "mcq_single",
"subject_id": 4,
"stem": "<p>What is the capital of France?</p>",
"explanation": "<p>Paris is the capital of France.</p>",
"difficulty": 2,
"default_points": 1,
"options": [
{ "option_text": "Paris", "is_correct": true },
{ "option_text": "Lyon", "is_correct": false },
{ "option_text": "Marseille", "is_correct": false }
],
"answer_key": { "negative_marking": false, "min_score": 0 },
"published": true,
"tag_ids": [5]
}
Options
Each entry of options:
| Field | Type | Description |
|---|---|---|
option_text |
string |
HTML. An option whose text is blank is dropped. |
is_correct |
boolean |
Whether this option is a correct answer. |
match_key |
string |
Optional. Used by module types that pair options. |
feedback |
string |
Optional feedback for this option. |
id |
integer |
Only on Update Question: an existing option to keep and update in place. |
Options are saved in the order you send them.
Answer key by type
answer_key is cleaned per type before it is saved, so unknown keys are dropped and missing keys take their defaults.
| Type | Answer key |
|---|---|
mcq_single |
{negative_marking: boolean, min_score: number}. The correct answer lives on the options: exactly one must have is_correct: true. min_score is the penalty for a wrong pick and is 0 or below; a value above zero is saved as 0. |
mcq_multiple |
{partial_policy, negative_marking, min_score}. partial_policy is all_or_nothing (the default, also used for any other value) or proportional. At least one option must be correct. |
true_false |
{correct: boolean}. No options. |
short_answer |
{accepted, case_insensitive, trim, use_regex, numeric, tolerance}. accepted is a list of accepted answers and must not be empty. case_insensitive and trim default to true. With use_regex on, each accepted answer is a pattern, and patterns that do not compile are dropped. numeric compares as numbers, within tolerance (0 or more). |
essay |
{min_words: integer, max_words: integer}, both 0 or more. Defaults to 0. |
Other types define their own answer key shape in the module that registers them.
{
"title": "Boiling point of water",
"question_type": "short_answer",
"stem": "<p>At sea level, water boils at how many degrees Celsius?</p>",
"answer_key": {
"accepted": ["100"],
"numeric": true,
"tolerance": 0.5
}
}
Question types
The five built-in types are mcq_single, mcq_multiple, true_false, short_answer and essay. Modules add cloze and image_choice, and, with Plugixa Exam Pro PRO, ordering, matching, emq, file_upload, hotspot, image_match and range. List Question Types returns what the site accepts right now.
Responses
201 Created. data is the new question.
{
"success": true,
"data": {
"id": 12,
"subject_id": 4,
"question_type": "mcq_single",
"title": "Capital of France",
"slug": "capital-of-france",
"stem": "<p>What is the capital of France?</p>",
"difficulty": 2,
"default_points": 1,
"answer_key": { "negative_marking": false, "min_score": 0 },
"explanation": "<p>Paris is the capital of France.</p>",
"shuffle_options": true,
"published": true,
"version": 1,
"approval_status": "approved",
"rubric": null,
"custom_fields": null,
"render_hint": null,
"created_at": "2026-11-12 09:30:00",
"modified_at": "2026-11-12 09:30:00",
"created_by": 1,
"modified_by": 1,
"deleted_at": null,
"options": [
{ "id": 41, "question_id": 12, "ordering": 1, "option_text": "Paris", "is_correct": true, "match_key": null, "feedback": null },
{ "id": 42, "question_id": 12, "ordering": 2, "option_text": "Lyon", "is_correct": false, "match_key": null, "feedback": null },
{ "id": 43, "question_id": 12, "ordering": 3, "option_text": "Marseille", "is_correct": false, "match_key": null, "feedback": null }
],
"tag_ids": [5]
}
}
| Field | Type | Meaning |
|---|---|---|
data.id |
integer |
The new question’s id |
data.slug |
string |
The slug actually saved |
data.version |
integer |
1 for a new question |
data.answer_key |
object|null |
The answer key as saved, after cleaning |
data.options |
object[] |
The saved options, in order, each with its new id |
data.options[].ordering |
integer |
Position, from 1 |
data.tag_ids |
integer[] |
Tag ids. Only present when the Tags app is on |
data.custom_fields |
object|null |
PRO Custom field values, with the Custom fields app on |
data.created_at |
string |
UTC datetime |
Errors
| Status | Code | When |
|---|---|---|
400 |
plugixa_exam_title_required |
title is missing or blank. |
400 |
plugixa_exam_unknown_type |
question_type is not a registered type. |
400 |
plugixa_exam_needs_options |
The type uses options and fewer than 2 non-blank options were sent. |
400 |
plugixa_exam_needs_one_correct |
mcq_single without exactly one correct option. |
400 |
plugixa_exam_needs_correct |
mcq_multiple with no correct option. |
400 |
plugixa_exam_needs_accepted |
short_answer with no accepted answers. |
400 |
plugixa_exam_emq_answer_missing, plugixa_exam_emq_needs_stems, plugixa_exam_matching_incomplete, plugixa_exam_needs_image, plugixa_exam_needs_images, plugixa_exam_needs_pairs, plugixa_exam_needs_targets, plugixa_exam_range_out_of_bounds, plugixa_exam_upload_types_refused |
Checks of the module question types PRO failed. |
500 |
plugixa_exam_insert_failed |
The question could not be saved. |
Plus the errors every endpoint can return.
Examples
curl -s -X POST "https://your-site.example/wp-json/plugixa-exam/v1/questions" \
-H "Authorization: Bearer $PLUGIXA_EXAM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Capital of France","question_type":"mcq_single","subject_id":4,"stem":"<p>What is the capital of France?</p>","options":[{"option_text":"Paris","is_correct":true},{"option_text":"Lyon","is_correct":false},{"option_text":"Marseille","is_correct":false}],"published":true}'
<?php
$ch = curl_init('https://your-site.example/wp-json/plugixa-exam/v1/questions');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PLUGIXA_EXAM_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'title' => 'Capital of France',
'question_type' => 'mcq_single',
'subject_id' => 4,
'stem' => '<p>What is the capital of France?</p>',
'options' => [
['option_text' => 'Paris', 'is_correct' => true],
['option_text' => 'Lyon', 'is_correct' => false],
['option_text' => 'Marseille', 'is_correct' => false],
],
'published' => true,
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://your-site.example/wp-json/plugixa-exam/v1/questions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PLUGIXA_EXAM_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Capital of France',
question_type: 'mcq_single',
subject_id: 4,
stem: '<p>What is the capital of France?</p>',
options: [
{ option_text: 'Paris', is_correct: true },
{ option_text: 'Lyon', is_correct: false },
{ option_text: 'Marseille', is_correct: false },
],
published: true,
}),
});
const { data } = await response.json();
Quick Links
Share