Abilities API
Developer GuideWP Sell Services registers 12 abilities through the WordPress Abilities API, and Pro adds 3 more. They give AI agents, MCP clients, and any Abilities-aware tool a described, permission-checked way to work with your marketplace -- browse services, place and manage orders, message, review, and read earnings -- without that client needing to learn the REST surface.
Every ability is registered with an input schema, an output schema, an execute callback, and its own permission check.
They are a facade over the REST API, not a second implementation. Each
execute callback builds a WP_REST_Request against the plugin's own
wpss/v1 routes and dispatches it internally. That has three consequences worth
knowing:
- Behaviour cannot drift between an ability and its REST route -- there is one code path.
- The route's own permission callback still runs, on top of the ability's. An ability whose permission check passes can still be refused by the endpoint.
- Any filter you have added to a REST route applies to the ability too, for free.
Requirements#
The Abilities API is part of WordPress 6.9 and later. WP Sell Services
supports WordPress 6.4+, so on 6.4-6.8 the registrar detects that
wp_register_ability() is absent and registers nothing. No error, no notice:
the marketplace works exactly as before, and the abilities simply are not there.
To check on a given site:
wp eval 'var_dump( function_exists( "wp_register_ability" ) );'
wp eval 'print_r( array_keys( wp_get_abilities() ) );'
The category#
All abilities live under one category:
| Slug | Label |
|---|---|
wpss-marketplace |
Service Marketplace |
Registered on wp_abilities_api_categories_init; the abilities themselves on
wp_abilities_api_init.
Free abilities#
All are exposed to REST (show_in_rest) and none are marked destructive.
| Ability | Permission | Read-only | Input |
|---|---|---|---|
wpss/browse-services |
Public | Yes | search, category, min_price, max_price, sort_by, page, per_page |
wpss/view-service |
Public | Yes | id |
wpss/create-service |
wpss_manage_services |
No | title, description, category, packages[] |
wpss/manage-orders |
Logged in | No | action, order_id, status, page, per_page |
wpss/send-message |
Logged in | No | order_id, message |
wpss/view-earnings |
wpss_manage_services |
Yes | period |
wpss/request-withdrawal |
wpss_manage_services |
No | amount, method |
wpss/post-buyer-request |
Logged in | No | title, description, budget, category, deadline |
wpss/submit-proposal |
wpss_respond_to_requests |
No | request_id, price, delivery_days, cover_letter |
wpss/leave-review |
Logged in | No | order_id, rating, comment |
wpss/view-notifications |
Logged in | Yes | unread_only, page, per_page |
wpss/manage-favorites |
Logged in | No | action, service_id |
wpss/browse-services and wpss/view-service are the only two with a permission
callback that returns true unconditionally -- they expose the same public
catalog a visitor can already see. Everything else requires at least a logged-in
user.
wpss/manage-orders and wpss/manage-favorites take an action enum rather
than splitting into one ability per verb, which keeps the surface small for
clients enumerating what they can do.
Pro abilities [PRO]#
Registered only when Pro is active with a valid license -- an expired license means Pro loads nothing, so these disappear along with the rest of Pro.
| Ability | Permission | Read-only | Input |
|---|---|---|---|
wpss/analytics-overview |
manage_options or wpss_view_analytics |
Yes | period, metrics[] |
wpss/manage-vendor-subscriptions |
manage_options |
Yes | action, plan_id, vendor_id |
wpss/configure-stripe-connect |
manage_options |
Yes | action |
Consuming them#
REST#
Abilities are discoverable and runnable under the core namespace:
/wp-json/wp-abilities/v1/abilities
/wp-json/wp-abilities/v1/abilities/wpss/browse-services
Note this is wp-abilities/v1, WordPress's namespace -- not the plugin's
wpss/v1. The two are separate surfaces over the same services.
A client sees only the abilities whose permission callback passes for the authenticated user, so an anonymous request lists the two public ones and a vendor's token lists considerably more.
JavaScript#
Use @wordpress/abilities rather than hand-rolling fetches, so permission
filtering and schema validation come for free:
import { store as abilitiesStore } from '@wordpress/abilities';
Choosing abilities or REST#
They are complementary, not alternatives:
- Abilities are self-describing. A client can enumerate what exists, read the input schema, and call it without prior knowledge of your API. That is what makes them useful to an AI agent.
- REST is finer-grained and complete. Every route in REST API Controllers is available; abilities cover the twelve highest-value marketplace actions.
Build a normal integration on REST. Reach for abilities when the caller is an agent, an MCP server, or anything that has to discover capability at runtime.
Adding your own#
Register on the same hook and reuse the existing category so your ability appears alongside the marketplace's:
add_action( 'wp_abilities_api_init', function () {
if ( ! function_exists( 'wp_register_ability' ) ) {
return; // WordPress < 6.9.
}
wp_register_ability( 'my-plugin/export-orders', array(
'label' => __( 'Export Orders', 'my-plugin' ),
'description' => __( 'Export marketplace orders as CSV for a date range.', 'my-plugin' ),
'category' => 'wpss-marketplace',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'from' => array( 'type' => 'string', 'format' => 'date' ),
'to' => array( 'type' => 'string', 'format' => 'date' ),
),
),
'output_schema' => array( 'type' => 'object' ),
'execute_callback' => 'my_plugin_export_orders',
'permission_callback' => static function (): bool {
return current_user_can( 'wpss_manage_orders' );
},
'meta' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
),
'show_in_rest' => true,
),
) );
} );
Two things to get right:
- Gate on a real capability. Your permission callback is the only thing standing between an agent and the action. Reuse the plugin's capabilities -- see Capabilities & Roles.
- Annotate honestly.
readonlyanddestructiveare how a client decides whether to confirm with a human first. Marking a write as read-only invites an agent to call it unattended.
Troubleshooting#
| Symptom | Cause |
|---|---|
No wpss/* abilities at all |
WordPress below 6.9, so registration self-skips |
| Pro abilities missing | Pro inactive or its license invalid |
| Ability listed but a client cannot call it | Either the ability's permission callback, or the underlying REST route's, returned false for that user |
| Yours does not appear | Registered on the wrong hook, or show_in_rest not set |
| Changes not reflected | Object cache -- flush and retry |