Skip to content
CartPops Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Store API and REST

The cartpops WooCommerce Store API namespace, the add-to-cart fragment, and the cartpops/v1 REST routes, with their authentication, rate limits and caching.

CartPops V2 talks to the server in three ways. WooCommerce’s Store API carries the cart itself, with CartPops data added under its own namespace. The classic add-to-cart response carries one extra fragment. A small set of cartpops/v1 REST routes covers the rest.

The cartpops Store API namespace

CartPops registers a cartpops extension namespace on two WooCommerce Store API schemas. You see it under extensions.cartpops in the response.

On the cart (GET /wc/store/v1/cart and the other cart responses)

PropType
drawer_config?object

Drawer settings: position, widths, animation, animation duration, border radius, dark mode and colors.

Typeobject
launcher?object

Launcher settings: enabled, position, show_count and show_total.

Typeobject
coupons?array

Coupons as CartPops presents them, with code, label, whether it is a system reward and whether it can be removed. Always set by CartPops.

Typearray
item_presentations?array

Up to 100 rows, one per cart line, holding the output of the cartpops_cart_item_data filter: key, name, short_description, variationSummary, extraLines and customPresentation, plus the line fields below. Always set by CartPops.

Typearray
optional_locked_keys?string[]

Cart item keys that CartPops locks against changes. Always set by CartPops. Empty in Free.

Typestring[]

Your own keys from cartpops_store_api_cart_extension_data appear here too. The coupons, item_presentations and optional_locked_keys keys are always restored by CartPops after the filters run.

On each cart item

PropType
key?string

The exact WooCommerce cart item key.

Typestring
product_id?integer

The parent product ID. For variable products the Store API item id is the variation ID.

Typeinteger
visible?boolean

Whether WooCommerce allows this line to show in a mini cart.

Typeboolean
quantityEditable?boolean

Whether the drawer offers quantity controls for this line.

Typeboolean
removable?boolean

Whether the drawer offers removal for this line.

Typeboolean
quantityLimits?object

minimum, maximum, multipleOf and unlimited, from WooCommerce's quantity rules.

Typeobject
backorderNotice?string

A plain text backorder notice, up to 500 bytes.

Typestring

This is how customPresentation reaches your own client code. Read extensions.cartpops.item_presentations from a Store API cart response, find the row whose key matches the cart item, and use its customPresentation.

async function readCustomPresentation( restRoot ) {
	const response = await fetch( `${ restRoot }wc/store/v1/cart`, {
		credentials: 'same-origin',
	} );
	const cart = await response.json();
	const rows = cart?.extensions?.cartpops?.item_presentations ?? [];

	return Object.fromEntries(
		rows.map( ( row ) => [ row.key, row.customPresentation?.my_plugin ] )
	);
}

Pass your site’s REST root for restRoot, for example the value of rest_url() from PHP. It ends with a slash.

The add-to-cart fragment

When WooCommerce answers a classic AJAX add to cart (?wc-ajax=add_to_cart) or a fragment refresh, CartPops adds one fragment to the response through the woocommerce_add_to_cart_fragments filter:

.cartpops-cart-json

Its value is a script element of type application/json with the class cartpops-cart-json. The element holds the current cart state as JSON. When the browser receives it through the added_to_cart event, CartPops reads the state from it and skips a second request.

  • CartPops adds this fragment only when it is enabled.
  • It is the only fragment CartPops adds. V1’s HTML fragments are gone.
  • The JSON shape is internal. Do not parse it, and do not rename or replace the key.
  • If another plugin has put a non-string value into the fragments array, CartPops leaves the fragments untouched.

cartpops/v1 REST routes

All routes live under /wp-json/cartpops/v1. They are used by the CartPops drawer and admin screens. Treat all of them as internal.

Authentication

The routes fall into three groups.

  • Cart session routes. These act on the shopper’s cart. They need a signed WooCommerce Cart-Token header. The token comes from a Store API response: read the Cart-Token header from a same-origin GET /wc/store/v1/cart and send it back. A missing or invalid token returns HTTP 403 with the code cartpops_invalid_cart_token. If the cart session cannot be started, the response is HTTP 503.
  • Logged-in shoppers. A logged-in customer must also send a valid X-WP-Nonce header (the wp_rest nonce). A guest must not send X-WP-Nonce at all. A guest request that includes one is rejected with HTTP 403 and the code cartpops_invalid_nonce.
  • Admin routes. These need a logged-in user who can manage_woocommerce, plus X-WP-Nonce.

