---
title: "Get the Sales By Product report"
method: GET
path: "/public/v1/reports/sales-by-product"
tags: ["SalesByProduct"]
---

# Get the Sales By Product report

`GET /public/v1/reports/sales-by-product`

## Request

- Base URL: `https://app.distru.com`
- URL: `https://app.distru.com/public/v1/reports/sales-by-product`
- Auth: HTTP bearer

Returns one row per product with its quantity sold and total sales over the reported date range, alongside the product's descriptive attributes (SKU, unit type, category, subcategory, group, vendor, owner, unit cost, sale price, wholesale price, shipped-from license, and UPC). Quantity sold and total sales are aggregated from sales order items over the date range and are net of returns, so a product with more returns than sales can report negative figures. This is a read-only aggregate report; it does not touch inventory, compliance (Metrc/BioTrack), or any order records.

Use this to pull period sales performance broken down by product (for example a monthly or quarterly sell-through summary). It is not the endpoint for line-item detail — it collapses every matching order item into a single per-product total, so you cannot see which orders contributed. Filters combine with AND (a row must satisfy all supplied filters); within a single multi-value filter the values combine with OR.

Date range: when `order_datetime` is omitted the report covers the last 30 days, resolved in the requesting user's company timezone. Canceled orders are excluded unless the `status` filter explicitly lists `CANCELED`.

Response shape: each row is returned as it appears in the report's CSV export. Cells that look numeric (money, quantities) are returned as strings (currency and percent formatting stripped) so they match the rest of the API; everything else is a string too. An optional product attribute the product doesn't have comes back as `null` (the one exception is `sku`, which is always a string and is empty rather than null when unset). Because every numeric cell is a string, an all-digit identifier such as a numeric `sku` or `upc` comes back as a string, and one that carries a significant leading zero keeps its full display string so the zero isn't lost. Any Product custom fields configured for the company are appended as extra columns keyed by the field's slug, so rows for such companies carry additional keys beyond the documented ones. Report-level information (the resolved human-readable date range and the ordered column definitions) is returned under `meta`.

Each row also carries the IDs of the records it names — `product_id` and `vendor_id`, each right after its name column — so a row can be followed to the product or company endpoints without a name lookup. The CSV export leaves these ID columns out.

Requires the "view the sales by product report" permission.

## Query parameters

- `status` string[]
- `order_datetime` string
- `search` string
- `customer_ids` string[]
- `exclude_customer_ids` string[]
- `owner_ids` string[]
- `location_ids` string[]
- `user_ids` string[]
- `shipped_from_license_ids` string[]

## Response `200`

The Sales By Product report

- SalesByProductReport: The Sales By Product report
  - `data` SalesByProductReportRow[], required: The report rows
    - `category` string, required: The product's category name. Always present (every product is categorized).
    - `group` string: The product's group name, or null when the product has none.
    - `name` string, required: The product name. Inactive products are prefixed to mark them as such.
    - `product_id` string, required: The product's ID — the `id` [GET /public/v1/products](#get-products) returns.
    - `product_owner` string: The product owner's full name, or null when no owner is set.
    - `quantity_sold` string, required: Units sold over the date range, net of returns. Can be negative when returns exceed sales.
    - `sale_price` string, required: The product's sale price. Always present.
    - `shipped_from_license` string: The license number the sold items shipped from, or null when none applies.
    - `sku` string, required: The product SKU. Always present; an empty string when the product has no SKU. A purely numeric SKU is still returned as a string; one with a significant leading zero keeps its full display string so the zero isn't lost.
    - `subcategory` string: The product's subcategory name, or null when none is set.
    - `total_sales` string, required: Total sales value over the date range, net of returns. Can be negative when returns exceed sales.
    - `unit_cost` string: The product's unit cost, or null when no cost is recorded.
    - `unit_type` string, required: The product's unit type name. Always present.
    - `upc` string: The product's UPC, or null when the product has no UPC. A purely numeric UPC is still returned as a string; one with a significant leading zero keeps its full display string so the zero isn't lost.
    - `vendor` string, required: The product's vendor (supplier) company name. Always present.
    - `vendor_id` string, required: The vendor's company ID — the `id` [GET /public/v1/companies](#get-companies) returns.
    - `wholesale_price` string: The product's wholesale price, or null when no wholesale price is set.
  - `meta` SalesByProductReportMeta, required: Report-level metadata
    - `columns` SalesByProductReportColumn[], required: The report's column definitions in order, including any appended Product custom-field columns. Use these keys to read each data row.
      - `key` string, required: The key this column appears under in every data row (the label slugified to lower_snake_case, e.g. `quantity_sold`). Custom-field columns key off the field's slug.
      - `label` string, required: The human-readable column heading, e.g. `Quantity Sold`.
    - `date_range` string, required: The resolved date range the report covers, as a display string (reflects the last-30-days default when `order_datetime` was omitted).
    - `report` string, required: The report identifier, always `sales_by_product`.

## Other responses

- `400`: Invalid parameters
- `401`: Missing or invalid API token
- `403`: The API token lacks the required permission, or team restrictions hide the record

## Changes

- **2026-10-07** `e8639f7dafed`: 2 info
  - added the required property `data/items/product_id` to the response with the `200` status
  - added the required property `data/items/vendor_id` to the response with the `200` status

[Operation history](https://skmtc.dev/distru/apis/distru-api/changes/public/v1/reports/sales-by-product/get.md)

---

[API](https://skmtc.dev/distru/apis/distru-api.md) - [All operations](https://skmtc.dev/distru/apis/distru-api/llms.txt) - [OpenAPI document](https://skmtc.dev/distru/apis/distru-api/revisions/9d851a88e82f?raw)
