Home / Blog / Amazon Best Sellers API Pagination: All 50 Ranks with count and offset

Amazon Best Sellers API Pagination: All 50 Ranks with count and offset

Why does the Amazon Best Sellers API return only 30 products, and how do you page through the rest? count and offset select a window of one 50-product page: count=50 gets the whole list for 1 credit, offset + count can never pass 50, and past rank 50 only subcategories add products. Tested Python with a credit cap and deduplication included.

October 30, 2026
6 min read
SellerMagnet Team
Share & Bookmark
Terminal running a Python script that checks Best Sellers API count and offset windows before sending them

The SellerMagnet /api/amazon-bestsellers endpoint returns up to 50 products per category, the first page of its Best Sellers chart. Pagination works with two parameters: count (default 30, maximum 50) and offset (default 0), which pick a window of that one page. offset + count can never pass 50, so a call without count returns ranks 1 to 30 and count=50 returns the whole list for 1 credit. Past rank 50, only subcategories give you more products.

Key Takeaways

  • The endpoint reads only the first page of a category's Best Sellers chart, 50 products: no page parameter, no rank past 50.
  • count defaults to 30, so a call without it silently stops at rank 30.
  • One count=50 call costs 1 credit, the same as a call for 5 products; offset only helps to split work.
  • count above 50, offset + count above 50 and non-integer values are billed 400s: check the window before you send it.
  • For more than 50 products, walk the category's subcategories from the free category tree and deduplicate ASINs across the lists.

How many products does the Best Sellers API return?

Each call reads the first page of one category's Best Sellers chart (the URL the API builds ends in ?pg=1), and that page holds 50 products: 30 that Amazon renders straight away and 20 more it loads as you scroll. The API fetches the second part only when your window reaches past rank 30, then hands back the slice you asked for. The table shows what common parameter combinations return and what they cost.

count and offset on one 50-product Best Sellers page.
ParametersRanks returnedCreditsNote
no count, no offset1 to 301the default hides ranks 31-50
count=501 to 501the whole page in one call
count=20&offset=3031 to 501only the lazy-loaded part
count=30&offset=30-1billed 400: offset + count > 50
count=51-1billed 400: Count Max: 50

Shape of a count=50 response (trimmed, illustrative values)

{
  "success": true,
  "data": {
    "categoryId": "electronics",
    "link": "https://www.amazon.com/gp/bestsellers/electronics?pg=1",
    "bestsellers": [
      {
        "rank": 1,
        "asin": "B0ELEC0001",
        "productTitle": "Illustrative product",
        "price": {"currency_symbol": "$", "currency_code": "USD", "currency_name": "United States Dollar", "price": 19.99},
        "reviewRating": 4.6,
        "reviewAmount": 48211
      }
    ]
  }
}

Why does my bestsellers call return only 30 products?

Because count defaults to 30. A request with just category_id and marketplaceId gets ranks 1 to 30 and says nothing about the other 20. Nothing in the response marks the list as cut short: the bestsellers array simply has 30 entries. Send count=50 whenever you want the full list. It costs the same single credit.

Terminal-theme table of Best Sellers API count and offset combinations with the ranks returned and the billed 400 cases
Every window lives inside the same 50 products; two combinations never get past the check.

How do count and offset work together?

offset skips that many ranks and count takes the next ones, always within the same 50. count=20&offset=30 returns ranks 31 to 50, count=10&offset=40 ranks 41 to 50. The server rejects count above 50 with "Count Max: 50", a window past rank 50 with "offset + count must be at most 50", and anything that is not a whole number (count at least 1, offset at least 0) with "count must be a whole number of at least 1" or the same message for offset. All of these come after the credit is taken. Before 19 Sep 2026, offset was accepted and ignored, so lists stored by older code always started at rank 1.

How do I get more than the top 50 Amazon best sellers?

Go down the category tree. Every subcategory has its own Best Sellers page with its own top 50, and the category IDs come from a free JSON file per marketplace (/static/data/<CC>.json, the same data the category explorer uses). The script below checks four windows without sending them, then fetches the full list of Electronics on amazon.com and of its first subcategories, stops at a credit cap and keeps each ASIN once. It reuses sellermagnet.py (ApiError, new_session) and retry.py (get_with_retry) from the Python quickstart.

top50.py - full lists for a category and its subcategories, with a credit cap

