---
title: REST API
---

Every Mantle endpoint lives under the `mantle/v1` namespace. `REST_Base` composes that namespace from a base path of `mantle` and a version of `v1`, so all controllers inherit it.

## Authentication

Mantle's routes are cookie-and-nonce authenticated like the rest of the WordPress REST API, and each route declares a `permission_callback` that checks a Mantle capability. From the browser, use the nonce WordPress already provides; from a script, use an authentication plugin appropriate to your site.

<!-- wp:docspress/api-request {"method":"GET","endpoint":"/wp-json/mantle/v1/settings","headers":"Accept: application/json\nX-WP-Nonce: REPLACE_WITH_REST_NONCE","requestBody":"","requestBodyFormat":"json","responseStatus":"200 OK","responseBody":"{\n  \"maintenance_enabled\": false,\n  \"security_enabled\": false,\n  \"hosting_provider\": \"\"\n}","responseBodyFormat":"json","runnable":false,"editable":true,"allowUnsafe":false,"timeout":10000} /-->

<!-- wp:docspress/callout {"tone":"warning","title":"Routes appear only when their module is enabled","content":"<p>Module-owned controllers are registered during <code>Module_Loader::init()</code>, and only for enabled modules. On a site with <code>monitoring</code> and <code>convergence_cache</code> switched off, none of the <code>/monitoring/*</code> or <code>/cache/*</code> routes exist and requests to them return <code>404 rest_no_route</code>. Enable the module before treating a missing route as a bug.</p>","collapsible":false} /-->

## Enumerate the routes on your own site

Because the surface is module-dependent, list it rather than trusting a static table:

<!-- wp:docspress/terminal-session {"title":"List every mantle/v1 route","shell":"bash","prompt":"$","command":"wp eval '$r = rest_get_server()->get_routes(); foreach ( $r as $route => $h ) { if ( 0 === strpos( $route, \"/mantle/v1\" ) ) { echo $route . PHP_EOL; } }'","output":"/mantle/v1\n/mantle/v1/settings\n/mantle/v1/settings/reset\n/mantle/v1/modules\n..."} /-->

## Core routes

Registered by controllers in `includes/Controller/REST/`, independent of module state.

### Settings

| Route | Methods |
|-------|---------|
| `/mantle/v1/settings` | `GET`, `POST` |
| `/mantle/v1/settings/<setting_key>` | `GET`, `POST` |
| `/mantle/v1/settings/reset` | `DELETE` |

`setting_key` matches `[a-zA-Z0-9_]+`. String values are sanitised with `sanitize_text_field()` unless the key is in the controller's textarea allow-list.

### Setup and connection

| Route | Methods |
|-------|---------|
| `/mantle/v1/setup` | `GET`, `POST` |
| `/mantle/v1/setup/<setting_key>` | `GET`, `POST` |
| `/mantle/v1/pin` | `POST` |
| `/mantle/v1/pin/<pin>` | `POST` |
| `/mantle/v1/connector` | `GET` |
| `/mantle/v1/connector/callback` | `GET` |
| `/mantle/v1/auth/google/refresh` | `POST` |
| `/mantle/v1/order` | `POST` |

`pin` matches exactly four digits (`\d{4}`).

### Modules

| Route | Methods |
|-------|---------|
| `/mantle/v1/modules` | `GET` |
| `/mantle/v1/modules/<module_id>` | `POST` |

`module_id` matches `[a-zA-Z0-9_-]+`.

### Capabilities, roles, and users

| Route | Methods |
|-------|---------|
| `/mantle/v1/capabilities` | `GET` |
| `/mantle/v1/capabilities/check` | `GET` |
| `/mantle/v1/capabilities/sync` | `POST` |
| `/mantle/v1/capabilities/reset` | `POST` |
| `/mantle/v1/roles` | `GET` |
| `/mantle/v1/roles/<role>/capabilities` | `POST` |
| `/mantle/v1/power-admin/users` | `GET` |
| `/mantle/v1/power-admin/check` | `GET` |
| `/mantle/v1/power-admin/assign` | `POST` |

### Licence and deployment

| Route | Methods |
|-------|---------|
| `/mantle/v1/license` | `GET`, `POST` |
| `/mantle/v1/license/remote/register` | `GET` |
| `/mantle/v1/deployments` | `GET` |

### WAF

| Route | Methods |
|-------|---------|
| `/mantle/v1/waf` | `GET`, `POST`, `DELETE` |
| `/mantle/v1/waf/sync` | `GET` |
| `/mantle/v1/waf/<key>` | `GET` |
| `/mantle/v1/waf/<id>` | `DELETE` |

### Plugin and theme commands

| Route | Methods |
|-------|---------|
| `/mantle/v1/plugins/command` | `POST` |
| `/mantle/v1/plugins/command/batch` | `POST` |
| `/mantle/v1/plugins/command/log` | `GET` |

<!-- wp:docspress/callout {"tone":"danger","title":"The plugin command routes execute privileged operations","content":"<p><code>/plugins/command</code> and <code>/plugins/command/batch</code> drive plugin lifecycle actions remotely. They are gated by the <code>mantle_execute_plugin_commands</code> capability, which is granted only to <code>power_admin</code>, and by a signed-command mechanism with a shared secret and rate limit stored in the <code>plugin_sync_command_secret</code> and <code>plugin_sync_command_rate_limit</code> settings. Treat that secret as a credential.</p>","collapsible":false} /-->

## Module routes

