Working with an assistant guide
Build a WordPress Assistant Permission Matrix for One SEO Job
List the reads and writes one SEO job needs, give a dedicated user the smallest role that covers them, and test both allowed and denied calls on every route the password reaches.
On this page
Give an SEO assistant the smallest WordPress role that covers one named job, then prove it. Write down the reads and writes the job needs, create a dedicated user with the matching role, and run one allowed call and one denied call through every route the credential can reach, at minimum the plugin’s assistant tools and WordPress’s own REST API. The result is a one-page matrix that says what the assistant can do, what it cannot, and what it can do outside any review step.
The reason to test instead of assume: an Application Password is not a restricted key. WordPress describes it as a password tied to a specific WordPress user account, meant for API authentication, such as the REST API and, where enabled, XML-RPC, rather than browser logins (Application Passwords, checked September 27, 2026). The core team’s Application Passwords integration guide, checked September 27, 2026, lists scoping a password to limit its access as future work. Whatever the user’s role allows over the REST API, the password allows.
Define the reads and writes the job needs
Pick one job and name it precisely. The worked example here (illustrative) is: “Propose new meta descriptions for the service pages on garden.example.”
Then list every operation that job requires, and nothing else:
| Operation | Why the job needs it | WP Visibility 2.10.3 ability | Capability it checks |
|---|---|---|---|
| Read the page body | To draft from the page’s own text | serialize-content |
edit_post on that page |
| Read current SEO fields | To see the existing description | get-post-seo |
edit_post on that page |
| Propose a description | The job itself | update-post-seo |
edit_post on that page |
| Audit one page | Optional, to check length | audit-post |
edit_post on that page |
The job does not need settings, redirects, bulk updates, the site audit, the review queue, or the activity log. In 2.10.3 all of those check manage_options.
edit_post is a meta capability: WordPress resolves it per post into the primitive capabilities for that post type and state (map_meta_cap(), checked September 27, 2026). For pages, that means the page capabilities. WordPress’s Roles and Capabilities article, checked September 27, 2026, lists edit_pages and edit_others_pages under Editor and not under Author, and manage_options under Administrator.
Choose the smallest role that covers it
Read the job’s capability column against the default roles:
| Role | Can this example job run? | What else the role allows |
|---|---|---|
| Contributor | No: has no page capabilities | Write and edit its own unpublished posts |
| Author | No: has no page capabilities | Publish and edit its own posts, upload files |
| Editor | Yes | Publish and manage all posts and pages, including other users’ |
| Administrator | Yes | Everything on the site, including settings, plugins, and users |
Editor is the smallest default role for this job. If the job only touched blog posts written by the assistant’s own user, Author would do; an Author cannot edit other users’ posts. A custom role with a narrower set of capabilities is possible with a role management plugin (the same article lists several); test it the same way.
Also note what the role adds that the job does not need. An Editor can publish, edit, and delete any post through WordPress’s own editor and REST API. That authority comes with the role and does not pass through the plugin’s review queue.
Create the user, assign the role, and add an Application Password named after the client. Create the Application Password Your Assistant Signs In With has the steps.
Test the allowed operations
Run these against a staging copy of the site first. Replace the user, password, host, and IDs.
Confirm the user and role WordPress sees for this credential:
curl -s -u 'seo-agent:xxxx xxxx xxxx xxxx xxxx xxxx' \
'https://staging.garden.example/wp-json/wp/v2/users/me?context=edit'
The response’s roles and capabilities fields are the authority to record; the REST API users reference, checked September 27, 2026, documents both in the edit context.
Then, with the Agent module on (the endpoint does not exist while it is off), call one allowed read through the assistant tools:
curl -s -u 'seo-agent:xxxx xxxx xxxx xxxx xxxx xxxx' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get-post-seo","arguments":{"post_id":42}}}' \
https://staging.garden.example/wp-json/wp-visibility/mcp
Expect the page’s SEO fields. Then, with the Autopilot module on and Assistant changes left at Hold for my review (the default), ask the assistant, in the client, to propose one description, and confirm a proposal appears in WP Visibility → Review queue. With Autopilot off, or with that setting at Apply immediately, the same call writes the description directly.
Test the denials on every route
A permission matrix without denials is a guess. Pick calls the job must not be able to make and confirm each one fails.
Through the assistant tools, call an administrator ability:
curl -s -u 'seo-agent:xxxx xxxx xxxx xxxx xxxx xxxx' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get-settings","arguments":{}}}' \
https://staging.garden.example/wp-json/wp-visibility/mcp
An Editor should get a tool result marked "isError": true rather than settings, inside an HTTP 200 response. From the built-in endpoint its text starts with ability_invalid_permissions, the refusal WordPress core’s Abilities API returns when an ability’s permission check fails (WP_Ability source, checked September 27, 2026).
Through core REST, try a read that needs manage_options:
curl -s -o /dev/null -w '%{http_code}\n' -u 'seo-agent:xxxx xxxx xxxx xxxx xxxx xxxx' \
https://staging.garden.example/wp-json/wp/v2/settings
An Editor should get 403: the route checks manage_options (get_item_permissions_check(), checked September 27, 2026), and WordPress answers a signed-in user who fails a check with 403 (rest_authorization_required_code(), checked September 27, 2026). An Administrator gets 200, which is the point: the same password that holds proposals in the queue can read and change site settings directly.
Do not test destructive routes on a live site. On staging, if you want to see the authority outside the queue, create and then trash a throwaway draft through /wp-json/wp/v2/posts with the same credential. If that succeeds, the credential can change content without any proposal.
Document authority outside the proposal path
Record the results in a matrix like this one (illustrative results; fill in your own):
| Operation | Route | Expected | Observed | Held for review? |
|---|---|---|---|---|
| Read page SEO fields | Assistant tools | Allowed | No, reads are not held | |
| Propose a description | Assistant tools | Allowed | Yes, with Hold for my review | |
| Read plugin settings | Assistant tools | Denied | Not applicable | |
| Read site settings | Core REST /wp/v2/settings |
Denied | Not applicable | |
| Create or edit a post | Core REST /wp/v2/posts |
Allowed for Editor | No | |
| Publish a post | Core REST or editor | Allowed for Editor | No |
The “Held for review?” column is where most assumptions break. In WP Visibility 2.10.3, only seven of the plugin’s write abilities, update-post-seo among them, are held as proposals (others, such as create-draft, run directly), and only with the Agent and Autopilot modules on and Assistant changes set to Hold for my review. Core WordPress routes the user can reach are not held and are not recorded in the plugin’s activity log. WordPress’s Application Passwords section on the user profile shows usage metadata such as last used time and IP, per the Application Passwords documentation above, which helps confirm which credential was active.
Add three lines under the matrix: who owns the credential, when it should be revoked, and how to stop it quickly. Pause all agent access in the settings (or Pause agent on the Review queue) stops the plugin’s tools; revoking the password ends the connection everywhere. Stop the Assistant Now covers both. Editors cannot pause the assistant through its tools in 2.10.3, because pause-agent checks manage_options, so the pause is an administrator’s action in this setup.
Checklist
- One job named, with its operations and capabilities listed.
- Smallest role chosen, and the extra authority it grants written down.
- Dedicated user and a named Application Password.
users/me?context=editoutput saved.- At least one allowed and one denied call per route, run on staging.
- Matrix records which operations are held for review and which are not.
- Owner, revocation date, and stop procedure written under the matrix.
For the general case for scoping access, see How to Control an Assistant’s SEO Changes.
