Skip to content
ThemesIonic — home
WooCommerce

Working with the WooCommerce Product API

There are two product APIs in WooCommerce and they are built for opposite jobs. Picking the wrong one is why integrations end up authenticating a server-to-server sync from inside a browser.

4 min read advanced

Decide which API you need before you generate a single key. WooCommerce exposes two, and they solve different problems:

  • The REST API under /wp-json/wc/v3/ is authenticated and administrative. It reads and writes products, variations, orders and customers. It is for your ERP, your PIM, a stock sync, a migration script — anything running on a server you control.
  • The Store API under /wp-json/wc/store/v1/ is public and unauthenticated. It serves product data and cart operations to front-end code, and it exposes only what a shopper is allowed to see.

If credentials would end up in a browser bundle, you have chosen wrong. That is the single most consequential decision here; everything below is about the first one.

The endpoints you will actually use

The product surface is a small, predictable set:

Route Purpose
/wc/v3/products List and create products
/wc/v3/products/<id> Read, update, delete one product
/wc/v3/products/<id>/variations Variations of a variable product
/wc/v3/products/categories Category terms
/wc/v3/products/tags Tag terms
/wc/v3/products/attributes Global attributes and their terms
/wc/v3/products/reviews Reviews
/wc/v3/products/batch Bulk create, update and delete in one request

Brands, where your install has them in core, follow the same pattern as categories and tags — the taxonomy layout is covered in product brands. Older namespaces wc/v1 and wc/v2 still respond on many stores, and there is an older pre-REST endpoint that predates all of them. Write new code against v3.

Authentication, and why it fails

Generate a key pair under WooCommerce → Settings → Advanced → REST API, scoped read, write or read/write. Grant read-only unless the integration genuinely writes; a stock sync that only pulls does not need permission to delete your catalogue.

Over HTTPS, send the consumer key and secret as HTTP Basic credentials. That is the whole mechanism, which is why almost every failure has one of three causes:

  • The Authorization header never arrives. Many CGI and FastCGI configurations strip it. Test the same credentials as query parameters over HTTPS; success there proves the keys are valid and the server is the problem.
  • The route does not exist. A 404 with rest_no_route, or a 404 on /wp-json/ itself, usually means pretty permalinks are off or rewrite rules are stale — see permalinks not working.
  • The key is scoped read-only and you are issuing a POST. The response says so; people rarely read it.

WordPress application passwords are the other route in. They authenticate as a real user, and WooCommerce then applies that user's capabilities, which is often cleaner for internal tooling than a floating key pair. The general mechanics are the same as the WordPress API.

Reading without melting the server

Collection routes are paginated. Request per_page up to 100 and page through using the total-count and total-pages headers in the response rather than guessing when to stop. Ask for less: the core _fields parameter trims the payload to the properties you need, and a products listing with images, meta and descriptions stripped out is a fraction of the size.

For incremental syncs, filter on modification date rather than pulling everything and diffing locally. And when you need one specific item, query by SKU rather than storing WooCommerce IDs in the other system — SKUs survive a migration, IDs do not.

Writing: the parts that surprise people

Prices are strings, not numbers, and regular_price and sale_price are what you set. The price property is computed and read-only; writing to it does nothing.

Variable products are a two-step creation, and it is strict. The parent must be created with its attributes marked as used for variations before the variations exist; a variation posted with an attribute the parent does not declare will save quietly and then never be selectable on the front end. The underlying model is the same one described in product variations.

Images are supplied as source URLs and sideloaded by WooCommerce, so the write is only as fast as your image host. Custom fields go in meta_data as key and value pairs.

The batch route accepts create, update and delete arrays in one request and is capped at a filterable number of items — a hundred by default on most installs. It is dramatically faster than individual calls, but there is no idempotency key anywhere in this API. A retried batch after a timeout will happily create everything twice, so make writes safe by looking up the SKU first.

When the API is the wrong tool

For a one-off catalogue load from a spreadsheet, the built-in importer is faster to get right than a script, and it handles the same fields — see importing products with CSV.

When a request fails in a way the response body does not explain, the error is usually thrown inside a hook your integration triggered. Turn on private logging as described in enabling debug mode, reproduce once, and read the fatal — a 500 from the REST API is a PHP error like any other.

Frequently asked

The most common cause is the server dropping the HTTP Authorization header before PHP sees it, which is common on CGI and FastCGI setups. Confirm by sending the same credentials as query string parameters over HTTPS; if that works, the keys are fine and the header is being stripped.
The wc/v3 namespace is an authenticated admin API for integrations that read and write the catalogue and orders. The Store API is public and unauthenticated, designed for front-end code that browses products and manages a cart, and it deliberately exposes nothing a shopper should not see.
In two steps. Create the parent with type set to variable and its attributes flagged as used for variations, then post each variation to the variations endpoint under the returned parent ID. A variation created without a matching parent attribute will save but never be selectable.

Related guides