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

ParamTypeDefaultDescription
presettoday / week / month / quarter / year / allmonthShorthand for common ranges.
fromISO 8601 dateOverride the preset start.
toISO 8601 dateOverride 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

  • revenue is 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 by status client-side.
  • daily[] is one row per day in the range, with 0s for days without activity — no gaps.
  • label is 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/salespreset, 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.
  • customerName is 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 by category, with an "Uncategorised" group for products that don't set one.
  • stock may 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_EXPORT scheduled-report type doesn't have an API equivalent. Use GET /api/v1/orders with 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.