{
    "specVersion": "3.1.0",
    "specFormat": "openapi",
    "specFile": "openapi.yaml",
    "installSpecPath": "/media/com_nextexams/openapi.yaml",
    "title": "Next Exams REST API",
    "summary": "Manage a question bank, and read exams, attempts and results.",
    "description": "The integration surface for Next Exams: subjects, tags and questions you can\ncreate and edit, exams you can read, publish and delete, attempts and per-exam\nreports you can read — enough to sync a question bank from another system, feed\na gradebook or a BI tool, or automate publishing.\n\n**Authentication.** Every request carries a Next Exams API token, created under\n*Next Exams → API tokens*. A token is shown once and stored only as a hash. It\nacts as the user who created it, and is limited three ways at once: by the\nscopes it was given, by that user's Next Exams role, and by Joomla's\n`core.manage` permission on Next Exams. A scope a token lacks is refused with\n`403 MISSING_SCOPE`, whatever the role allows.\n\n**It is off when the API Tokens app is off.** *Next Exams → App Manager →\nAPI tokens* is the kill switch: with it off, every request answers\n`403 API_DISABLED`. Apps that are switched off answer `403 APP_DISABLED`.\n\nResponses are projections, not table rows: each resource returns exactly the\nfields documented here, with real booleans and numbers, and dates as ISO 8601\nin UTC. Secrets the administrator screens carry — a guest's result token, an\nexam's access code — are never returned.\n",
    "apiVersion": "1.0.0",
    "license": {
        "name": "GNU General Public License v2.0 or later",
        "url": "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html"
    },
    "contact": {
        "name": "NextSoftware",
        "url": "https://nextsoftware.dev/",
        "email": "support@nextsoftware.dev"
    },
    "style": "rest-path",
    "guide": "rest-api/getting-started",
    "navSection": "api",
    "navEventsSection": "webhooks",
    "baseUrl": {
        "template": "{protocol}://{host}{basePath}/api/index.php",
        "variables": {
            "protocol": {
                "description": "Use http only for local development.",
                "example": "https",
                "enum": [
                    "https",
                    "http"
                ]
            },
            "host": {
                "description": "The host your Joomla site runs on.",
                "example": "your-site.example",
                "enum": null
            },
            "basePath": {
                "description": "Empty when Joomla is installed at the domain root. Otherwise the subfolder it is installed in, with a leading slash and no trailing slash — for example /exams.",
                "example": "",
                "enum": null
            }
        },
        "example": "https://your-site.example/api/index.php"
    },
    "auth": {
        "type": "bearer",
        "header": "Authorization",
        "scheme": "Bearer",
        "queryParam": null,
        "example": "Authorization: Bearer <your-api-token>",
        "alternatives": [
            {
                "type": "header",
                "name": "X-NextExams-Token",
                "note": "The same token in a custom header. Some Apache/PHP setups strip the\n`Authorization` header before PHP sees it; a custom `X-` header always\narrives. Use this when bearer requests answer `401 MISSING_TOKEN`.\n"
            }
        ],
        "enablement": [],
        "notes": [
            "`Authorization: Bearer nxk_…` — a token from *Next Exams → API tokens*.\n"
        ]
    },
    "conventions": [],
    "groups": [
        {
            "id": "Subjects",
            "title": "Subjects",
            "description": "The hierarchical taxonomy questions and exams are filed under."
        },
        {
            "id": "Tags",
            "title": "Tags",
            "description": "Free labels on questions."
        },
        {
            "id": "Questions",
            "title": "Questions",
            "description": "The question bank — every type, with its options and answer key."
        },
        {
            "id": "Exams",
            "title": "Exams",
            "description": "Reading, publishing and deleting exams."
        },
        {
            "id": "Attempts",
            "title": "Attempts",
            "description": "Who sat which exam, and how they did."
        },
        {
            "id": "Reports",
            "title": "Reports",
            "description": "Per-exam analytics — the numbers behind the Reports screen."
        }
    ],
    "endpoints": [
        {
            "id": "listSubjects",
            "slug": "subjects",
            "pagePath": "api/subjects",
            "group": "Subjects",
            "method": "GET",
            "operation": "/v1/nextexams/subjects",
            "summary": "List subjects",
            "title": "List Subjects",
            "description": "Subjects in tree order, paged. Scope `subjects:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "limit",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 20,
                    "enum": null,
                    "example": null,
                    "description": "Page size. (min 1)"
                },
                {
                    "name": "offset",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 0,
                    "enum": null,
                    "example": null,
                    "description": "Rows to skip. (min 0)"
                },
                {
                    "name": "search",
                    "in": "query",
                    "type": "string",
                    "required": false,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "Matches the title (and, for questions, the stem)."
                },
                {
                    "name": "published",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": null,
                    "enum": [
                        0,
                        1
                    ],
                    "example": null,
                    "description": "1 for published only, 0 for unpublished only."
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "A page of subjects.",
                    "example": {
                        "data": [
                            {
                                "id": 1,
                                "parent_id": 1,
                                "path": "string",
                                "level": 1,
                                "code": "string",
                                "title": "string",
                                "alias": "string",
                                "description": "string",
                                "ordering": 1,
                                "published": true,
                                "created": "2026-01-01T12:00:00Z",
                                "modified": "2026-01-01T12:00:00Z"
                            }
                        ],
                        "meta": {
                            "total": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].parent_id",
                            "type": "integer",
                            "description": "0 at the top level."
                        },
                        {
                            "path": "data[].path",
                            "type": "string",
                            "description": "Ids from the root down, joined by '/'."
                        },
                        {
                            "path": "data[].level",
                            "type": "integer",
                            "description": "1 at the top level."
                        },
                        {
                            "path": "data[].code",
                            "type": "string|null",
                            "description": "Your own reference code."
                        },
                        {
                            "path": "data[].title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].description",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].ordering",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "meta.total",
                            "type": "integer",
                            "description": "Matching records in all pages."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/subjects\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "List subjects endpoint",
            "seoDescription": "GET /subjects lists the Next Exams subject tree, paged, with path, level and published state.",
            "seoKeywords": "subjects api, taxonomy, list subjects"
        },
        {
            "id": "createSubject",
            "slug": "post-subjects",
            "pagePath": "api/post-subjects",
            "group": "Subjects",
            "method": "POST",
            "operation": "/v1/nextexams/subjects",
            "summary": "Create a subject",
            "title": "Create Subject",
            "description": "Scope `subjects:write`; the role needs `subjects.create`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "title",
                        "type": "string",
                        "required": false,
                        "description": "Required on create.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "parent_id",
                        "type": "integer",
                        "required": false,
                        "description": "0 for the top level.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "code",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "alias",
                        "type": "string",
                        "required": false,
                        "description": "Made from the title when empty.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "description",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "published",
                        "type": "integer",
                        "required": false,
                        "description": "",
                        "enum": [
                            0,
                            1
                        ],
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "title": "Algebra",
                    "parent_id": 0,
                    "code": "MATH-ALG"
                }
            },
            "responses": [
                {
                    "status": 201,
                    "description": "Created. `data.id` is the new record.",
                    "example": {
                        "data": {
                            "id": 42
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "bad_request",
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X POST \"https://your-site.example/api/index.php/v1/nextexams/subjects\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\":\"Algebra\",\"parent_id\":0,\"code\":\"MATH-ALG\"}'"
                }
            ],
            "sections": [],
            "seoTitle": "Create subject endpoint",
            "seoDescription": "POST /subjects creates a Next Exams subject at the top level or under a parent.",
            "seoKeywords": "create subject, subjects api"
        },
        {
            "id": "getSubject",
            "slug": "get-subjects",
            "pagePath": "api/get-subjects",
            "group": "Subjects",
            "method": "GET",
            "operation": "/v1/nextexams/subjects/{id}",
            "summary": "Get a subject",
            "title": "Get Subject",
            "description": "Scope `subjects:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The subject.",
                    "example": {
                        "data": {
                            "id": 1,
                            "parent_id": 1,
                            "path": "string",
                            "level": 1,
                            "code": "string",
                            "title": "string",
                            "alias": "string",
                            "description": "string",
                            "ordering": 1,
                            "published": true,
                            "created": "2026-01-01T12:00:00Z",
                            "modified": "2026-01-01T12:00:00Z"
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.parent_id",
                            "type": "integer",
                            "description": "0 at the top level."
                        },
                        {
                            "path": "data.path",
                            "type": "string",
                            "description": "Ids from the root down, joined by '/'."
                        },
                        {
                            "path": "data.level",
                            "type": "integer",
                            "description": "1 at the top level."
                        },
                        {
                            "path": "data.code",
                            "type": "string|null",
                            "description": "Your own reference code."
                        },
                        {
                            "path": "data.title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.description",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.ordering",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/subjects/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Get subject endpoint",
            "seoDescription": "GET /subjects/{id} returns one Next Exams subject.",
            "seoKeywords": "get subject, subjects api"
        },
        {
            "id": "updateSubject",
            "slug": "patch-subjects",
            "pagePath": "api/patch-subjects",
            "group": "Subjects",
            "method": "PATCH",
            "operation": "/v1/nextexams/subjects/{id}",
            "summary": "Update a subject",
            "title": "Update Subject",
            "description": "Changes only the fields you send. Moving a subject (`parent_id`) rebuilds\nthe path of its whole subtree; a move under its own descendant is refused.\nScope `subjects:write`; the role needs `subjects.edit`.\n",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "title",
                        "type": "string",
                        "required": false,
                        "description": "Required on create.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "parent_id",
                        "type": "integer",
                        "required": false,
                        "description": "0 for the top level.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "code",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "alias",
                        "type": "string",
                        "required": false,
                        "description": "Made from the title when empty.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "description",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "published",
                        "type": "integer",
                        "required": false,
                        "description": "",
                        "enum": [
                            0,
                            1
                        ],
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "title": "Linear algebra"
                }
            },
            "responses": [
                {
                    "status": 200,
                    "description": "Saved.",
                    "example": {
                        "data": {
                            "id": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "bad_request",
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X PATCH \"https://your-site.example/api/index.php/v1/nextexams/subjects/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\":\"Linear algebra\"}'"
                }
            ],
            "sections": [],
            "seoTitle": "Update subject endpoint",
            "seoDescription": "PATCH /subjects/{id} changes only the fields sent; moving a subject rebuilds its subtree path.",
            "seoKeywords": "update subject, move subject, patch"
        },
        {
            "id": "deleteSubject",
            "slug": "delete-subjects",
            "pagePath": "api/delete-subjects",
            "group": "Subjects",
            "method": "DELETE",
            "operation": "/v1/nextexams/subjects/{id}",
            "summary": "Delete a subject",
            "title": "Delete Subject",
            "description": "Scope `subjects:delete`; the role needs `subjects.delete`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 204,
                    "description": "Deleted.",
                    "example": null,
                    "fields": [],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X DELETE \"https://your-site.example/api/index.php/v1/nextexams/subjects/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Delete subject endpoint",
            "seoDescription": "DELETE /subjects/{id} removes a Next Exams subject.",
            "seoKeywords": "delete subject"
        },
        {
            "id": "setSubjectStatus",
            "slug": "subjects-status",
            "pagePath": "api/subjects-status",
            "group": "Subjects",
            "method": "POST",
            "operation": "/v1/nextexams/subjects/{id}/status",
            "summary": "Publish or unpublish a subject",
            "title": "Set Subject Status",
            "description": "Scope `subjects:write`; the role needs `subjects.edit`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "published",
                        "type": "boolean",
                        "required": true,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "published": true
                }
            },
            "responses": [
                {
                    "status": 200,
                    "description": "The new state.",
                    "example": {
                        "data": {
                            "id": 1,
                            "published": true
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X POST \"https://your-site.example/api/index.php/v1/nextexams/subjects/1/status\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"published\":true}'"
                }
            ],
            "sections": [],
            "seoTitle": "Publish subject endpoint",
            "seoDescription": "POST /subjects/{id}/status publishes or unpublishes a Next Exams subject.",
            "seoKeywords": "publish subject, unpublish"
        },
        {
            "id": "listTags",
            "slug": "tags",
            "pagePath": "api/tags",
            "group": "Tags",
            "method": "GET",
            "operation": "/v1/nextexams/tags",
            "summary": "List tags",
            "title": "List Tags",
            "description": "Scope `questions:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "limit",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 20,
                    "enum": null,
                    "example": null,
                    "description": "Page size. (min 1)"
                },
                {
                    "name": "offset",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 0,
                    "enum": null,
                    "example": null,
                    "description": "Rows to skip. (min 0)"
                },
                {
                    "name": "search",
                    "in": "query",
                    "type": "string",
                    "required": false,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "Matches the title (and, for questions, the stem)."
                },
                {
                    "name": "published",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": null,
                    "enum": [
                        0,
                        1
                    ],
                    "example": null,
                    "description": "1 for published only, 0 for unpublished only."
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "A page of tags.",
                    "example": {
                        "data": [
                            {
                                "id": 1,
                                "title": "string",
                                "alias": "string",
                                "ordering": 1,
                                "published": true,
                                "created": "2026-01-01T12:00:00Z",
                                "modified": "2026-01-01T12:00:00Z"
                            }
                        ],
                        "meta": {
                            "total": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].ordering",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "meta.total",
                            "type": "integer",
                            "description": "Matching records in all pages."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/tags\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "List tags endpoint",
            "seoDescription": "GET /tags lists the free labels on Next Exams questions.",
            "seoKeywords": "tags api, question tags"
        },
        {
            "id": "createTag",
            "slug": "post-tags",
            "pagePath": "api/post-tags",
            "group": "Tags",
            "method": "POST",
            "operation": "/v1/nextexams/tags",
            "summary": "Create a tag",
            "title": "Create Tag",
            "description": "Scope `questions:write`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "title",
                        "type": "string",
                        "required": false,
                        "description": "Required on create.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "alias",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "published",
                        "type": "integer",
                        "required": false,
                        "description": "",
                        "enum": [
                            0,
                            1
                        ],
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "title": "Calculator allowed"
                }
            },
            "responses": [
                {
                    "status": 201,
                    "description": "Created. `data.id` is the new record.",
                    "example": {
                        "data": {
                            "id": 42
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "bad_request",
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X POST \"https://your-site.example/api/index.php/v1/nextexams/tags\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\":\"Calculator allowed\"}'"
                }
            ],
            "sections": [],
            "seoTitle": "Create tag endpoint",
            "seoDescription": "POST /tags creates a Next Exams question tag.",
            "seoKeywords": "create tag"
        },
        {
            "id": "getTag",
            "slug": "get-tags",
            "pagePath": "api/get-tags",
            "group": "Tags",
            "method": "GET",
            "operation": "/v1/nextexams/tags/{id}",
            "summary": "Get a tag",
            "title": "Get Tag",
            "description": "Scope `questions:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The tag.",
                    "example": {
                        "data": {
                            "id": 1,
                            "title": "string",
                            "alias": "string",
                            "ordering": 1,
                            "published": true,
                            "created": "2026-01-01T12:00:00Z",
                            "modified": "2026-01-01T12:00:00Z"
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.ordering",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/tags/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Get tag endpoint",
            "seoDescription": "GET /tags/{id} returns one Next Exams tag.",
            "seoKeywords": "get tag"
        },
        {
            "id": "updateTag",
            "slug": "patch-tags",
            "pagePath": "api/patch-tags",
            "group": "Tags",
            "method": "PATCH",
            "operation": "/v1/nextexams/tags/{id}",
            "summary": "Update a tag",
            "title": "Update Tag",
            "description": "Changes only the fields you send. Scope `questions:write`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "title",
                        "type": "string",
                        "required": false,
                        "description": "Required on create.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "alias",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "published",
                        "type": "integer",
                        "required": false,
                        "description": "",
                        "enum": [
                            0,
                            1
                        ],
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "title": "string",
                    "alias": "string",
                    "published": 0
                }
            },
            "responses": [
                {
                    "status": 200,
                    "description": "Saved.",
                    "example": {
                        "data": {
                            "id": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "bad_request",
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X PATCH \"https://your-site.example/api/index.php/v1/nextexams/tags/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\":\"string\",\"alias\":\"string\",\"published\":0}'"
                }
            ],
            "sections": [],
            "seoTitle": "Update tag endpoint",
            "seoDescription": "PATCH /tags/{id} renames or re-aliases a Next Exams tag.",
            "seoKeywords": "update tag"
        },
        {
            "id": "deleteTag",
            "slug": "delete-tags",
            "pagePath": "api/delete-tags",
            "group": "Tags",
            "method": "DELETE",
            "operation": "/v1/nextexams/tags/{id}",
            "summary": "Delete a tag",
            "title": "Delete Tag",
            "description": "Removes the tag from every question that carries it. Scope `questions:delete`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 204,
                    "description": "Deleted.",
                    "example": null,
                    "fields": [],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X DELETE \"https://your-site.example/api/index.php/v1/nextexams/tags/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Delete tag endpoint",
            "seoDescription": "DELETE /tags/{id} removes a tag from every question that carries it.",
            "seoKeywords": "delete tag"
        },
        {
            "id": "setTagStatus",
            "slug": "tags-status",
            "pagePath": "api/tags-status",
            "group": "Tags",
            "method": "POST",
            "operation": "/v1/nextexams/tags/{id}/status",
            "summary": "Publish or unpublish a tag",
            "title": "Set Tag Status",
            "description": "Scope `questions:write`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "published",
                        "type": "boolean",
                        "required": true,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "published": true
                }
            },
            "responses": [
                {
                    "status": 200,
                    "description": "The new state.",
                    "example": {
                        "data": {
                            "id": 1,
                            "published": true
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X POST \"https://your-site.example/api/index.php/v1/nextexams/tags/1/status\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"published\":true}'"
                }
            ],
            "sections": [],
            "seoTitle": "Publish tag endpoint",
            "seoDescription": "POST /tags/{id}/status publishes or unpublishes a Next Exams tag.",
            "seoKeywords": "publish tag"
        },
        {
            "id": "listQuestions",
            "slug": "questions",
            "pagePath": "api/questions",
            "group": "Questions",
            "method": "GET",
            "operation": "/v1/nextexams/questions",
            "summary": "List bank questions",
            "title": "List Questions",
            "description": "The question bank, paged. Questions that are private to one exam (made\nthere with *Keep in bank* off) and retired versions are not listed.\nScope `questions:read`.\n",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "limit",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 20,
                    "enum": null,
                    "example": null,
                    "description": "Page size. (min 1)"
                },
                {
                    "name": "offset",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 0,
                    "enum": null,
                    "example": null,
                    "description": "Rows to skip. (min 0)"
                },
                {
                    "name": "search",
                    "in": "query",
                    "type": "string",
                    "required": false,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "Matches the title (and, for questions, the stem)."
                },
                {
                    "name": "published",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": null,
                    "enum": [
                        0,
                        1
                    ],
                    "example": null,
                    "description": "1 for published only, 0 for unpublished only."
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "A page of questions.",
                    "example": {
                        "data": [
                            {
                                "id": 1,
                                "subject_id": 1,
                                "question_type": "string",
                                "title": "string",
                                "alias": "string",
                                "stem": "string",
                                "explanation": "string",
                                "difficulty": 1,
                                "default_points": 1.5,
                                "answer_key": {},
                                "shuffle_options": true,
                                "published": true,
                                "version": 1,
                                "approval_status": "approved",
                                "owner_exam_id": 1,
                                "created": "2026-01-01T12:00:00Z",
                                "modified": "2026-01-01T12:00:00Z",
                                "exam_count": 1
                            }
                        ],
                        "meta": {
                            "total": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].subject_id",
                            "type": "integer",
                            "description": "0 when filed under no subject."
                        },
                        {
                            "path": "data[].question_type",
                            "type": "string",
                            "description": "One of the values GET /question-types returns."
                        },
                        {
                            "path": "data[].title",
                            "type": "string",
                            "description": "The internal name authors see; examinees see the stem."
                        },
                        {
                            "path": "data[].alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].stem",
                            "type": "string",
                            "description": "The question as examinees see it (HTML)."
                        },
                        {
                            "path": "data[].explanation",
                            "type": "string|null",
                            "description": "Shown with the released result (HTML)."
                        },
                        {
                            "path": "data[].difficulty",
                            "type": "integer",
                            "description": "1 (easy) to 5 (hard)."
                        },
                        {
                            "path": "data[].default_points",
                            "type": "number",
                            "description": ""
                        },
                        {
                            "path": "data[].answer_key",
                            "type": "object|null",
                            "description": "The grading configuration. Its shape depends on `question_type`."
                        },
                        {
                            "path": "data[].shuffle_options",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].version",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].approval_status",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].owner_exam_id",
                            "type": "integer",
                            "description": "0 for a bank question, the exam's id for one private to an exam, -1 for a retired version."
                        },
                        {
                            "path": "data[].created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].exam_count",
                            "type": "integer",
                            "description": "Exams that use it."
                        },
                        {
                            "path": "meta.total",
                            "type": "integer",
                            "description": "Matching records in all pages."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/questions\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "List questions endpoint",
            "seoDescription": "GET /questions lists the Next Exams question bank, paged and searchable, leaving out exam-private and retired questions.",
            "seoKeywords": "question bank api, list questions, search questions"
        },
        {
            "id": "createQuestion",
            "slug": "post-questions",
            "pagePath": "api/post-questions",
            "group": "Questions",
            "method": "POST",
            "operation": "/v1/nextexams/questions",
            "summary": "Create a question",
            "title": "Create Question",
            "description": "Creates a bank question. `question_type` is one of the values\n`GET /v1/nextexams/question-types` returns; choice types carry `options`\n(mark the right one with `is_correct`), the others an `answer_key`.\nScope `questions:write`; the role needs `questions.create`. Creating a\nquestion private to an exam (`owner_exam_id`) also needs `exams:write`.\n",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "title",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "question_type",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "stem",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "explanation",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "subject_id",
                        "type": "integer",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "difficulty",
                        "type": "integer",
                        "required": false,
                        "description": "(min 1, max 5)",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "default_points",
                        "type": "number",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "shuffle_options",
                        "type": "boolean|integer",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "published",
                        "type": "boolean|integer",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "options",
                        "type": "object[]",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": [
                            {
                                "name": "id",
                                "type": "integer",
                                "required": false,
                                "description": "Send it back to keep the option (and the answers given to it).",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "option_text",
                                "type": "string",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "is_correct",
                                "type": "boolean|integer",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "match_key",
                                "type": "string|null",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "feedback",
                                "type": "string|null",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            }
                        ]
                    },
                    {
                        "name": "answer_key",
                        "type": "object",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "tags",
                        "type": "array",
                        "required": false,
                        "description": "Tag ids, tag titles, or `{id, title}` objects; a new title creates the tag.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "owner_exam_id",
                        "type": "integer",
                        "required": false,
                        "description": "Create the question private to this exam. Needs `exams:write` too.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "expected_version",
                        "type": "integer",
                        "required": false,
                        "description": "Refuse with 409 STALE if the stored version differs.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "new_version",
                        "type": "object",
                        "required": false,
                        "description": "Save as a new version, swapped into the listed exams.",
                        "enum": null,
                        "default": null,
                        "children": [
                            {
                                "name": "exam_ids",
                                "type": "integer[]",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            }
                        ]
                    }
                ],
                "example": {
                    "title": "Two plus two",
                    "question_type": "mcq_single",
                    "stem": "<p>What is 2 + 2?</p>",
                    "default_points": 1,
                    "difficulty": 1,
                    "options": [
                        {
                            "option_text": "3",
                            "is_correct": false
                        },
                        {
                            "option_text": "4",
                            "is_correct": true
                        }
                    ]
                }
            },
            "responses": [
                {
                    "status": 201,
                    "description": "Created. `data.id` is the new record.",
                    "example": {
                        "data": {
                            "id": 42
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "bad_request",
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X POST \"https://your-site.example/api/index.php/v1/nextexams/questions\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\":\"Two plus two\",\"question_type\":\"mcq_single\",\"stem\":\"<p>What is 2 + 2?</p>\",\"default_points\":1,\"difficulty\":1,\"options\":[{\"option_text\":\"3\",\"is_correct\":false},{\"option_text\":\"4\",\"is_correct\":true}]}'"
                }
            ],
            "sections": [],
            "seoTitle": "Create question endpoint",
            "seoDescription": "POST /questions creates a question of any of the fifteen Next Exams types, with options, answer key and tags.",
            "seoKeywords": "create question, mcq api, answer key, question options"
        },
        {
            "id": "getQuestion",
            "slug": "get-questions",
            "pagePath": "api/get-questions",
            "group": "Questions",
            "method": "GET",
            "operation": "/v1/nextexams/questions/{id}",
            "summary": "Get a question",
            "title": "Get Question",
            "description": "The whole question: options with their stable ids, answer key, tags and\nwhere it is used. The body of this response is a valid `PUT` body.\nScope `questions:read`.\n",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The question.",
                    "example": {
                        "data": {
                            "id": 1,
                            "subject_id": 1,
                            "question_type": "string",
                            "title": "string",
                            "alias": "string",
                            "stem": "string",
                            "explanation": "string",
                            "difficulty": 1,
                            "default_points": 1.5,
                            "answer_key": {},
                            "shuffle_options": true,
                            "published": true,
                            "version": 1,
                            "approval_status": "string",
                            "owner_exam_id": 1,
                            "created": "2026-01-01T12:00:00Z",
                            "modified": "2026-01-01T12:00:00Z",
                            "options": [
                                {
                                    "id": 1,
                                    "ordering": 1,
                                    "option_text": "string",
                                    "is_correct": true,
                                    "match_key": "string",
                                    "feedback": "string"
                                }
                            ],
                            "tags": [
                                {
                                    "id": 1,
                                    "title": "string"
                                }
                            ],
                            "usage": {
                                "exam_count": 1,
                                "answered": true
                            }
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.subject_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.question_type",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.stem",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.explanation",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.difficulty",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.default_points",
                            "type": "number",
                            "description": ""
                        },
                        {
                            "path": "data.answer_key",
                            "type": "object|null",
                            "description": ""
                        },
                        {
                            "path": "data.shuffle_options",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.version",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.approval_status",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.owner_exam_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.options[].id",
                            "type": "integer",
                            "description": "Stable across saves when sent back."
                        },
                        {
                            "path": "data.options[].ordering",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.options[].option_text",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.options[].is_correct",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.options[].match_key",
                            "type": "string|null",
                            "description": "The partner of a matching pair, or an image-choice option's picture."
                        },
                        {
                            "path": "data.options[].feedback",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.tags[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.tags[].title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.usage.exam_count",
                            "type": "integer",
                            "description": "Exams that use it."
                        },
                        {
                            "path": "data.usage.answered",
                            "type": "boolean",
                            "description": "Whether any attempt has answered it — then answer-affecting changes need a new version."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/questions/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Get question endpoint",
            "seoDescription": "GET /questions/{id} returns a whole question with stable option ids, answer key, tags and usage — a valid PUT body.",
            "seoKeywords": "get question, option ids, answer key"
        },
        {
            "id": "replaceQuestion",
            "slug": "put-questions",
            "pagePath": "api/put-questions",
            "group": "Questions",
            "method": "PUT",
            "operation": "/v1/nextexams/questions/{id}",
            "summary": "Replace a question",
            "title": "Replace Question",
            "description": "**A question is saved whole**: options, answer key and tags you leave out\nare removed. Read it, change what you need, send it back. A body with no\n`options` key is refused with `422 OPTIONS_REQUIRED` while the question\nhas options — send `[]` to remove them deliberately.\n\nOption ids survive a save: send each option's `id` back and answers already\ngiven to it keep grading. A change that would alter how existing answers\nare graded is refused with `409 ANSWERED_NEEDS_VERSION` unless the body\ncarries `new_version`. Send `expected_version` to be refused with\n`409 STALE` if someone saved in between.\n\nScope `questions:write`; the role needs `questions.edit`.\n",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "title",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "question_type",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "stem",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "explanation",
                        "type": "string",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "subject_id",
                        "type": "integer",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "difficulty",
                        "type": "integer",
                        "required": false,
                        "description": "(min 1, max 5)",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "default_points",
                        "type": "number",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "shuffle_options",
                        "type": "boolean|integer",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "published",
                        "type": "boolean|integer",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "options",
                        "type": "object[]",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": [
                            {
                                "name": "id",
                                "type": "integer",
                                "required": false,
                                "description": "Send it back to keep the option (and the answers given to it).",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "option_text",
                                "type": "string",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "is_correct",
                                "type": "boolean|integer",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "match_key",
                                "type": "string|null",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            },
                            {
                                "name": "feedback",
                                "type": "string|null",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            }
                        ]
                    },
                    {
                        "name": "answer_key",
                        "type": "object",
                        "required": false,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "tags",
                        "type": "array",
                        "required": false,
                        "description": "Tag ids, tag titles, or `{id, title}` objects; a new title creates the tag.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "owner_exam_id",
                        "type": "integer",
                        "required": false,
                        "description": "Create the question private to this exam. Needs `exams:write` too.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "expected_version",
                        "type": "integer",
                        "required": false,
                        "description": "Refuse with 409 STALE if the stored version differs.",
                        "enum": null,
                        "default": null,
                        "children": []
                    },
                    {
                        "name": "new_version",
                        "type": "object",
                        "required": false,
                        "description": "Save as a new version, swapped into the listed exams.",
                        "enum": null,
                        "default": null,
                        "children": [
                            {
                                "name": "exam_ids",
                                "type": "integer[]",
                                "required": false,
                                "description": "",
                                "enum": null,
                                "default": null,
                                "children": []
                            }
                        ]
                    }
                ],
                "example": {
                    "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": [
                {
                    "status": 200,
                    "description": "Saved.",
                    "example": {
                        "data": {
                            "id": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "bad_request",
                "invalid_token",
                "missing_scope",
                "not_found",
                "http_409",
                "http_422"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X PUT \"https://your-site.example/api/index.php/v1/nextexams/questions/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -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]}}'"
                }
            ],
            "sections": [],
            "seoTitle": "Replace question endpoint",
            "seoDescription": "PUT /questions/{id} replaces a Next Exams question whole, keeping option ids and protecting answers already given.",
            "seoKeywords": "update question, replace question, new version, stale, put"
        },
        {
            "id": "deleteQuestion",
            "slug": "delete-questions",
            "pagePath": "api/delete-questions",
            "group": "Questions",
            "method": "DELETE",
            "operation": "/v1/nextexams/questions/{id}",
            "summary": "Delete a question",
            "title": "Delete Question",
            "description": "A question an exam still uses is refused with `409 QUESTION_IN_USE`, naming\nthe exams in `meta`. One that only past attempts reference is retired\ninstead of deleted, so those attempts keep their record.\nScope `questions:delete`; the role needs `questions.delete`.\n",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 204,
                    "description": "Deleted or retired.",
                    "example": null,
                    "fields": [],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found",
                "http_409"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X DELETE \"https://your-site.example/api/index.php/v1/nextexams/questions/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Delete question endpoint",
            "seoDescription": "DELETE /questions/{id} deletes an unused question, retires one past attempts reference, and refuses one an exam uses.",
            "seoKeywords": "delete question, retire question, question in use"
        },
        {
            "id": "setQuestionStatus",
            "slug": "questions-status",
            "pagePath": "api/questions-status",
            "group": "Questions",
            "method": "POST",
            "operation": "/v1/nextexams/questions/{id}/status",
            "summary": "Publish or unpublish a question",
            "title": "Set Question Status",
            "description": "Scope `questions:write`; the role needs `questions.edit`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "published",
                        "type": "boolean",
                        "required": true,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "published": true
                }
            },
            "responses": [
                {
                    "status": 200,
                    "description": "The new state.",
                    "example": {
                        "data": {
                            "id": 1,
                            "published": true
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X POST \"https://your-site.example/api/index.php/v1/nextexams/questions/1/status\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"published\":true}'"
                }
            ],
            "sections": [],
            "seoTitle": "Publish question endpoint",
            "seoDescription": "POST /questions/{id}/status publishes or unpublishes a Next Exams question.",
            "seoKeywords": "publish question"
        },
        {
            "id": "listQuestionVersions",
            "slug": "questions-versions",
            "pagePath": "api/questions-versions",
            "group": "Questions",
            "method": "GET",
            "operation": "/v1/nextexams/questions/{id}/versions",
            "summary": "List a question's versions",
            "title": "List Question Versions",
            "description": "Every saved version, newest first, each with a snapshot of the question as it was. Scope `questions:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The versions.",
                    "example": {
                        "data": [
                            {
                                "id": 1,
                                "version": 1,
                                "author": "string",
                                "created": "2026-01-01T12:00:00Z",
                                "snapshot": {}
                            }
                        ]
                    },
                    "fields": [
                        {
                            "path": "data[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].version",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].author",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].snapshot",
                            "type": "object|null",
                            "description": "The question as it was saved in this version."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/questions/1/versions\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Question versions endpoint",
            "seoDescription": "GET /questions/{id}/versions lists every saved version of a question with a snapshot of each.",
            "seoKeywords": "question versions, version history"
        },
        {
            "id": "listQuestionTypes",
            "slug": "question-types",
            "pagePath": "api/question-types",
            "group": "Questions",
            "method": "GET",
            "operation": "/v1/nextexams/question-types",
            "summary": "List question types",
            "title": "List Question Types",
            "description": "The values `question_type` accepts on this site, with their labels in the site language. Scope `lookups:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The question types.",
                    "example": {
                        "data": [
                            {
                                "value": "mcq_single",
                                "label": "Multiple choice (single answer)"
                            }
                        ]
                    },
                    "fields": [
                        {
                            "path": "data[].value",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].label",
                            "type": "string",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/question-types\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Question types endpoint",
            "seoDescription": "GET /question-types lists the fifteen Next Exams question types with their labels.",
            "seoKeywords": "question types, mcq, essay, cloze, hotspot"
        },
        {
            "id": "listExams",
            "slug": "exams",
            "pagePath": "api/exams",
            "group": "Exams",
            "method": "GET",
            "operation": "/v1/nextexams/exams",
            "summary": "List exams",
            "title": "List Exams",
            "description": "Scope `exams:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "limit",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 20,
                    "enum": null,
                    "example": null,
                    "description": "Page size. (min 1)"
                },
                {
                    "name": "offset",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 0,
                    "enum": null,
                    "example": null,
                    "description": "Rows to skip. (min 0)"
                },
                {
                    "name": "search",
                    "in": "query",
                    "type": "string",
                    "required": false,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "Matches the title (and, for questions, the stem)."
                },
                {
                    "name": "published",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": null,
                    "enum": [
                        0,
                        1
                    ],
                    "example": null,
                    "description": "1 for published only, 0 for unpublished only."
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "A page of exams.",
                    "example": {
                        "data": [
                            {
                                "id": 1,
                                "title": "string",
                                "alias": "string",
                                "description": "string",
                                "intro": "string",
                                "subject_id": 1,
                                "published": true,
                                "attempt_mode": "exam",
                                "layout": "single_page",
                                "time_limit": 1,
                                "max_attempts": 1,
                                "pass_mark": 1.5,
                                "shuffle_questions": true,
                                "shuffle_options": true,
                                "access_type": "open",
                                "result_release": "immediate",
                                "release_at": "2026-01-01T12:00:00Z",
                                "show_answers": true,
                                "open_from": "2026-01-01T12:00:00Z",
                                "open_until": "2026-01-01T12:00:00Z",
                                "cooldown_minutes": 1,
                                "allow_pause": true,
                                "track_integrity": true,
                                "certificate_template_id": 1,
                                "created": "2026-01-01T12:00:00Z",
                                "modified": "2026-01-01T12:00:00Z",
                                "question_count": 1
                            }
                        ],
                        "meta": {
                            "total": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].description",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].intro",
                            "type": "string|null",
                            "description": "Shown on the start screen (HTML)."
                        },
                        {
                            "path": "data[].subject_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].attempt_mode",
                            "type": "string",
                            "description": "One of `exam`, `practice`."
                        },
                        {
                            "path": "data[].layout",
                            "type": "string",
                            "description": "One of `single_page`, `one_per_page`."
                        },
                        {
                            "path": "data[].time_limit",
                            "type": "integer",
                            "description": "Minutes; 0 is untimed."
                        },
                        {
                            "path": "data[].max_attempts",
                            "type": "integer",
                            "description": "0 is unlimited."
                        },
                        {
                            "path": "data[].pass_mark",
                            "type": "number",
                            "description": "Percent."
                        },
                        {
                            "path": "data[].shuffle_questions",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].shuffle_options",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].access_type",
                            "type": "string",
                            "description": "One of `open`, `access_level`, `guest`, `code`, `restricted`."
                        },
                        {
                            "path": "data[].result_release",
                            "type": "string",
                            "description": "One of `immediate`, `after_close`, `manual`, `scheduled`."
                        },
                        {
                            "path": "data[].release_at",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].show_answers",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].open_from",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].open_until",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].cooldown_minutes",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].allow_pause",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].track_integrity",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data[].certificate_template_id",
                            "type": "integer",
                            "description": "0 when the exam issues no certificate."
                        },
                        {
                            "path": "data[].created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].question_count",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "meta.total",
                            "type": "integer",
                            "description": "Matching records in all pages."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/exams\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "List exams endpoint",
            "seoDescription": "GET /exams lists Next Exams exams with their settings and question counts.",
            "seoKeywords": "exams api, list exams"
        },
        {
            "id": "getExam",
            "slug": "get-exams",
            "pagePath": "api/get-exams",
            "group": "Exams",
            "method": "GET",
            "operation": "/v1/nextexams/exams/{id}",
            "summary": "Get an exam",
            "title": "Get Exam",
            "description": "The exam's settings and its structure — sections, the questions placed in them and the random-draw rules. Scope `exams:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The exam.",
                    "example": {
                        "data": {
                            "id": 1,
                            "title": "string",
                            "alias": "string",
                            "description": "string",
                            "intro": "string",
                            "subject_id": 1,
                            "published": true,
                            "attempt_mode": "exam",
                            "layout": "single_page",
                            "time_limit": 1,
                            "max_attempts": 1,
                            "pass_mark": 1.5,
                            "shuffle_questions": true,
                            "shuffle_options": true,
                            "access_type": "open",
                            "result_release": "immediate",
                            "release_at": "2026-01-01T12:00:00Z",
                            "show_answers": true,
                            "open_from": "2026-01-01T12:00:00Z",
                            "open_until": "2026-01-01T12:00:00Z",
                            "cooldown_minutes": 1,
                            "allow_pause": true,
                            "track_integrity": true,
                            "certificate_template_id": 1,
                            "created": "2026-01-01T12:00:00Z",
                            "modified": "2026-01-01T12:00:00Z",
                            "in_progress_attempts": 1,
                            "sections": [
                                {
                                    "id": 1,
                                    "title": "string",
                                    "description": "string",
                                    "ordering": 1,
                                    "section_type": "fixed",
                                    "points_per_question": 1.5,
                                    "questions": [
                                        {
                                            "question_id": 1,
                                            "ordering": 1,
                                            "points": 1.5,
                                            "question_title": "string",
                                            "question_type": "string"
                                        }
                                    ],
                                    "pool_rules": [
                                        {
                                            "id": 1,
                                            "subject_id": 1,
                                            "difficulty": 1,
                                            "question_type": "string",
                                            "draw_count": 1,
                                            "points_per_question": 1.5
                                        }
                                    ]
                                }
                            ]
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.alias",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.description",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.intro",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.subject_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.attempt_mode",
                            "type": "string",
                            "description": "One of `exam`, `practice`."
                        },
                        {
                            "path": "data.layout",
                            "type": "string",
                            "description": "One of `single_page`, `one_per_page`."
                        },
                        {
                            "path": "data.time_limit",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.max_attempts",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.pass_mark",
                            "type": "number",
                            "description": ""
                        },
                        {
                            "path": "data.shuffle_questions",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.shuffle_options",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.access_type",
                            "type": "string",
                            "description": "One of `open`, `access_level`, `guest`, `code`, `restricted`."
                        },
                        {
                            "path": "data.result_release",
                            "type": "string",
                            "description": "One of `immediate`, `after_close`, `manual`, `scheduled`."
                        },
                        {
                            "path": "data.release_at",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.show_answers",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.open_from",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.open_until",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.cooldown_minutes",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.allow_pause",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.track_integrity",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.certificate_template_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.created",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.modified",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.in_progress_attempts",
                            "type": "integer",
                            "description": "Attempts running now."
                        },
                        {
                            "path": "data.sections[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].title",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].description",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].ordering",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].section_type",
                            "type": "string",
                            "description": "`fixed` places chosen questions; `pool` draws at random by its rules. One of `fixed`, `pool`."
                        },
                        {
                            "path": "data.sections[].points_per_question",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].questions[].question_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].questions[].ordering",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].questions[].points",
                            "type": "number|null",
                            "description": "Overrides the question's default points."
                        },
                        {
                            "path": "data.sections[].questions[].question_title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].questions[].question_type",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].pool_rules[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].pool_rules[].subject_id",
                            "type": "integer|null",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].pool_rules[].difficulty",
                            "type": "integer|null",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].pool_rules[].question_type",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].pool_rules[].draw_count",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.sections[].pool_rules[].points_per_question",
                            "type": "number|null",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/exams/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Get exam endpoint",
            "seoDescription": "GET /exams/{id} returns an exam's settings and structure: sections, placed questions and random-draw rules.",
            "seoKeywords": "get exam, exam structure, sections, question pool"
        },
        {
            "id": "deleteExam",
            "slug": "delete-exams",
            "pagePath": "api/delete-exams",
            "group": "Exams",
            "method": "DELETE",
            "operation": "/v1/nextexams/exams/{id}",
            "summary": "Delete an exam",
            "title": "Delete Exam",
            "description": "Scope `exams:delete`; the role needs `exams.delete`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 204,
                    "description": "Deleted.",
                    "example": null,
                    "fields": [],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X DELETE \"https://your-site.example/api/index.php/v1/nextexams/exams/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Delete exam endpoint",
            "seoDescription": "DELETE /exams/{id} removes a Next Exams exam.",
            "seoKeywords": "delete exam"
        },
        {
            "id": "setExamStatus",
            "slug": "exams-status",
            "pagePath": "api/exams-status",
            "group": "Exams",
            "method": "POST",
            "operation": "/v1/nextexams/exams/{id}/status",
            "summary": "Publish or unpublish an exam",
            "title": "Set Exam Status",
            "description": "Scope `exams:publish`; the role needs `exams.publish`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": {
                "contentType": "application/json",
                "required": true,
                "description": "",
                "fields": [
                    {
                        "name": "published",
                        "type": "boolean",
                        "required": true,
                        "description": "",
                        "enum": null,
                        "default": null,
                        "children": []
                    }
                ],
                "example": {
                    "published": true
                }
            },
            "responses": [
                {
                    "status": 200,
                    "description": "The new state.",
                    "example": {
                        "data": {
                            "id": 1,
                            "published": true
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.published",
                            "type": "boolean",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s -X POST \"https://your-site.example/api/index.php/v1/nextexams/exams/1/status\" \\\n  -H \"Authorization: Bearer $API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"published\":true}'"
                }
            ],
            "sections": [],
            "seoTitle": "Publish exam endpoint",
            "seoDescription": "POST /exams/{id}/status publishes or unpublishes a Next Exams exam, needing the exams:publish scope.",
            "seoKeywords": "publish exam, schedule exam"
        },
        {
            "id": "listAttempts",
            "slug": "attempts",
            "pagePath": "api/attempts",
            "group": "Attempts",
            "method": "GET",
            "operation": "/v1/nextexams/attempts",
            "summary": "List attempts",
            "title": "List Attempts",
            "description": "Attempts, newest first. Scope `attempts:read`; the role needs `attempts.view`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "limit",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 20,
                    "enum": null,
                    "example": null,
                    "description": "Page size. (min 1)"
                },
                {
                    "name": "offset",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": 0,
                    "enum": null,
                    "example": null,
                    "description": "Rows to skip. (min 0)"
                },
                {
                    "name": "search",
                    "in": "query",
                    "type": "string",
                    "required": false,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "Matches the title (and, for questions, the stem)."
                },
                {
                    "name": "exam_id",
                    "in": "query",
                    "type": "integer",
                    "required": false,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "Only attempts at this exam. (min 1)"
                },
                {
                    "name": "status",
                    "in": "query",
                    "type": "string",
                    "required": false,
                    "default": null,
                    "enum": [
                        "in_progress",
                        "submitted",
                        "graded",
                        "locked_review",
                        "abandoned"
                    ],
                    "example": null,
                    "description": "Only attempts in this state."
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "A page of attempts.",
                    "example": {
                        "data": [
                            {
                                "id": 1,
                                "exam_id": 1,
                                "exam_title": "string",
                                "user_id": 1,
                                "user_name": "string",
                                "guest_name": "string",
                                "guest_email": "string",
                                "attempt_number": 1,
                                "status": "in_progress",
                                "started": "2026-01-01T12:00:00Z",
                                "submitted": "2026-01-01T12:00:00Z",
                                "expires_at": "2026-01-01T12:00:00Z",
                                "time_spent": 1,
                                "score": 1.5,
                                "max_score": 1.5,
                                "percent": 1.5,
                                "passed": true,
                                "is_graded": true
                            }
                        ],
                        "meta": {
                            "total": 1
                        }
                    },
                    "fields": [
                        {
                            "path": "data[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].exam_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].exam_title",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].user_id",
                            "type": "integer|null",
                            "description": "Null for a guest."
                        },
                        {
                            "path": "data[].user_name",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].guest_name",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].guest_email",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].attempt_number",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].status",
                            "type": "string",
                            "description": "One of `in_progress`, `submitted`, `graded`, `locked_review`, `abandoned`."
                        },
                        {
                            "path": "data[].started",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].submitted",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].expires_at",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data[].time_spent",
                            "type": "integer|null",
                            "description": "Seconds."
                        },
                        {
                            "path": "data[].score",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data[].max_score",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data[].percent",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data[].passed",
                            "type": "boolean|null",
                            "description": ""
                        },
                        {
                            "path": "data[].is_graded",
                            "type": "boolean",
                            "description": "False while a question still waits for a person to grade it."
                        },
                        {
                            "path": "meta.total",
                            "type": "integer",
                            "description": "Matching records in all pages."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/attempts\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "List attempts endpoint",
            "seoDescription": "GET /attempts lists exam attempts newest first, filterable by exam and status, with score and pass state.",
            "seoKeywords": "attempts api, exam results api, gradebook sync"
        },
        {
            "id": "getAttempt",
            "slug": "get-attempts",
            "pagePath": "api/get-attempts",
            "group": "Attempts",
            "method": "GET",
            "operation": "/v1/nextexams/attempts/{id}",
            "summary": "Get an attempt",
            "title": "Get Attempt",
            "description": "The attempt with every response, how it was graded, and its integrity signals. Scope `attempts:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The record's id. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The attempt.",
                    "example": {
                        "data": {
                            "id": 1,
                            "exam_id": 1,
                            "exam_title": "string",
                            "user_id": 1,
                            "user_name": "string",
                            "guest_name": "string",
                            "guest_email": "string",
                            "attempt_number": 1,
                            "status": "in_progress",
                            "started": "2026-01-01T12:00:00Z",
                            "submitted": "2026-01-01T12:00:00Z",
                            "expires_at": "2026-01-01T12:00:00Z",
                            "time_spent": 1,
                            "score": 1.5,
                            "max_score": 1.5,
                            "percent": 1.5,
                            "passed": true,
                            "is_graded": true,
                            "integrity_signals": {},
                            "grader_comment": "string",
                            "responses": [
                                {
                                    "id": 1,
                                    "question_id": 1,
                                    "question_type": "string",
                                    "question_title": "string",
                                    "response": "string",
                                    "is_correct": true,
                                    "points_awarded": 1.5,
                                    "points_possible": 1.5,
                                    "auto_graded": true,
                                    "grader_comment": "string",
                                    "graded_at": "2026-01-01T12:00:00Z"
                                }
                            ]
                        }
                    },
                    "fields": [
                        {
                            "path": "data.id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.exam_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.exam_title",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.user_id",
                            "type": "integer|null",
                            "description": ""
                        },
                        {
                            "path": "data.user_name",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.guest_name",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.guest_email",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.attempt_number",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.status",
                            "type": "string",
                            "description": "One of `in_progress`, `submitted`, `graded`, `locked_review`, `abandoned`."
                        },
                        {
                            "path": "data.started",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.submitted",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.expires_at",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        },
                        {
                            "path": "data.time_spent",
                            "type": "integer|null",
                            "description": ""
                        },
                        {
                            "path": "data.score",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.max_score",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.percent",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.passed",
                            "type": "boolean|null",
                            "description": ""
                        },
                        {
                            "path": "data.is_graded",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.integrity_signals",
                            "type": "object|null",
                            "description": "Detection-only counters (focus losses, pastes, tab switches, fullscreen\nexits, per-question timings). Never a verdict on their own."
                        },
                        {
                            "path": "data.grader_comment",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].question_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].question_type",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].question_title",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].response",
                            "type": "object",
                            "description": "What the examinee answered, in the question type's own shape; null when unanswered."
                        },
                        {
                            "path": "data.responses[].is_correct",
                            "type": "boolean|null",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].points_awarded",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].points_possible",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].auto_graded",
                            "type": "boolean",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].grader_comment",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data.responses[].graded_at",
                            "type": "string|null",
                            "description": "ISO 8601 in UTC, or null when not set. (date-time)"
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope",
                "not_found"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/attempts/1\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Get attempt endpoint",
            "seoDescription": "GET /attempts/{id} returns an attempt with every response, how it was graded, and its integrity signals.",
            "seoKeywords": "get attempt, responses, grading, integrity signals"
        },
        {
            "id": "getExamReport",
            "slug": "reports-overview",
            "pagePath": "api/reports-overview",
            "group": "Reports",
            "method": "GET",
            "operation": "/v1/nextexams/reports/{exam_id}/overview",
            "summary": "Exam overview",
            "title": "Get Exam Report",
            "description": "Pass rate, score distribution and timing over the exam's graded attempts. Scope `reports:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "exam_id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The exam to report on. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "The overview.",
                    "example": {
                        "data": {
                            "attempts": 1,
                            "pass_count": 1,
                            "pass_rate": 1.5,
                            "mean": 1.5,
                            "median": 1.5,
                            "min": 1.5,
                            "max": 1.5,
                            "stddev": 1.5,
                            "avg_seconds": 1,
                            "histogram": [
                                {
                                    "bucket": "40-50",
                                    "count": 1
                                }
                            ]
                        }
                    },
                    "fields": [
                        {
                            "path": "data.attempts",
                            "type": "integer",
                            "description": "Graded attempts counted."
                        },
                        {
                            "path": "data.pass_count",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data.pass_rate",
                            "type": "number",
                            "description": "Percent."
                        },
                        {
                            "path": "data.mean",
                            "type": "number|null",
                            "description": "Mean percent."
                        },
                        {
                            "path": "data.median",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.min",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.max",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.stddev",
                            "type": "number|null",
                            "description": ""
                        },
                        {
                            "path": "data.avg_seconds",
                            "type": "integer|null",
                            "description": ""
                        },
                        {
                            "path": "data.histogram[]",
                            "type": "object[]",
                            "description": "Attempts per 10-percent band."
                        },
                        {
                            "path": "data.histogram[].bucket",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data.histogram[].count",
                            "type": "integer",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/reports/1/overview\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Exam overview report endpoint",
            "seoDescription": "GET /reports/{exam_id}/overview returns pass rate, mean, median, spread and a score histogram for an exam.",
            "seoKeywords": "exam analytics api, pass rate, score distribution"
        },
        {
            "id": "getItemAnalysis",
            "slug": "reports-items",
            "pagePath": "api/reports-items",
            "group": "Reports",
            "method": "GET",
            "operation": "/v1/nextexams/reports/{exam_id}/items",
            "summary": "Item analysis",
            "title": "Get Item Analysis",
            "description": "Per question: how often it was answered right (difficulty) and how well it\nseparates strong from weak candidates (item-total Pearson discrimination).\nScope `reports:read`.\n",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "exam_id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The exam to report on. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "One row per question.",
                    "example": {
                        "data": [
                            {
                                "question_id": 1,
                                "title": "string",
                                "question_type": "string",
                                "responses": 1,
                                "difficulty": 1.5,
                                "correct_rate": 1.5,
                                "discrimination": 1.5,
                                "avg_seconds": 1
                            }
                        ]
                    },
                    "fields": [
                        {
                            "path": "data[].question_id",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].title",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].question_type",
                            "type": "string",
                            "description": ""
                        },
                        {
                            "path": "data[].responses",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].difficulty",
                            "type": "number|null",
                            "description": "Share answered correctly, 0 to 1."
                        },
                        {
                            "path": "data[].correct_rate",
                            "type": "number|null",
                            "description": "The same, as a percent."
                        },
                        {
                            "path": "data[].discrimination",
                            "type": "number|null",
                            "description": "Item-total Pearson correlation, -1 to 1."
                        },
                        {
                            "path": "data[].avg_seconds",
                            "type": "integer|null",
                            "description": ""
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/reports/1/items\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Item analysis endpoint",
            "seoDescription": "GET /reports/{exam_id}/items returns per-question difficulty and item-total Pearson discrimination.",
            "seoKeywords": "item analysis api, difficulty index, discrimination index, psychometrics"
        },
        {
            "id": "getStudentReport",
            "slug": "reports-students",
            "pagePath": "api/reports-students",
            "group": "Reports",
            "method": "GET",
            "operation": "/v1/nextexams/reports/{exam_id}/students",
            "summary": "Per-student summary",
            "title": "Get Student Report",
            "description": "One row per examinee — attempts, best and average percent, passes. Guests are grouped into one row with no user id. Scope `reports:read`.",
            "stability": "stable",
            "deprecated": null,
            "auth": true,
            "params": [
                {
                    "name": "exam_id",
                    "in": "path",
                    "type": "integer",
                    "required": true,
                    "default": null,
                    "enum": null,
                    "example": null,
                    "description": "The exam to report on. (min 1)"
                }
            ],
            "requestBody": null,
            "responses": [
                {
                    "status": 200,
                    "description": "One row per examinee.",
                    "example": {
                        "data": [
                            {
                                "user_id": 1,
                                "name": "string",
                                "attempts": 1,
                                "best": 1.5,
                                "average": 1.5,
                                "passed": 1
                            }
                        ]
                    },
                    "fields": [
                        {
                            "path": "data[].user_id",
                            "type": "integer|null",
                            "description": "Null for the row that groups guests."
                        },
                        {
                            "path": "data[].name",
                            "type": "string|null",
                            "description": ""
                        },
                        {
                            "path": "data[].attempts",
                            "type": "integer",
                            "description": ""
                        },
                        {
                            "path": "data[].best",
                            "type": "number|null",
                            "description": "Best percent."
                        },
                        {
                            "path": "data[].average",
                            "type": "number|null",
                            "description": "Average percent."
                        },
                        {
                            "path": "data[].passed",
                            "type": "integer",
                            "description": "Passing attempts."
                        }
                    ],
                    "notes": ""
                }
            ],
            "errors": [
                "invalid_token",
                "missing_scope"
            ],
            "rateLimit": null,
            "samples": [
                {
                    "lang": "bash",
                    "label": "curl",
                    "code": "curl -s \"https://your-site.example/api/index.php/v1/nextexams/reports/1/students\" \\\n  -H \"Authorization: Bearer $API_TOKEN\""
                }
            ],
            "sections": [],
            "seoTitle": "Student report endpoint",
            "seoDescription": "GET /reports/{exam_id}/students returns one row per examinee: attempts, best and average percent, passes.",
            "seoKeywords": "student report api, per student results"
        }
    ],
    "errors": [
        {
            "id": "invalid_token",
            "status": 401,
            "code": "INVALID_TOKEN",
            "when": "No token (`MISSING_TOKEN`), or one that is wrong, expired, revoked, or whose\nowner can no longer sign in (`INVALID_TOKEN`).\n",
            "body": {
                "errors": [
                    {
                        "title": "Invalid or expired API token.",
                        "code": "INVALID_TOKEN"
                    }
                ]
            },
            "global": false
        },
        {
            "id": "missing_scope",
            "status": 403,
            "code": "MISSING_SCOPE",
            "when": "The token is valid but this request is not allowed, and `code` says why:\n`MISSING_SCOPE` (the token lacks the scope), `FORBIDDEN` (the owner's role\ndoes not allow it), `ACCESS_DENIED` (the owner cannot manage Next Exams),\n`API_DISABLED` (the API Tokens app is off), `APP_DISABLED` (the feature's\napp is off — `meta.app` names it), `ACCOUNT_UNUSABLE` (the owner is\nblocked or must reset their password), `DEMO_LOCKED` (a demo site).\n",
            "body": {
                "errors": [
                    {
                        "title": "The token is missing the required scope: exams:delete",
                        "code": "MISSING_SCOPE"
                    }
                ]
            },
            "global": false
        },
        {
            "id": "bad_request",
            "status": 400,
            "code": "BAD_REQUEST",
            "when": "The body failed validation, or is not a JSON object (`INVALID_JSON`).",
            "body": {
                "errors": [
                    {
                        "title": "Field required: Title",
                        "code": "BAD_REQUEST"
                    }
                ]
            },
            "global": false
        },
        {
            "id": "not_found",
            "status": 404,
            "code": "NOT_FOUND",
            "when": "No record has this id.",
            "body": {
                "errors": [
                    {
                        "title": "Not found.",
                        "code": "NOT_FOUND"
                    }
                ]
            },
            "global": false
        },
        {
            "id": "http_409",
            "status": 409,
            "code": null,
            "when": "The save or delete would break something that depends on the record:\n`QUESTION_IN_USE`, `ANSWERED_NEEDS_VERSION`, `TYPE_LOCKED`,\n`STALE`. `meta` carries the\ndetails (for example the exams that use a question).\n",
            "body": null,
            "global": true
        },
        {
            "id": "http_422",
            "status": 422,
            "code": null,
            "when": "The request is well formed but would lose data (`OPTIONS_REQUIRED`).",
            "body": null,
            "global": true
        }
    ],
    "rateLimits": [],
    "webhooks": {
        "description": "Outbound events. When an exam event happens, Next Exams sends an HTTP POST to each endpoint subscribed to it, so another system can react without polling.\n\nA webhook says something happened; the REST API reads the full record. A gradebook typically listens for `attempt.graded`, then fetches the attempt if it needs every response.",
        "configuredIn": "Next Exams → Webhooks",
        "transport": {
            "method": "POST",
            "contentType": "application/json"
        },
        "envelope": {
            "example": {
                "event": "attempt.graded",
                "timestamp": "2026-10-18T12:51:30+00:00",
                "site": "https://your-site.example",
                "data": []
            },
            "fields": [
                {
                    "path": "event",
                    "type": "string",
                    "description": "The event name. The same as the `X-NextExams-Event` header."
                },
                {
                    "path": "timestamp",
                    "type": "string",
                    "description": "ISO 8601 with offset, when the event was sent. Unchanged on a retry."
                },
                {
                    "path": "site",
                    "type": "string",
                    "description": "The sending site's root address, so one receiver can serve several sites."
                },
                {
                    "path": "data",
                    "type": "object",
                    "description": "The event's own fields."
                }
            ]
        },
        "signing": {
            "header": "X-NextExams-Signature",
            "algorithm": "HMAC-SHA256",
            "format": "sha256=<hex>",
            "signedPayload": "The raw request body, exactly as received.",
            "secretSetting": "Signing secret (each endpoint in Next Exams → Webhooks)",
            "notes": [
                "Every delivery is signed with the endpoint's own secret. An endpoint URL is not a secret — without verification, anyone who learns it can post invented results to your integration.",
                "Compute the HMAC over the bytes as received: re-encoding the parsed JSON changes key order and whitespace, and the check will fail. Compare with a constant-time function.",
                "Regenerating a secret takes effect on the next delivery, so update the receiver first.",
                "`X-NextExams-Delivery` is the delivery's id and is the same on every retry of it — dedupe on it. A retry resends the identical body, timestamp included."
            ],
            "sample": {
                "lang": "php",
                "label": "Verify a Next Exams signature in PHP",
                "code": "$body     = file_get_contents('php://input');\n$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);\n$given    = $_SERVER['HTTP_X_NEXTEXAMS_SIGNATURE'] ?? '';\n\nif (!hash_equals($expected, $given)) {\n    http_response_code(401);\n    exit;\n}\n\n$event = json_decode($body, true);"
            }
        },
        "delivery": {
            "retries": 4,
            "ordering": "none",
            "log": "**Next Exams → Webhooks** lists each endpoint's recent deliveries with the status code and any error — the first place to look when an integration \"didn't get\" something.",
            "consumerAdvice": [
                "**Any 2xx answer is a success.** Anything else, a timeout (8 seconds), or a redirect is a failure.",
                "**Failed deliveries are retried** 10, 20, 30 and 40 minutes apart, then marked failed after five attempts in all. Retries are sent by the Webhooks scheduled task.",
                "**Be idempotent.** Dedupe on `X-NextExams-Delivery`; a retry carries the same id.",
                "**Respond quickly.** Acknowledge receipt and do the slow work asynchronously.",
                "**The endpoint must be public.** Next Exams refuses loopback, private, reserved and link-local addresses, at send time as well as save time, and does not follow redirects.",
                "**Ignore fields you do not recognise.** New fields may be added to payloads."
            ]
        },
        "selection": "An endpoint receives only the events it subscribes to. An endpoint with no events ticked receives nothing.",
        "events": [
            {
                "name": "attempt.submitted",
                "slug": "attempt-submitted",
                "pagePath": "api/events/attempt-submitted",
                "summary": "An attempt is handed in",
                "trigger": "The examinee submits, the timer runs out, an invigilator hands the attempt in, or the auto-close sweep submits it. An attempt abandoned without being handed in sends nothing.",
                "description": "",
                "testOnly": false,
                "fields": [
                    {
                        "name": "attempt_id",
                        "type": "integer",
                        "description": "The attempt. Fetch it with GET /attempts/{id}."
                    },
                    {
                        "name": "exam_id",
                        "type": "integer"
                    },
                    {
                        "name": "exam_title",
                        "type": "string"
                    },
                    {
                        "name": "user_id",
                        "type": "integer|null",
                        "description": "Null for a guest."
                    },
                    {
                        "name": "guest_email",
                        "type": "string|null"
                    },
                    {
                        "name": "examinee_name",
                        "type": "string"
                    },
                    {
                        "name": "attempt_number",
                        "type": "integer"
                    },
                    {
                        "name": "status",
                        "type": "string",
                        "description": "`submitted` while a question waits for a person to grade it, `graded` when final."
                    },
                    {
                        "name": "passed",
                        "type": "boolean",
                        "description": "Meaningful only once `is_graded` is true."
                    },
                    {
                        "name": "is_graded",
                        "type": "boolean"
                    },
                    {
                        "name": "score",
                        "type": "number|null"
                    },
                    {
                        "name": "max_score",
                        "type": "number|null"
                    },
                    {
                        "name": "percent",
                        "type": "number|null"
                    },
                    {
                        "name": "submitted",
                        "type": "string|null",
                        "description": "UTC, as YYYY-MM-DD HH:MM:SS."
                    },
                    {
                        "name": "result_url",
                        "type": "string",
                        "description": "The examinee's result page."
                    }
                ],
                "payload": {
                    "event": "attempt.submitted",
                    "timestamp": "2026-10-18T12:51:30+00:00",
                    "site": "https://your-site.example",
                    "data": {
                        "attempt_id": 263,
                        "exam_id": 7,
                        "exam_title": "Safety induction",
                        "user_id": 412,
                        "guest_email": null,
                        "examinee_name": "Dana Kovacs",
                        "attempt_number": 1,
                        "status": "submitted",
                        "passed": false,
                        "is_graded": false,
                        "score": null,
                        "max_score": 20,
                        "percent": null,
                        "submitted": "2026-10-18 12:51:30",
                        "result_url": "https://your-site.example/exams/result/263"
                    }
                },
                "variants": [],
                "seoTitle": "attempt.submitted webhook",
                "seoDescription": "Sent when an attempt is handed in by the examinee, the timer, an invigilator or the auto-close sweep.",
                "seoKeywords": "attempt submitted webhook"
            },
            {
                "name": "attempt.graded",
                "slug": "attempt-graded",
                "pagePath": "api/events/attempt-graded",
                "summary": "An attempt's grade is final",
                "trigger": "Every question of the attempt has been graded — straight after submission for an automatically graded exam, after a person grades the rest otherwise.",
                "description": "The event a gradebook should listen for: `score`, `percent` and `passed` are final. The values are sent whatever the exam's result-release policy says; a receiver that shows them to the examinee should respect the policy itself.",
                "testOnly": false,
                "fields": [
                    {
                        "name": "attempt_id",
                        "type": "integer"
                    },
                    {
                        "name": "exam_id",
                        "type": "integer"
                    },
                    {
                        "name": "exam_title",
                        "type": "string"
                    },
                    {
                        "name": "user_id",
                        "type": "integer|null"
                    },
                    {
                        "name": "guest_email",
                        "type": "string|null"
                    },
                    {
                        "name": "examinee_name",
                        "type": "string"
                    },
                    {
                        "name": "attempt_number",
                        "type": "integer"
                    },
                    {
                        "name": "status",
                        "type": "string"
                    },
                    {
                        "name": "passed",
                        "type": "boolean"
                    },
                    {
                        "name": "is_graded",
                        "type": "boolean"
                    },
                    {
                        "name": "score",
                        "type": "number"
                    },
                    {
                        "name": "max_score",
                        "type": "number"
                    },
                    {
                        "name": "percent",
                        "type": "number"
                    },
                    {
                        "name": "submitted",
                        "type": "string"
                    },
                    {
                        "name": "result_url",
                        "type": "string"
                    }
                ],
                "payload": {
                    "event": "attempt.graded",
                    "timestamp": "2026-10-18T12:51:30+00:00",
                    "site": "https://your-site.example",
                    "data": {
                        "attempt_id": 263,
                        "exam_id": 7,
                        "exam_title": "Safety induction",
                        "user_id": 412,
                        "guest_email": null,
                        "examinee_name": "Dana Kovacs",
                        "attempt_number": 1,
                        "status": "graded",
                        "passed": true,
                        "is_graded": true,
                        "score": 18,
                        "max_score": 20,
                        "percent": 90,
                        "submitted": "2026-10-18 12:51:30",
                        "result_url": "https://your-site.example/exams/result/263"
                    }
                },
                "variants": [],
                "seoTitle": "attempt.graded webhook",
                "seoDescription": "Sent when every question of an attempt is graded — the event a gradebook integration listens for.",
                "seoKeywords": "attempt graded webhook, gradebook"
            },
            {
                "name": "certificate.issued",
                "slug": "certificate-issued",
                "pagePath": "api/events/certificate-issued",
                "summary": "A certificate is issued",
                "trigger": "A passing attempt earns its certificate.",
                "description": "",
                "testOnly": false,
                "fields": [
                    {
                        "name": "certificate_id",
                        "type": "integer"
                    },
                    {
                        "name": "number",
                        "type": "string",
                        "description": "The certificate number, from the template's numbering pattern."
                    },
                    {
                        "name": "attempt_id",
                        "type": "integer"
                    },
                    {
                        "name": "exam_id",
                        "type": "integer"
                    },
                    {
                        "name": "exam_title",
                        "type": "string"
                    },
                    {
                        "name": "user_id",
                        "type": "integer|null"
                    },
                    {
                        "name": "guest_email",
                        "type": "string|null"
                    },
                    {
                        "name": "recipient_name",
                        "type": "string"
                    },
                    {
                        "name": "issued_at",
                        "type": "string",
                        "description": "UTC, as YYYY-MM-DD HH:MM:SS."
                    },
                    {
                        "name": "expires_at",
                        "type": "string|null",
                        "description": "Null when it never expires."
                    }
                ],
                "payload": {
                    "event": "certificate.issued",
                    "timestamp": "2026-10-18T12:51:31+00:00",
                    "site": "https://your-site.example",
                    "data": {
                        "certificate_id": 91,
                        "number": "2026/0042",
                        "attempt_id": 263,
                        "exam_id": 7,
                        "exam_title": "Safety induction",
                        "user_id": 412,
                        "guest_email": null,
                        "recipient_name": "Dana Kovacs",
                        "issued_at": "2026-10-18 12:51:31",
                        "expires_at": null
                    }
                },
                "variants": [],
                "seoTitle": "certificate.issued webhook",
                "seoDescription": "Sent when a passing attempt earns its certificate, with the certificate number and expiry.",
                "seoKeywords": "certificate issued webhook"
            },
            {
                "name": "ping",
                "slug": "ping",
                "pagePath": "api/events/ping",
                "summary": "You press Send test",
                "trigger": "The Send test button on a webhook endpoint. Never sent by a real event, and carries no X-NextExams-Delivery header.",
                "description": "",
                "testOnly": true,
                "fields": [
                    {
                        "name": "message",
                        "type": "string"
                    },
                    {
                        "name": "webhook_id",
                        "type": "integer",
                        "description": "The endpoint that was tested."
                    }
                ],
                "payload": {
                    "event": "ping",
                    "timestamp": "2026-10-18T12:00:00+00:00",
                    "site": "https://your-site.example",
                    "data": {
                        "message": "NextExams webhook test ping.",
                        "webhook_id": 3
                    }
                },
                "variants": [],
                "seoTitle": "ping webhook",
                "seoDescription": "The test delivery the Send test button posts, to check an endpoint is reachable and its signature check works.",
                "seoKeywords": "webhook test, ping"
            }
        ]
    },
    "seoOverview": {
        "title": "REST API Reference",
        "description": "Next Exams REST API reference: bearer token auth with scopes, subjects, tags, questions with answer keys, exams, attempts, reports, error codes and webhooks.",
        "keywords": "next exams api, rest api, openapi, bearer token, joomla web services, exam api, question bank api"
    },
    "seoEvents": {
        "title": "Webhook events",
        "description": "Next Exams webhook events: attempt submitted, attempt graded, certificate issued, and the test ping — envelope, signing and delivery.",
        "keywords": "next exams webhooks, webhook events, hmac signature, attempt.graded"
    },
    "changelog": [],
    "generatedBy": "NextExtManager",
    "generated": "2026-10-30T18:39:44+00:00"
}