Reference
REST API
Every endpoint of the Companion plugin's REST namespace — method, path, auth and purpose — plus the auth model, headers, error envelope, rate limits and the fields it adds to WooCommerce's Store API.
The Companion plugin serves its own REST API under one namespace, and adds fields to WooCommerce’s Store API and to core WordPress
routes. This page lists all 76 method and path pairs registered by the 24 controllers in includes/REST/, as the code on
develop (v0.4.3) registers them. Where the plugin’s older docs disagree, this page follows the code.
https://<your-wordpress-site>/wp-json/retail-store-companion/v1The namespace is spelled companion. The plugin’s PHP namespace, options and constants are spelled compaion; that is the existing
convention, not a typo here.
Auth model
Section titled “Auth model”| Label | Meaning |
|---|---|
| public | No authentication (permission_callback is __return_true) |
| JWT | Requires Authorization: Bearer <jwt>. A missing header is 401 auth_required; a bad, expired or revoked token is 401 token_invalid. |
| JWT-opt | Public, but a Bearer token is verified when sent. On review, contact and stock-notification writes an invalid token is a 401 (never silently a guest). On payment routes an invalid token is ignored and the caller is a guest. |
| JWT-or-guest | Reviews only: with a Bearer header it must be valid; without one the caller is a guest, refused with 401 login_required when WordPress requires registration to comment. |
| admin | current_user_can( 'manage_options' ): in practice a wp-admin cookie plus an X-WP-Nonce header. Refusals are rest_forbidden (401 for a guest, 403 for a user without the capability). |
| order key | The WooCommerce order key in the body, compared with hash_equals. |
| Cart-Token | The Store API Cart-Token header, validated with WooCommerce’s own cart-token utilities. |
| preview token | A one-time link token or a preview session token issued by the plugin. |
| Turnstile | A Cloudflare Turnstile check on the body field turnstile_token, enforced only when the global switch and both keys are set and the form’s own switch is on. |
| RL | Rate-limited; see Rate limits. |
Tokens
Section titled “Tokens”| Token | Format | Lifetime | Notes |
|---|---|---|---|
| Access token (JWT) | HS256, signed with RSC_JWT_SECRET |
15 minutes | Claims user_id, email, woo_customer_id, gen, iat, exp. Without the secret, token operations return 500 jwt_secret_missing. |
| Refresh token | Opaque <user_id>.<64 hex>; only its hash is stored |
30 days | Rotated on every refresh. Reusing a rotated token returns 401 refresh_token_reuse, revokes every session and emails a security alert. At most 20 sessions per user. |
| Email tokens | Opaque, single use, hash stored | Verify email 24 h, password reset 1 h, stock-alert verify 24 h | Invalid or expired: 400 invalid_token |
Revocation uses a per-user generation counter (bumped on logout-all, password change or reset, refresh-token reuse, and admin revokes) plus a per-token blacklist for a single logout. The same checks apply when the JWT is used on WooCommerce’s own routes.
Auth and tokens
Section titled “Auth and tokens”AuthController and TokenController.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /auth/register |
public | Create a customer. Password at least 8 characters with a letter and a digit; confirm_password must match; terms_accepted must be true. 409 email_exists. Sends the verification email unless verification is disabled. |
| GET | /auth/verify-email?token= |
public | Spend the verification token and mark the email verified. 400 invalid_token. |
| POST | /auth/resend-verification |
public, RL | Resend the verification email. Same answer whether or not the account exists. |
| POST | /auth/forgot-password |
public | Email a reset link. Always 200, so it cannot reveal accounts. |
| POST | /auth/reset-password |
public | Spend the reset token, set new_password, revoke every refresh token and JWT of the user. |
| POST | /auth/token |
public | Sign in with email and password. Returns jwt, refresh_token, user. 401 invalid_credentials, 403 email_not_verified. |
| POST | /auth/token/refresh |
public | Rotate the refresh token and issue a new JWT. 401 invalid_refresh_token, 401 refresh_token_reuse. |
| POST | /auth/token/validate |
JWT | Returns valid: true and user_id. |
| POST | /auth/logout |
JWT | Blacklist the current JWT and revoke the refresh_token in the body (required). |
| POST | /auth/social-login |
public, RL | Verify a Google or Facebook token (provider, access_token), then link, sign in or create an account. password is needed only to link Facebook to an existing account. |
| GET | /auth/me |
JWT | The current user. |
The user object is id, email, first_name, last_name, display_name, woo_customer_id, avatar_url, linked_providers
(drawn from credentials, google, facebook). Apple sign-in is not offered.
Social sign-in refusals: 401 social_token_invalid, 409 social_link_required (Facebook needs the account password),
401 social_link_bad_password, 403 social_login_not_allowed (staff accounts), 403 social_identity_mismatch.
Account
Section titled “Account”AccountController. Every route requires JWT.
| Method | Path | Purpose |
|---|---|---|
| POST | /account/logout-all-devices |
Revoke every refresh token and bump the JWT generation. |
| POST | /account/change-password |
Check current_password (400 incorrect_password), set new_password, revoke everything, then return a fresh jwt and refresh_token for this device. |
| POST | /account/set-password |
Add a first password to a social-only account (at least 8 characters, a letter and a digit). 400 password_already_set. |
| DELETE | /account/social/{provider} |
Unlink google or facebook. 404 not_linked; 400 last_sign_in_method when it is the only way left to sign in. |
| POST | /account/avatar |
Upload a profile picture: multipart field avatar, up to 2 MB, JPEG, PNG, WebP or GIF. |
| DELETE | /account/avatar |
Remove the uploaded picture (falls back to the social picture or Gravatar). |
| GET | /account/stats |
orders_count, total_spent (a major-unit decimal string), currency, minor_unit. 501 woocommerce_missing without WooCommerce. |
| GET | /account/compare |
The synced compare list; deleted products are dropped. |
| PUT (also POST, PATCH) | /account/compare |
Replace the list with product_ids; unknown, unpublished and duplicate ids are dropped. |
Address book
Section titled “Address book”AddressBookController. Registered only with WooCommerce active; every route requires JWT.
| Method | Path | Purpose |
|---|---|---|
| GET | /account/addresses |
The ordered book, seeded on first read: addresses plus limit (10). |
| POST | /account/addresses |
Add an entry. 201. |
| PUT | /account/addresses/{id} |
Replace an entry’s fields. |
| DELETE | /account/addresses/{id} |
Remove an entry; its default roles pass to another entry. |
| POST | /account/addresses/{id}/default |
Body role: billing or shipping. Make the entry that role’s default. |
Validation errors are 422 rs_address_invalid. Defaults are mirrored into WooCommerce’s billing_* and shipping_* fields.
Storefront settings and content
Section titled “Storefront settings and content”All public, all sent with Cache-Control: public, max-age=300, stale-while-revalidate=600.
| Method | Path | Controller | Purpose |
|---|---|---|---|
| GET | /storefront/settings |
StorefrontController |
The whole public storefront config: colors, css, css_dark, turnstile, social, compare, wishlist, product_page, reviews, stock_notifications, footer, header, general, promo_modal, mobile_tab_bar, contact, faq, scripts, pages, seo, timezone. Secrets are never included. |
| GET | /storefront/pages/{key}?lang= |
StorefrontController |
One Policy Page: title, html, excerpt, lang, modified, optional seo. key is returns, shipping, privacy, terms or cookies. 404 rsc_page_not_found when none is picked. |
| GET | /slider |
SliderController |
Home hero slider |
| GET | /shop-slider |
ShopSliderController |
Shop page slider |
| GET | /storefront/cta |
CtaController |
Home call-to-action banner |
| GET | /storefront/promos |
PromoController |
Promo tiles |
| GET | /storefront/sale-banner |
SaleBannerController |
Sale banner |
| GET | /storefront/deals |
DealsController |
Today’s Deals section config |
| GET | /storefront/testimonials |
TestimonialsController |
Curated testimonials |
| GET | /brands/facets?category= |
BrandFacetsController |
Brand directory facets: total, categories, letters. WooCommerce only. |
Catalog helpers
Section titled “Catalog helpers”| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /products/{id}/variations |
public | Display-safe price and stock per published variation. Prices are major-unit strings. 404 for an unpublished parent, [] for a non-variable product, 503 without WooCommerce. |
| POST | /preview/sessions |
preview token | Trade a one-time link token (12 hours, single use) for a 1-hour preview session. The product must be draft, pending, scheduled or private, and the user must be able to edit it. 201. |
| GET | /preview/products/{id} |
preview token in X-RSC-Preview-Token |
The unpublished product, its variations, scheduled_for and edit_url. Once published, only status and slug. |
Every preview refusal is preview_unavailable (403 or 404). Preview responses carry Cache-Control: no-store, private and
X-Robots-Tag: noindex.
Reviews
Section titled “Reviews”ReviewsController.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /products/{id}/reviews/summary |
public | product_id, average, count, and a 5-to-1 star breakdown |
| GET | /products/{id}/reviews |
public (JWT-opt) | Threaded list. Query page, per_page, rating, media (photo or video), orderby (recent, highest, lowest, helpful). A valid token adds the viewer’s own held replies. |
| POST | /products/{id}/reviews |
JWT-or-guest, Turnstile | Submit a review: rating 1 to 5, review, guest name and email, turnstile_token, media[] upload handles. Always held for moderation. 403 verification_required, 403 reviews_disabled. 201. |
| POST | /products/{id}/reviews/media |
JWT-or-guest, RL for guests | Upload one photo (JPEG, PNG, WebP) or video (MP4, QuickTime, WebM) before the review. Defaults: 6 files, 10 MB per image, 50 MB per video. 201 with a handle. |
| POST | /products/{id}/reviews/{review}/replies |
JWT-or-guest; Turnstile and RL for guests | A Shopper’s reply (body, parent, guest name and email). Always held. 201. |
| POST | /products/{id}/reviews/{review}/helpful |
JWT-or-guest | Toggle a helpful vote. |
| POST | /products/{id}/reviews/{review}/report |
JWT-or-guest, RL for guests | Flag a review, with an optional reason. |
| GET | /products/{id}/reviews/eligibility |
JWT | verified and ordered_at: whether the customer bought the product. |
Wishlist, newsletter, stock alerts, contact
Section titled “Wishlist, newsletter, stock alerts, contact”| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /wishlist |
JWT | success, count, items (product summaries, minor-unit prices) |
| POST | /wishlist/{product_id} |
JWT | Add a product or variation. 404 product_not_found, 409 already_in_wishlist. |
| DELETE | /wishlist/{product_id} |
JWT | Remove a product. |
| POST | /wishlist/merge |
JWT | Bulk-add a guest’s local list: product_ids, at most 100. |
| POST | /newsletter/subscribe |
public, RL | email and optional source. Stored locally, then sent through the Brevo form. 201 new, 200 already subscribed, 503 newsletter_unavailable. |
| POST | /stock-notifications |
public (JWT-opt); Turnstile and RL for guests | Back-in-stock sign-up: product_id, guest email, attributes, turnstile_token. A guest sign-up stays pending until confirmed. 503 stock_notifications_disabled. |
| GET | /stock-notifications |
JWT | The caller’s open sign-ups, with product cards. |
| DELETE | /stock-notifications/{id} |
JWT | Cancel one of the caller’s sign-ups. |
| GET | /stock-notifications/verify?token= |
public (email token) | Confirm a guest sign-up (link valid 24 hours). |
| GET | /stock-notifications/unsubscribe?token= |
public (email token) | One-click cancel from an email. |
| POST | /contact |
public (JWT-opt), Turnstile, RL | Deliver a contact message through the chosen Contact Form 7 form. Fields name, email (required), phone, comment, turnstile_token. 422 contact_invalid, 422 contact_rejected, 503 contact_unavailable. Registered only while Contact Form 7 is active. |
Wishlist routes return 503 woocommerce_unavailable without WooCommerce and 503 wishlist_unavailable without their tables.
Payment
Section titled “Payment”PaymentController. Payment responses are never cached.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /payment/config |
public | Public SDK values for each enabled gateway (Stripe, PayPal): gateway id, processor, public key, mode, currency, and Stripe’s optional express wallets. Cache-Control: no-store. |
| POST | /payment/stripe/verify |
order key | Body order_id, order_key. After a 3-D Secure challenge, has the Stripe plugin re-check the payment intent and answers status: paid, pending or failed. Any request the key does not open gets failed. |
| POST | /payment/paypal/order |
Cart-Token (JWT-opt), RL | Create the PayPal order for exactly the cart total: 201 with paypal_order_id, amount (minor units), currency. 401 rs_cart_token_invalid, 409 rs_cart_session_conflict, 409 rs_cart_empty, 503 rs_paypal_unavailable, 502 rs_paypal_order_failed, 429 rate_limited. |
| GET | /payment/placed-order |
Cart-Token (JWT-opt), RL | The order recorded for this cart when the Store API placed it: order is null or id, number, key, status, total, currency, settled. |
These are not routes, but they change WooCommerce’s POST wc/store/v1/checkout:
| Guard | Effect |
|---|---|
CheckoutProtection |
One checkout per cart at a time (409 rs_checkout_in_progress); 5 checkouts per minute per customer or Shopper address while the shared secret is set; decline reasons replaced by a generic message with the same code |
PayPalAmountGuard |
409 rs_payment_not_prepared or 409 rs_payment_amount_changed unless the PayPal order is the one prepared for this cart and matches the order total |
ExpressAmountGuard |
409 rs_payment_amount_changed unless payment_data carries rs_express_amount and rs_express_currency equal to the order’s |
StorefrontExpressRequest |
Honours the X-RS-Express-Checkout marker (with a Cart-Token) so the Stripe plugin’s wallet address repair runs |
HeadlessStripeChallenge |
Keeps the 3-D Secure challenge on the storefront page |
Every route requires admin (manage_options).
| Method | Path | Purpose |
|---|---|---|
| GET | /admin/settings |
Bootstrap payload for every settings tab. Secrets come back as "" with a has_value flag. |
| POST | /admin/settings |
Save the sections the body carries. The whole save is refused if the Turnstile secret equals the site key, the newsletter provider is not ready, or an image id is not a Media Library image. |
| GET | /admin/products/search?q= |
Product typeahead, up to 20 published products. |
| DELETE | /admin/sessions |
Sign every user out. |
| DELETE | /admin/sessions/{user_id} |
Sign one user out on every device. |
| DELETE | /admin/sessions/{user_id}/{hash} |
Revoke one refresh-token session. That session’s current JWT stays valid until it expires (up to 15 minutes). |
| GET | /admin/seo/sync |
The active SEO plugin and the last “Sync SEO” run. |
| POST | /admin/seo/sync |
Start a “Sync SEO” background run. 202. |
| GET | /admin/headers |
Headers, the main header, presets. |
| POST | /admin/headers |
Create a header: duplicate (from), from a preset (preset), import (document) or fresh (name). |
| GET | /admin/headers/{id} |
One header document (default or header_ plus an id). |
| PUT (also POST, PATCH) | /admin/headers/{id} |
Replace a header document. |
| DELETE | /admin/headers/{id} |
Delete a header. |
| POST | /admin/headers/{id}/set-main |
Make this the storefront’s main header. |
| GET | /admin/header-elements |
The header editor’s element palette and schema. |
Headers
Section titled “Headers”| Header | Direction | Used by |
|---|---|---|
Authorization: Bearer <jwt> |
request | Every JWT route, and the WooCommerce bridge |
X-WP-Nonce (or _wpnonce) |
request | Admin routes, and the bridge’s cookie carve-out |
Cart-Token |
request | /payment/paypal/order, /payment/placed-order, the Store API checkout guards, the express marker |
X-RS-Shopper-Address, X-RS-Shopper-Signature |
request | The signed Shopper address: <unix>.<hex HMAC-SHA256(secret, "<unix>.<address>")>, ±300 seconds. Unsigned, the connection address is used. |
X-RS-Express-Checkout: 1 |
request | Marks a storefront express request (only with a Cart-Token) |
X-RSC-Preview-Token |
request | /preview/products/{id} |
Cache-Control: public, max-age=300, stale-while-revalidate=600 |
response | Public settings and content reads |
Cache-Control: no-store |
response | Payment routes |
Cache-Control: no-store, private, X-Robots-Tag: noindex |
response | Preview routes |
Retry-After: 60 |
response | A 429 from the payment routes |
Turnstile is a body field, turnstile_token, not a header. Missing: 400 turnstile_required; rejected: 403 turnstile_failed;
Cloudflare unreachable or the secret wrong: 503 turnstile_unavailable.
Error envelope
Section titled “Error envelope”Errors use WordPress’s standard REST error shape, { "code": "…", "message": "…", "data": { "status": 401 } }, sometimes with
extra keys under data (such as fields on contact_invalid). Older plugin docs describe a success: false envelope; the code
does not send one.
{ "code": "token_invalid", "message": "…", "data": { "status": 401 }}Rate limits
Section titled “Rate limits”| Where | Limit | Tunable with |
|---|---|---|
/auth/resend-verification |
1 per email per 60 s | none |
/auth/social-login |
60 per minute per connection address (0 turns it off) | rsc_social_login_rate_limit |
| Facebook link password | 5 wrong tries per account per 15 min | none |
/newsletter/subscribe |
1 per email per 60 s, and 20 per minute per connection address | rsc_newsletter_ip_rate_limit |
/stock-notifications (guests) |
1 per email per 60 s | none |
/contact |
1 per email per 60 s, counted after a message is sent | none |
| Review media upload (guests) | 30 per hour per connection address | none |
| Review reply and report (guests) | 20 per hour per connection address | none |
/payment/paypal/order |
10 per minute per cart, 60 per minute per Shopper address | rsc_paypal_order_rate_limit, rsc_paypal_order_ip_rate_limit |
/payment/placed-order |
10 per minute per cart, 60 per minute per Shopper address | rsc_placed_order_rate_limit, rsc_placed_order_ip_rate_limit |
POST wc/store/v1/checkout |
5 per minute per customer or Shopper address, only while RSC_STOREFRONT_SHARED_SECRET is set |
rsc_checkout_rate_limit |
Store API extensions
Section titled “Store API extensions”The plugin adds fields to WooCommerce’s Store API (/wp-json/wc/store/v1) so the storefront needs no extra requests. Most are
top-level fields; only the free-shipping meter uses WooCommerce’s extension API.
| Store API route | Field or parameter added |
|---|---|
cart (every cart response) |
extensions.retail_store.free_shipping: threshold, remaining (minor-unit strings), qualified; or null |
order/{id} |
payment_method, payment_method_title, payment_method_description, bank_accounts (BACS only) |
products, products/{id} |
color on each pa_color term; sale_price_ends; upsell_ids, cross_sell_ids; videos; trust_badges; payment_security |
products/{id}, or products?slug= |
seo (only for a single product; omitted when nothing is set) |
products |
Query params rsc_sale_ending_before, rsc_orderby=sale_ending |
products/categories, products/tags, products/brands |
seo |
products/brands |
featured, primary_category; query params rsc_featured, rsc_category, rsc_letter |
products/categories |
trending_rank |
products/reviews |
media |
On core WordPress, wp/v2/posts gains a read-only rsc_seo field; product video, colour swatch and brand “featured” meta are
registered with show_in_rest for their edit screens. The JWT bridge also lets the Companion’s JWT authenticate wc/v3 and
wc/store requests, and hard-gates protected namespaces (default wc/v3), where WooCommerce consumer keys are rejected.