Reports endpoints
Three read-only report endpoints let you pull the same data that
backs the /dashboard/reports pages as structured JSON. Use them when
you want live report data inside a BI tool, internal dashboard, or
nightly sync — without setting up a scheduled report and parsing CSV.
All three require reports:read.
Related: Scheduled reports — if you want the same data emailed as CSV/PDF on a recurring schedule, use that instead of polling these endpoints.
GET /api/v1/reports/sales
Sales totals and daily breakdown over a date range.
Required scope: reports:read
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
preset | today / week / month / quarter / year / all | month | Shorthand for common ranges. |
from | ISO 8601 date | — | Override the preset start. |
to | ISO 8601 date | — | Override the preset end. |
If both from and to are supplied, they override the preset. Dates
are parsed with new Date() — any format that accepts works
(2026-04-01, 2026-04-01T00:00:00Z).
Example
curl "https://distribu.app/api/v1/reports/sales?preset=month" \
-H "Authorization: Bearer dk_..."
Response
{
"data": {
"range": {
"from": "2026-04-01T00:00:00.000Z",
"to": "2026-04-17T23:59:59.999Z",
"preset": "month",
"label": "April 2026"
},
"totals": {
"orderCount": 42,
"revenue": 18743.50,
"averageOrderValue": 446.27
},
"daily": [
{
"date": "2026-04-01",
"orderCount": 3,
"revenue": 1240.00
}
],
"byStatus": [
{ "status": "SUBMITTED", "orderCount": 2, "revenue": 410.00 },
{ "status": "CONFIRMED", "orderCount": 5, "revenue": 2100.50 },
{ "status": "SHIPPED", "orderCount": 10, "revenue": 4500.00 },
{ "status": "DELIVERED", "orderCount": 23, "revenue": 10533.00 },
{ "status": "CANCELLED", "orderCount": 2, "revenue": 1200.00 }
]
}
}
Notes
revenueis summed across all orders in the range regardless of status — cancelled orders are included in the status breakdown but also counted in the top-line total. If you want "realised revenue," filter bystatusclient-side.daily[]is one row per day in the range, with0s for days without activity — no gaps.labelis a human-readable summary like "April 2026" or "2026-04-01 → 2026-04-17" for custom ranges.
GET /api/v1/reports/customers
Per-customer spend over a date range — one row per customer with orders in the range.
Required scope: reports:read
Query parameters
Same as /reports/sales — preset, from, to.
Example
curl "https://distribu.app/api/v1/reports/customers?preset=quarter" \
-H "Authorization: Bearer dk_..."
Response
{
"data": {
"range": {
"from": "2026-01-01T00:00:00.000Z",
"to": "2026-03-31T23:59:59.999Z",
"preset": "quarter",
"label": "Q1 2026"
},
"totals": {
"customerCount": 17,
"orderCount": 84,
"revenue": 52410.00
},
"rows": [
{
"customerId": "clxxcustomer1...",
"customerName": "Acme Restaurant Group",
"customerEmail": "buyer@acme.com",
"orderCount": 12,
"revenue": 8420.50,
"lastOrderAt": "2026-03-28T14:22:00.000Z"
}
]
}
}
Sorted by revenue descending.
Notes
- Only customers with at least one order in the range appear. Silent customers are omitted.
customerNameis the name on the customer record, not a snapshot — if the name was edited after the orders were placed, you see the current name.
GET /api/v1/reports/inventory
Current inventory valuation — stock × price across your active catalog.
Required scope: reports:read
Query parameters
None. This endpoint reports current state, not a historical snapshot.
Example
curl https://distribu.app/api/v1/reports/inventory \
-H "Authorization: Bearer dk_..."
Response
{
"data": {
"totals": {
"productCount": 142,
"totalUnits": 8430,
"totalValue": 67231.25
},
"rows": [
{
"productId": "clxx1234...",
"name": "Widget Blue",
"sku": "WDG-001",
"category": "Hardware",
"stock": 42,
"unitPrice": 8.50,
"value": 357.00,
"isActive": true
}
],
"groups": [
{
"category": "Hardware",
"productCount": 38,
"totalUnits": 2105,
"totalValue": 18432.50
}
]
}
}
Notes
- Inactive products are excluded. If you set a product inactive, its stock drops out of this report.
value = stock × unitPrice— the current price, not a historical cost. This is a sell-side valuation, not cost-of-goods.groups[]breaks the catalog down bycategory, with an "Uncategorised" group for products that don't set one.stockmay be negative transiently during high-concurrency order bursts; treat negatives as zero for display purposes.
Rate limiting
These endpoints are on the same 60 req/min/key budget as the rest of the API — see API overview. Pulling a fresh sales report every minute is fine; pulling it every second is not.
What's not here (yet)
- Orders export endpoint. The
ORDERS_EXPORTscheduled-report type doesn't have an API equivalent. UseGET /api/v1/orderswith date-range query params to paginate instead. - Product-spend reports. No "top-selling product by revenue" endpoint today.
- Custom column selection. Each report's shape is fixed.
Next: Webhooks endpoints.
