# Wc Store API

> Build against the WooCommerce Store API for shopper-facing products, cart, checkout, and headless carts, including route selection, nonce and cart-token handling, extension data, and payment requirements.

- Skill: `vikingokft/wc-store-api` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vikingokft/wc-store-api`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vikingokft/wc-store-api/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs, Coding & Dev Tools, API Design, REST & GraphQL APIs
- Tags: Cart, Cart Token, Checkout, Headless, Nonce, Php, Store Api, Woocommerce
- Author: vikingokft (https://skillmd.com/u/vikingokft)
- Updated: 2026-08-22
- Page: https://skillmd.com/skills/vikingokft/wc-store-api

---


# WooCommerce Store API

The Store API is WooCommerce's shopper-facing REST surface. It powers Cart and Checkout blocks and is the right API for product browsing, current-cart reads/writes, current-customer checkout data, and headless cart/checkout flows.

It is **not** the admin/integration REST API (`/wp-json/wc/v3` or `/wp-json/wc/v4`). Store API routes are public by design and return data for the current shopper/session only. If you need store settings, arbitrary orders/customers by ID, private product data, or back-office CRUD, use authenticated WC REST (`wc/v4`) or a custom WP REST route with explicit permissions.

## Misconception this skill corrects

> "Checkout is WooCommerce REST, so I will call `/wp-json/wc/v4/orders` from the browser."

That leaks the wrong model. WC REST is capability/consumer-key based and exposes admin-style resources. Store API is cart/session based, uses `Nonce` or `Cart-Token` for write protection, and is shaped for blocks/headless storefronts. Do not ship WC consumer keys to a browser to make checkout work.

## When to use this skill

Trigger when ANY of the following is true:

- Building or debugging Cart/Checkout Blocks behavior.
- Calling `/wp-json/wc/store/v1/...` from JS, a mobile client, or a headless frontend.
- Adding extension data under `extensions.<namespace>` to cart items, cart, checkout, or products.
- Mutating custom cart state from block checkout via `/cart/extensions` or `extensionCartUpdate`.
- Filtering payment-method availability for block checkout with Store API payment requirements.
- The diff contains `woocommerce_store_api_register_endpoint_data`, `woocommerce_store_api_register_update_callback`, `woocommerce_store_api_register_payment_requirements`, `Cart-Token`, `wc_store_api`, or `extensionCartUpdate`.

## Mental model

| Need | Use |
|---|---|
| Public products/product filters for storefront UI | Store API `/wc/store/v1/products` |
| Current shopper cart read/write | Store API `/wc/store/v1/cart...` |
| Checkout the current cart | Store API `/wc/store/v1/checkout` |
| Add data to existing Store API responses | `woocommerce_store_api_register_endpoint_data()` |
| Let block UI update plugin cart state | `/cart/extensions` via `woocommerce_store_api_register_update_callback()` |
| Admin/server CRUD for orders, products, refunds, fulfillments, settings | WC REST `wc/v4` |
| Plugin-specific private endpoint | Custom `register_rest_route()` with `permission_callback` |

All stable Store API resources are under `/wp-json/wc/store/v1`. Woo also registers `/wc/store` as an alias to the current v1 routes, but hardcode `/wc/store/v1` in clients so versioning is explicit.

## Route map

Source-verified in `src/StoreApi/RoutesController.php` and `src/StoreApi/Routes/V1/`:

| Area | Common routes |
|---|---|
| Cart | `GET /cart`, `POST /cart/add-item`, `POST /cart/update-item`, `POST /cart/remove-item`, `POST /cart/apply-coupon`, `POST /cart/remove-coupon`, `POST /cart/update-customer`, `POST /cart/select-shipping-rate`, `POST /cart/extensions` |
| Cart collections | `/cart/items`, `/cart/items/<key>`, `/cart/coupons`, `/cart/coupons/<code>` |
| Checkout | `GET /checkout`, `POST /checkout`, `PUT /checkout`, `POST /checkout/<id>` |
| Order | `GET /order/<id>` for the current shopper/order key flow, not arbitrary admin lookup |
| Products | `/products`, `/products/<id>`, `/products/<slug>`, `/products/collection-data` |
| Taxonomies | `/products/categories`, `/products/tags`, `/products/attributes`, `/products/attributes/<id>/terms`, `/products/brands` |
| Reviews/batch | `/products/reviews`, `/batch` |

Private routes under `/wc/private` and experimental/feature-gated agentic checkout routes are not plugin extension contracts. Do not build public plugin behavior on them.

## Nonce, Cart-Token, and sessions

Store API writes do **not** use `X-WP-Nonce` / `wp_rest`. They use a header named `Nonce` whose value is created with:

```php
wp_create_nonce( 'wc_store_api' );
```

Cart routes send fresh headers with each response:

- `Nonce`
- `Nonce-Timestamp`
- `Cart-Token`
- `Cart-Hash`
- `User-ID`
- `Cache-Control: no-store`

For update methods (`POST`, `PUT`, `PATCH`, `DELETE`), `AbstractCartRoute` requires `Nonce` unless a valid `Cart-Token` header is present. Missing nonce returns `woocommerce_rest_missing_nonce` (401); invalid nonce returns `woocommerce_rest_invalid_nonce` (403).

Headless flow:

```bash
curl -i https://store.example/wp-json/wc/store/v1/cart
# Save the Cart-Token response header.

