PlusWaseDevelopers

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.

A product exists for the API only when it is active, not in To be removed, and published to the calling connection. Prices are US dollars as decimal strings ("59.99"). A variant's cost is never returned.

Endpoints

RequestAnswer
GET/pingA health check; needs no key.
GET/storeThe store behind the key.
GET/productsA page of product cards.
GET/products/{slug}One product.
GET/products/{slug}/relatedUp to 8 cards: products sharing a collection first, then a tag, then the newest.
GET/collectionsPublished collections with at least one product on this site.
GET/brandsThe same for brands.
GET/tagsThe tags of this site's products, most used first.
POST/order-numbersOrder numbers reserved for this site (used by the plugin).
POST/ordersAn order pushed by the site (used by the plugin).
POST/orders/{id}/refundA refund of that order.
POST/orders/{id}/notesA note on that order.
GET/pluginThe latest PlusWase Hub version (the plugin's update check).
GET/plugin/downloadThe 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

ParameterMeaning
searchWords in the product name (100 characters at most).
collectionA collection handle. The answer's context describes it; an unknown or unpublished one answers 404.
brandA brand handle, the same way. Used when collection is not given.
tagA tag slug (winter-wear for "Winter Wear"). Used when neither of the above is given.
sortnewest, 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_page1-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"
}
FieldMeaning
options[].displaybutton, color_pick (show hex swatches), select or image.
images[].variant_idnull for the product's own photos; a variation id for a variant's photo.
variations[].idPlusWase's variant id. It is never reused, and it is what an order line sends back as vid.
variations[].stock_statusoutofstock for a paused variant; keep it visible but not purchasable.
variations[].compare_atThe "was" price, set only when it is higher than price.
seo.descriptionThe 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:

WooCommercePaymentFulfillment
pendingpendingunfulfilled
failedunpaidunfulfilled
on-holdpendingon hold
processingpaid (pending for payment_method cod)unfulfilled
completedpaidfulfilled
cancelledunchangedcanceled (not required)
refundedrefundedunchanged

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.