Skip to content

REST API Controllers Reference

WP Sell Services registers 23 REST controllers plus a set of generic utility routes. WP Sell Services Pro adds 10 more. Everything lives under one namespace:

/wp-json/wpss/v1/

Every route below is generated from the 1.4.0 source. Path parameters are shown as WordPress route regex ((?P<id>[\d]+)) so you can match them exactly.

For authentication, pagination, error shapes, and the generic endpoints, see REST API Overview.

POST/PUT/PATCH means the route is registered as EDITABLE, so WordPress accepts all three verbs on it.

Two namespaces – and only one of them is the API

Section titled “Two namespaces – and only one of them is the API”

There are two namespaces, and the split is not the one the names suggest.

Namespace What is on it
wpss/v1 Everything. All 23 free controllers and all 10 Pro controllers. Pro extends the API; it does not run a parallel one.
wpss-pro/v1 Exactly four cart-adapter routes, and only while the matching cart plugin is active.

The four wpss-pro/v1 routes are:

Method Route Present when
POST /wpss-pro/v1/surecart/sync-products SureCart is active
GET /wpss-pro/v1/surecart/orders SureCart is active
GET /wpss-pro/v1/fluentcart/products FluentCart is active
GET /wpss-pro/v1/fluentcart/orders FluentCart is active

Build every client against wpss/v1. Prefixing a Pro endpoint with wpss-pro/v1 returns rest_no_route on every single one.

