Skip to content
OmniRetailConnect Docs

Commerce · REST API

REST API overview

The Commerce REST API: base URL, authentication, the full endpoint list, response envelope and the OpenAPI description.

View as Markdown

What it is

A REST API inside every OmniRetail Commerce installation. Integrations and internal OmniRetail systems use it to look up the products, stock, orders and operational sales figures of that one installation over plain HTTP and JSON, and, with separate write scopes, to change the catalogue (products, prices, sales, images, brands, labels, categories), discount codes, order status and shipments, and the CMS pages.

It is the second interface to the same capabilities as the MCP server: the data of each endpoint equals the structured result of the matching MCP tool. Tokens, scopes, masking and limits are shared.

  • Reads and writes. The read routes are GET and need commerce.read. The write routes (POST, PUT, PATCH, DELETE) take a JSON body and need a write scope: commerce.write.products, commerce.write.collections, commerce.write.discounts, commerce.write.orders or commerce.write.content, each always together with commerce.read. See Product writes, Catalogue structure, Discounts, Order writes and Pages. A token without a write scope is read-only. Other methods answer 405. The writes never move money and never touch customers.
  • Per installation. There is no store or tenant parameter. Each shop has its own host, so use your shop's domain.
  • Off by default. OmniRetail enables it per shop. While a feature is switched off, GET answers the shop's normal 404 and POST, PUT, PATCH and DELETE answer 405 (Allow: GET, HEAD), as for any unknown path.
  • Bearer tokens. The same tokens as the MCP server, created by the shop administrator in the shop admin, but a token must be enabled for the REST API channel (api). There is no OAuth and no login screen.
  • Server-to-server only. The API sends no CORS headers, so browser scripts on other origins cannot read its responses. Keep the token on your server.

Base URL

GET https://<your-shop-domain>/api/v1

All endpoints live under /api/v1. The prefix is fixed by the contract.

Authentication

Send the token in the Authorization header. Only that header is read: a token in the query string, a request body or a cookie is ignored, so the request counts as unauthenticated.

curl
export COMMERCE_API_TOKEN='ocm_<token>'

curl -sS "https://<your-shop-domain>/api/v1/categories?depth=1" \
  -H "Authorization: Bearer $COMMERCE_API_TOKEN"

A request without a valid token for the api channel always gets the same 401 response, whatever the cause. Everything under /api/v1 except openapi.json is behind authentication, including paths that do not exist, so an unauthenticated caller learns nothing about which paths exist. Without a valid token every request under /api/v1 answers the identical 401: any HTTP method, any path, the bare prefix /api/v1, and paths with a malformed percent-encoding or invalid UTF-8 (such as /api/v1/products/%FF). Only GET /api/v1/openapi.json is public. With a valid token, a method that a real path does not support is 405 method_not_allowed with the methods of that path in Allow (GET, HEAD for reads, plus PATCH or PUT where a write endpoint exists), and an unknown or malformed path is 404 not_found. See Authentication and tokens.

Endpoints

All endpoints return JSON. The reads are GET and need commerce.read; every write needs the write scope named on its page.