One route needs no authentication: GET /bundle-builder/configs in Pro.

Routes

Route Edition Access Rate limit
GET /cart Free Cart session None
POST /cart/remove-items Free Cart session 30 per minute
POST /coupon Free Cart session 10 per minute
DELETE /coupon Free Cart session 20 per minute
GET /drawer-data Free Cart session None
GET /recommendations Free Cart session None
POST /recommendations/add Free Cart session 20 per minute
GET, POST, DELETE /settings Free Admin POST 30 per minute, DELETE 5 per hour
GET /settings/export Free Admin None
POST /settings/import Free Admin 5 per hour
GET /shipping-meter Pro Cart session None
GET /smart-addons Pro Cart session None
POST /smart-addons/toggle Pro Cart session 30 per minute
GET /bundle-builder/companions Pro Cart session None
GET /bundle-builder/configs Pro Public None
POST /bundle-builder/add Pro Cart session 20 per minute
POST /spotlight/add Pro Cart session 20 per minute
POST /analytics/events Pro Cart session 60 per minute
GET /analytics/summary Pro Admin None
DELETE /analytics/reset Pro Admin 5 per hour
GET /products/search Pro Admin None
GET /conditions/types Pro Admin None

The rate limits are the current values, not a promise.

A few request shapes, for orientation:

  • GET /cart returns the cart state CartPops renders: cartItems, cartCount, cartTotal, cartSubtotal, cartFees, cartTax, coupons and related fields.
  • POST /cart/remove-items takes { "keys": [ ... ] } with 1 to 100 unique cart item keys and returns the new cart state.
  • POST and DELETE /coupon take { "code": "..." }.
  • GET /drawer-data takes an optional fields query value, a comma separated list such as recommendations,shipping_meter.
  • POST /recommendations/add takes { "product_id": 123 } and adds only published, visible, purchasable, in-stock simple products.

Same-origin example

async function readCartPopsCart( restRoot ) {
	// Get a signed cart token from the Store API.
	const storeResponse = await fetch( `${ restRoot }wc/store/v1/cart`, {
		credentials: 'same-origin',
	} );
	const cartToken = storeResponse.headers.get( 'Cart-Token' );

	// Use it on the CartPops route. A guest sends no X-WP-Nonce header.
	const response = await fetch( `${ restRoot }cartpops/v1/cart`, {
		credentials: 'same-origin',
		headers: { 'Cart-Token': cartToken },
	} );

	return response.json();
}

A logged-in shopper must also add an X-WP-Nonce header with a valid wp_rest nonce to the second request.

Origin check

CartPops checks the Origin and Referer headers of every browser request to cartpops/v1. Only your home URL and site URL origins are allowed by default. Another origin gets HTTP 403 with the code cartpops_forbidden_origin. A request that sends neither header, such as one from a server, passes this check and still needs the authentication above. To allow another origin, use the cartpops_rest_allowed_origins filter.

Rate limiting

The limited routes count requests per client IP address and per visitor, in fixed windows. When a limit is used up, the response is HTTP 429 with the code cartpops_rate_limit_exhausted and the headers Retry-After, X-RateLimit-Limit and X-RateLimit-Remaining. Behind a CDN or proxy, every visitor can look like the same IP address. Set cartpops_rest_trusted_proxy_cidrs to fix that. If limiting cannot work, for example because of a bad proxy list, the response is HTTP 503 with the code cartpops_rate_limit_unavailable.

Caching

Admin, cart session and write routes send Cache-Control: no-store, no-cache, must-revalidate, private, Pragma: no-cache and Expires: 0, plus Vary: Cookie, Authorization, Origin. Do not cache them in a CDN. Only GET /bundle-builder/configs is treated as public data, and it varies on Origin.

Other protections

  • JSONP is rejected with HTTP 400.
  • HTTP method overrides are rejected with HTTP 400.
  • Each write route has a request body size limit, for example 8 KiB for coupon requests. A larger body gets HTTP 413.
  • A method that a route does not support gets HTTP 405.

Was this page helpful?