Commerce · REST API
Sales summary
Operational sales metrics for a date range, optionally grouped by day, week or month.
Operational metrics, not accounting revenue
These figures are for monitoring. They will not necessarily reconcile with finance reports, and no field is called revenue. The exact rules are returned with every response in definitions and explained below.
Get a sales summary
GET
/api/v1/sales-summary
Needs commerce.read. Equivalent MCP tool: get_sales_summary.
| Name | Type | Required | Description |
|---|---|---|---|
from |
date | Required | YYYY-MM-DD, Europe/Amsterdam, inclusive. |
to |
date | Required | YYYY-MM-DD, Europe/Amsterdam, inclusive. Not before from; at most 366 days in total. |
group_by |
string | Optional | none (default), day, week (ISO, starting Monday) or month. |
curl
export COMMERCE_API_TOKEN='ocm_<token>'
curl -sS "https://<your-shop-domain>/api/v1/sales-summary?from=2026-10-01&to=2026-10-07&group_by=day" \
-H "Authorization: Bearer $COMMERCE_API_TOKEN"
JavaScript
// Server-side code only: never put the token in browser code.
// Top-level await needs an ES module (a .mjs file or "type": "module").
const token = process.env.COMMERCE_API_TOKEN; // ocm_<token>
const response = await fetch('https://<your-shop-domain>/api/v1/sales-summary?from=2026-10-01&to=2026-10-07&group_by=day', {
headers: { Authorization: `Bearer ${token}` },
});
console.log(response.status, await response.json());
PHP
<?php
$token = getenv('COMMERCE_API_TOKEN'); // ocm_<token>
$curl = curl_init('https://<your-shop-domain>/api/v1/sales-summary?from=2026-10-01&to=2026-10-07&group_by=day');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],
]);
$body = json_decode(curl_exec($curl), true);
echo curl_getinfo($curl, CURLINFO_RESPONSE_CODE), PHP_EOL;
print_r($body);
Python
import json
import os
import urllib.error
import urllib.request
token = os.environ["COMMERCE_API_TOKEN"] # ocm_<token>
request = urllib.request.Request(
"https://<your-shop-domain>/api/v1/sales-summary?from=2026-10-01&to=2026-10-07&group_by=day",
headers={"Authorization": f"Bearer {token}"},
)
try:
with urllib.request.urlopen(request) as response:
print(response.status, json.load(response))
except urllib.error.HTTPError as error:
# Errors are JSON too: {"error": {"code": ..., "message": ...}}
print(error.code, json.load(error))
{
"data": {
"currency": "EUR",
"period": {
"from": "2026-10-01",
"to": "2026-10-07",
"timezone": "Europe/Amsterdam"
},
"orders_count": 42,
"gross_sales": {
"amount_minor": 318450,
"currency": "EUR",
"formatted": "€ 3.184,50"
},
"refunded": {
"amount_minor": 12900,
"currency": "EUR",
"formatted": "€ 129,00"
},
"net_sales": {
"amount_minor": 305550,
"currency": "EUR",
"formatted": "€ 3.055,50"
},
"average_order_value": {
"amount_minor": 7582,
"currency": "EUR",
"formatted": "€ 75,82"
},
"buckets": [
{
"start": "2026-10-01",
"orders_count": 7,
"gross_sales": {
"amount_minor": 52210,
"currency": "EUR",
"formatted": "€ 522,10"
},
"refunded": {
"amount_minor": 0,
"currency": "EUR",
"formatted": "€ 0,00"
},
"net_sales": {
"amount_minor": 52210,
"currency": "EUR",
"formatted": "€ 522,10"
}
},
{
"start": "2026-10-02",
"orders_count": 5,
"gross_sales": {
"amount_minor": 38900,
"currency": "EUR",
"formatted": "€ 389,00"
},
"refunded": {
"amount_minor": 12900,
"currency": "EUR",
"formatted": "€ 129,00"
},
"net_sales": {
"amount_minor": 26000,
"currency": "EUR",
"formatted": "€ 260,00"
}
}
],
"definitions": [
"Operational sales metrics for monitoring. Not accounting revenue; will not necessarily reconcile with finance reports.",
"All amounts are integer minor units in EUR and tax-inclusive. Days are Europe/Amsterdam calendar days; from and to are both inclusive.",
"orders_count: orders whose placed_at lies within the period and whose status is not cancelled. Orders are counted by order date (placed_at); orders that were never placed are excluded."
]
}
}
Shortened for readability: 2 of 7 daily buckets are shown, so the buckets shown do not add up to the totals. In a real response the buckets add up to the totals.
| Field | Type | Description |
|---|---|---|
currency |
string | EUR. |
period |
object | from, to and timezone. |
orders_count |
integer | Orders placed in the period, counted by order date. Cancelled orders are excluded. |
gross_sales |
money | Sum of the tax-inclusive totals of the counted orders. |
refunded |
money | Succeeded refunds, counted by refund date. |
net_sales |
money | gross_sales minus refunded. Can be negative. |
average_order_value |
money | gross_sales divided by orders_count, rounded; 0 without orders. |
buckets |
array | Empty for group_by=none; otherwise one entry per day, week or month with start, orders_count, gross_sales, refunded and net_sales. |
definitions |
array of string | The rules of the metrics as text. |
How the metrics are defined
- Orders are counted by order date (
placed_at), in Europe/Amsterdam calendar days with both ends inclusive. Cancelled orders and orders that were never placed are excluded. - Refunds are counted by refund date, regardless of when the order was placed. This includes refunds of orders placed before the period and of orders that are now cancelled. Pending and failed refunds are ignored.
- All amounts are integer minor units in EUR and tax-inclusive.
- Buckets are contiguous and zero-filled over the whole period: a day, an ISO week starting Monday, or a calendar month.
startis the first local date of the bucket; the first bucket starts atfromeven when that is mid-week or mid-month. Bucket sums add up to the totals. - A day is a calendar day in Europe/Amsterdam, so it is 23 or 25 hours long on the two daylight-saving changes. The 366-day limit counts calendar days.
- The response contains no customer data.
Errors
| Status | error.code | When |
|---|---|---|
401 |
unauthenticated |
Missing, malformed, unknown, revoked or expired token, or a token without the api channel. |
422 |
invalid_argument |
A missing or malformed date, to before from, a period longer than 366 days, an unknown group_by or an unknown parameter. |
429 |
too_many_requests |
Rate limit reached. Wait the number of seconds in Retry-After. |
The same figures are available over MCP as get_sales_summary.