Products API
The Products resource lives in the ec/v1 namespace. Reads are public; writes require manage_options or the wpec_manage_products capability. The OpenAPI spec is authoritative for exact field lists — this page shows the shapes you hit most.
List products
GET /ec/v1/products
Query parameters
All are optional. Multiple filters combine; attribute filters are ANDed by default (filter_logic).
| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
per_page | int | 20 | Items per page (max 100) |
q / search | string | — | Search term (q preferred; search accepted) |
status | string | — | draft · active · archived |
type | string | — | simple · variable |
category | string | — | Category slug filter |
category_id | int | — | Category ID filter |
brand_id | int | — | Single brand ID |
brand_ids | string | — | Comma-separated brand IDs |
min_price | float | — | Minimum price |
max_price | float | — | Maximum price |
on_sale | bool | false | Only products currently on sale |
min_rating | float | — | Minimum average rating |
stock_status | string | — | Filter by stock status |
attr_{slug} | string | — | Attribute filter, e.g. attr_color=red,blue (comma = OR within the attribute) |
filter_logic | string | and | and / or — how multiple attribute filters combine |
orderby | string | id | Sort field |
order | string | DESC | ASC / DESC |
category (slug) and category_id (numeric ID) are different parameters — pick the one matching what you hold. When neither stock_status nor a search is set and the store's out-of-stock visibility setting is off, out-of-stock products are excluded automatically.
Response
List responses use the { data, meta } envelope (not a success wrapper):
{
"data": [
{
"id": 1,
"title": "Product Name",
"slug": "product-name",
"type": "simple",
"status": "active",
"sku": "PROD-001",
"price": "29.99",
"sale_price": null,
"stock_status": "in_stock",
"stock_quantity": 50,
"featured_image": { "id": 42, "url": "https://…", "thumbnail": "https://…", "medium": "https://…", "large": "https://…" },
"image": "https://…",
"categories": [ { "id": 1, "name": "Category" } ],
"category_ids": [1]
}
],
"meta": { "page": 1, "per_page": 20, "total": 150, "total_pages": 8 },
"inventory_settings": { "stock_display_format": "low_stock", "low_stock_threshold": 5 }
}
inventory_settings is included so a storefront can render stock badges consistently. Public (non-admin) list requests without a search term are cached.
Get a single product
GET /ec/v1/products/{id}
Returns the full product object directly (no wrapper). For variable products the response includes the enabled variants (with computed min/max price); it always includes featured_image, image_sources (AVIF/WebP when present), gallery_images, categories, and category_ids.
By slug
GET /ec/v1/products/slug/{slug}
Same shape as get-by-ID — useful for headless storefront routing.
Barcode lookup (admin)
GET /ec/v1/products/lookup?barcode=5201234567890&warehouse_id=0
Admin-only (wpec_manage_products). Resolves a product or variant by EAN/barcode across ean + variant barcode — used by the stocktake scanner. warehouse_id is optional (0 = default).
Create product
POST /ec/v1/products
Requires manage_options or wpec_manage_products.
{
"title": "New Product",
"type": "simple",
"status": "draft",
"sku": "NEW-001",
"ean": "5201234567890",
"brand": "Acme",
"product_condition": "new",
"short_description": "Brief summary",
"description": "Full description here",
"price": 39.99,
"regular_price": 39.99,
"sale_price": 29.99,
"stock_quantity": 100,
"track_inventory": true,
"low_stock_threshold": 5,
"category_ids": [1, 3],
"featured_image_id": 42,
"gallery_image_ids": [43, 44],
"weight": 0.5
}
title is the only required field. type defaults to simple, status to draft, product_condition to new. See the OpenAPI spec for the complete schema (also accepts mpn, attributes, length/width/height).
Update product
PUT /ec/v1/products/{id}
PATCH /ec/v1/products/{id}
Partial updates — send only the fields that changed. All fields optional.
Delete product
DELETE /ec/v1/products/{id}
Requires wpec_manage_products. Returns { "message": "Product deleted successfully" } with 204.
Duplicate product
POST /ec/v1/products/{id}/duplicate
Duplicates the product as a draft — copies fields, categories, images, and attributes, and generates a unique SKU/slug. Returns the new product (201). Admin-only.
Weighted search
GET /ec/v1/search?q=shoe&per_page=8
Public. A weighted product and category search (separate from the list filter). Returns a combined shape:
{
"products": { "items": [ /* … */ ], "total": 12 },
"categories": { "items": [ /* … */ ] }
}
per_page is capped at 20 (default 8). Results pass through the wpec_search_results filter.
Variants
Variable products expose variant CRUD + drag-reorder (all writes require wpec_manage_products):
| Method | Endpoint | Purpose |
|---|---|---|
GET | /ec/v1/products/{id}/variants | List a product's variants (public) |
POST | /ec/v1/products/{id}/variants | Create a variant |
DELETE | /ec/v1/products/{id}/variants | Delete all variants of the product |
POST | /ec/v1/products/{id}/variants/reorder | Reorder — body { "order": [ { "id": 1, "sort_order": 0 }, … ] } |
GET | /ec/v1/variants/{id} | Read a single variant (public) |
PUT / PATCH | /ec/v1/variants/{id} | Update a variant |
DELETE | /ec/v1/variants/{id} | Delete a single variant |
Filters
Applied while building API responses:
| Filter | Signature | Purpose |
|---|---|---|
wpec_api_product_response | ($data, $product) | Transform a single product response |
wpec_api_products_response | ($data, $args, $total) | Transform the product list array |
wpec_product_query_args | ($args) | Modify the underlying product query args |
wpec_products_per_page | (int $default) | Default page size when per_page is absent |
wpec_search_results | ($results, $query) | Transform /search output |
Where to go next
- REST API Overview · Cart · Orders
- Authentication — the
wpec_manage_productscapability and license gating.