These are registered through `get_rest_controllers()` when their module initialises, so they exist only while the owning module is enabled — with the AI Chatbot group noted below as an exception.

### Convergence Cache (`convergence_cache`)

| Route | Methods |
|-------|---------|
| `/mantle/v1/cache/info` | `GET` |
| `/mantle/v1/cache/provider` | `POST` |
| `/mantle/v1/cache/cloudflare/enable` | `POST` |
| `/mantle/v1/cache/admin-bar/enable` | `POST` |
| `/mantle/v1/cache/clear/object` | `POST` |
| `/mantle/v1/cache/clear/page` | `POST` |
| `/mantle/v1/cache/clear/edge` | `POST` |
| `/mantle/v1/cache/clear/all` | `POST` |
| `/mantle/v1/cache/clear/pressable` | `POST` |
| `/mantle/v1/cache/clear/wpengine` | `POST` |
| `/mantle/v1/cache/clear/pantheon` | `POST` |
| `/mantle/v1/cache/clear/cloudflare` | `POST` |

### Monitoring (`monitoring`)

| Route | Methods |
|-------|---------|
| `/mantle/v1/monitoring/pagespeed/` | `GET` |
| `/mantle/v1/monitoring/pagespeed/<identifier>/` | `GET` |
| `/mantle/v1/monitoring/contact-groups/` | `GET` |
| `/mantle/v1/monitoring/uptime/` | `GET` |
| `/mantle/v1/monitoring/uptime/<identifier>/` | `GET` |
| `/mantle/v1/monitoring/uptime/<identifier>/history` | `GET` |
| `/mantle/v1/monitoring/uptime/<identifier>/periods/` | `GET` |
| `/mantle/v1/monitoring/uptime/<identifier>/alerts/` | `GET` |
| `/mantle/v1/monitoring/ssl/` | `GET` |
| `/mantle/v1/monitoring/ssl/<identifier>/` | `GET` |
| `/mantle/v1/monitoring/heartbeat/` | `GET` |
| `/mantle/v1/monitoring/heartbeat/<identifier>/` | `GET` |

`identifier` matches `[0-9]+`. Note that the `history` sub-route has no trailing slash while its siblings do.

### Plugin Sync (`plugin_sync`)

| Route | Methods |
|-------|---------|
| `/mantle/v1/plugins/status` | `GET` |
| `/mantle/v1/plugins/sync` | `POST` |
| `/mantle/v1/plugins/sync/status` | `GET` |
| `/mantle/v1/plugins/command/enable` | `POST` |
| `/mantle/v1/plugins/command/disable` | `POST` |
| `/mantle/v1/themes/status` | `GET` |

### Client Info (`client_info`, core)

| Route | Methods |
|-------|---------|
| `/mantle/v1/clients` | `GET`, `POST` |
| `/mantle/v1/clients/logo` | `POST` |

### AI Chatbot

| Route | Methods |
|-------|---------|
| `/mantle/v1/ai-chatbot/conversations` | `GET`, `POST` |
| `/mantle/v1/ai-chatbot/conversations/<id>` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
| `/mantle/v1/ai-chatbot/messages` | `GET`, `POST`, `DELETE` |
| `/mantle/v1/ai/generate-block` | `POST` |
| `/mantle/v1/ai/plan-edit` | `POST` |
| `/mantle/v1/ai/rewrite-block` | `POST` |

`id` matches `[0-9]+`.

<!-- wp:docspress/callout {"tone":"warning","title":"These routes are not module-gated","content":"<p>Unlike the other groups in this section, the AI routes are registered by <code>Controller\\AI_Chatbot</code>, which <code>Bootstrap::load_controllers()</code> instantiates unconditionally. They exist even when the <code>ai_chatbot</code> module is inactive — verified on an installation with the module switched off. Access is controlled by the <code>mantle_access_chatbot</code> capability and each route's permission callback, not by the module toggle.</p>","collapsible":false} /-->

### Communication (`communication`)

| Route | Methods |
|-------|---------|
| `/mantle/v1/channels` | `GET` |
| `/mantle/v1/slack/<channel_name>` | `GET` |
| `/mantle/v1/chat/slack/channel` | `POST` |
| `/mantle/v1/chat/slack/ws-token` | `GET` |
| `/mantle/v1/chat/slack/users` | `GET` |
| `/mantle/v1/chat/attachments` | `POST` |

`channel_name` matches `\d+`. The `users` route accepts an `ids` argument; `attachments` handles uploads. Both are declared to the front end by `Communication_Module` as `usersRoute` and `attachmentsRoute`.

### Linchpin Licenses (`linchpin_licenses`)

| Route | Methods |
|-------|---------|
| `/mantle/v1/licenses/request-install` | `POST` |

## Adding routes from a module

Override `get_rest_controllers()` on your module. Mantle registers the returned controllers when the module initialises, so the routes automatically disappear when the module is disabled.

<!-- wp:docspress/colorful-code {"language":"php","filename":"includes/Modules/Reports/Reports_Module.php","code":"/**\n * REST controllers owned by this module.\n *\n * @return array\n */\nprotected function get_rest_controllers(): array {\n\treturn [\n\t\tnew Reports_REST(),\n\t];\n}","highlightedLines":"6","showLineNumbers":true,"caption":"Returned controllers are registered only while the module is enabled.","copyMode":"all","diffMode":"none"} /-->

`Abstract_Module` declares `get_rest_controllers()` as `protected` and returns an empty array by default, so override it with the same visibility.

See [Creating a module](../guides/creating-a-module.md) for the full module contract.