Two things change which routes exist on a given site, so enumerate rather than assume:

  • The active e-commerce rail. /payments/* – in free and Pro – registers only when wpss_uses_standalone_payments() is true, i.e. no cart plugin has claimed payments. Activate WooCommerce, EDD, FluentCart or SureCart and those routes stop registering entirely; a call to them answers 404 rest_no_route from WordPress core. That is by design: when a cart plugin is enabled it owns all payment, and the plugin does not offer a second way in.
  • Pro + a valid license. The 10 Pro controllers register on wpss_loaded.

To see the truth for a site, read the index: GET /wp-json/wpss/v1.

Registered by the API bootstrap rather than a dedicated controller.

Method Route Purpose
GET / Namespace index – the WordPress-generated route list for wpss/v1. The authoritative answer to “what exists on this site”.
GET /categories Service categories
GET /tags Service tags
GET /settings Public marketplace settings
GET /me Current user summary
GET /dashboard Dashboard payload for the current user
GET /search Cross-entity search
POST /batch Batch several reads into one request
POST /tour/complete Mark the guided onboarding tour finished for the current user. Called by the bundled tour; persists per-user so the tour does not replay.
Method Route
GET, POST /services
GET /services/grid
GET, POST/PUT/PATCH, DELETE /services/(?P<id>[\d]+)
GET /services/(?P<id>[\d]+)/packages
GET /services/(?P<id>[\d]+)/faqs
GET /services/(?P<id>[\d]+)/reviews
GET, POST /services/(?P<id>[\d]+)/addons
POST/PUT/PATCH, DELETE /services/(?P<id>[\d]+)/addons/(?P<addon_id>[\d]+)

/services/grid returns the lighter payload used by the catalog grid. Prefer it over /services when you only need cards.

Method Route
GET /orders
GET, POST/PUT/PATCH /orders/(?P<id>[\d]+)
GET, POST /orders/(?P<id>[\d]+)/messages
GET, POST /orders/(?P<id>[\d]+)/deliverables
POST /orders/(?P<id>[\d]+)/(?P<action>…)
GET, POST /orders/(?P<id>[\d]+)/requirements
POST /orders/(?P<id>[\d]+)/requirements/skip
DELETE /orders/(?P<id>[\d]+)/requirements/files/(?P<file_id>[\d]+)
GET /orders/(?P<id>[\d]+)/sub-orders
GET /orders/(?P<id>[\d]+)/timeline
POST /orders/(?P<id>[\d]+)/pay

Order transitions all go through one action route rather than a verb per transition. (?P<action>…) accepts exactly:

start | deliver | complete | revision | cancel | dispute
hold | resume | accept-cancellation | reject-cancellation

Changed in 1.4.0: accept and reject were removed. They had no handler behind them and returned a misleading success on some paths, so a client built against them was never actually transitioning anything. There is no replacement – an order is accepted by being paid. deliver now routes through DeliveryService rather than writing the status directly, so a delivery made over REST produces the same records and notifications as one made in the dashboard.

Terminal window
curl -X POST https://yoursite.com/wp-json/wpss/v1/orders/42/deliver \
-H "X-WP-Nonce: $NONCE"

/sub-orders lists the milestone, tip, and extension sub-orders attached to a parent order – see Sub-Order Pattern.

GET /orders/{id}/timeline (new in 1.4.0) returns the merged, chronological event history for one order – status transitions, deliveries, revisions, milestone and extension events, and payment events – as a single list. It is what an order-detail screen renders instead of stitching four endpoints together.

POST /orders/{id}/pay and POST /milestones/{id}/pay do not take money. They resolve where the buyer must go to pay, and enforce the milestone lock-step guard while doing so.

POST /orders/{id}/pay – 200:

{
"success": true,
"order_id": 42,
"checkout_url": "https://yoursite.com/checkout/order-pay/1042/?key=wc_order_…",
"platform": "milestone"
}

platform is the sub-order type when the row is one (milestone, tip, extension) and an empty string for a normal order.

POST /milestones/{id}/pay – 200:

{
"success": true,
"milestone_id": 57,
"checkout_url": "https://yoursite.com/checkout/order-pay/1043/?key=wc_order_…"
}

Errors on both:

Status Code When
404 wpss_order_not_found / wpss_milestone_not_found No such row, or the row is not a milestone
409 wpss_order_not_payable / wpss_milestone_not_payable The row is not in pending_payment
409 wpss_milestone_locked An earlier phase is still open

checkout_url is a BROWSER url, not an API endpoint

Section titled “checkout_url is a BROWSER url, not an API endpoint”

This is the single most common way to get this wrong. checkout_url is a page for a human, resolved through the wpss_pay_order_url filter by whatever e-commerce rail is active. Do not fetch it, do not parse it, do not reconstruct it. A native client must open it in a webview (or the system browser) and watch for the return URL.

On the WooCommerce rail it is a WooCommerce order-pay page (/checkout/order-pay/{wc_id}/?key=…), rendered by WooCommerce with whatever gateways the store has enabled. There is no JSON behind it.

Side effect on Woo: generating this URL creates a real WooCommerce order. Pro’s WCPayOrderResolver creates (or reuses) an unpaid WC order for the amount owed so the link survives an email with no cart session. It is idempotent – the WC order id is stored on the WPSS row as wc_pay_order_id and reused while the order still needs payment – but calling /pay speculatively on a site with Woo active still puts a pending order in the store. Call it when the buyer is actually about to pay.

On the standalone rail it is …/checkout/?pay_order={id} and nothing is created.

EDD, FluentCart and SureCart have no pay-order rail at all. They do not hook the filter, so checkout_url falls back to the standalone ?pay_order=N URL, which those checkouts do not understand – the buyer lands on an empty cart. See WooCommerce Checkout for the support matrix.

Method Route
GET, POST /orders/(?P<order_id>[\d]+)/milestones
GET, DELETE /milestones/(?P<id>[\d]+)
POST /milestones/(?P<id>[\d]+)/pay
POST /milestones/(?P<id>[\d]+)/submit
POST /milestones/(?P<id>[\d]+)/approve
POST /milestones/(?P<id>[\d]+)/decline

The terminal actions are approve and decline (not “reject”) – the same vocabulary as the milestone hooks.

Method Route
GET, POST /orders/(?P<order_id>[\d]+)/extensions
POST /orders/(?P<order_id>[\d]+)/extension
POST /extensions/(?P<id>[\d]+)/decline
GET, POST /orders/(?P<order_id>[\d]+)/tip
GET /vendors/(?P<vendor_id>[\d]+)/tips
GET /vendors/(?P<vendor_id>[\d]+)/tips/total
Method Route
GET /vendors
GET /vendors/(?P<id>[\d]+)
GET, POST/PUT/PATCH /vendors/me
POST/PUT/PATCH /vendors/me/vacation
GET /vendors/(?P<id>[\d]+)/services
GET /vendors/(?P<id>[\d]+)/reviews
GET /vendors/(?P<id>[\d]+)/stats
POST /vendors/register
Method Route
GET /seller-levels
GET /seller-levels/(?P<level>[a-z_]+)
GET /vendors/me/level
GET /vendors/(?P<vendor_id>[\d]+)/level
Method Route
GET /vendors/(?P<vendor_id>[\d]+)/portfolio
GET, POST /portfolio
GET, POST/PUT/PATCH, DELETE /portfolio/(?P<id>[\d]+)
POST /portfolio/(?P<id>[\d]+)/featured
POST /portfolio/reorder
Method Route
GET, POST /reviews
GET, POST/PUT/PATCH, DELETE /reviews/(?P<id>[\d]+)
POST /orders/(?P<order_id>[\d]+)/review
POST /reviews/(?P<id>[\d]+)/reply
POST /reviews/(?P<id>[\d]+)/helpful
GET /services/(?P<service_id>[\d]+)/reviews/summary
GET /vendors/(?P<vendor_id>[\d]+)/reviews/summary
Method Route
GET, POST /buyer-requests
GET /buyer-requests/mine
GET, POST/PUT/PATCH, DELETE /buyer-requests/(?P<id>[\d]+)
GET, POST /buyer-requests/(?P<id>[\d]+)/proposals
POST /buyer-requests/(?P<id>[\d]+)/proposals/(?P<proposal_id>[\d]+)/accept
POST /buyer-requests/(?P<id>[\d]+)/proposals/(?P<proposal_id>[\d]+)/reject
GET, POST /proposals
GET, POST/PUT/PATCH /proposals/(?P<id>[\d]+)
POST /proposals/(?P<id>[\d]+)/withdraw
GET /proposals/stats
Method Route
GET /conversations
GET /conversations/(?P<id>[\d]+)
GET, POST /conversations/(?P<id>[\d]+)/messages
POST /conversations/(?P<id>[\d]+)/read
GET /conversations/unread-count
GET /orders/(?P<order_id>[\d]+)/conversation
POST /orders/(?P<order_id>[\d]+)/conversation/messages
Method Route
GET /disputes
GET /disputes/(?P<id>[\d]+)
GET, POST /orders/(?P<order_id>[\d]+)/dispute
POST /disputes/(?P<id>[\d]+)/respond
GET, POST /disputes/(?P<id>[\d]+)/evidence
GET /disputes/(?P<id>[\d]+)/timeline
POST /disputes/(?P<id>[\d]+)/escalate
POST /disputes/(?P<id>[\d]+)/cancel
POST /disputes/(?P<id>[\d]+)/resolve
POST /disputes/(?P<id>[\d]+)/assign
GET /disputes/options

resolve and assign require dispute-management capability. See Admin Mediation.

Method Route
GET /earnings/summary
GET /earnings/history
GET /wallet/transactions
GET, POST /withdrawals
POST/PUT/PATCH /withdrawals/(?P<id>[\d]+)
GET /withdrawals/methods
Method Route
GET /payments/methods
POST /payments/create-intent
POST /payments/confirm

These are the standalone checkout payment routes shipped in free. Pro replaces them with a wider, gateway-specific set – see Payments (Pro).

These routes do not exist on every site. As of 1.4.0 the whole controller is skipped unless wpss_uses_standalone_payments() is true (src/API/PaymentController.php). With WooCommerce, EDD, FluentCart or SureCart enabled, that rail owns all payment and these routes are never registered – a client calling them gets 404 rest_no_route. Do not treat that as an error to retry: check GET /wpss/v1 (or GET /settings) and send the buyer to the rail’s own checkout instead.

Switching rails never rewrites past orders – an order paid through a gateway keeps its record, and that gateway’s webhooks keep working.

Method Route
GET /cart
POST /cart/add
DELETE /cart/(?P<item_key>[a-z0-9]+)
POST /cart/checkout

Cart items are addressed by item_key, not by service id – one service can appear more than once with different packages and add-ons.

Authentication ships in the free plugin, not Pro.

Method Route
POST /auth/login
POST /auth/register
POST /auth/logout
GET /auth/me
POST /auth/forgot-password
POST /auth/change-password
GET, POST /auth/devices
DELETE /auth/devices/(?P<device_id>[a-zA-Z0-9_-]+)

/auth/devices registers a device for push notifications – what a mobile client calls after login.

Favorites, media, notifications, moderation, audit log

Section titled “Favorites, media, notifications, moderation, audit log”
Method Route
GET /favorites
POST, DELETE /favorites/(?P<service_id>[\d]+)
GET /services/(?P<service_id>[\d]+)/favorited
GET, POST /media
GET, DELETE /media/(?P<id>[\d]+)
GET /notifications
GET /notifications/unread-count
POST /notifications/(?P<id>[\d]+)/read
POST /notifications/read-all
DELETE /notifications/(?P<id>[\d]+)
GET /moderation/pending
GET /moderation/count
GET /moderation/(?P<service_id>[\d]+)
POST /moderation/(?P<service_id>[\d]+)/approve
POST /moderation/(?P<service_id>[\d]+)/reject
GET /audit-log
Method Route
POST /realtime/auth

Private-channel authorization for the realtime (WebSocket) layer. The plugin speaks the Pusher protocol, so this works with Pusher.com or any self-hosted Pusher-compatible server (e.g. Soketi). Client connection settings (key, host, cluster, port, TLS) are exposed under the realtime key of GET /settings; the app secret never leaves the server.

Authorize a private-channel subscription per the Pusher auth contract. Called automatically by the bundled wpss-realtime.js client (with the X-WP-Nonce header); external clients can call it with any supported authentication method.

Authentication Required: Yes (logged-in user)

Parameters:

  • socket_id (string, required) - Pusher socket ID of the connecting client (format 123.456)
  • channel_name (string, required) - Private channel to subscribe to

Allowed channels:

  • private-wpss-user-{ID} - Only the user themself
  • private-wpss-order-{ID} - The order’s customer, its vendor, or administrators

Response (200):

{
"auth": "app_key:hmac_sha256_signature"
}

Errors:

  • 401 rest_not_logged_in - Not authenticated
  • 403 wpss_realtime_forbidden - Channel not owned by the current user (any channel outside the two shapes above is also refused)
  • 404 wpss_realtime_disabled - Realtime is not enabled/configured on this site

Example:

Terminal window
curl -X POST \
https://yoursite.com/wp-json/wpss/v1/realtime/auth \
-u "username:xxxx xxxx xxxx xxxx" \
-d "socket_id=1234.5678" \
-d "channel_name=private-wpss-user-45"

Events published by the plugin:

  • notification.created on private-wpss-user-{ID} - payload { id, type }
  • message.created on private-wpss-order-{ID} and the recipient’s private-wpss-user-{ID} - payload { order_id, sender_id, message_id, excerpt }

Available only when WP Sell Services Pro is active with a valid license.

Method Route
GET /payments/methods
POST /payments/stripe/create-intent
POST /payments/stripe/confirm
POST /payments/paypal/create-order
POST /payments/paypal/capture
POST /payments/razorpay/create-order
POST /payments/razorpay/verify
POST /payments/offline/submit
GET /payments/(?P<order_id>[\d]+)/status
Method Route
GET /wallet/balance
GET /wallet/transactions
POST /wallet/withdraw
GET /wallet/withdrawals
GET /wallet/providers
Method Route
POST /stripe-connect/onboard
GET /stripe-connect/status
POST /stripe-connect/disconnect
GET /stripe-connect/accounts
GET /stripe-connect/accounts/(?P<vendor_id>[\d]+)
GET, POST/PUT/PATCH /stripe-connect/settings
Method Route
GET, POST /paypal-payouts/batches
GET /paypal-payouts/batches/(?P<id>[\d]+)
POST /paypal-payouts/batches/(?P<id>[\d]+)/sync
GET /paypal-payouts/pending
GET, POST/PUT/PATCH /paypal-payouts/profile

/pending is what an owner exports to pay vendors manually; sync reconciles a submitted batch back against the wallet ledger.

Method Route
GET, POST /commission-rules
GET /commission-rules/preview
GET, POST/PUT/PATCH, DELETE /commission-rules/(?P<id>[\d]+)

preview resolves which rule would apply to a given vendor/category/amount without persisting anything – use it to explain a rate in your own UI.

Method Route
GET, POST /subscription-plans
GET, POST/PUT/PATCH, DELETE /subscription-plans/(?P<id>[\d]+)
GET /subscription-plans/my-subscription
POST, DELETE /subscription-plans/subscribe
Method Route
GET, POST /recurring-services
GET /recurring-services/(?P<id>[\d]+)
GET /recurring-services/my-subscriptions
GET /recurring-services/vendor-subscriptions
POST /recurring-services/(?P<id>[\d]+)/cancel
POST /recurring-services/(?P<id>[\d]+)/pause
POST /recurring-services/(?P<id>[\d]+)/resume

Recurring services sit behind a default-off feature flag in 1.3.0. The routes register, but the feature’s UI is hidden until you opt in – see Recurring Services.

Method Route Scope
GET /analytics/overview Marketplace-wide – admin only
GET /analytics/revenue Marketplace-wide – admin only
GET /analytics/vendor/overview The calling vendor
GET /analytics/vendor/revenue The calling vendor
GET /analytics/vendor/orders The calling vendor
GET /analytics/vendor/services The calling vendor
POST /analytics/export Queue a data export

The two un-prefixed routes (/analytics/overview, /analytics/revenue) are the platform-owner numbers and were previously undocumented; the /vendor/* ones are scoped to whoever is calling. /analytics/export is POST, not GET – it starts an export rather than returning one.

/analytics/revenue takes period, one of 7days | 30days (default) | 90days | 12months.

Both admin routes register even on an unlicensed site, deliberately: an unlicensed call answers 403 wpss_pro_license_required rather than 404 rest_no_route, so a client can tell “you need a license” apart from “this build does not have that endpoint”. Anonymous callers get 401 rest_not_logged_in; a logged-in non-admin gets 403.

Method Route
POST /storage/upload
GET /storage/(?P<file_id>[\d]+)/url
DELETE /storage/(?P<file_id>[\d]+)
GET /storage/providers

/storage/{file_id}/url returns a time-limited signed URL. Do not cache it past its expiry.

Method Route
GET, POST/PUT/PATCH /white-label

Returns the active branding (name, logo, colors) so a headless or mobile client can render the same identity as the site.