Catalog API
Base URL https://api.pluswase.com/api/v1. Every call needs the connection's key (see Authentication) and sees only what is published to that site.
"59.99"). A variant's cost is never returned.Endpoints
| Request | Answer |
|---|---|
GET/ping | A health check; needs no key. |
GET/store | The store behind the key. |
GET/products | A page of product cards. |
GET/products/{slug} | One product. |
GET/products/{slug}/related | Up to 8 cards: products sharing a collection first, then a tag, then the newest. |
GET/collections | Published collections with at least one product on this site. |
GET/brands | The same for brands. |
GET/tags | The tags of this site's products, most used first. |
POST/order-numbers | Order numbers reserved for this site (used by the plugin). |
POST/orders | An order pushed by the site (used by the plugin). |
POST/orders/{id}/refund | A refund of that order. |
POST/orders/{id}/notes | A note on that order. |
GET/plugin | The latest PlusWase Hub version (the plugin's update check). |
GET/plugin/download | The PlusWase Hub zip of that version. |
GET /store
{
"name": "Demo Store",
"domain": "demo1.pluswase.com",
"currency": "USD",
"connection_id": "f55nzxhnaa",
"plugin_min_version": "0.1.0"
}
GET /products
| Parameter | Meaning |
|---|---|
search | Words in the product name (100 characters at most). |
collection | A collection handle. The answer's context describes it; an unknown or unpublished one answers 404. |
brand | A brand handle, the same way. Used when collection is not given. |
tag | A tag slug (winter-wear for "Winter Wear"). Used when neither of the above is given. |
sort | newest, oldest, price_asc, price_desc, title_asc, title_desc. Without it: newest first, or inside a collection or brand that group's own order. |
page, per_page | 1-based page; 12 per page by default, 48 at most. |
curl "https://api.pluswase.com/api/v1/products?collection=hoodie&per_page=12" \
-H "X-Api-Key: plw_live_…"
{
"context": { "kind": "collection", "handle": "hoodie", "title": "Hoodie" },
"items": [
{
"slug": "racing-hoodie-cotton-gildan",
"name": "Racing Hoodie Cotton Gildan",
"price_min": "59.99",
"price_max": "59.99",
"compare_at_min": "79.99",
"image": "https://img.mystore.com/products/vb89n9xbcd/2026/11/bio2t0br8207.webp",
"permalink_path": "/products/racing-hoodie-cotton-gildan/"
}
],
"total": 1,
"page": 1,
"per_page": 12,
"pages": 1
}
context is null without a filter, and has kind "tag" with the tag's name as title for a tag. compare_at_min is set only when the cheapest variant is marked down. image is on the store's verified image domain once there is one, else on PlusWase's storage address.
GET /products/{slug}
{
"slug": "racing-hoodie-cotton-gildan",
"name": "Racing Hoodie Cotton Gildan",
"description_html": "<p>Heavyweight cotton, relaxed fit.</p>",
"short_description": "",
"sku": "RH-GILDAN",
"tags": [{ "slug": "racing", "name": "Racing" }],
"seo": { "title": "Racing Hoodie Cotton Gildan", "description": "Heavyweight cotton, relaxed fit." },
"options": [
{ "name": "Color", "display": "color_pick", "values": [{ "label": "Black", "hex": "#000000", "image": null }] },
{ "name": "Size", "display": "button", "values": [{ "label": "M", "hex": null, "image": null }] }
],
"images": [
{ "src": "https://img.mystore.com/products/…/bio2t0br8207.webp", "alt": "Front", "variant_id": null },
{ "src": "https://img.mystore.com/products/…/t7kpc2pg8git.webp", "alt": "Black", "variant_id": "18" }
],
"variations": [
{
"id": "18",
"sku": "RH-GILDAN-BLK-M",
"attributes": { "Color": "Black", "Size": "M" },
"price": "59.99",
"compare_at": "79.99",
"stock_status": "instock",
"images": [{ "src": "https://img.mystore.com/products/…/t7kpc2pg8git.webp", "alt": "Black" }]
}
],
"collections": [{ "handle": "hoodie", "title": "Hoodie" }],
"brands": [],
"updated_at": "2026-11-05T22:40:48.688Z"
}
| Field | Meaning |
|---|---|
options[].display | button, color_pick (show hex swatches), select or image. |
images[].variant_id | null for the product's own photos; a variation id for a variant's photo. |
variations[].id | PlusWase's variant id. It is never reused, and it is what an order line sends back as vid. |
variations[].stock_status | outofstock for a paused variant; keep it visible but not purchasable. |
variations[].compare_at | The "was" price, set only when it is higher than price. |
seo.description | The seller's own, or the start of the description. |
GET /collections, /brands, /tags
[
{
"handle": "hoodie",
"title": "Hoodie",
"product_count": 1,
"image": "https://img.mystore.com/products/…/bio2t0br8207.webp",
"description": null
}
]
Collections and brands come in the seller's order and carry the group's own image and description when it has them (else the image of its first product). Tags have the same fields, with the slug as handle, the name as title, and no image or description.
Orders (used by the plugin)
The plugin reserves order numbers and pushes orders; you do not need to call these yourself. They are listed so you can read what reaches PlusWase.
POST /order-numbers
{ "count": 20 } → { "numbers": ["1001", "1002", …] }
Numbers come from one sequence shared by every PlusWase store, so no two stores show the same number. 50 at most per call.
POST /orders
{
"idempotency_key": "B-18",
"order_id": 18,
"order_number": "1001",
"number_source": "plw",
"status": "processing",
"currency": "USD",
"paid_at": "2026-11-05T22:10:00+00:00",
"shipping_total": 0, "total_tax": 0, "discount_total": 0, "shipping_method": "",
"email": "buyer@example.com", "phone": "", "customer_note": "",
"shipping": { "first_name": "Ann", "last_name": "Lee", "address_1": "1 Main St", "address_2": "",
"city": "Austin", "state": "TX", "postcode": "78701", "country": "US" },
"items": [{ "line_id": "9", "vid": "18", "sku": "RH-GILDAN-BLK-M", "qty": 2,
"unit_price": 59.99, "unit_subtotal": 59.99, "tax": 0 }]
}
The same idempotency_key updates the same order. The answer is { id, number, status, payment_status }. WooCommerce statuses map to PlusWase's:
| WooCommerce | Payment | Fulfillment |
|---|---|---|
pending | pending | unfulfilled |
failed | unpaid | unfulfilled |
on-hold | pending | on hold |
processing | paid (pending for payment_method cod) | unfulfilled |
completed | paid | fulfilled |
cancelled | unchanged | canceled (not required) |
refunded | refunded | unchanged |
An order first pushed as cancelled or refunded is answered { "ignored": true }. A line whose vid is not published to this site, a number this site did not reserve ({ "error": "number_refused" }) and a currency other than USD are refused with 422.
POST /orders/{id}/refund and /orders/{id}/notes
{ "amount": 20, "total_refunded": 20, "reason": "Damaged", "refund_id": 512 } → { "id": "p1knhvtggp", "payment_status": "partially_refunded" }
{ "note": "Customer called about the size." } → { "ok": true }
{id} is the PlusWase order id from the push answer. refund_id is the WooCommerce refund id: a refund already taken in under that id only answers the current payment status, so a retried push is recorded once.