Home / Blog / Amazon Sales Rank History API: BSR Over Time for Any ASIN as JSON

Amazon Sales Rank History API: BSR Over Time for Any ASIN as JSON

Get an ASIN's Best Sellers Rank history as JSON: the root-category series with timestamps, the per-category series, the current rank and the category path. How to read gaps and -1 entries, turn the series into a daily table and a 30-day trend, and pair it with monthly sales.

September 20, 2026
4 min read
SellerMagnet Team
Share & Bookmark
Blueprint flow from an ASIN through the statistics API to sales rank series, a daily table and a trend

To get an Amazon product's sales rank history as JSON, call the SellerMagnet /api/amazon-product-statistics endpoint with the ASIN and marketplace. The response carries stats.salesRankHistory, the Best Sellers Rank in the root category over time with full timestamps, stats.salesRank, one daily series per category the product is ranked in, the current bestSellerRank, and the category path. Prices, ratings and monthly sales come in the same response for the same credit.

Key Takeaways

  • stats.salesRankHistory is the root-category BSR as [timestamp, rank] pairs; stats.salesRank has one [date, rank] series per category.
  • A rank of -1 means the product was unranked at that moment - drop it, do not average it.
  • bestSellerRank is the current root-category rank; '-' when there is none.
  • Lower is better: build trends on the median of the last 30 ranked days, not on single readings.
  • graphs=true adds a ready-made PNG of the rank series for the same credit, when the series has at least one rank.

What does the sales rank history look like?

Two series and a summary. salesRankHistory is the product's main Best Sellers Rank - the one Amazon lists first, which is the root category for almost every product - sampled whenever the rank was recorded, with the time of day. salesRank is a dictionary keyed by category name (in the marketplace language) with a [date, rank] series per category, cut to the day; several readings can share a date, so keep the last one. rootCategory and categoryTree give the path from root to leaf. The history is already recorded back to trackingSince: one call returns it, no polling job of your own is needed to build it.

Rank fields of /api/amazon-product-statistics for a PlayStation 5 on amazon.it (trimmed)

{
  "success": true,
  "data": {
    "asin": "B0CLTBHXWQ",
    "bestSellerRank": 15,
    "rootCategory": {"id": 412603031, "name": "Videogiochi"},
    "categoryTree": [
      {"catId": 412603031, "name": "Videogiochi"},
      {"catId": 20904349031, "name": "PlayStation 5"},
      {"catId": 20904364031, "name": "Console"}
    ],
    "trackingSince": "2023-12-18",
    "stats": {
      "salesRankHistory": [["2025-06-12 09:14:00", 21], ["2025-06-13 02:10:00", -1], ["2025-06-14 01:58:00", 15]],
      "salesRank": {
        "Videogiochi": [["2025-06-12", 21], ["2025-06-13", -1], ["2025-06-14", 15]],
        "Console": [["2025-06-12", 2], ["2025-06-14", 1]]
      },
      "monthlySoldHistory": [["2025-05", 1000], ["2025-06", 1000]]
    }
  }
}
Rank-related fields and how to read them.
FieldShapeNotes
stats.salesRankHistory[["YYYY-MM-DD HH:MM:SS", rank], ...]Root category, with time of day; -1 = unranked at that moment
stats.salesRank{category name: [["YYYY-MM-DD", rank], ...]}One series per category, cut to the day; may contain -1; several entries can share a date - keep the last
bestSellerRankinteger or "-"Current root rank; if the latest reading is -1, falls back to the previous reading when that one is at most two days earlier, otherwise "-"
rootCategory / categoryTreeobject / listRoot category and the path to the leaf category
stats.monthlySoldHistory[["YYYY-MM", units], ...]Amazon's 'bought in past month', last value per month

Why does the series contain -1 and gaps?

A -1 is a recorded moment at which the product had no rank - out of stock, suppressed, or freshly listed. Gaps are simply times with no recording. Neither is a rank: averaging -1 into a trend drags it towards zero, which reads as "best seller". Drop the -1 readings, keep the last value per day, and compare periods on the median, which shrugs off a single spike from a lightning deal.

rank_trend.py - daily table and a 30-day trend from the root series

import os
import statistics

import requests


def rank_history(asin: str, marketplace_id: str) -> dict:
    resp = requests.get("https://sellermagnet-api.com/api/amazon-product-statistics",
                        params={"asin": asin, "marketplaceId": marketplace_id,
                                "api_key": os.environ["SELLERMAGNET_API_KEY"]}, timeout=90)
    try:
        body = resp.json()
    except ValueError:  # e.g. an HTML error page from a proxy
        body = {}
    if resp.status_code != 200 or not body.get("success"):
        raise RuntimeError(f"{asin}: {resp.status_code} {body.get('message')}")
    return body["data"]