curl -H "Cart-Token: $CART_TOKEN" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"id":123,"quantity":1}' \
  https://store.example/wp-json/wc/store/v1/cart/add-item
```

Same-site browser flow can use cookies + the `Nonce` header returned by the API. A `Cart-Token` is usually easier for headless clients because it avoids cookie affinity and also bypasses the nonce requirement for cart/checkout updates.

Store API CORS is stricter than the default WP REST behavior because cart/checkout responses can include shopper data. `Cart-Token` and `Nonce` are allowed request headers; only `Cart-Token` is exposed in CORS responses. A valid cart token can allow access from origins that would otherwise fail origin checks.

## Extending response data

Register extension data after `woocommerce_blocks_loaded`; the Store API container is not ready before then. Use the helper functions rather than instantiating `ExtendSchema` yourself.

Extensible endpoint identifiers:

| Identifier | Constant | `data_callback` args |
|---|---|---|
| `cart` | `CartSchema::IDENTIFIER` | none |
| `cart-item` | `CartItemSchema::IDENTIFIER` | `$cart_item` |
| `checkout` | `CheckoutSchema::IDENTIFIER` | none |
| `product` | `ProductSchema::IDENTIFIER` | `WC_Product $product` |

Example: add public product badge data under `extensions.myplugin`:

```php
use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema;

add_action( 'woocommerce_blocks_loaded', static function (): void {
    woocommerce_store_api_register_endpoint_data( array(
        'endpoint'        => ProductSchema::IDENTIFIER,
        'namespace'       => 'myplugin',
        'data_callback'   => static function ( WC_Product $product ): array {
            return array(
                'badge' => (string) $product->get_meta( '_myplugin_badge' ),
            );
        },
        'schema_callback' => static function (): array {
            return array(
                'badge' => array(
                    'description' => __( 'Short public badge text.', 'myplugin' ),
                    'type'        => array( 'string', 'null' ),
                    'readonly'    => true,
                ),
            );
        },
        'schema_type'     => ARRAY_A,
    ) );
} );
```

Rules for extension data:

- Namespace is required and should be your plugin slug.
- `data_callback` and `schema_callback` must return arrays. Returning anything else is logged and becomes empty data for non-admins.
- Data appears under the endpoint's `extensions` object, not as a top-level field.
- Do not mutate cart/order state from a response `data_callback`; keep it read-only.
- Do not expose secrets, private settings, arbitrary customer/order lookups, or admin-only product data. Store API is public.

## Updating cart state from blocks

Use `/cart/extensions` for plugin state that the shopper can change in cart/checkout UI: gift wrap, delivery instruction, insurance toggle, pickup choice, custom fee option, etc.

PHP registration:

```php
add_action( 'woocommerce_blocks_loaded', static function (): void {
    woocommerce_store_api_register_update_callback( array(
        'namespace' => 'myplugin',
        'callback'  => static function ( array $data ): void {
            $gift_wrap = ! empty( $data['gift_wrap'] );
            WC()->session->set( 'myplugin_gift_wrap', $gift_wrap );
        },
    ) );
} );
```

JS call from a checkout/cart block extension:

```js
import { extensionCartUpdate } from '@woocommerce/blocks-checkout';

