Skip to content

WooCommerce REST API

woocommerce-rest-api

Call the WooCommerce REST API at wc/v3, including authentication, pagination, batch writes, and webhook registration, and keep it separate from the Store API. Use when an external system integrates with a WooCommerce store over HTTP.

Version 0.3.2  ·  MIT  ·  Source  ·  Rule

Install this skill

Copy the skill and its rule into your project:

npx @sadrmanesh/commerce-agent-skills add woocommerce-rest-api

The skill goes to .agents/skills/ and the rule to .agents/rules/. See Command line for other directories.

Use this skill for a server-side client of a WooCommerce store. The browser checkout uses a different API. Payment notification handling belongs to the webhook reliability skill.

Which API

API Namespace Who calls it
WooCommerce REST API /wp-json/wc/v3/ Your server, with a consumer key and secret.
Store API /wp-json/wc/store/v1/ The storefront, with a cart token and nonce.
Legacy REST API /wc-api/v3/ Old clients only. Do not start new work here.

The current REST API requires WooCommerce 3.5+, WordPress 4.4+, and pretty permalinks. Default permalinks do not serve these routes.

Never put a consumer secret in browser JavaScript, a mobile binary, or a Store API request.

Authentication

Create a key at WooCommerce > Settings > Advanced > REST API. The key inherits the WordPress user you bind it to, and the permission you select: Read, Write, or Read/Write. Deleting that user disables the key. The consumer secret is shown once.

Over HTTPS, send HTTP Basic authentication with the consumer key as the username and the consumer secret as the password.

curl --user "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET" \
  "https://shop.example/wp-json/wc/v3/orders?per_page=20"

Some FastCGI hosts drop the Authorization header and WooCommerce then reports that the consumer key is missing. Only then, and only over HTTPS, send consumer_key and consumer_secret as query parameters. Query secrets land in access logs and caches. Prefer fixing the host header.

Over plain HTTP, WooCommerce expects one-legged OAuth 1.0a. For a new integration, use HTTPS instead of adding an OAuth client.

Store the secret in the server environment or a secret manager. Do not commit it.

Reads

  • Follow pages with page and per_page. per_page above 100 is rejected. Read X-WP-Total and X-WP-TotalPages.
  • Request only the fields you need when the endpoint supports _fields.
  • Compare instants with date_created_gmt and date_modified_gmt. The unsuffixed fields are in the site timezone.
  • Product variations are a child resource: /wp-json/wc/v3/products/<id>/variations.
  • Error bodies use the WordPress shape { "code", "message", "data": { "status" } }. Branch on code and HTTP status, not on the message string.

Endpoint groups and the batch body are in the endpoint notes.

Writes

The REST API does not provide a general idempotency key. A retried POST /orders can create a second order.

  1. Put your own key in meta, for example meta_data: [{ "key": "_idempotency_key", "value": "<key>" }].
  2. Before creating, search orders by that meta. Treat a hit as the original result.
  3. Meta search is not a unique index. Two concurrent creates can still both miss. For payments, dedupe on the provider transaction id as well. See the order and webhook skill.

Batch routes (POST /products/batch, /orders/batch, and the customer and coupon equivalents) accept create, update, and delete. The batch is not one database transaction. Read each item result. A 200 response can still contain per-item errors.

Update orders and products with the resource id. Do not match a later update on SKU alone unless you have already resolved the SKU to one id. SKUs can change.

Webhooks registered over REST

POST /wp-json/wc/v3/webhooks with name, topic, delivery_url, and secret. The secret is write-only on later reads. If you omit it, WooCommerce falls back to the API user's consumer secret. Set an explicit secret.

The first save of an active webhook delivers a ping, not a full order or product. Acknowledge that ping with a 2xx response and do not create an order from it.

Topic names look like order.updated and product.created. The delivered resource body matches the REST representation. Verify X-WC-Webhook-Signature against the raw body before trusting it. The signature procedure is in the order and webhook skill.

Gotchas

  • A Read key cannot create products. A Write key cannot list orders. Check the key permission and the user's capability when you get 401 or 403.
  • Order status names in the API omit the wc- prefix (processing, not wc-processing).
  • Prices are strings in major currency units, such as "19.00". Do not send a float.
  • HPOS does not change the REST shape. Clients should keep using this API rather than reading the database.
  • Webhook delivery that returns 301 or 302 can be counted as success while WC_Webhook::deliver() sends the body with redirects disabled. Serve the JSON handler on the exact delivery_url.

