Skip to content
weLabsweLabsStorefront Playbook
Live demoQuickstart

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/v1

The namespace is spelled companion. The plugin’s PHP namespace, options and constants are spelled compaion; that is the existing convention, not a typo here.

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.
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.

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.

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.

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.

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.
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.

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.

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.
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.

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 }
}
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

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.

Storefront Playbook · Built by weLabsFeaturesFAQTalk to us