---
title: "List top products"
method: GET
path: "/reports/top_products"
tags: ["Reports"]
---

# List top products

`GET /reports/top_products`

## Request

- Base URL: `https://api.privy.com/v1`
- URL: `https://api.privy.com/v1/reports/top_products`
- Auth: HTTP bearer

Retrieve your store's top products ranked by views or sales over a
30, 60, or 90 day window, or all-time where the platform supports it.

Entries are the connected store platform's product ids (e.g. the
numeric Shopify product id) ranked best-first. Each entry carries the
product `title`, resolved live from the connected store; it is `null`
when the store cannot supply it (no connected store, the product was
deleted, or the store API was unreachable). Full product details are
at `GET /v1/products/{id}`. Every response repeats this in an in-band
`note`.

Rankings are served from a daily-refreshed cache. `refreshed_at` is
the cache timestamp; `null` means this metric/window combination has
never been computed (as opposed to computed and empty) — the request
itself enqueues the background refresh that fills it, so retry after
a short wait.

Platform support differs by metric, and for `views` by window.
Windowed view rankings (`window_days` 30, 60, or 90) require a
connected Shopify store: dated product views are only tracked through
Shopify's Web Pixel, so those windows return an empty ranking on every
other platform. All-time view rankings (`window_days=0`) come from the
store platform's own view counter and are available for BigCommerce
only — BigCommerce exposes a per-product view count with no date
dimension, so it can be ranked all-time but never windowed. Shopify
has no such counter, so `window_days=0` returns an empty ranking for a
Shopify-only store; a store connected to both platforms is ranked from
its BigCommerce counter.

The all-time ranking is built by scanning the BigCommerce catalog, and
that scan is capped at 25,000 products. On a larger catalog it covers
the products scanned rather than the whole catalog, so treat it as
approximate there.

`sales` is derived from synced order data and works for any store that
syncs orders to Privy (Shopify, BigCommerce, Wix, and orders submitted
through the API). There is no all-time sales bucket, so
`metric=sales` with `window_days=0` is rejected.

**Required scope:** `reports_read`

## Query parameters

- `metric` 'views' | 'sales'
- `window_days` 0 | 30 | 60 | 90
- `page` integer
- `per_page` integer

## Headers

- `Privy-Account-Id` string

## Response `200`

The ranked product ids for the requested metric and window.

- object
  - `data` object[]
    - `rank` integer: 1-based rank, best first.
    - `product_id` string: The connected store platform's product id (e.g. the numeric Shopify product id).
    - `title` string, nullable: Product title resolved live from the connected store; `null` when the store cannot supply it.
  - `metric` 'views' | 'sales'
  - `window_days` 0 | 30 | 60 | 90: The window this ranking covers. `0` means all-time.
  - `refreshed_at` string, date-time, nullable: When this ranking was last computed. `null` if it has never been computed. For the rolling windows the request enqueues the refresh that fills it. The all-time views ranking is computed only for BigCommerce stores that have configured a most-viewed dynamic product block, so `null` there means either the store's platform produces no all-time view counts or no such block exists yet.
  - `note` string: Fixed caveat that entries are store product ids with live-resolved titles, and full details live at GET /v1/products/{id}.
  - `pagination` PaginationEnvelope
    - `page` integer
    - `per_page` integer
    - `total_count` integer
    - `total_pages` integer

## Other responses

- `401`: Missing or invalid bearer token.
- `403`: `insufficient_scope` when the token lacks the required scope for this endpoint.
- `422`: One or more fields failed validation.
- `429`: Rate limit exceeded. Retry after the specified time.

## Changes

- **2026-11-05** `cf0885e90d5a`: 8 warning
  - added the new `forbidden` enum value to the `error/code` response property for the response status `401`
  - added the new `forbidden` enum value to the `error/code` response property for the response status `403`
  - added the new `forbidden` enum value to the `error/code` response property for the response status `422`
  - added the new `forbidden` enum value to the `error/code` response property for the response status `429`
  - …4 more
- **2026-11-03** `fe500ab59b3b`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/privy/apis/privy-api/changes/reports/top_products/get.md)

---

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