"""Amazon Best Sellers API pagination: the full top 50 of a category in one call, then more products from its
subcategories.

/amazon-bestsellers reads the first Best Sellers page of a category, and that page holds 50 products. count (default
30, max 50) and offset (default 0) pick a window of it: offset + count must stay at or under 50, so there is no rank
51, and a call without count returns ranks 1 to 30 only. Each request costs 1 credit, and the count/offset 400s come
after the credit is taken, so the window is checked here before anything is sent.
Helpers: sellermagnet.py and retry.py from https://sellermagnet-api.com/blog/amazon-product-api-python
"""
import json

import requests

from retry import get_with_retry
from sellermagnet import ApiError, new_session

MARKETPLACE = "ATVPDKIKX0DER"   # amazon.com
TREE_FILE = "US.json"           # the free category tree: https://sellermagnet-api.com/static/data/US.json
PARENT = "electronics"          # the list to start from; its direct subcategories follow
MAX_LISTS = 5                   # credit cap: each list is 1 credit, plus every billed 502/503 retry
MAX_FAILED = 2                  # stop after this many lists that keep failing
PAGE = 50                       # products on one Best Sellers page


def window_ok(count, offset=0):
    """The window rule the endpoint enforces with a billed 400."""
    return (isinstance(count, int) and isinstance(offset, int)
            and count >= 1 and offset >= 0 and offset + count <= PAGE)


def bestsellers(session, category_id, count=PAGE, offset=0):
    if not window_ok(count, offset):
        raise ValueError(f"count={count} offset={offset}: offset + count must stay within 1 to {PAGE}")
    data = get_with_retry(session, "amazon-bestsellers", category_id=category_id, marketplaceId=MARKETPLACE,
                          count=count, offset=offset)
    return data.get("bestsellers") or []


def find(nodes, category_id):
    """A node anywhere in the tree, matched case-insensitively like the endpoint does."""
    for node in nodes:
        if str(node.get("CategoryId")).lower() == category_id.lower():
            return node
        hit = find(node.get("Subcategories") or [], category_id)
        if hit:
            return hit
    return None


def gaps(items, count, offset):
    """Ranks the window should hold but no card carries, and cards without an ASIN (both are only set when found)."""
    ranks = {i["rank"] for i in items if isinstance(i.get("rank"), int)}
    missing = sorted(set(range(offset + 1, offset + count + 1)) - ranks)
    return missing, sum(1 for i in items if not i.get("asin"))


def main():
    for count, offset in ((50, 0), (20, 30), (30, 30), (51, 0)):
        print(f"window count={count:<2} offset={offset:<2} -> {'ok' if window_ok(count, offset) else 'not sent'}")

    with open(TREE_FILE, encoding="utf-8") as fh:
        tree = json.load(fh)
    parent = find(tree, PARENT)
    if parent is None:
        print(f"{PARENT}: not in {TREE_FILE}, not sent (the endpoint would answer a billed 400 Invalid CategoryId)")
        return
    lists = [(PARENT, parent["CategoryName"])]
    lists += [(c["CategoryId"], c["CategoryName"]) for c in parent.get("Subcategories") or []]

    session = new_session()
    best = {}                   # asin -> (best rank, list it came from)
    fetched = failed = 0
    print(f"\n{'category_id':<11}  {'list':<26}  {'items':>5}  {'new':>4}  note")
    for category_id, name in lists[:MAX_LISTS]:
        if failed >= MAX_FAILED:
            print(f"stopped after {MAX_FAILED} failed lists")
            break
        try:
            items = bestsellers(session, category_id)
        except ApiError as err:
            print(f"{category_id:<11}  {name[:26]:<26}  error {err}")
            if err.status in (401, 403):   # key or credits: every other list fails the same way
                break
            failed += err.status >= 500
            continue
        except (requests.ConnectionError, requests.Timeout) as err:
            print(f"{category_id:<11}  {name[:26]:<26}  error {type(err).__name__}")
            failed += 1
            continue
        fetched += 1
        missing, no_asin = gaps(items, PAGE, 0)
        new = 0
        for item in items:
            asin, rank = item.get("asin"), item.get("rank")
            if not asin:
                continue
            new += asin not in best
            if asin not in best or (rank or PAGE + 1) < (best[asin][0] or PAGE + 1):
                best[asin] = (rank, name)
        notes = [f"no card for rank {', '.join(map(str, missing[:3]))}{' ...' if len(missing) > 3 else ''}"
                 if missing else "", f"{no_asin} card(s) without ASIN" if no_asin else ""]
        note = "; ".join(n for n in notes if n)
        print(f"{category_id:<11}  {name[:26]:<26}  {len(items):>5}  {new:>4}  {note}".rstrip())

    left = max(0, len(lists) - MAX_LISTS)
    print(f"\n{len(best)} unique ASINs from {fetched} lists: {fetched} credits plus any billed retries"
          f"{f'; {left} more subcategories left out by MAX_LISTS' if left else ''}")


