Contents
On this page
Create Exam - Plugixa Exam
POST /plugixa-exam/v1/exams
Create an exam, with its settings and, optionally, its sections and questions in one call.
Scope exams:write; the token’s user needs the exams:create permission. Creating it already published also needs the exams:publish scope and the user’s exams:publish permission.
Note. An exam created without
publishedis unpublished. Sending"published": truepublishes it at once, which needsexams:publishas well; without it the request answers403and nothing is created. Since 1.4.1: before,exams:writealone could publish.
Request
POST https://your-site.example/wp-json/plugixa-exam/v1/exams
Request body
Content type: application/json
Only title is required. Every other field has a default, and an enumerated field sent with a value outside its list quietly takes its default instead of failing. “Site default” means the value set under Plugixa Exam -> Settings; the default shown in brackets is the one a new install starts with.
Basics
| Field | Type | Required | Description |
|---|---|---|---|
title |
string |
Yes | Must not be blank. |
slug |
string |
No | The exam’s address on your site. Made from the title when empty. Made unique by appending -2, -3, … |
description |
string |
No | Plain text. HTML tags are removed. |
intro |
string |
No | HTML shown before the exam starts. Tags outside the question-text allow-list are stripped. |
subject_id |
integer |
No | The subject the exam belongs to. Defaults to 0. Not checked for existence. |
published |
boolean |
No | Defaults to false. Accepts true/false, and also "1"/"0" or "yes"/"no". true needs exams:publish. |
certificate_template_id |
integer |
No | The certificate template to issue. Defaults to 0. |
Timing and attempts
| Field | Type | Required | Description |
|---|---|---|---|
time_limit |
integer |
No | Minutes. 0 (the default) for an untimed exam. |
max_attempts |
integer |
No | Attempts allowed per person. 0 (the default) for unlimited. |
cooldown_minutes |
integer |
No | Minutes to wait between attempts. Defaults to 0. |
open_from |
string |
No | When the exam opens, as a date and time (2026-11-16 08:00:00). Stored in UTC. A value that cannot be read as a date is stored as no date. |
open_until |
string |
No | When the exam closes. Must be after open_from when both are set. |
window_close_action |
string |
No | When a timed attempt would run past open_until: close cuts it off at the closing time, run_out lets the person keep their full time. Site default (close). |
attempt_mode |
string |
No | exam or practice. Site default (exam). |
practice_reveal |
string |
No | For practice mode: on_submit or per_question. Site default (on_submit). |
Who may sit it
| Field | Type | Required | Description |
|---|---|---|---|
access_type |
string |
No | One of the values below. Defaults to access_level. |
access_level |
string |
No | Only kept when access_type is access_level: a WordPress role or capability the person must hold. Empty means any signed-in user. |
access_code |
string |
No | The code people must enter when access_type is code. Only changed when the field is in the body. An empty value clears the code, except on an exam whose access_type is code. Never returned. |
access_type |
Who may sit the exam |
|---|---|
access_level |
Signed-in users, limited by access_level when it is set |
open |
Anyone. Visitors without an account are handled by the site’s anonymous-attempts setting |
guest |
Anyone. Visitors without an account always give a name and email first |
code |
Anyone who enters the access code |
restricted |
Signed-in users who match the exam’s access rules PRO. Without the Cohorts app, only administrators |
Results
| Field | Type | Required | Description |
|---|---|---|---|
pass_mark |
number |
No | Percent needed to pass, 0 to 100, rounded to 2 decimals. Defaults to 50. |
result_release |
string |
No | When people see their result: immediate, after_close (once open_until has passed), scheduled (once release_at has passed) or manual (held back until you change the rule). Site default (immediate). |
release_at |
string |
No | Date and time for scheduled release. Stored in UTC. |
show_answers |
boolean |
No | Show the correct answers with the result. Defaults to true. |
pass_message |
string |
No | HTML shown on a pass. Tags outside the question-text allow-list are stripped. |
fail_message |
string |
No | HTML shown on a fail. Same rules. |
lock_policy |
string |
No | When a finished attempt is locked for review: auto (when something awaits manual marking or the attempt limit is reached), cap_only (only when the attempt limit is reached), manual or never. Practice attempts never lock. Site default (auto). |
The paper
| Field | Type | Required | Description |
|---|---|---|---|
layout |
string |
No | single_page or one_per_page. Site default (single_page). |
shuffle_questions |
boolean |
No | Defaults to false. |
shuffle_options |
boolean |
No | Defaults to false. |
review_before_submit |
boolean |
No | Site default (true). |
allow_early_finish |
boolean |
No | Site default (true). |
require_answer |
string |
No | For unanswered questions: off, warn or block. Site default (off). |
fullscreen_mode |
string |
No | off, offered or required. Site default (off). |
allow_pause |
boolean |
No | Site default (false). |
max_pauses |
integer |
No | 0 to 100. Defaults to 0. |
max_pause_minutes |
integer |
No | 0 to 1440. Defaults to 0. |
track_integrity |
boolean |
No | Site default (true). |
Unfinished attempts
| Field | Type | Required | Description |
|---|---|---|---|
auto_close_action |
string |
No | What happens to an attempt closed for the person: submit or abandon. Site default (submit). |
idle_timeout_minutes |
integer |
No | Minutes without activity before an open attempt is closed, 0 to 525600. Site default (1440). |
auto_close_held |
boolean |
No | Whether an attempt an invigilator has put on hold may still be closed automatically. Defaults to false. |
auto_close_time_spent |
string |
No | Time recorded for an attempt closed automatically: capped (at most the time allowed) or wallclock. Site default (capped). |
auto_close_notify |
string |
No | auto, normal or silent (no email when an attempt is closed automatically). Site default (auto). |
Sections, settings and custom fields
| Field | Type | Required | Description |
|---|---|---|---|
sections |
object[] |
No | The paper’s sections, in order. See below. Leave it out for an exam with no questions yet. |
settings |
object |
No | Extra per-exam flags, as a flat object of strings, numbers and booleans (or the same as a JSON string). Nested values are dropped, text values are cut to 500 characters, at most 50 keys are kept. |
custom_fields |
object |
No | PRO Custom field values, when the Custom Fields app is on. |
Each section:
| Field | Type | Description |
|---|---|---|
title |
string |
Optional |
description |
string |
Optional |
section_type |
string |
fixed (the default) lists its questions; pool draws them at random by rules |
points_per_question |
number |
Optional points for every question in the section |
questions |
array |
fixed only. Question ids, or {"question_id": 41, "points": 2} objects to set the points for one question. Repeats and 0 are skipped. The ids are not checked for existence |
rules |
object[] |
pool only. Each: subject_id (includes its child subjects), difficulty, question_type, tag_ids (array), all optional, plus draw_count (at least 1, default 1) and optional points_per_question |
The settings keys lms_provider, lms_course_id, lms_step_id and lms_completes_course link the exam to an LMS course PRO, and are checked when the LMS Bridge app is on.
{
"title": "Algebra Midterm",
"description": "Linear equations and inequalities.",
"intro": "<p>You have 45 minutes. Calculators are allowed.</p>",
"subject_id": 12,
"time_limit": 45,
"max_attempts": 2,
"cooldown_minutes": 60,
"open_from": "2026-11-16 08:00:00",
"open_until": "2026-11-20 18:00:00",
"pass_mark": 60,
"shuffle_questions": true,
"access_type": "code",
"access_code": "ALG-2026",
"result_release": "after_close",
"pass_message": "<p>Well done, you passed.</p>",
"layout": "one_per_page",
"sections": [
{
"title": "Part A",
"section_type": "fixed",
"questions": [41, { "question_id": 42, "points": 2 }]
},
{
"title": "Part B",
"section_type": "pool",
"points_per_question": 1,
"rules": [
{ "subject_id": 15, "question_type": "mcq_single", "draw_count": 5 }
]
}
]
}
Responses
201 Created. data is the new exam, with its sections.
{
"success": true,
"data": {
"id": 7,
"title": "Algebra Midterm",
"slug": "algebra-midterm",
"description": "Linear equations and inequalities.",
"intro": "<p>You have 45 minutes. Calculators are allowed.</p>",
"subject_id": 12,
"time_limit": 45,
"max_attempts": 2,
"cooldown_minutes": 60,
"open_from": "2026-11-16 08:00:00",
"open_until": "2026-11-20 18:00:00",
"pass_mark": 60,
"shuffle_questions": true,
"shuffle_options": false,
"access_type": "code",
"access_level": null,
"result_release": "after_close",
"release_at": null,
"show_answers": true,
"pass_message": "<p>Well done, you passed.</p>",
"fail_message": "",
"lock_policy": "auto",
"attempt_mode": "exam",
"practice_reveal": "on_submit",
"layout": "one_per_page",
"review_before_submit": true,
"allow_early_finish": true,
"fullscreen_mode": "off",
"require_answer": "off",
"allow_pause": false,
"max_pauses": 0,
"max_pause_minutes": 0,
"auto_close_action": "submit",
"idle_timeout_minutes": 1440,
"window_close_action": "close",
"auto_close_held": false,
"auto_close_time_spent": "capped",
"auto_close_notify": "auto",
"track_integrity": true,
"certificate_template_id": 0,
"settings": null,
"custom_fields": null,
"published": false,
"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,
"sections": [
{
"id": 21,
"exam_id": 7,
"title": "Part A",
"description": "",
"ordering": 1,
"section_type": "fixed",
"points_per_question": null,
"questions": [
{ "id": 101, "exam_id": "7", "section_id": 21, "question_id": 41, "ordering": 1, "points": null },
{ "id": 102, "exam_id": "7", "section_id": 21, "question_id": 42, "ordering": 2, "points": 2 }
],
"rules": []
},
{
"id": 22,
"exam_id": 7,
"title": "Part B",
"description": "",
"ordering": 2,
"section_type": "pool",
"points_per_question": 1,
"questions": [],
"rules": [
{
"id": 9,
"section_id": 22,
"subject_id": 15,
"difficulty": null,
"question_type": "mcq_single",
"draw_count": 5,
"points_per_question": null,
"tag_ids": []
}
]
}
],
"has_access_code": true
}
}
| Field | Type | Meaning |
|---|---|---|
data.id |
integer |
The new exam’s id |
data.slug |
string |
The exam’s address on your site, made unique |
data.access_level |
string|null |
null unless access_type is access_level |
data.published |
boolean |
false unless you sent published |
data.settings |
object|null |
The extra flags, null when none were sent |
data.custom_fields |
object|null |
Custom field values PRO |
data.sections[].section_type |
string |
fixed or pool |
data.sections[].questions |
object[] |
Questions placed in a fixed section, in order. points is null when the question keeps its own points. exam_id arrives as a string here |
data.sections[].rules |
object[] |
Draw rules of a pool section |
data.has_access_code |
boolean |
Whether an access code is set. The code itself is never returned |
Errors
| Status | Code | When |
|---|---|---|
403 |
plugixa_exam_forbidden |
published is true and the token lacks exams:publish, or the user the publish permission. |
400 |
plugixa_exam_title_required |
title is missing or blank. |
400 |
plugixa_exam_bad_window |
open_until is not after open_from. |
400 |
plugixa_exam_lms_link_invalid |
PRO settings link to an LMS provider, course or step that is unknown, inactive or invalid. |
500 |
plugixa_exam_insert_failed |
The exam 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/exams" \
-H "Authorization: Bearer $PLUGIXA_EXAM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Algebra Midterm","subject_id":12,"time_limit":45,"pass_mark":60,"access_type":"code","access_code":"ALG-2026","sections":[{"title":"Part A","questions":[41,{"question_id":42,"points":2}]}]}'
<?php
$ch = curl_init('https://your-site.example/wp-json/plugixa-exam/v1/exams');
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' => 'Algebra Midterm',
'subject_id' => 12,
'time_limit' => 45,
'pass_mark' => 60,
'access_type' => 'code',
'access_code' => 'ALG-2026',
'sections' => [
[
'title' => 'Part A',
'questions' => [41, ['question_id' => 42, 'points' => 2]],
],
],
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://your-site.example/wp-json/plugixa-exam/v1/exams', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PLUGIXA_EXAM_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Algebra Midterm',
subject_id: 12,
time_limit: 45,
pass_mark: 60,
access_type: 'code',
access_code: 'ALG-2026',
sections: [
{ title: 'Part A', questions: [41, { question_id: 42, points: 2 }] },
],
}),
});
const { data } = await response.json();
Quick Links
Share