Contents
REST API - Plugixa Advanced User Role Editor
Everything the admin app does, it does over REST, under the namespace
plugixa-aure/v1. Anything the screens can do is therefore scriptable, and every
rule the screens enforce is enforced at the route rather than in the page.
This is the API the admin app uses. It is not a separately versioned public API, and the plugin makes no promise that routes, parameters or response shapes stay the same between versions. Pin the plugin version you build against, and test after updating.
Authentication
The plugin adds no authentication of its own. It relies on whatever WordPress provides, and then checks the permissions of the user WordPress identified.
| Caller | How |
|---|---|
| The admin app | The logged-in cookie plus a REST nonce in the X-WP-Nonce header, the normal WordPress way |
| A script or another system | Any method WordPress accepts for REST requests, such as an application password for an account that holds the permissions below |
No route is open to visitors. Read routes are protected too, because they return the complete picture of who can do what on the site.
Permissions
Each route requires one of the plugin’s own capabilities. Administrators hold all four from activation. Other roles get the first two when they are added under Who can use this editor in Settings.
| Called in the tables below | Capability required |
|---|---|
| View | plugixa_aure_view, or any of the three below |
| Edit roles | plugixa_aure_edit_roles |
| Edit people | plugixa_aure_edit_users |
| Settings | plugixa_aure_manage_settings |
A caller without the capability gets the error code plugixa_aure_forbidden.
Passing the route’s permission is not the end of it. Every write also goes through the safety guardrails: nobody can grant a permission they do not hold, lock themselves out, or remove the last administrator, whichever way the request arrives.
Response shape
A successful response wraps its payload:
{
"success": true,
"data": { }
}
A refusal is a standard WordPress REST error with a code starting
plugixa_aure_, a readable message and an HTTP status. An unexpected failure
returns plugixa_aure_error with status 500 and no detail.
Routes
Paths are relative to /wp-json/plugixa-aure/v1.
Start-up
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /bootstrap |
View | Version, settings, feature list, interface strings, what the caller may do, and site details |
Roles
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /roles |
View | Every role with its permissions. Each role carries a version used by the changes routes |
| POST | /roles |
Edit roles | Create a role. slug and name are required; from copies an existing role; capabilities sets its permissions |
| PATCH | /roles/{slug} |
Edit roles | Rename (name), change permissions (capabilities), or both |
| DELETE | /roles/{slug} |
Edit roles | Delete a role. reassign_to names the role its holders move to |
| POST | /roles/{slug}/preview |
Edit roles | What a set of capabilities would change for one role, without saving |
| GET | /catalogue |
View | The plain-language permission areas, the permissions in them, the new-role templates and WordPress’s default roles |
Capabilities
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /capabilities |
View | Every permission on the site and where it comes from |
| POST | /capabilities |
Edit roles | Add a permission by name (cap) |
| GET | /capabilities/orphans |
View | Leftover permissions that nothing on the site claims |
| DELETE | /capabilities/{cap} |
Edit roles | Remove a leftover permission from every role |
Users
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /users |
Edit people | A page of people. page, per_page (default 25, at most 100), search, role |
| GET | /users/{id} |
Edit people | One person: roles, own permissions, and a version used by the changes routes |
| PATCH | /users/{id} |
Edit people | Set roles (in order), capabilities (the account’s own), or both |
| POST | /users/{id}/preview |
Edit people | What a new list of roles would change for one person, without saving |
| GET | /users/migrate |
Edit people | The state of the background move between roles |
| POST | /users/migrate |
Edit people | Start a move. from, to and mode (add, replace or remove) are required |
| DELETE | /users/migrate |
Edit people | Cancel the move |
Changes: preview and apply
| Method | Route | Needs | What it does |
|---|---|---|---|
| POST | /changes/preview |
Edit roles or Edit people | What a set of changes to roles and people would do, and whether it would be allowed. Saves nothing |
| POST | /changes/apply |
Edit roles or Edit people | Saves the set, or refuses all of it |
Either capability lets a caller in; the body is then checked part by part. The
roles part needs Edit roles and the users part needs Edit people. See the
worked example below.
History
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /history |
Edit roles | The History list. per_page (default 50, at most 200) |
| POST | /history |
Edit roles | Take a restore point now. Optional label |
| POST | /history/{id}/restore |
Edit roles | Restore a point, roles and people |
| GET | /snapshots |
Edit roles | The stored restore points. per_page (default 50, at most 200) |
| POST | /snapshots |
Edit roles | Take a restore point now. Optional label |
| POST | /snapshots/{id}/restore |
Edit roles | Restore a point |
| DELETE | /snapshots/{id} |
Edit roles | Delete a restore point |
Transfer
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /transfer/export |
Edit roles | The export as filename and bundle. roles is an optional comma-separated list of slugs |
| POST | /transfer/inspect |
Edit roles | Reads a file’s contents (file, as text) and reports what importing it would do |
| POST | /transfer/import |
Edit roles | Imports file. decisions maps each role slug to add, overwrite or skip |
The file is sent as a string in the JSON body, not as an upload, and may be at
most one megabyte. A decision that is not one of the three words is treated as
skip.
Redirects
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /redirects |
Edit roles | The redirect rules, the roles, the events and the destinations available |
| PATCH | /redirects |
Edit roles | Replaces every rule. Send roles and site whole |
Settings
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /settings |
Settings | The settings, the feature list, and the roles that can be given access to the editor |
| PATCH | /settings |
Settings | settings changes values; modules switches features on or off by id |
The settings object accepts show_deprecated, show_administrator,
confirm_dangerous, snapshot_retention (1 to 200) and delegate_roles. Other
keys are ignored.
Insights
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /insights/health |
Edit roles | The Health check’s findings and a summary |
| GET | /insights/menu |
View | The stored admin menu as a tree, with the permission each item needs |
| GET | /insights/screens |
Edit roles | Admin screens grouped by the permission that opens them |
Access
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /access/actions |
View | The actions Check access can ask about |
| POST | /access/check |
View | Whether user can do action, optionally on a specific object, with the reasons |
| GET | /posts |
View | Finds content to check against. type, search, per_page (default 20, at most 50) |
| POST | /simulate |
View | Whether user holds cap, with optional args, and how WordPress decides. Described in full below |
Admin menu PRO
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /menu-control |
Edit roles | The menu rules, the roles, the stored menu and the items that can never be hidden |
| PATCH | /menu-control |
Edit roles | Replaces every rule. roles is required and maps role slug to mode (hide or allow) and items |
A top-level item’s key is its menu slug, such as edit.php. A submenu item’s key
is parent|slug, such as edit.php|post-new.php.
Screen clean-up PRO
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /admin-cleanup |
Edit roles | The clean-up rules, the roles, what has been seen on the site, and the block editor panels |
| PATCH | /admin-cleanup |
Edit roles | Replaces every rule. roles is required and maps role slug to the list of keys that role hides |
The keys are widget:<id>, toolbar:<id>, meta_box:<post type>:<id>,
panel:<name>, notices and front_toolbar. Unknown keys are dropped.
Activity log PRO
| Method | Route | Needs | What it does |
|---|---|---|---|
| GET | /activity |
Edit roles | A page of entries, newest first. page, per_page (default 25, at most 100) and the filters below |
| GET | /activity/export |
Edit roles | The matching entries as filename and csv, oldest first. Takes the same filters |
| POST | /activity/{id}/revert |
Edit roles | Undo one entry |
| GET | /activity/settings |
Settings | The log’s settings |
| PATCH | /activity/settings |
Settings | retention_days (30, 90, 180, 365 or 0 for forever), alert_critical, alert_email |
| Filter | Value |
|---|---|
actor |
An account id |
source |
screen, restore, import, revert, cli, background, rescue, api or admin |
operation |
A kind of change, such as role.capabilities or user.roles |
target |
A role slug, permission or account id |
from, to |
Dates as YYYY-MM-DD |
Features that are switched off
A feature switched off in Settings does not register its routes at all. Calling one returns WordPress’s ordinary “no route” 404, not a permission error. The same is true of the three Pro groups on the free edition, where that code is not present.
Worked example: preview, then apply
The role and people screens collect every unsaved edit, send the set to
/changes/preview, show the result for review, and then send the same set to
/changes/apply. One apply is one restore point.
The body
Both routes take the same body.
{
"roles": {
"editor": {
"read": true,
"edit_posts": true,
"publish_posts": true,
"install_plugins": true
}
},
"users": {
"12": {
"roles": ["author", "editor"],
"capabilities": { "edit_theme_options": true }
}
},
"versions": {
"role:editor": "3f9c2a71b0de",
"user:12": "a41d07c95e22"
},
"acknowledged": []
}
| Key | Contents |
|---|---|
roles |
Role slug to that role’s whole permission map. true grants, false blocks |
users |
Account id to roles (the whole list, in order) and, optionally, capabilities (the account’s whole own map) |
versions |
Optional. role:<slug> or user:<id> to the version you loaded from /roles or /users/{id} |
acknowledged |
The key of each warning you have confirmed |
Send whole maps. The difference is worked out on the server against what is stored. A permission the role holds now and that is missing from the map you send is taken away. The map above is shortened for the page; a real one lists every permission the role should end up with.
Values that are not true or false are dropped rather than read as a block.
If you send versions and one no longer matches, the change is refused with
plugixa_aure_changed_elsewhere: somebody changed that role or person after you
loaded it.
1. Preview
POST /wp-json/plugixa-aure/v1/changes/preview
{
"success": true,
"data": {
"roles": [
{
"role": "editor",
"name": "Editor",
"people": 5,
"granted": ["install_plugins"],
"revoked": [],
"denied": [],
"cleared": []
}
],
"users": [
{
"user": 12,
"name": "Sam Carter",
"rolesAdded": ["editor"],
"rolesRemoved": [],
"reordered": false,
"granted": ["edit_theme_options"],
"revoked": [],
"denied": [],
"cleared": []
}
],
"totals": { "changes": 3, "people": 5 },
"warnings": [
{
"key": "role:editor:install_plugins",
"cap": "install_plugins",
"severity": "critical",
"target": "Editor",
"explain": "…",
"confirm": true
},
{
"key": "user:12:edit_theme_options",
"cap": "edit_theme_options",
"severity": "high",
"target": "Sam Carter",
"explain": "…",
"confirm": false
}
],
"blockers": [],
"allowed": true
}
}
| Field | Meaning |
|---|---|
granted, revoked, denied, cleared |
Permissions newly granted, taken away, newly blocked, and no longer blocked |
people |
How many people the change reaches. null when head counts are switched off |
warnings |
Grants rated critical or high. Those with confirm: true must be acknowledged |
blockers |
Reasons the change would be refused, each with a code, a message and the cap it concerns |
allowed |
true when there are no blockers |
A warning’s key has one of three forms:
| Key | Raised when |
|---|---|
role:<slug>:<cap> |
A role is granted a dangerous permission |
user:<id>:<cap> |
One account is granted a dangerous permission |
user:<id>:role:<slug> |
A person is given a role that holds a full-control permission |
Only critical grants need confirming; a high one is reported with
confirm: false. confirm is false for every warning when the
confirm_dangerous setting is switched off.
In this example the Editor role had four holders, and the person being given it makes five.
2. Apply
Send the same body again, with the key of every warning that has
confirm: true copied into acknowledged.
POST /wp-json/plugixa-aure/v1/changes/apply
{
"roles": { "editor": { "read": true, "edit_posts": true, "publish_posts": true, "install_plugins": true } },
"users": { "12": { "roles": ["author", "editor"], "capabilities": { "edit_theme_options": true } } },
"versions": { "role:editor": "3f9c2a71b0de", "user:12": "a41d07c95e22" },
"acknowledged": ["role:editor:install_plugins"]
}
{
"success": true,
"data": {
"snapshot": 42,
"totals": { "changes": 3, "people": 5 }
}
}
snapshot is the restore point taken before the save. It is 0 when the body
changed nothing.
When apply refuses
| Code | Status | Meaning |
|---|---|---|
plugixa_aure_unacknowledged |
422 | A warning that needs confirming is missing from acknowledged |
plugixa_aure_changed_elsewhere |
409 | A version you sent no longer matches |
plugixa_aure_forbidden |
403 | The body has a part the caller may not change |
plugixa_aure_administrator_locked |
403 | The body changes Administrator while editing it is switched off in Settings |
plugixa_aure_unknown_role |
404 | A role slug does not exist |
plugixa_aure_unknown_user |
404 | An account id does not exist |
plugixa_aure_no_roles |
422 | A person would be left with no role |
plugixa_aure_cap_is_role |
409 | A personal permission has the same name as a role the person holds |
Apply runs the same plan as preview and refuses the whole set if the plan has any blocker, returning the first one. The acknowledgement is checked on the server, so a script cannot skip it by not showing a dialog. A guardrail refusal comes back with its own code and message.
Asking one question: /simulate
POST /simulate answers whether one account holds one capability and returns
each step of the decision. It needs View, reads the account’s saved roles and
permissions, and changes nothing. It is a POST so that the account and the
question travel in the body and do not end up in server access logs.
/access/check asks in terms of the actions the Check access screen lists.
/simulate takes a capability name directly, including meta capabilities such
as edit_post.
The simulate body
{
"user": 12,
"cap": "edit_post",
"args": [41]
}
| Key | Required | What it holds |
|---|---|---|
user |
Yes | An account id |
cap |
Yes | A capability name. It is lower-cased and stripped of anything that is not a letter, digit, underscore or hyphen |
args |
No | The extra arguments WordPress takes for this capability, in order. For edit_post that is the post id. Only plain values are kept; nested arrays and objects are dropped |
The simulate answer
{
"success": true,
"data": {
"cap": "edit_post",
"allowed": false,
"user": { "id": 12, "login": "maria", "name": "Maria Lopez", "roles": ["author"] },
"required": ["edit_others_posts", "edit_published_posts"],
"trace": [
{ "layer": "meta_cap", "effect": "rewrite", "source": "map_meta_cap", "cap": "edit_post", "detail": "..." },
{ "layer": "role", "effect": "absent", "source": "", "cap": "edit_others_posts", "detail": "..." },
{ "layer": "role", "effect": "grant", "source": "author", "cap": "edit_published_posts", "detail": "..." }
],
"unexplained": false
}
}
| Key | What it holds |
|---|---|
cap |
The capability that was checked, after cleaning |
allowed |
WordPress’s own answer for that account |
user |
The account’s id, login, name and roles in the order it holds them |
required |
The capabilities WordPress actually requires for this question and these arguments |
trace |
The steps, in the order they were worked out |
unexplained |
true when the roles and the account’s own permissions do not account for allowed |
Each step in trace has a layer, an effect, a source, a readable detail
and, except for the super administrator step, the cap it concerns.
layer |
source |
What the step says |
|---|---|---|
super_admin |
network |
On multisite, the account is a network super administrator. Nothing else is checked |
meta_cap |
map_meta_cap |
rewrite: WordPress turned cap into the capabilities in required. deny: WordPress refuses the request outright and no capability can grant it |
role |
A role slug | That role allows (grant) or blocks (deny) the capability. absent, with an empty source, when no role the account holds mentions it |
user |
The account’s login | The capability is allowed or blocked on the account itself, which comes after every role |
filter |
user_has_cap |
Present only when unexplained is true. Other code granted or blocked the capability |
When unexplained is true, a plugin, the theme or the site’s own code changed
the answer through the user_has_cap filter. The route reports the
disagreement. It cannot say which code was responsible, and a filter that
depends on the page being requested may answer differently on the real screen.
An account id that does not exist is not an error. The route returns
allowed: false with one user step that says so.
The same check is available on the command line as wp plugixa-aure explain.
See WP-CLI commands.
Adding your own routes
plugixa_aure_register_rest_routes fires after the plugin has registered its
routes, with the namespace and the service container. See
Hooks.
Troubleshooting
| Symptom | Usual cause |
|---|---|
| 401 or 403 from the browser | The nonce is missing or stale. Reload the admin |
plugixa_aure_forbidden from a script |
The account lacks the capability the route needs |
| 404 on a route that should exist | Its feature is switched off in Settings, or it is a Pro route on the free edition |
Apply refuses with plugixa_aure_unacknowledged |
Copy each warning key with confirm: true from the preview into acknowledged |
| Apply took permissions away you did not mean to | The role’s map was not sent whole |
What to do next
- React to changes in PHP instead: Hooks.
- Read what the guardrails refuse: Safety guardrails.
- Undo a save made through the API: History and restore.