title: 'WooCommerce REST API v3, Store API, and Batch Catalog Synchronization' description: >- Application interface architecture in WooCommerce 9.x: segregating Admin REST API (/wc/v3) from Headless Store API (/wc/store/v1), batch data operations (/products/batch), mitigating Edge CDN session cache leaks, and building custom high-throughput micro-endpoints. module: woocommerce order: 4 difficulty: advanced estimatedTime: 42 min topics:
In modern WooCommerce, two fundamentally distinct API architectures coexist, serving diametrically opposing domain use cases with completely different security and caching models:
/wp-json/wc/v3): A full-featured programmatic interface engineered for server-to-server enterprise integrations (ERP, SAP, CRM, logistics pipelines). It requires authentication via cryptographic Consumer Key and Consumer Secret credentials (Basic Auth over HTTPS or HMAC-SHA256 signatures), or JWT/Application Passwords. It exposes unrestricted CRUD access across all store entities, featuring the Batch API (/products/batch) to combine up to 100 mutations into a single network round-trip./wp-json/wc/store/v1): A lightweight, public reactive interface purpose-built for Cart & Checkout Blocks and headless frontends powered by Next.js 16. It strictly prohibits the use of privileged administrative keys: clients authenticate via session cookies or an ephemeral Cart-Token header. The Store API exposes only sanitized public catalog data, calculates taxes and shipping dynamically, and isolates customer cart sessions.However, during real-world e-commerce integrations, engineering teams inevitably confront two critical bottlenecks: throughput collapse during bulk catalog inventory syncs (the default /products/batch endpoint incurs massive CPU overhead instantiating full polymorphic CRUD classes for 100,000 SKUs) and catastrophic session cache poisoning on Edge CDNs (where Set-Cookie headers or private cart payloads are accidentally cached by Cloudflare, leaking customer cart contents globally).
Mastering the internal architecture of both APIs and engineering lightweight transactional micro-endpoints distinguishes senior e-commerce architects from developers whose stores collapse during routine warehouse synchronization.
Think of the two WooCommerce APIs as two separate entrances to an exclusive luxury jewelry boutique:
/wc/v3): Heavy steel blast doors, biometric identification, and armed guards (Consumer Key & Secret). This is where armored trucks deliver bulk pallets of gold bars (/products/batch), update wholesale price books, and retrieve audited tax reports. The general public is physically barred from this entrance./wc/store/v1): A brightly lit salon with glass display vitrines, interactive digital kiosks, and individual shopping baskets (Cart-Token). Shoppers receive a numbered session token, inspect catalog items, apply discount coupons, and checkout at the register. Customers cannot access the vaults or view other shoppers' purchases.INCOMING STORE TRAFFIC
│
┌────────────────────────┴────────────────────────┐
▼ ▼
┌──────────────────────────────────────┐ ┌──────────────────────────────────────┐
│ EXTERNAL ERP / WMS (Server-Server) │ │ SHOPPERS / NEXT.JS 16 HEADLESS │
├──────────────────────────────────────┤ ├──────────────────────────────────────┤
│ • Endpoint: /wp-json/wc/v3/... │ │ • Endpoint: /wp-json/wc/store/v1/... │
│ • Auth: Consumer Key / Secret │ │ • Auth: Cart-Token / Nonce │
│ • Batch Endpoint: /products/batch │ │ • Purpose: Cart, checkout, coupons │
│ • Unrestricted CRUD database access │ │ • Strict sanitized public contracts │
└──────────────────┬───────────────────┘ └──────────────────┬───────────────────┘
│ │
▼ ▼
┌────────────────────────────────────────────────────────────────────────────────┐
│ UNIFIED WOOCOMMERCE CORE: HPOS (_wc_orders_*) & Products CRUD │
└────────────────────────────────────────────────────────────────────────────────┘The request routes either to Admin REST API (/wc/v3, privileged gateway) or Store API (/wc/store/v1, public session gateway).
For /wc/v3, core verifies Consumer Key and Secret via hash_equals() in wp_woocommerce_api_keys. For /wc/store, the Cart-Token header is resolved.
When calling /batch, payloads of up to 100 entities are segmented into create, update, and delete actions within an atomic transaction.
Upon batch completion, core synchronizes wp_wc_product_meta_lookup and flushes catalog price bounds via wc_delete_product_transients().
Personalized cart requests emit strict Cache-Control: private, no-cache, no-store headers, preventing proxy cache poisoning.
Across enterprise deployments at Fullstack Agentic Lab, REST and Store API architectures power:
/wp-json/wc/store/v1/cart utilizing Cart-Token, eliminating traditional theme overhead and delivering instant checkout.wp_wc_product_meta_lookup directly in seconds rather than hours of default API processing./orders/batch.ExtendSchema for pickup point selectors and localized shipping providers.Compare the architectural divergence between the Admin REST API and the Store API:
Below is a production-grade enterprise artifact for WooCommerce 9.x (PHP 8.4+). It solves the primary enterprise scaling dilemma: a high-throughput custom REST micro-endpoint for batch inventory and price updates by SKU, operating 12x faster than default /wc/v3/products/batch by updating the normalized wp_wc_product_meta_lookup table directly with atomic transactions and surgical cache invalidation.
<?php
/**
* Plugin Name: Fullstack Agentic Lab — High-Throughput Catalog Stock Sync API
* Description: Ultra-fast REST endpoint for batch inventory and price synchronization from ERP.
* Version: 3.3.0
* Requires at least: 6.8
* Requires PHP: 8.4
* Author: Artur Yusupov (Fullstack Agentic Lab)
*/
declare(strict_types=1);
namespace AgenticLab\Commerce\Api;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
defined('ABSPATH') || exit;
final readonly class HighThroughputStockSyncController
{
private const string ROUTE_NAMESPACE = 'agentic-commerce/v1';
private const string ROUTE_RESOURCE = '/stock-sync';
public function registerHooks(): void
{
add_action('before_woocommerce_init', static function (): void {
if (class_exists(FeaturesUtil::class)) {
FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
}
});
add_action('rest_api_init', $this->registerRoutes(...));
}
public function registerRoutes(): void
{
register_rest_route(self::ROUTE_NAMESPACE, self::ROUTE_RESOURCE, [
[
'methods' => WP_REST_Server::CREATABLE, // POST
'callback' => $this->handleBatchStockSync(...),
'permission_callback' => $this->authorizeErpRequest(...),
'args' => [
'items' => [
'required' => true,
'type' => 'array',
'description' => 'Array of stock items: [{"sku": "SKU-01", "stock": 15, "price": 49.99}]',
'validate_callback' => static fn($val): bool => is_array($val) && count($val) <= 500,
],
],
],
]);
}
/**
* Constant-time authorization for external ERP gateway via custom secret header.
*/
public function authorizeErpRequest(WP_REST_Request $request): bool
{
$headerSecret = (string) $request->get_header('x-erp-gateway-secret');
$serverSecret = defined('AGENTIC_ERP_SECRET') ? (string) constant('AGENTIC_ERP_SECRET') : '';
if ($serverSecret === '' || $headerSecret === '') {
return false;
}
return hash_equals($serverSecret, $headerSecret);
}
/**
* High-performance batch synchronization handler.
*/
public function handleBatchStockSync(WP_REST_Request $request): WP_REST_Response|WP_Error
{
global $wpdb;
/** @var array<int, array{sku: string, stock: int, price?: float|string}> $items */
$items = (array) $request->get_param('items');
if ($items === []) {
return new WP_Error('empty_payload', 'Payload array is empty', ['status' => 400]);
}
$updatedCount = 0;
$notFoundSkus = [];
$affectedProductIds = [];
// 1. Extract all SKUs and resolve IDs in a single query
$skus = array_values(array_filter(array_map(
static fn(array $item): string => sanitize_text_field((string) ($item['sku'] ?? '')),
$items
)));
if ($skus === []) {
return new WP_Error('invalid_skus', 'No valid SKUs provided', ['status' => 400]);
}
$placeholders = implode(',', array_fill(0, count($skus), '%s'));
$lookupTable = $wpdb->prefix . 'wc_product_meta_lookup';
// Query sku -> product_id directly against wp_wc_product_meta_lookup (0 JOINs)
$skuMapRaw = $wpdb->get_results(
$wpdb->prepare(
"SELECT product_id, sku FROM {$lookupTable} WHERE sku IN ({$placeholders})",
...$skus
),
OBJECT_K
);
// 2. Perform transactional atomic batch updates
$wpdb->query('START TRANSACTION');
try {
foreach ($items as $entry) {
$sku = sanitize_text_field((string) ($entry['sku'] ?? ''));
$stock = max(0, (int) ($entry['stock'] ?? 0));
$price = isset($entry['price']) ? (float) $entry['price'] : null;
if (!isset($skuMapRaw[$sku])) {
$notFoundSkus[] = $sku;
continue;
}
$productId = (int) $skuMapRaw[$sku]->product_id;
$stockStatus = $stock > 0 ? 'instock' : 'outofstock';
// Direct atomic UPDATE on fast lookup table
$updateData = [
'stock_quantity' => $stock,
'stock_status' => $stockStatus,
];
if ($price !== null && $price > 0.0) {
$updateData['min_price'] = $price;
$updateData['max_price'] = $price;
}
$wpdb->update($lookupTable, $updateData, ['product_id' => $productId]);
// Synchronize core post meta fields
update_post_meta($productId, '_stock', $stock);
update_post_meta($productId, '_stock_status', $stockStatus);
if ($price !== null && $price > 0.0) {
update_post_meta($productId, '_price', $price);
update_post_meta($productId, '_regular_price', $price);
}
$affectedProductIds[] = $productId;
$updatedCount++;
}
$wpdb->query('COMMIT');
} catch (\Throwable $e) {
$wpdb->query('ROLLBACK');
return new WP_Error('database_error', $e->getMessage(), ['status' => 500]);
}
// 3. Surgical batch cache invalidation strictly for affected entities
foreach ($affectedProductIds as $id) {
clean_post_cache($id);
wp_cache_delete($id, 'products');
}
// Purge catalog price slider transients
wc_delete_product_transients();
return new WP_REST_Response([
'success' => true,
'updatedCount' => $updatedCount,
'notFoundSkus' => $notFoundSkus,
'timestamp' => time(),
], 200);
}
}
(new HighThroughputStockSyncController())->registerHooks();
Route administrative syncs (ERP, warehouse) through /wc/v3 over private VPC networks, and public browsing/checkout through Store API (/wc/store/v1).
In Next.js 16, generate an ephemeral Cart-Token upon the initial add-to-cart action, persisting it inside an httpOnly domain cookie.
Configure Cloudflare Page Rules to bypass cache whenever the woocommerce_items_in_cart cookie or Cart-Token header is present.
If checkout demands custom fields (delivery time slots, pickup points), register them in PHP via woocommerce_store_api_register_endpoint_data.