To estimate how many units an Amazon product sells per month, call the SellerMagnet /api/amazon-product-search-estimated-sells endpoint with the ASIN and marketplace. It returns estimated_monthly_sales together with the product's current sales rank and root category. Where Amazon shows a bought in past month figure, the estimate is anchored on it; where it does not, a model built on sales rank, review count and listing age takes over. One request, one credit.
Key Takeaways
- estimated_monthly_sales is units per month for that ASIN on that marketplace - not revenue, not the family.
- Amazon's own 'bought in past month' figure anchors the estimate whenever Amazon publishes it for the product.
- Without it, the estimate comes from sales rank, reviews and listing age, and is damped for old listings and cut back for newly tracked ones.
- sales_rank is the last recorded root-category rank (null if none was ever recorded); category is the root category name.
- The endpoint covers the 11 marketplaces with recorded history; others get a free 400.
What does the sales estimate API return?
Four fields beside the ASIN. estimated_monthly_sales is the number to use; estimatedSells carries the same value under the endpoint's original name, kept for integrations that already read it. sales_rank is the last recorded rank in the root category - the last positive reading, so a product that dropped out of the rankings keeps its last known rank, and null only when no rank was ever recorded - and category is the root category's name.
Response from /api/amazon-product-search-estimated-sells (example values)
{
"success": true,
"data": {
"asin": "B0CLTBHXWQ",
"estimated_monthly_sales": 1218,
"estimatedSells": 1218,
"sales_rank": 15,
"category": "Videogiochi",
"marketplace_domain": "amazon.it"
}
}
| Field | Type | Meaning |
|---|---|---|
estimated_monthly_sales | integer | Estimated units sold in a month on this marketplace |
estimatedSells | integer | Same value, original field name |
sales_rank | integer or null | Last recorded root-category sales rank; null if never ranked |
category | string or null | Root category name in the marketplace language |
marketplace_domain | string | The storefront the estimate is for |
How is the estimate calculated?
Two paths. If Amazon publishes a bought in past month figure for the product, the estimate is anchored on it - that figure is rounded down by Amazon (1K+, 500+), so the estimate sits a little above it - within Amazon's rounding bucket, so 1K+ means 1,000 to 1,999. If there is no such figure, the estimate comes from the product's sales rank (BSR) history - the most recent rank readings, newest weighted highest - scaled by its review count and rating, then damped by listing age: a product listed more than a year ago earns less per rank than a fresh one, and a product that has been tracked for under 90 days is cut back hard (square-rooted) because its rank history is thin. The model has no per-category sales curves: the same rank is treated alike in every department, which is where most of its error comes from.

How accurate is an Amazon sales estimate?
As accurate as its best input. Where Amazon shows the monthly figure, the estimate is within the rounding of that figure. Where it does not, treat the number as an order of magnitude: good enough to sort a category into fast, medium and slow sellers, not good enough to plan inventory to the unit. You can check it yourself: the price history endpoint returns stats.monthlySoldHistory, Amazon's figure month by month, for any ASIN that has one.
estimate_check.py - the estimate next to Amazon's recorded monthly figure
import os
import requests
API = "https://sellermagnet-api.com/api"
KEY = os.environ["SELLERMAGNET_API_KEY"]
def get(endpoint: str, **params) -> dict:
resp = requests.get(f"{API}/{endpoint}", params={**params, "api_key": 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"{endpoint}: {resp.status_code} {body.get('message')}")
return body["data"]
asin, marketplace = "B0CLTBHXWQ", "APJ6JRA9NG5V4"
estimate = get("amazon-product-search-estimated-sells", asin=asin, marketplaceId=marketplace)
history = get("amazon-product-statistics", asin=asin, marketplaceId=marketplace)
sold = history["stats"].get("monthlySoldHistory") or [] # [["YYYY-MM", units], ...]
latest = sold[-1] if sold else None
print("estimate :", estimate["estimated_monthly_sales"], "units / month")
print("rank :", estimate["sales_rank"], "in", estimate["category"])
print("Amazon's figure:", f"{latest[1]} (month {latest[0]})" if latest else "not shown for this product")
Two credits, two questions
The estimate answers "how many per month, now"; the statistics call answers "what did Amazon report each month". For a one-off check of a handful of ASINs, pay both. For a research sweep over a category, the estimate alone is enough.
How do I rank a category by estimated sales?
Take the ASINs from a bestsellers or search call, estimate each, and sort. Fifty ASINs cost fifty credits plus the one list call; the loop below, reusing get() from the first script, writes a CSV you can open anywhere. Stay inside the per-key concurrency limit - the loop is sequential, so it does. Skip ASINs that fail validation before sending them - a malformed ASIN is a billed 400, an unknown one a billed 404.
rank_by_sales.py - estimate every ASIN of a list and sort
import csv
import re
# get() is the helper from estimate_check.py above
ASIN_RE = re.compile(r"^[A-Z0-9]{10}$")
def estimate_all(asins: list, marketplace: str) -> list:
rows = []
for asin in dict.fromkeys(a.strip().upper() for a in asins): # dedupe, keep order
if not ASIN_RE.match(asin):
continue # never send a malformed ASIN
try:
data = get("amazon-product-search-estimated-sells", asin=asin, marketplaceId=marketplace)
except RuntimeError as err:
print("skip", err) # 404: not listed there
continue
rows.append({"asin": asin, "units": data["estimated_monthly_sales"],
"rank": data["sales_rank"], "category": data["category"]})
return sorted(rows, key=lambda r: r["units"], reverse=True)
rows = estimate_all(["B0CLTBHXWQ", "B0CL61F39H"], "APJ6JRA9NG5V4")
with open("category_by_sales.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["asin", "units", "rank", "category"])
writer.writeheader()
writer.writerows(rows)

Which marketplaces and errors should I expect?
Estimates need recorded history, which exists for amazon.com, .ca, .com.mx, .com.br, .co.uk, .de, .fr, .it, .es, .in and .co.jp. Any other marketplace ID returns HTTP 400 not available for the requested marketplace before a credit is taken. A malformed ASIN returns a billed 400 Invalid product ASIN; an ASIN unknown on that marketplace a billed 404. If the estimate itself cannot be computed, the answer is a 500 and the credit is refunded - the error-handling guide has the full table.
Frequently Asked Questions
How can I estimate Amazon sales from BSR with an API?
Call /api/amazon-product-search-estimated-sells with the ASIN and marketplace. It returns estimated_monthly_sales, using Amazon's bought-last-month figure when shown and a sales rank (BSR) model otherwise.
Is the estimate units or revenue?
Units per month for that ASIN on that marketplace. Multiply by the Buy Box price from a product lookup for revenue.
Why is the estimate different from what I see on the product page?
Amazon rounds its figure down (1K+, 500+); the estimate sits slightly above it. Products without a figure use a rank model, which is an order of magnitude, not a count.
Does the estimate cover a whole variation family?
The request is per child ASIN. Amazon's bought-last-month figure, where shown, may already cover the family, so do not sum children that share it.
Which marketplaces are supported?
The 11 with recorded history: US, CA, MX, BR, UK, DE, FR, IT, ES, IN and JP. Others receive a free 400.
Bottom line: one credit per ASIN gives you units per month plus rank and category, anchored on Amazon's own figure whenever it exists. The sales estimate page shows the endpoint, and a free account includes 150 credits - enough to rank two categories of fifty.