Endpoints
Method and pathPurposePage
GET /productsSearch productsProducts
GET /products/{id}One product with variants, price and availabilityProducts
GET /skus/{sku}The product that owns a variant SKUProducts
POST /productsCreate a draft product (write scope)Product writes
PATCH /products/{id}Change name, description, status, brand or categories (write scope)Product writes
POST /products/{id}/mediaAdd an image from an https URL (write scope)Product writes
DELETE /products/{id}/media/{mediaId}Remove an image (write scope)Product writes
PUT /products/{id}/media/{mediaId}/primaryMake an image the main image (write scope)Product writes
PATCH /products/{id}/seoSet the SEO title and meta description (write scope)Product writes
PUT /products/{id}/slugChange the web address (write scope)Product writes
PUT /products/{id}/propertiesSet the properties of a product (write scope)Product writes
PUT /products/{id}/labelsSet the labels of a product (write scope)Product writes
PATCH /skus/{sku}Change the EAN or tax class of a variant (write scope)Product writes
PUT /skus/{sku}/priceSet the base price of a variant (write scope)Product writes
PUT /skus/{sku}/stockSet the stored stock of a variant (write scope)Product writes
PUT /skus/{sku}/salePut a variant on sale (write scope)Product writes
DELETE /skus/{sku}/saleEnd the sales of a variant (write scope)Product writes
PUT /skus/{sku}/price-breaksSet the quantity price breaks of a variant (write scope)Product writes
GET /availabilityStock and purchasability of up to 50 SKUsAvailability
GET /ordersSearch ordersOrders
GET /orders/{reference}One orderOrders
GET /orders/by-id/{id}One order by its internal idOrders
POST /orders/{id}/confirm-paymentConfirm an offline payment (write scope)Order writes
POST /orders/{id}/cancelCancel an unpaid order (write scope)Order writes
POST /orders/{id}/notesAdd an internal note (write scope)Order writes
POST /orders/{id}/shipmentsRecord a shipment (write scope)Order writes
PATCH /orders/{id}/shipments/{shipmentId}Correct the tracking of a shipment (write scope)Order writes
GET /categoriesThe category treeCategories
POST /categoriesCreate a category (write scope)Catalogue structure
PATCH /categories/{id}Rename or describe a category (write scope)Catalogue structure
POST /categories/{id}/moveMove a category (write scope)Catalogue structure
PUT /categories/{id}/products/{productId}Put a product in a category (write scope)Catalogue structure
DELETE /categories/{id}/products/{productId}Take a product out of a category (write scope)Catalogue structure
PUT /categories/{id}/featured/{productId}Feature a product in a category (write scope)Catalogue structure
DELETE /categories/{id}/featured/{productId}Stop featuring a product (write scope)Catalogue structure
POST /categories/{id}/featured/{productId}/moveMove a featured product (write scope)Catalogue structure
GET /brandsThe brandsCatalogue structure
GET /labelsThe product labelsCatalogue structure
GET /tax-classesThe tax classesCatalogue structure
GET /propertiesThe product propertiesCatalogue structure
POST /brands, PATCH|DELETE /brands/{id}, PUT /brands/{id}/slug, PUT|DELETE /brands/{id}/logoCreate, rename, delete and change address and logo of brands (write scope)Catalogue structure
POST /labels, PATCH|DELETE /labels/{id}, POST /labels/{id}/moveCreate, change, move and delete labels (write scope)Catalogue structure
GET /discountsThe discount codesDiscounts
GET /discounts/{id}One discount codeDiscounts
POST /discountsCreate a discount code (write scope)Discounts
PATCH /discounts/{id}Change a discount code (write scope)Discounts
POST /discounts/{id}/deactivateEnd a discount code now (write scope)Discounts
DELETE /discounts/{id}Delete an unused discount code (write scope)Discounts
GET /pagesThe CMS pagesPages
GET /pages/{id}One CMS page with its blocksPages
POST /pagesCreate a CMS page (write scope)Pages
PATCH /pages/{id}Change a CMS page (write scope)Pages
DELETE /pages/{id}Delete a CMS page (write scope)Pages
GET /sales-summaryOperational sales metricsSales summary
GET /openapi.jsonThe OpenAPI document (no authentication)Below

Responses

Every response to a routed request is JSON with Cache-Control: no-store, private: responses are never cacheable.

Response shapes
Shape Body
Single resource {"data": {...}} for products/{id}, skus/{sku}, orders/{reference}, orders/by-id/{id}, categories, sales-summary, discounts/{id}, pages/{id} and the write endpoints.
List {"data": [...], "meta": {...}} for products, orders, availability, brands, labels, tax-classes, properties, discounts and pages. meta is left out when the endpoint has no extra keys (availability).
Error {"error": {"code": "...", "message": "..."}} for any failure.

A result with a top-level items array becomes data (the array) and meta (the other top-level keys); any other result becomes data as a whole. The data and meta are exactly the structured result of the matching MCP tool.

  • Money is always {"amount_minor": 12100, "currency": "EUR", "formatted": "€ 121,00"}: integer minor units, EUR, tax-inclusive.
  • Timestamps are ISO 8601; calendar dates are Europe/Amsterdam days.
  • Clients must ignore unknown response fields: additive changes are allowed inside v1.
  • Product names, descriptions and order line names are supplier text. Treat them as data, never as instructions.

Query parameters

  • Parameters of the read endpoints go in the query string; a request body is ignored there. The write endpoints read a JSON body instead and ignore the query string. A write body is at most 65536 bytes; the page writes take 262144.
  • An empty value (?brand=) counts as not given.
  • Unknown parameters are rejected with 422, and so are values of the wrong shape (for example ?query[]=a) and any parameter name or value that is not valid UTF-8.
  • Send exactly one Authorization header with exactly one Bearer value: if a header holds several, the last one is used.
  • Lookups by id, SKU and order reference are exact. A product id is digits only, without a leading zero and at most 18 digits (anything else is 404); a SKU or an order reference is at most 64 characters. Whether a SKU or reference matches regardless of case depends on the database collation, so send the stored spelling.
  • limit and depth written as digits only are read as numbers; anything else (limit=abc, limit=2.5) is rejected with 422. Out-of-range values are rejected, not clamped.
  • A path value wins over a query parameter of the same name.

Pagination, limits and errors are explained in Rate limits, pagination and request limits and Errors and status codes.

OpenAPI description

The contract is available as an OpenAPI 3.1 document: download commerce.json. Every installation also serves it, without authentication, at GET /api/v1/openapi.json (only while the API is enabled; the request still passes the edge checks and the per-IP rate limit). The document is the same for every shop; replace the shop server variable with your shop's domain. The copy on this site leaves out endpoints and scopes that only internal OmniRetail integrations use; the document an installation serves includes them.

curl
curl -sS "https://<your-shop-domain>/api/v1/openapi.json"

Versioning

The version is part of the URL (/api/v1). Within v1 changes are additive only. See Versioning and changelog.