await extensionCartUpdate( {
    namespace: 'myplugin',
    data: { gift_wrap: true },
    overwriteDirtyCustomerData: {
        shipping_address: false,
        billing_address: false,
    },
} );
```

`/cart/extensions` loads the cart, runs your callback with `data`, recalculates totals, and returns the updated cart response. In WC 10.8, `overwriteDirtyCustomerData` can be a boolean or an object with `shipping_address` / `billing_address` booleans, so extensions can avoid overwriting the shopper's unsaved address edits independently.

## Payment requirements

Payment requirements are extra cart-wide support flags. Store API merges your returned strings with the default requirement `products` and compares them against each gateway's `$supports` array.

```php
add_action( 'woocommerce_blocks_loaded', static function (): void {
    woocommerce_store_api_register_payment_requirements( array(
        'data_callback' => static function (): array {
            if ( myplugin_cart_requires_saved_card() ) {
                return array( 'tokenization' );
            }
            return array();
        },
    ) );
} );
```

This filters which gateways are valid for the current Store API cart. It does not register gateway UI. Checkout Block payment UI is still a JS payment-method integration; the PHP gateway class still owns `process_payment()`.

## Validation and quantity hooks

For Store API add-to-cart validation, prefer:

```php
add_action( 'woocommerce_store_api_validate_add_to_cart', static function ( WC_Product $product, array $request ): void {
    if ( myplugin_product_is_locked( $product ) ) {
        throw new Exception( __( 'This product cannot be added to the cart.', 'myplugin' ) );
    }
}, 10, 2 );
```

Quantity constraints shown in Store API cart/item schemas come from `QuantityLimits`. Filter:

```php
woocommerce_store_api_product_quantity_minimum
woocommerce_store_api_product_quantity_maximum
woocommerce_store_api_product_quantity_multiple_of
woocommerce_store_api_product_quantity_editable
```

Each receives the value, `WC_Product $product`, and optional `$cart_item`.

## Product endpoint notes for 10.8

`GET /wc/store/v1/products` is public and cache-sensitive. WC 10.8 fixed transient bloat caused by arbitrary product IDs in the `related` query parameter. In 10.8 source, `related` is an integer product ID, sanitized with `absint`, and invalid/non-visible products throw `woocommerce_rest_product_not_found`.

Rules:

- Pass a single product ID to `related`, not arrays or comma lists.
- Do not use Store API products as a private catalog endpoint. Visibility/readability checks still matter.
- Use WC REST `wc/v4/products` with proper auth when you need admin/private product fields.

## Critical rules

- **Store API is `wc/store/v1`, not `wc/v4`.** Use Store API for shopper cart/checkout; use WC REST for admin/integration CRUD.
- **Never ship WC consumer keys or secret gateway keys to a public client.**
- **Use header `Nonce`, action `wc_store_api`; not `X-WP-Nonce`, action `wp_rest`.**
- **Use `Cart-Token` for headless cart continuity.** Get it from `GET /cart`, send it back as a request header.
- **Register Store API extension callbacks on `woocommerce_blocks_loaded`.**
- **Keep extension data public and read-only.** Use `/cart/extensions` for mutations.
- **Return arrays from Store API callbacks.** Non-array return values are logged and dropped.
- **Namespace extension data.** Do not write top-level response fields or generic namespaces like `custom`.
- **Do not subclass internal Store API route classes for plugin endpoints.** Use `register_rest_route()` for your own routes; use Store API helpers only where WC exposes extension points.
- **Do not disable nonce checks outside local/dev testing.** `woocommerce_store_api_disable_nonce_check` is a development escape hatch, not production configuration.
- **Use Store API rate-limit hooks for public write-heavy Store API flows.** `woocommerce_store_api_rate_limit_options`, `woocommerce_store_api_rate_limit_id`, and `woocommerce_store_api_rate_limit_exceeded` affect Store API requests; unrelated custom REST routes need their own limits.

## Cross-references

- Run **`wc-rest-api-v4`** when the task is admin/integration REST, fulfillments, settings, private product/order data, or server-to-server API clients.
- Run **`wc-customer-and-sessions`** when code uses `WC()->session`, `WC()->customer`, or needs to understand frontend/REST session bootstrapping.
- Run **`wc-payment-gateway`** when Store API payment requirements intersect with a gateway's `$supports` and `process_payment()`.
- Run **`wc-hpos-compatibility`** when checkout/order code stores custom order meta.
- Run **`wp-rest-api`** when creating custom plugin endpoints outside Store API.

## What this skill does NOT cover

- Full React payment-method UI registration (`registerPaymentMethod`, express methods, saved-token components). This skill covers Store API server-side availability and data extension points.
- Admin REST `wc/v4` route details. Use `wc-rest-api-v4`.
- Store API internals/private route implementation. Internal classes are useful source references, not a public inheritance contract.
- GraphQL or the newer dual-code API experiments in WC 10.8.

## References

- Official Store API overview: <https://developer.woocommerce.com/docs/apis/store-api/>.
- Store API nonce tokens: <https://developer.woocommerce.com/docs/apis/store-api/nonce-tokens/>.
- Store API cart tokens: <https://developer.woocommerce.com/docs/apis/store-api/cart-tokens/>.
- Extending Store API data: <https://developer.woocommerce.com/docs/apis/store-api/extending-store-api/extend-store-api-add-data/>.
- Route registration: [wp-content/plugins/woocommerce/src/StoreApi/RoutesController.php](RoutesController.php).
- Cart route headers/session/nonce rules: [wp-content/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractCartRoute.php](AbstractCartRoute.php).
- Store API authentication/CORS/rate limit logic: [wp-content/plugins/woocommerce/src/StoreApi/Authentication.php](Authentication.php).
- Extension helpers: [wp-content/plugins/woocommerce/src/StoreApi/functions.php](functions.php) and [wp-content/plugins/woocommerce/src/StoreApi/Schemas/ExtendSchema.php](ExtendSchema.php).
- `/cart/extensions`: [wp-content/plugins/woocommerce/src/StoreApi/Routes/V1/CartExtensions.php](CartExtensions.php) and [wp-content/plugins/woocommerce/src/StoreApi/Schemas/V1/CartExtensionsSchema.php](CartExtensionsSchema.php).
- Product `related` query handling: [wp-content/plugins/woocommerce/src/StoreApi/Routes/V1/Products.php](Products.php) and [wp-content/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php](ProductQuery.php).

