REST API Controllers Reference
WP Sell Services registers 25 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/PATCHmeans the route is registered asEDITABLE, so WordPress accepts all three verbs on it.
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 25 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 |
|---|---|---|
| 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.
Route presence is conditional
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 whenwpss_uses_standalone_payments()is true, i.e. no cart plugin has claimed payments. Activate WooCommerce, EDD or FluentCart and those routes stop registering entirely; a call to them answers404 rest_no_routefrom 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.
Free controllers
Generic utility routes
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. |
Services
| Method | Route |
|---|---|
| GET, POST | /services |
| GET | /services/grid |
| GET | /services/limits |
| 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.
/services/limits returns the ceilings a single service may not exceed, so a
client can build its create-service form without discovering each one by being
rejected. Public read; no authentication needed.
{
"max_packages": 3,
"max_gallery": 4,
"max_videos": 1,
"max_extras": 3,
"max_faq": 5,
"max_requirements": 5
}
-1 means unlimited. The numbers above are the Free defaults; Pro raises
max_gallery, max_extras, max_faq and max_requirements to -1 and leaves
max_packages and max_videos where they are. Each value passes through its own
wpss_service_max_* filter, so a site can set its own ceilings - read the route
rather than hard-coding these numbers.
Orders
| 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]+)/receipts |
| 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:
acceptandrejectwere 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.delivernow routes throughDeliveryServicerather than writing the status directly, so a delivery made over REST produces the same records and notifications as one made in the dashboard.
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.
Paying an order or a phase
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_urlis a BROWSER url, not an API endpointThis is the single most common way to get this wrong.
checkout_urlis a page for a human, resolved through thewpss_pay_order_urlfilter 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
WCPayOrderResolvercreates (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 aswc_pay_order_idand reused while the order still needs payment -- but calling/payspeculatively 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 and FluentCart have no pay-order rail at all. They do not hook the filter, so
checkout_urlfalls back to the standalone?pay_order=NURL, which those checkouts do not understand -- the buyer lands on an empty cart. See WooCommerce Checkout for the support matrix.
Milestones
| 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 |
| POST | /milestones/(?P<id>[\d]+)/request-revision |
The terminal actions are approve and decline (not "reject") -- the same vocabulary as the milestone hooks.
request-revision is the buyer's third option on a submitted phase: it takes a
reason, returns the phase to revision_requested so the seller can resubmit,
and posts the reason into the parent contract's conversation. A phase has no
conversation of its own. Only the buyer may call it, and only while the phase
is pending_approval.
Extensions and tips
| 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 |
Vendors
| 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 |
Seller levels
| Method | Route |
|---|---|
| GET | /seller-levels |
| GET | /seller-levels/(?P<level>[a-z_]+) |
| GET | /vendors/me/level |
| GET | /vendors/(?P<vendor_id>[\d]+)/level |
Portfolio
| 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 |
Reviews
| 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 |
Buyer requests and proposals
| 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 |
Conversations
| 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 |
Disputes
| 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.
Earnings and withdrawals
| Method | Route |
|---|---|
| GET | /earnings/summary |
| GET | /earnings/history |
| GET | /wallet/transactions |
| GET, POST | /withdrawals |
| POST/PUT/PATCH | /withdrawals/(?P<id>[\d]+) |
| GET | /withdrawals/methods |
Payments (free) -- standalone rail only
| 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 FluentCart enabled, that rail owns all payment and these routes are never registered -- a client calling them gets404 rest_no_route. Do not treat that as an error to retry: checkGET /wpss/v1(orGET /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.
Cart
| 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
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_-]+) |
| GET, DELETE | /auth/sessions |
| DELETE | /auth/sessions/(?P<uuid>[a-zA-Z0-9\-]+) |
/auth/devices registers a device for push notifications -- what a mobile
client calls after login.
/auth/sessions is a different thing and deliberately a separate route: it
lists and revokes the app tokens a member holds. DELETE /auth/sessions ends
them all; the {uuid} form ends one. They are not on /auth/devices because
that route already means push tokens.
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 |
Reporting and blocking
Both ship in the free plugin. A store submitting a marketplace app needs them: App Store Guideline 1.2 asks for a way to report content and a way to block the person who posted it.
| Method | Route |
|---|---|
| GET, POST | /reports |
| POST | /reports/(?P<id>[\d]+)/resolve |
| GET | /blocks |
| POST, DELETE | /blocks/(?P<user_id>[\d]+) |
POST /reports files a report against any target type; the vocabulary of
target types is filterable, so the same controller serves services, orders,
vendors and messages. GET /reports is the owner's queue and is admin-only.
Reporting asks the owner to act. Blocking lets a member act immediately, which is the one that actually ends a bad interaction, so the two are separate routes rather than one moderation surface. Blocks are stored in user meta, and enforcement lives at the points where two members can reach each other rather than in the controller.
Realtime
| 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.
POST /realtime/auth
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 (format123.456)channel_name(string, required) - Private channel to subscribe to
Allowed channels:
private-wpss-user-{ID}- Only the user themselfprivate-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 authenticated403 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:
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.createdonprivate-wpss-user-{ID}- payload{ id, type }message.createdonprivate-wpss-order-{ID}and the recipient'sprivate-wpss-user-{ID}- payload{ order_id, sender_id, message_id, excerpt }
Pro controllers [PRO]
Available only when WP Sell Services Pro is active with a valid license.
Payments (Pro)
| 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 |
Wallet
| Method | Route |
|---|---|
| GET | /wallet/balance |
| GET | /wallet/transactions |
| POST | /wallet/withdraw |
| GET | /wallet/withdrawals |
| GET | /wallet/providers |
Stripe Connect
| 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 |
PayPal mass payouts
| 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.
Commission rules
| 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.
Vendor subscription plans
| 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 |
Recurring services
| 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.
Analytics
| 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.
Cloud storage
| 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.
White label
| 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.
Related Documentation
- REST API Overview - Authentication, error handling, pagination, CORS
- Hooks and Filters - Available action and filter hooks
- Custom Integrations - Building custom controllers
- Money Flow - How the payment routes settle