if __name__ == "__main__":
    main()

Output (illustrative data: 4 lists, a retried 502, a 500 and two incomplete lists)

window count=50 offset=0  -> ok
window count=20 offset=30 -> ok
window count=30 offset=30 -> not sent
window count=51 offset=0  -> not sent

category_id  list                        items   new  note
electronics  Electronics                    50    49  no card for rank 17; 1 card(s) without ASIN
281407       Accessories & Supplies         50    38
502394       Camera & Photo                 50    45
1077068      Car Electronics                48    48  no card for rank 21, 42
2811119011   Cell Phones & Accessories   error 500: An internal error occurred. Please try again shortly.

180 unique ASINs from 4 lists: 4 credits plus any billed retries; 14 more subcategories left out by MAX_LISTS

Reading the output

  • new counts ASINs not seen in an earlier list. Products from the Electronics top 50 show up again in their subcategory, so the unique total is below 50 per list.
  • no card for rank lists ranks the window should hold but no card carries. The API sets rank and asin only when it finds them on the card, and keeps identical cards once, so a list can come back short.
  • error 500 is an internal error: refunded, retried once by get_with_retry, then reported. Camera & Photo answered a 502 first; that attempt was billed and retried.
Terminal-theme flow from the free category tree to the parent list and its subcategory lists, deduplicated by ASIN
The tree is free; each list costs 1 credit, so the cap decides how deep you go.

What does paging through best sellers cost?

1 credit per request. A full list is one count=50 call, so a category plus N subcategories costs N + 1 credits. Add every billed retry: 502 and 503 answers are charged on each attempt, while a 500 without Retry-After is refunded. The window errors and an unknown category_id ("Invalid CategoryId") are billed 400s, which is why the script validates both before sending; a missing category_id or marketplaceId is a free 400. The category tree file costs nothing. The example run above used 5 credits: four lists plus the billed 502.

What a Best Sellers list cannot tell you

A list is a snapshot of one page at fetch time; per Amazon, Best Sellers lists update hourly. The price on a card is the price shown on that page (a range resolves to its low end), not the Buy Box of every variation, and price.price can be null. Category IDs are marketplace-specific: a numeric ID from the US tree is unknown on amazon.de. To find IDs, see Amazon category IDs for the bestsellers API; to spot newcomers between snapshots, see finding new products on bestseller lists.

Frequently Asked Questions

How many products does the Amazon Best Sellers API return?

Up to 50 per category. The endpoint reads the category's first Best Sellers page, which holds 50 products. Without a count parameter it returns 30; send count=50 to get all of them in one call.

Can I get rank 51 to 100 from the Best Sellers API?

Not from this endpoint. It reads only the first page of the chart (pg=1), offset + count must stay at or under 50, and there is no page parameter. For more products, request subcategories.

Is offset useful if count=50 already returns everything?

Mostly for splitting work: count=20&offset=30 returns ranks 31 to 50 only. Every call costs 1 credit whatever its size, so one count=50 call is cheaper than two smaller ones.

Is a count or offset error free?

No. count above 50, offset + count above 50 and values that are not whole numbers are rejected with a 400 after the credit is taken. Check the window in your code before sending.

Why do I get fewer than 50 products back?

Cards whose rank or ASIN the API could not read come back without that field, and identical cards are kept once. Count the ranks you received instead of assuming 50.

Bottom line: send count=50 for the whole list, use offset only to split a list you already planned, and walk subcategories when you need more than 50 products. The bestsellers endpoint page lists every field, and a free account includes 150 credits.

Related Articles

Continue exploring topics related to this article.

Back to Blog

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