def daily_ranks(series: list) -> dict:
    """[[timestamp, rank], ...] -> {date: rank}; -1 (unranked) dropped, last reading per day kept."""
    out = {}
    for stamp, rank in series:
        if isinstance(rank, (int, float)) and rank > 0:
            out[stamp[:10]] = int(rank)
    return out


def trend(daily: dict, days: int = 30) -> dict:
    dates = sorted(daily)
    recent, earlier = dates[-days:], dates[-2 * days:-days]
    now = statistics.median(daily[d] for d in recent) if recent else None
    before = statistics.median(daily[d] for d in earlier) if earlier else None
    return {"median_now": now, "median_before": before,  # windows are ranked DAYS, not calendar days
            "change_pct": round((now - before) / before * 100, 1) if now and before else None}


data = rank_history("B0CLTBHXWQ", "APJ6JRA9NG5V4")
daily = daily_ranks(data["stats"]["salesRankHistory"])
print("current:", data.get("bestSellerRank"), "in", data["rootCategory"]["name"])
print("days with a rank:", len(daily), "since", data.get("trackingSince"))
print("30-day trend:", trend(daily))   # negative change_pct = rank improved
Blueprint line chart of an example root-category sales rank improving from 42 to 15 over 30 days, rank 1 at the top
Thirty days of root-category rank; the unranked -1 readings are left out of the line.

Which category's rank should I track?

The root category rank (salesRankHistory, bestSellerRank) is comparable across products of the same department and is what most tools call BSR. The leaf category ranks in salesRank ("Console": 1) are what shoppers see as #1 in Console and are easy to win in a small niche. Track the root for demand, the leaf for badges. Note that salesRank is keyed by name in the marketplace language - Videogiochi on amazon.it, Video Games on amazon.com - so key your storage by the catId from categoryTree if you compare marketplaces.

Blueprint table of the rank fields: root series with timestamps, per-category daily series, current rank and category path
Root for demand, leaf for badges; both come from the same call.

How do I turn rank into sales?

Rank is relative: #15 in Videogiochi means fifteen products sold more recently, not a number of units. For units, read stats.monthlySoldHistory from the same response - Amazon's bought in past month figure - or call the sales estimate endpoint, which uses that figure where it exists and a rank model where it does not. For the live category leaderboard around your product, the bestsellers endpoint lists the top 50 of any category ID.

Current rank, category and last month's units with curl and jq

curl -sG "https://sellermagnet-api.com/api/amazon-product-statistics" \
  --data-urlencode "asin=B0CLTBHXWQ" \
  --data-urlencode "marketplaceId=APJ6JRA9NG5V4" \
  --data-urlencode "api_key=$SELLERMAGNET_API_KEY" \
  | jq -r '.data | [.bestSellerRank, .rootCategory.name, (.stats.monthlySoldHistory | last | .[1])] | @tsv'

Marketplaces and billing

Recorded history exists for amazon.com, .ca, .com.mx, .com.br, .co.uk, .de, .fr, .it, .es, .in and .co.jp; other marketplace IDs get a free 400. A malformed ASIN is a billed 400, an unknown one a billed 404. Timestamps are UTC.

Frequently Asked Questions

How do I get Amazon sales rank history with an API?

Call /api/amazon-product-statistics with the ASIN and marketplace and read stats.salesRankHistory (root category, timestamped) and stats.salesRank (one daily series per category).

What does a sales rank of -1 mean?

The product had no rank at that moment - out of stock, suppressed or new. Drop those readings before averaging; bestSellerRank shows '-' instead.

How far back does the rank history go?

To trackingSince, the date recording started for that ASIN. There is no date-range parameter; filter on your side.

Is BSR the same across categories?

No. The root-category rank is what most tools call BSR; leaf-category ranks are the badges shoppers see, and each series is separate in stats.salesRank.

Can I get the rank history as a chart?

Yes. Add graphs=true and data.graphs.salesRankHistory holds a PNG URL for the same single credit; the key is absent when the series holds no rank at all.

Why is the sales rank history empty for my ASIN?

Child ASINs of a variation family often carry no rank of their own; the family's rank sits on the parent listing. An empty series with bestSellerRank '-' usually means exactly that.

Bottom line: one call returns the root and per-category rank series, drop the -1 readings, trend on medians, and read units from monthlySoldHistory beside it. The sales rank history page shows the endpoint, and a free account includes 150 credits.

Ready to Extract Amazon Data at Scale?

Start building with SellerMagnet API today. Real-time product data, competitive pricing, and review analytics at your fingertips.

150 free API credits • No credit card required • Cancel anytime