Next Restaurant 6.1.0: move your data in, run payroll, and connect other systems Next Inventory 6.2.0: a REST API and webhooks for your other systems Next DB 6.1: Auto-Updating Tables, Printing, Phone Layout and SEO Next Task 6.0.0: a task manager for Joomla Next Inventory 6.1.1: smoother Check Out, no more list limits, and much faster PHP2Shell: Is Your Joomla Site Safe? What to Do Today Introducing Next Boards: Kanban project and task boards for Joomla Building the Future of Joomla Extensions
NextSoftware

Grant Pass

POST /v1/nextexams/passes/grants

Grant a pass

Gives a user a plan, from starts_at (default now) for the plan's duration. Idempotent on source_ref: a second call with the same reference answers the first pass with 200 instead of granting again. The user must already exist (404 USER_NOT_FOUND); creating accounts is the caller's job. The source is recorded as api. Each exam's own attempt limit still applies. Scope passes:write; the role needs passes.grant.

Request

POST https://your-site.example/api/index.php/v1/nextexams/passes/grants

Request body

Content type: application/json

Field Type Required Description
user_id integer No (min 1)
email string No (email)
plan_id integer Yes (min 1)
starts_at string No When the pass starts, ISO 8601 (UTC unless an offset is given). Default now.
source_ref string No Your own reference (an order line). A retry with the same reference answers the first pass. (max length 191)
{
    "email": "[email protected]",
    "plan_id": 3,
    "source_ref": "order-1042-line-1"
}

Responses

200 — This reference was already used; the pass it granted.

{
    "data": {
        "id": 1,
        "user_id": 1,
        "user_name": "string",
        "user_email": "string",
        "plan_id": 1,
        "plan_title": "string",
        "source": "string",
        "source_ref": "string",
        "starts_at": "2026-01-01T12:00:00Z",
        "expires_at": "2026-01-01T12:00:00Z",
        "status": "active",
        "created": "2026-01-01T12:00:00Z"
    }
}
Field Type Meaning
data.id integer
data.user_id integer
data.user_name string|null
data.user_email string|null
data.plan_id integer
data.plan_title string|null
data.source string manual, api, or ext:<element> for a pass an extension granted.
data.source_ref string|null The caller's own reference, unique per source.
data.starts_at string|null ISO 8601 in UTC, or null when not set. (date-time)
data.expires_at string|null ISO 8601 in UTC, or null when not set. (date-time)
data.status string One of active, expired, revoked.
data.created string|null ISO 8601 in UTC, or null when not set. (date-time)

201 — Granted.

{
    "data": {
        "id": 1,
        "user_id": 1,
        "user_name": "string",
        "user_email": "string",
        "plan_id": 1,
        "plan_title": "string",
        "source": "string",
        "source_ref": "string",
        "starts_at": "2026-01-01T12:00:00Z",
        "expires_at": "2026-01-01T12:00:00Z",
        "status": "active",
        "created": "2026-01-01T12:00:00Z"
    }
}
Field Type Meaning
data.id integer
data.user_id integer
data.user_name string|null
data.user_email string|null
data.plan_id integer
data.plan_title string|null
data.source string manual, api, or ext:<element> for a pass an extension granted.
data.source_ref string|null The caller's own reference, unique per source.
data.starts_at string|null ISO 8601 in UTC, or null when not set. (date-time)
data.expires_at string|null ISO 8601 in UTC, or null when not set. (date-time)
data.status string One of active, expired, revoked.
data.created string|null ISO 8601 in UTC, or null when not set. (date-time)

Errors

Status Code When
401 INVALID_TOKEN No token (MISSING_TOKEN), or one that is wrong, expired, revoked, or whose owner can no longer sign in (INVALID_TOKEN).
403 MISSING_SCOPE The token is valid but this request is not allowed, and code says why: MISSING_SCOPE (the token lacks the scope), FORBIDDEN (the owner's role does not allow it), ACCESS_DENIED (the owner cannot manage Next Exams), API_DISABLED (the API Tokens app is off), APP_DISABLED (the feature's app is off — meta.app names it), ACCOUNT_UNUSABLE (the owner is blocked or must reset their password), DEMO_LOCKED (a demo site).
404 USER_NOT_FOUND (no account has this id or email) or PLAN_NOT_FOUND.
409 The save or delete would break something that depends on the record: QUESTION_IN_USE, ANSWERED_NEEDS_VERSION, TYPE_LOCKED, STALE. meta carries the details (for example the exams that use a question).
422 The request is well formed but would lose data (OPTIONS_REQUIRED).

Examples

curl

curl -s -X POST "https://your-site.example/api/index.php/v1/nextexams/passes/grants" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","plan_id":3,"source_ref":"order-1042-line-1"}'

PHP

<?php
$ch = curl_init('https://your-site.example/api/index.php/v1/nextexams/passes/grants');

curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => '{"email":"[email protected]","plan_id":3,"source_ref":"order-1042-line-1"}',
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

JavaScript

const API_TOKEN = process.env.API_TOKEN;

const response = await fetch("https://your-site.example/api/index.php/v1/nextexams/passes/grants", {
  method: 'POST',
  headers: {
    "Authorization": `Bearer ${API_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "email": "[email protected]",
    "plan_id": 3,
    "source_ref": "order-1042-line-1"
  }),
});
const data = await response.json();

All Extensions