Skip to main content

Commerce API

Build your own storefront, mobile app or checkout on top of ConnectWiz. This API reads the workspace catalogue and writes into the same order ledger the panel, the assistant, the chat widget and your flows all use — so a customer who bought through your site and once over the phone has one history, not two.

Getting a key​

In the panel, open Settings → Developer → API keys (until October 2026 the keys lived under Settings → Catalogue, which now only points there). Choose a name, tick the scopes you need, and create it.

The token is shown once. Only its hash is stored, so there is no way to show it again — the same bargain WooCommerce's own REST keys make. The list keeps a short hint (the first characters) so you can tell which key is in which environment without holding the secret.

Do not use the widget key

The widget key is public: it sits in the page source of every site running the chat. A commerce key is a server credential. Keep it out of browsers, out of mobile app bundles, and out of your repository.

Scopes​

ScopeLets the key
catalogue:readread what is for sale, with prices and stock
orders:readread one named customer's orders
orders:writeplace an order for a named customer
tools:runrun the workspace's AI tools — see the AI tools API

Grant only what a key needs. A storefront that shows prices needs the first and nothing else, and a key that can do all three because it needed one of them is how a leaked read token becomes a write incident.

Authenticating​

Send the token as a bearer:

Authorization: Bearer cwz_…

Refusals are deliberately distinct so you can tell them apart while debugging:

StatuserrorMeaning
401missing_tokenNo Authorization header
401unknown_tokenNothing here knows that token
403revoked_tokenA real key that has been revoked in the panel
403missing_scope:<scope>A real key without that scope

Reading the catalogue​

GET /api/commerce/catalogue?q=kimono&limit=50
{
"data": [
{
"id": "01a0…",
"sku": "KMN-1",
"name": "Kimono",
"price": "1200.00",
"currency": "TRY",
"kind": "product",
"available": true
}
]
}

Only active, priced items are returned: an item you could display but not be charged for is one a customer will try to buy and be refused.

available has three states, not two

true and false mean what you expect. Absent means the shop does not count stock at all — which is not "sold out". Rendering a missing available as out-of-stock would refuse sales the business would happily make.

Naming the customer​

Every order verb needs to know who is buying. Give one of:

  • customer_id — a ConnectWiz contact id you already hold;
  • customer_email and/or customer_phone — matched against existing contacts.

When writing an order, an email or phone we have never seen opens a new customer rather than failing. A new buyer's first purchase must not be the one that breaks.

Placing an order​

POST /api/commerce/orders
Content-Type: application/json

{
"customer_email": "[email protected]",
"customer_name": "Ada Lovelace",
"items": [{ "item": "KMN-1", "quantity": 2 }],
"confirm": true
}

item is a catalogue id or a SKU. confirm defaults to true; send false if you write a basket first and confirm it after payment.

You cannot send a price

There is no parameter for a price, a name, a discount or a tax rate. All of them are read from your catalogue at the moment of the order and written onto the line as a snapshot — so what the customer agreed to is what they get read back even if you reprice the item tomorrow.

This is deliberate, and it protects you rather than us: a bug in your checkout, or somebody who steals your key, cannot write a number into your own ledger.

Refusals come back as 409 with a code:

errorMeaning
unknown_item:<key>Not in the catalogue, or no longer active
unpriced_item:<name>Nobody has priced it yet
mixed_currenciesOne order, one currency — there is no exchange rate we are entitled to choose for you
bad_quantityZero, negative or not a number
empty_orderNo lines

Reading a customer's orders​

GET /api/commerce/[email protected]

An order carries three independent things, and none of them can be read off another:

  • status — where the document is (draft, confirmed, cancelled);
  • delivery — where the goods are (unfulfilled, processing, shipped, delivered, returned), with tracking_number and carrier;
  • the money, which lives in the payments ledger and is never a field on the order — a parcel can be delivered and unpaid, or paid and unshipped.

There is deliberately no "every order in the workspace" call. That is a report, it belongs behind a staff login, and a key that could produce it would turn one leaked token into your whole ledger.

Rate limits​

The commerce endpoints share the standard API throttle. Treat a 429 as a signal to back off rather than to retry immediately.