{"id":42,"date":"2026-09-09T04:25:57","date_gmt":"2026-09-09T04:25:57","guid":{"rendered":"https:\/\/nirohelp.com\/docs\/42\/rest-api-reference\/"},"modified":"2026-09-19T20:59:44","modified_gmt":"2026-09-19T14:59:44","slug":"rest-api-reference","status":"publish","type":"nirohelp_doc","link":"https:\/\/nirohelp.com\/docs\/developers\/rest-api-reference\/","title":{"rendered":"REST API reference"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Every browser-to-server call in NiroHelp goes through one namespace,<br>\n<code>nirohelp\/v1<\/code>. The base URL is<br>\n<code>https:\/\/your-site.example\/wp-json\/nirohelp\/v1\/<\/code>. Routes are registered<br>\ncentrally in <code>Controllers\\Common\\API<\/code>; the handlers live in <code>app\/API\/<\/code>, one<br>\nclass per resource.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Authentication \u2014 read this first<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Application Passwords do not work on these routes.<\/strong> Almost every<br>\n<code>permission_callback<\/code> in the plugin calls <code>Traits\\Auth::has_nonce()<\/code>, which<br>\nrequires a valid <code>wp_rest<\/code> nonce in the <code>X-WP-Nonce<\/code> header. Application<br>\nPassword and Basic auth requests carry no such header, so they authenticate<br>\nsuccessfully and are then refused with <code>403 rest_forbidden<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -u \"admin:xxxx xxxx xxxx xxxx\" ...\/wp-json\/wp\/v2\/users\/me            # 200\n$ curl -u \"admin:xxxx xxxx xxxx xxxx\" ...\/wp-json\/nirohelp\/v1\/settings\/schema # 403\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The supported callers are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>The browser, as a logged-in user.<\/strong> A cookie plus the nonce. The admin SPA<br><br>  gets both from <code>apiFetch<\/code> in <code>spa\/lib\/api.ts<\/code>; front-end scripts get the<br><br>  nonce from the <code>NIROHELP<\/code> \/ <code>NIROHELP_DOCS<\/code> localized objects.<\/li>\n\n\n\n<li><strong>Anyone, on the seven <code>is_public<\/code> routes.<\/strong> The three doc reads, the three<br><br>  ticket taxonomy lists and ticket creation accept anonymous requests, because<br><br>  the embed widget in <code>assets\/tickets\/js\/embed.js<\/code> runs on other people&#8217;s sites<br><br>  and cannot carry a nonce. Those callbacks are hardened internally instead \u2014<br><br>  <code>POST \/tickets\/<\/code> strips a caller-supplied <code>user_id<\/code>, and <code>GET \/docs\/<\/code> only<br><br>  honours <code>status<\/code> for someone who can edit docs.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">There is one escape hatch, for local development only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ wp-config.php \u2014 NEVER on a production site.\ndefine( 'NIROHELP_SANDBOX', true );\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>is_sandbox_mode()<\/code> short-circuits <strong>every<\/strong> permission callback in<br>\n<code>app\/Traits\/Auth.php<\/code>, including the admin-only AI, settings, migration and<br>\ndashboard routes. It makes the whole API world-readable and world-writable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Permission callbacks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Route tables below cite these by name.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Callback<\/th><th>Passes when<\/th><\/tr><\/thead><tbody><tr><td><code>is_public<\/code><\/td><td>Always. No nonce, no login.<\/td><\/tr><tr><td><code>has_nonce<\/code><\/td><td>A valid <code>wp_rest<\/code> nonce is present. Login not required.<\/td><\/tr><tr><td><code>is_guest<\/code><\/td><td>Not logged in, and sandbox mode is off.<\/td><\/tr><tr><td><code>is_member<\/code><\/td><td>Logged in + nonce.<\/td><\/tr><tr><td><code>is_admin<\/code><\/td><td><code>manage_options<\/code> + nonce.<\/td><\/tr><tr><td><code>is_agent<\/code><\/td><td><code>edit_others_tickets<\/code> or <code>manage_options<\/code>, + nonce.<\/td><\/tr><tr><td><code>can_edit_docs<\/code><\/td><td>The doc CPT&#8217;s <code>edit_pages<\/code>, or a NiroHelp role holding bare <code>edit_posts<\/code>, + nonce.<\/td><\/tr><tr><td><code>can_delete_docs<\/code><\/td><td>Same, for <code>delete_posts<\/code> \/ <code>delete_pages<\/code>.<\/td><\/tr><tr><td><code>can_delete_tickets<\/code><\/td><td><code>delete_others_tickets<\/code> or <code>manage_options<\/code>, + nonce.<\/td><\/tr><tr><td><code>can_read_ticket<\/code><\/td><td><code>edit_others_tickets<\/code>, <code>manage_options<\/code>, or being the ticket&#8217;s own client, + nonce.<\/td><\/tr><tr><td><code>can_comment_on_ticket<\/code><\/td><td>Same as <code>can_read_ticket<\/code>.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><code>can_edit_docs<\/code> resolves to administrators, Editors and <code>nirohelp_manager<\/code>;<br>\n<code>nirohelp_agent<\/code> and <code>nirohelp_user<\/code> both declare <code>edit_posts =&gt; false<\/code>. See<br>\n<a href=\"https:\/\/nirohelp.com\/docs\/setup-configuration\/roles-and-permissions\/\">Roles and permissions<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Docs<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Permission<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>GET<\/td><td><code>\/docs\/<\/code><\/td><td><code>is_public<\/code><\/td><td><code>status<\/code>, <code>s<\/code>, <code>count<\/code>, <code>formatted<\/code>. <code>status<\/code> is honoured only for users who can edit docs; everyone else gets published docs. <code>count<\/code> is clamped by the <code>nirohelp_docs_api_max_per_page<\/code> filter (100).<\/td><\/tr><tr><td>GET<\/td><td><code>\/docs\/product\/{product}<\/code><\/td><td><code>is_public<\/code><\/td><td><code>{product}<\/code> is a <strong>term slug<\/strong>. Same <code>s<\/code> \/ <code>count<\/code> \/ <code>formatted<\/code> args.<\/td><\/tr><tr><td>POST<\/td><td><code>\/docs\/<\/code><\/td><td><code>can_edit_docs<\/code><\/td><td><code>title<\/code>, <code>description<\/code> required; <code>slug<\/code>, <code>status<\/code>, <code>meta<\/code> optional.<\/td><\/tr><tr><td>GET<\/td><td><code>\/docs\/{id}<\/code><\/td><td><code>is_public<\/code><\/td><td><\/td><\/tr><tr><td>PUT<\/td><td><code>\/docs\/{id}<\/code><\/td><td><code>can_edit_docs<\/code><\/td><td>All fields optional except <code>id<\/code>.<\/td><\/tr><tr><td>DELETE<\/td><td><code>\/docs\/{id}\/delete<\/code><\/td><td><code>can_delete_docs<\/code><\/td><td><\/td><\/tr><tr><td>POST<\/td><td><code>\/docs\/{id}\/vote<\/code><\/td><td><code>has_nonce<\/code><\/td><td>Body <code>{\"type\":\"upvote\"}<\/code> or <code>{\"type\":\"downvote\"}<\/code>. Returns the new count.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Tickets<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Permission<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>GET<\/td><td><code>\/tickets\/<\/code><\/td><td><code>is_member<\/code><\/td><td><code>status<\/code>, <code>client<\/code>, <code>agent_id<\/code>, <code>product<\/code>, <code>reason<\/code>, <code>urgency<\/code>, <code>date_from<\/code>, <code>date_to<\/code>, <code>search<\/code>, <code>date_query<\/code>, <code>per_page<\/code> (20), <code>page<\/code> (1).<\/td><\/tr><tr><td>POST<\/td><td><code>\/tickets\/<\/code><\/td><td><code>is_public<\/code><\/td><td><code>title<\/code> and <code>description<\/code> required. <code>user_id<\/code> is stripped unless the caller holds <code>edit_others_tickets<\/code> or <code>manage_options<\/code>.<\/td><\/tr><tr><td>GET<\/td><td><code>\/tickets\/{id}<\/code><\/td><td><code>can_read_ticket<\/code><\/td><td><\/td><\/tr><tr><td>PUT<\/td><td><code>\/tickets\/{id}<\/code><\/td><td><code>is_agent<\/code><\/td><td><code>title<\/code>, <code>slug<\/code>, <code>description<\/code>, <code>status<\/code>, <code>meta<\/code>.<\/td><\/tr><tr><td>DELETE<\/td><td><code>\/tickets\/{id}\/delete<\/code><\/td><td><code>can_delete_tickets<\/code><\/td><td><\/td><\/tr><tr><td>GET<\/td><td><code>\/tickets\/{id}\/comments<\/code><\/td><td><code>can_read_ticket<\/code><\/td><td>The comments are the private conversation, so this is gated per ticket, not merely on being logged in.<\/td><\/tr><tr><td>POST<\/td><td><code>\/tickets\/{id}\/comments<\/code><\/td><td><code>can_comment_on_ticket<\/code><\/td><td><code>message<\/code> required. Auto-transitions ticket status.<\/td><\/tr><tr><td>GET<\/td><td><code>\/tickets\/products\/<\/code><\/td><td><code>is_public<\/code><\/td><td><\/td><\/tr><tr><td>GET<\/td><td><code>\/tickets\/reasons\/<\/code><\/td><td><code>is_public<\/code><\/td><td><\/td><\/tr><tr><td>GET<\/td><td><code>\/tickets\/urgencies\/<\/code><\/td><td><code>is_public<\/code><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The agent on a ticket is changed through <code>meta<\/code> on <code>PUT \/tickets\/{id}<\/code>, which<br>\nfires <code>nirohelp-tickets-agent_changed<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Authentication routes<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Permission<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>POST<\/td><td><code>\/login\/<\/code><\/td><td><code>is_guest<\/code><\/td><td>Drives whichever login method is configured. <code>email<\/code> required; <code>code<\/code> on the second step of the OTP flow; <code>name<\/code> and <code>redirect<\/code> optional.<\/td><\/tr><tr><td>POST<\/td><td><code>\/wp-login\/<\/code><\/td><td><code>is_guest<\/code><\/td><td><code>email<\/code> + <code>password<\/code>. Runs <code>wp_authenticate()<\/code>, so the whole core <code>authenticate<\/code> filter chain applies.<\/td><\/tr><tr><td>POST<\/td><td><code>\/wp-signup\/<\/code><\/td><td><code>is_guest<\/code><\/td><td><code>name<\/code> + <code>email<\/code> + <code>password<\/code>. Gated on <code>nirohelp_can_register_users()<\/code>.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">See <a href=\"https:\/\/nirohelp.com\/docs\/ticket-management\/client-login\/\">Client login<\/a> for the three methods and<br>\nhow they differ.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Options<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Permission<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>GET<\/td><td><code>\/option<\/code><\/td><td><code>is_admin<\/code><\/td><td><code>key<\/code> required.<\/td><\/tr><tr><td>POST<\/td><td><code>\/option<\/code><\/td><td><code>is_admin<\/code><\/td><td><code>key<\/code> + <code>value<\/code>.<\/td><\/tr><tr><td>DELETE<\/td><td><code>\/option<\/code><\/td><td><code>is_admin<\/code><\/td><td><code>key<\/code> required.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Do not route settings through <code>\/option<\/code>.<\/strong> It writes a raw option value by<br>\nkey and so skips <code>Settings::sanitize()<\/code> \u2014 including the two repeater passes<br>\nthat zero unchecked row checkboxes and drop deleted rows. Use <code>\/settings<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Settings<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Permission<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>GET<\/td><td><code>\/settings\/schema<\/code><\/td><td><code>is_admin<\/code><\/td><td>The schema the admin screen renders from.<\/td><\/tr><tr><td>POST<\/td><td><code>\/settings<\/code><\/td><td><code>is_admin<\/code><\/td><td><code>settings<\/code>, an object keyed <code>[tab][section][field]<\/code>.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Migration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Five routes, all <code>is_admin<\/code>. They start and delete content in bulk and rely on<br>\nthe REST nonce alone.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>GET<\/td><td><code>\/migration\/sources<\/code><\/td><td>The importers, grouped, with availability and migrated counts.<\/td><\/tr><tr><td>GET<\/td><td><code>\/migration\/status<\/code><\/td><td>Where the current run has got to. Returns the importer&#8217;s own summary text verbatim.<\/td><\/tr><tr><td>POST<\/td><td><code>\/migration\/start<\/code><\/td><td>Begin an import.<\/td><\/tr><tr><td>POST<\/td><td><code>\/migration\/clean<\/code><\/td><td>Delete everything one source produced.<\/td><\/tr><tr><td>POST<\/td><td><code>\/migration\/reset<\/code><\/td><td>Clear the run state.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">AI<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">All <code>is_admin<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>POST<\/td><td><code>\/ai\/register<\/code><\/td><td><code>name<\/code>, <code>email<\/code>. Forwards to the service; it mails back a site key.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/verify<\/code><\/td><td><code>site_key<\/code> required.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/resend<\/code><\/td><td>Re-runs <code>register<\/code> with the stored name and email.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/reset<\/code><\/td><td>Unverifies remotely and clears local credentials.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/site-key<\/code><\/td><td>The stored site key. 403 until verified.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/sync<\/code><\/td><td>Push published docs to the vector store. 300s timeout.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/state<\/code><\/td><td>What the AI screens render from: verified, has_site_key, name, email, tickets_enabled. Never returns the key itself.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/usage<\/code><\/td><td>Proxies the service&#8217;s usage figures.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/auto-responder<\/code><\/td><td>Current auto-responder settings.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/auto-responder<\/code><\/td><td><code>delay_amount<\/code>, <code>delay_unit<\/code>, <code>threshold<\/code> required; <code>enabled<\/code>, <code>author_name<\/code>, <code>change_status<\/code> optional.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/custom-prompt<\/code><\/td><td>The stored instructions plus the default.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/custom-prompt<\/code><\/td><td><code>custom_prompt<\/code>. Refuses on an unverified site.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/chatbot<\/code><\/td><td>Stored chatbot configuration.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/chatbot<\/code><\/td><td>Save it.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/chatbot\/preview<\/code><\/td><td>Build the embed from posted values <strong>without<\/strong> storing them. Backs Preview, View Code and Download.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/chatbot\/plugin<\/code><\/td><td>The generated single-file plugin.<\/td><\/tr><tr><td>POST<\/td><td><code>\/ai\/copilot<\/code><\/td><td>Ask NiroHelp. <code>message<\/code> required, <code>session_id<\/code> optional.<\/td><\/tr><tr><td>GET<\/td><td><code>\/ai\/logs\/{type}<\/code><\/td><td><code>{type}<\/code> is <code>chatbot<\/code> or <code>responder<\/code>.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><code>GET \/ai\/usage<\/code> and <code>GET \/ai\/logs\/{type}<\/code> proxy<br>\n<code>my.nirosuite.com\/wp-json\/nirohelp-cloud\/v1\/{usage,logs}<\/code>, <strong>which do not exist<br>\nyet<\/strong> \u2014 both 404 today, so the usage panel and the two log screens report the<br>\nservice&#8217;s error rather than data. That is tracked on the service side<br>\n(<code>nirohelp-cloud#30<\/code>), not here.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Dashboard<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">All <code>is_admin<\/code>, all local counts \u2014 no remote call, so a slow service cannot<br>\nstall the screen.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Path<\/th><th>Returns<\/th><\/tr><\/thead><tbody><tr><td><code>\/dashboard\/stats<\/code><\/td><td>Ticket and doc counts, sentiment split, service levels. <code>range<\/code> in days.<\/td><\/tr><tr><td><code>\/dashboard\/queues<\/code><\/td><td>Three triage queues: needs attention, needs reply, new.<\/td><\/tr><tr><td><code>\/dashboard\/ai<\/code><\/td><td>Auto-responder outcomes and handovers.<\/td><\/tr><tr><td><code>\/dashboard\/chatbot<\/code><\/td><td>Chatbot activity for the window.<\/td><\/tr><tr><td><code>\/dashboard\/docs<\/code><\/td><td>The four docs panels.<\/td><\/tr><tr><td><code>\/dashboard\/activity<\/code><\/td><td>Merged ticket, doc and AI-reply events.<\/td><\/tr><tr><td><code>\/dashboard\/agents<\/code><\/td><td>Per-agent load and medians.<\/td><\/tr><tr><td><code>\/dashboard\/hot-spots<\/code><\/td><td>Three heatmaps.<\/td><\/tr><tr><td><code>\/dashboard\/briefing<\/code><\/td><td>The cached daily briefing, written by cron at 06:00.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">A note on <code>args<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>register_rest_route<\/code>&#8216;s <code>args<\/code> declaration documents a route; it does <strong>not<\/strong><br>\nfilter the request. Unregistered parameters still reach the callback, and a<br>\nmissing <code>required<\/code> one is rejected but an unexpected type is not. Validate<br>\ninside the handler.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Worked examples<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Filing a ticket anonymously \u2014 the one write that needs no credentials:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -H 'Content-Type: application\/json' \\\n  -d '{\"title\":\"Login fails\",\"description\":\"Steps to reproduce...\",\"email\":\"alice@example.com\",\"name\":\"Alice\"}' \\\n  https:&#047;&#047;your-site.example\/wp-json\/nirohelp\/v1\/tickets\/\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From the front end, where the nonce is already localized:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fetch( '\/wp-json\/nirohelp\/v1\/tickets\/', {\n    method: 'POST',\n    headers: {\n        'Content-Type': 'application\/json',\n        'X-WP-Nonce': NIROHELP.nonce,\n    },\n    body: JSON.stringify( { title: 'Login fails', description: 'Steps...' } ),\n} );\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From inside the admin SPA, use <code>apiFetch<\/code> from <code>spa\/lib\/api.ts<\/code> \u2014 it wires the<br>\nnonce and the root URL for you:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { apiFetch } from '@nirohelp\/lib\/api';\n\nconst stats = await apiFetch&lt; DashboardStats &gt;( { path: '\/dashboard\/stats' } );\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Extending it<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">New browser-to-server calls belong here, not on <code>admin-ajax<\/code>. Register the<br>\nroute in <code>Controllers\\Common\\API<\/code> through <code>Traits\\Rest::register_route()<\/code>, put<br>\nthe callback in <code>app\/API\/<\/code>, and give it a real <code>permission_callback<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A callback placed in <code>Controllers\/Admin\/<\/code> is never loaded during a REST<br>\nrequest \u2014 <code>Bootstrap\\Initializer<\/code> skips that whole group \u2014 so the route fails<br>\nwith no obvious cause.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One <code>wp_ajax_*<\/code> holdout remains, <code>Controllers\\Admin\\AJAX<\/code> for doc and topic<br>\nreordering. It is tracked for removal, not a precedent.<\/p>\n","protected":false},"author":1,"featured_media":0,"template":"","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}}},"nirohelp_product":[],"nirohelp_topic":[12],"class_list":["post-42","nirohelp_doc","type-nirohelp_doc","status-publish","hentry","nirohelp_topic-developers"],"_links":{"self":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc\/42","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc"}],"about":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/types\/nirohelp_doc"}],"author":[{"embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/users\/1"}],"wp:attachment":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/media?parent=42"}],"wp:term":[{"taxonomy":"nirohelp_product","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_product?post=42"},{"taxonomy":"nirohelp_topic","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_topic?post=42"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}