Abilities API

WP 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. readonly and destructive are 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

Related