Verification

  • A request with the wrong secret returns 401 and creates nothing.
  • Listing orders walks every X-WP-TotalPages page without duplicating or skipping ids.
  • Repeating the same create with the same idempotency key returns one order.
  • A webhook ping does not create an order. A signed order.updated delivery updates the matching local record.

Evaluation

Worked examples and the checks they must satisfy are in evals/tasks.json.

References

WooCommerce REST API endpoints

Base path: /wp-json/wc/v3/. All routes below are relative to that base. Authenticate every call as described in SKILL.md.

Resources

Path Typical use
products Create and update the parent product.
products/<id>/variations Create and update purchasable variants.
products/attributes Global attributes. Taxonomy name is pa_ plus the slug.
products/categories Product categories.
products/<id>/reviews Product reviews.
orders Order header, line items, shipping lines, fee lines, and coupons.
orders/<id>/notes Customer or private notes. customer_note: true emails the customer.
orders/<id>/refunds Create a refund. This is the REST equivalent of wc_create_refund().
customers WordPress users who are customers. Guest orders are not customers until an account exists.
coupons Coupon definitions. Applying a coupon still happens in checkout or by editing the order.
webhooks Register, pause, or delete a webhook. secret is write-only.
taxes, shipping/zones, payment_gateways, settings Store configuration. Prefer the admin UI unless you are provisioning stores.
system_status Environment diagnostic. Do not require it on the hot path.
reports/sales Aggregates. For large extracts, page through orders instead of assuming one report call is complete.

<id> is the integer resource id.

Batch

POST products/batch, orders/batch, customers/batch, and coupons/batch.

{
  "create": [],
  "update": [{ "id": 10, "status": "draft" }],
  "delete": [11]
}

Inspect create, update, and delete in the response. An item error does not roll back its siblings.

Pagination and filters

  • page starts at 1. per_page defaults to 10 and cannot exceed 100.
  • Order list filters include status, customer, product, after, before, modified_after, and modified_before. Pass the date filters as ISO-8601 timestamps.
  • Use modified_after for an incremental sync. A record can change while you are paging. Keep an overlap and dedupe by id plus date_modified_gmt. If the installed WooCommerce rejects modified_after, fall back to after and before.

Status values

Order statuses used by core: pending, processing, on-hold, completed, cancelled, refunded, failed, and checkout-draft. Custom statuses registered with woocommerce_register_shop_order_post_statuses also appear. Do not send the wc- internal prefix.

Product status uses WordPress post statuses such as publish, draft, and private. Catalog visibility is catalog_visibility (visible, catalog, search, hidden), which is not the same field.

Webhook topics

Common topics: order.created, order.updated, order.deleted, product.created, product.updated, product.deleted, customer.created, customer.updated, coupon.created, coupon.updated. Action topics bind a webhook to a WooCommerce action name. Prefer a resource topic unless you need a specific action.

Headers on delivery:

  • X-WC-Webhook-Source
  • X-WC-Webhook-Topic
  • X-WC-Webhook-Resource
  • X-WC-Webhook-Event
  • X-WC-Webhook-Signature
  • X-WC-Webhook-ID
  • X-WC-Webhook-Delivery-ID

X-WC-Webhook-Delivery-ID changes on every attempt. Do not use it as the only idempotency key.

Evaluation

Fixtures from evals/tasks.json. See evaluation fixtures for how the checks work.

https-basic-auth

Authenticate a server-side WooCommerce REST client over HTTPS without putting the secret in the browser.

Must include: /wp-json/wc/v3/, Basic

Must not include: wc-api/v3

Worked example

Call the current REST API from the server.

curl --user "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET" \
  "https://shop.example/wp-json/wc/v3/orders"

Send the consumer key and secret as HTTP Basic authentication over HTTPS only.

idempotent-order-create

Create an order through the REST API so a retried POST does not create a second order.

Must include: meta_data, _idempotency_key

Must not include: consumer_secret=

Worked example

Search for an existing order by meta before posting a new one.

{ "meta_data": [ { "key": "_idempotency_key", "value": "checkout-1042" } ] }

If wc_get style search on _idempotency_key hits, return that order instead of creating another.