---
title: "Get the Purchases By Product report"
method: GET
path: "/public/v1/reports/purchases-by-product"
tags: ["PurchasesByProduct"]
---

# Get the Purchases By Product report

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

## Request

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

Returns one row per purchased product, aggregating every non-draft purchase whose date falls in the reported range. Each row carries the product's total quantity purchased and total purchased amount (each purchase item's quantity times its price, summed and rounded to 2 decimal places), alongside the product's current descriptive attributes (SKU, unit type, category, subcategory, group, vendor, owner, unit cost, sale price, and wholesale price). The attribute columns reflect the product as it stands now, not as it was at purchase time.

This is a read-only aggregate. It does not touch inventory, compliance (Metrc/BioTrack), or any purchase records — call it to analyze historical purchasing, not to reconcile a single purchase (use the purchases endpoints for that).

Date filtering, and the range echoed back under `meta`, are interpreted in the account's configured time zone. When no date filter is provided, the report defaults to the last 30 days. Draft purchases are always excluded; only official purchases are counted.

Every value is returned as it appears in the report's CSV export, with numeric cells returned as strings (currency and comma formatting stripped), matching the rest of the API. Any Product custom fields configured for the company are appended as extra columns (their keys vary per company). 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.

Required permission: `reports_permissions_purchases_by_product`.

## Query parameters

- `order_datetime` string
- `search` string
- `owner_ids` string[]
- `location_ids` string[]

## Response `200`

The Purchases By Product report

- PurchasesByProductReport: The Purchases By Product report
  - `data` PurchasesByProductReportRow[], required: One row per purchased product matching the filters. Empty array when no non-draft purchases fall in the range.
    - `category` string: The product's current category name, or null when the product is uncategorized.
    - `group` string: The product's current group name, or null when the product has no group.
    - `name` string, required: The product name. Always present. When the product is inactive, its name is returned with an inactive marker prepended so it reads as archived.
    - `owner` string: Full name of the user who owns the product, or null when the product has no owner. This is the product's owner and is distinct from the `owner_ids` request filter, which narrows by each purchase's assigned owner (sales rep).
    - `product_id` string, required: The product's ID — the `id` [GET /public/v1/products](#get-products) returns.
    - `quantity_purchased` string, required: Total quantity purchased across all non-draft purchases of this product in the reported range, expressed in the product's `unit_type`. Always a number and never null (the row only exists because at least one purchase item matched).
    - `sale_price` string, required: The product's current sale price as a string. Always present and never null. Reflects the product now, not any historical purchase.
    - `sku` string, required: The product SKU. Always present, but an empty string (not null) when the product has no SKU set.
    - `subcategory` string: The product's current subcategory name, or null when none is set.
    - `total_purchased` string, required: Total purchased amount over the range: each purchase item's quantity times its price, rounded per item to 2 decimals, then summed across the matching purchases. A number in the account's currency, always present and never null.
    - `unit_cost` string: The product's current unit cost as a string, or null when no unit cost is set. Reflects the product as it stands now, not the cost recorded on any individual purchase in the range.
    - `unit_type` string, required: The product's unit type, returned verbatim as its configured display name (for example `Grams`, `Each`, or a company-defined unit). This is NOT a normalized SCREAMING_CASE enum — it is the label as it appears in the CSV export, so match on it case-sensitively and expect company-specific values. Always present.
    - `vendor` string: The product's current vendor name, or null when no vendor is assigned. This is the product's own vendor attribute, unrelated to which purchases were aggregated.
    - `vendor_id` string, required: The product's vendor's company ID — the `id` [GET /public/v1/companies](#get-companies) returns.
    - `wholesale_price` string: The product's current wholesale price as a string, or null when no wholesale price is set. Reflects the product now, not any historical purchase.
  - `meta` PurchasesByProductReportMeta, required: Report-level metadata returned alongside the report rows.
    - `columns` PurchasesByProductReportColumn[], required: Ordered definitions of every column in the report, including any per-company custom-field columns.
      - `key` string, required: The key this column uses in each data row (e.g. `quantity_purchased`). Matches the property names on a report row.
      - `label` string, required: The human-readable column header as shown in the CSV export (e.g. `Quantity Purchased`).
    - `date_range` string, required: Human-readable description of the date range the report resolved to, in the account's time zone (e.g. `Last 30 Days`, or an explicit range when `order_datetime` was supplied). Reflects the effective range, including the 30-day default when no filter was given.
    - `report` string, required: Stable identifier of this report, always `purchases_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/purchases-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)
