Skip to content

ChocoData-com/amazon-scraper

Repository files navigation

Amazon Scraper

Amazon Scraper

Amazon Scraper for extracting product prices, titles, ASINs, ratings, images, sellers and Best Sellers ranks from Amazon.com. This repo has a free Amazon web scraping script you can run right now, and an Amazon product data API with 3 endpoints returning real structured JSON.

Last updated: 2026-07-20. Working against Amazon.com as of July 2026, and re-verified whenever Amazon changes their markup.

Every JSON block on this page was captured from the live API on 2026-07-20. Where a block is trimmed, whether an array cut short or a subset of fields, the text above it says exactly what was cut and the values shown are verbatim. The full uncut samples are committed in amazon_scraper_api_data/, and every code example calls the API and is runnable from amazon_scraper_api_codes/.

pip install requests
export CHOCODATA_API_KEY="your_key"     # free: 1,000 requests, one-time, no card
python amazon_scraper_api_codes/search.py

Those three lines return a page of product cards, live from Amazon.com: 16 organic results on the run captured here, plus a sponsored block that varies call to call. Here is one card, trimmed to 9 of its 35 fields:

{
  "asin": "B09S3HNMHF",
  "title": "Samsung 14\" Galaxy Chromebook Go Laptop PC Computer, Intel Celeron N4500 Processor, 4GB RAM, 64GB Storage, ChromeOS, XE340XDA-KA2US, Student Laptop, Silver",
  "price": 222.09,
  "currency": "USD",
  "rating": 4.3,
  "reviews_count": 673,
  "organic_position": 3,
  "is_sponsored": false,
  "is_prime": false
}

...multiplied across all 22 cards, with all 35 fields on each:

Retrieved Amazon product data

That is the whole point of this repo. The rest of this page is the free script, what Amazon does to it, and the three endpoints with the parameters and response for each.


Contents


Free Amazon Scraper

Amazon server-renders its search results into HTML, so you can extract product data from Amazon search pages without a headless browser or JavaScript rendering. No key, no cost:

python free_scraper/amazon_free_scraper.py "standing desk"

Source: free_scraper/amazon_free_scraper.py. It parses every div[data-component-type="s-search-result"] card and emits asin, title, price, rating, url.

This is what it gives you:

Free Amazon scraper returning product data

That run returned 60 product cards with five fields on each. The next section is what happens when you run it repeatedly, and where that ceiling of five fields starts to matter.

Avoid getting blocked when scraping Amazon

Amazon serves a plain HTTP client more often than the vendor blogs suggest, so it is worth stating what actually happens rather than repeating the folklore. We ran the script above from one residential IP on 2026-07-20, changing the search term on every request so nothing came from a cache, in two arms of 12:

Arm Gap between requests Full pages Empty responses
Paced 60 seconds 9 of 12 3
Rapid 5 seconds 11 of 12 1

All 24 requests returned HTTP 200, and pacing did not help: the rapid arm scored better than the paced one. 20 of the 24 returned a full page of 60 product cards, between 1.70 MB and 3.09 MB.

The other 4 are the ones to design around:

What came back Status Size <title> Cards
A real results page 200 1.70 to 3.09 MB the search term 60
An empty shell 200 ~2.2 KB &nbsp; 0

Earlier the same day, from the same IP and the same script, we also saw a third state: HTTP 503, 2,671 bytes, titled Sorry! Something went wrong!, whose body opens To discuss automated access to Amazon data please contact and then gives an Amazon support address. That state did not reproduce during the controlled run above.

Free Amazon scraper returning an empty 200

So Amazon's answer to the same code, from the same machine, on the same day, moved between three different behaviours. The 503 announces itself in the status code, but the full page and the empty shell are both 200 and cannot be told apart by status code (a resp.ok check catches the 503 and misses the empty shell, which is the one that quietly loses you data).

What bites you Why What it costs you
A 200 is not a page 4 of 24 responses were HTTP 200 at ~2.2 KB with zero cards and a &nbsp; title. A real page is 1.70 to 3.09 MB. A scraper that checks resp.ok records these as "no results for this term". That is silent data loss, and it looks like a genuine finding in your database.
The behaviour moves over the day Three different outcomes from identical code and headers within a few hours, including a 503 phase that later stopped reproducing. You cannot test your way to confidence. The run that passes at 2pm tells you nothing about 2am.
It is not a header problem Every request above sent a Chrome User-Agent and Accept-Language. Served and empty responses used byte-identical headers. Header tweaking is not the fix, so time spent there is wasted.
Sponsored rows move the ranking under you Six identical searches for laptop returned 22, 22, 16, 16, 22 and 22 cards. The organic count was 16 every single time; the swing is entirely sponsored placements appearing or not. If you rank by array index you are ranking Amazon's ad auction. Key on organic_position instead.
The result set is not stable Across those same six runs, only 8 ASINs appeared in all six, out of 41 distinct ASINs seen. A single pull is a sample, not a census. Dedupe by ASIN across runs before you compute share or rank.
Prices are per-marketplace The same ASIN returns a different price and currency on each domain. Uncontrolled marketplace selection gives you numbers you cannot compare to each other.

There is also a ceiling on what the search page can tell you, whatever your success rate against it. A card carries the ASIN, title, price and rating. It does not carry the Buy Box, the seller, the offer count, the star distribution, the category ladder or the Best Sellers Rank, because Amazon does not render those on a search result. Those live on the product page, which is the next section.

Free scraper versus the Chocodata Amazon Scraper API

The popular free Amazon scrapers on GitHub inherit this same unreliability, because they fetch Amazon directly the way the script above does. Checked on 2026-07-20: tducret/amazon-scraper-python (878 stars) was last pushed in October 2020, scrapehero-code/amazon-scraper (437 stars) in June 2023, and drawrowfly/amazon-product-api (767 stars) in July 2024 with 55 open issues. Several of the maintained alternatives are vendor wrappers rather than scrapers: the four Oxylabs Amazon repos we checked all post to realtime.oxylabs.io with a username and password, so they need a paid key before they return anything.


Using the Chocodata Amazon Scraper API

The Chocodata Amazon Scraper API is the managed option, and the one this repo is built around. It returns the same search cards with 35 fields instead of 5, adds the product and Best Sellers surfaces with the Buy Box and Best Sellers Rank on them, and gives an empty result a different status code from a failed one so the two cannot be confused. Three endpoints for Amazon product data extraction at scale, 20 marketplaces, a ~99% success rate against the bot check, and no proxy management. Free for the first 1,000 requests.


Amazon Scraper API reference

Below is the Amazon Scraper API reference: authentication, the global parameters, errors and rate limits, then each of the three endpoints with its parameters and response.

Quickstart

curl "https://api.chocodata.com/api/v1/amazon/search?api_key=YOUR_KEY&query=laptop"
import requests

r = requests.get(
    "https://api.chocodata.com/api/v1/amazon/search",
    params={"api_key": "YOUR_KEY", "query": "laptop"},
    timeout=90,
)
top = r.json()["products"][0]
print(top["asin"], top["price"], top["rating"])
# B0GZZRM5XP 1339 4.2

After running the command, your terminal should look something like this:

Running the Amazon Scraper API search endpoint

Get a key at chocodata.com (1,000 requests, one-time, no card).

Authentication

Pass api_key as a query parameter on every request. That is the whole auth model.

Global parameters

Accepted by every endpoint below:

Param Type Required Default Description
api_key string yes - Your Chocodata API key.
domain enum no com Amazon marketplace to query. One of com, co.uk, de, fr, it, es, nl, pl, se, ca, com.mx, com.br, com.au, co.jp, sg, in, com.tr, ae, sa, eg. Price and currency are localised to the marketplace, so pin this for comparable pricing. Prices verified against com (USD), co.uk (GBP), ca (CAD), com.au (AUD), com.mx (MXN), de / fr / it / es / nl (EUR), pl (PLN), se (SEK), com.br (BRL) and com.tr (TRY).

Everything else is per-endpoint and documented in the endpoint's own table below.

A basic request costs 5 credits (= 1 request). render_js raises it to 15, and screenshot adds 10 on top. Responses are billed only on success (2xx).

Errors

Status error code Meaning Billed What to do
400 invalid_params A param is missing or the wrong shape, including an ASIN that is not 10 alphanumeric characters. Body lists each issue with its path. no Fix the query string.
401 INVALID_API_KEY Key missing, unrecognised, or revoked. no Check api_key. Get one at chocodata.com.
402 INSUFFICIENT_CREDITS Balance exhausted. no Top up or upgrade.
404 product_not_found The ASIN is well formed but Amazon returned 404: delisted, or never listed on that marketplace. retryable: false. no Fix the ASIN, or try another domain. Retrying will not help.
429 RATE_LIMITED Over 120 requests/60s, or over your plan's concurrency. no Back off and retry; see Rate limits.
502 target_unreachable, extraction_failed Amazon refused this attempt, or served a page the parser could not read. retryable: true. no Retry after ~10s.

Two response shapes exist: auth and billing errors nest under error.code (uppercase), while scrape-layer errors are flat with a lowercase error string.

A bad key, verbatim:

curl "https://api.chocodata.com/api/v1/amazon/product?api_key=totally_invalid_key_123&query=0143127748"
{"error":{"code":"INVALID_API_KEY","message":"Api key not recognised."}}

A malformed ASIN tells you which parameter failed and why, verbatim:

{"error":"invalid_params","issues":[{"validation":"regex","code":"invalid_string","message":"ASIN must be 10 alphanumeric chars","path":["query"]}]}

The scripts in this repo map the documented statuses onto actionable messages, so a typo'd key does not hand you a stack trace:

Amazon Scraper API error handling

Rate limits and concurrency

Two limits apply at once, and the one that binds first is usually the rate limit, not the concurrency cap.

Rate limit: 120 requests per 60 seconds, per key (sliding window). That is a hard ceiling of 2 requests/second sustained, whatever your plan.

Concurrency: how many requests you may have in flight at once, which varies by plan:

Plan Concurrent requests
Free 10
Vibe 30
Pro 50
Custom 100 to 500+

Exceed either and you get 429, not a queue. Every endpoint is a synchronous GET: there is no webhook, callback, or async job to poll. Requests are usually a few seconds (see Measured latency), which is why the examples use timeout=90.

Sizing, and note which limit actually binds. At Pro you get 50 concurrent slots, and at a ~3s median search those slots could in principle turn over 16 requests/second. They cannot: the 120/60s rate limit caps you at 2 requests/second first. So plan against 120 requests/minute. Keep your pool at or under your plan's concurrency and pace it to the rate limit:

from concurrent.futures import ThreadPoolExecutor
import requests

def one(q):
    r = requests.get("https://api.chocodata.com/api/v1/amazon/search",
                     params={"api_key": KEY, "query": q}, timeout=90)
    return q, (r.json().get("products", []) if r.ok else [])

queries = ["laptop", "monitor", "keyboard", "webcam", "ssd"]
with ThreadPoolExecutor(max_workers=10) as pool:   # <= your plan's concurrency
    for q, products in pool.map(one, queries):
        print(q, len(products))

1. Search: product listings, prices and ASINs

Ranked Amazon search result cards for a keyword, with organic and sponsored positions tracked separately, price, rating, Prime status and delivery lines.

Param Type Required Default Description
query string yes - Search keywords.
url string (URL) no - Alternative input: a full Amazon /s?k= search URL, fetched as supplied.
sort_by enum no best_match One of best_match, price_asc, price_desc, avg_customer_review, newest.
start_page int (>=1) no 1 Result page to fetch. Pages 1, 2 and 3 of one query returned 151 distinct ASINs between them, so paging does surface new products rather than repeat page 1.
curl "https://api.chocodata.com/api/v1/amazon/search?api_key=YOUR_KEY&query=laptop"

Real response. products is cut to 1 of 22; the product object itself is complete, all 35 fields present and verbatim except the one long more_buying_choices_link URL, truncated where marked (full sample):

{
  "page": 1,
  "products": [
    {
      "asin": "B09S3HNMHF",
      "title": "Samsung 14\" Galaxy Chromebook Go Laptop PC Computer, Intel Celeron N4500 Processor, 4GB RAM, 64GB Storage, ChromeOS, XE340XDA-KA2US, Student Laptop, Silver",
      "url": "https://www.amazon.com/SAMSUNG-Chromebook-Computer-Lightweight-12-Hour-Battery/dp/B09S3HNMHF",
      "price": 222.09,
      "price_strikethrough": null,
      "currency": "USD",
      "rating": 4.3,
      "reviews_count": 673,
      "sales_volume": "5K+ bought in past month",
      "image": "https://m.media-amazon.com/images/I/51Lko54--JL._AC_UY218_.jpg",
      "is_prime": false,
      "is_amazons_choice": true,
      "is_sponsored": false,
      "best_seller": false,
      "organic_position": 3,
      "sponsored_position": null,
      "shipping_information": "Join Prime to get FREE delivery Wed, Jul 22",
      "highest_price": null,
      "pricing_count": null,
      "manufacturer": null,
      "is_video": false,
      "brand": null,
      "badges": ["Overall Pick"],
      "offers": [],
      "delivery": [
        "Join Prime to get FREE delivery Wed, Jul 22",
        "Or Non-members get FREE delivery Sun, Jul 26"
      ],
      "stock": null,
      "variants": {
        "more_variants": null,
        "more_variants_link": null,
        "options": [{ "asin": "B09S3HNMHF", "title": null, "link": null }]
      },
      "options": "3 capacities",
      "more_buying_choices": "(13+ used & new offers)",
      "more_buying_choices_link": "/gp/offer-listing/B09S3HNMHF/ref=sr_1_5_olp?keywords=laptop&...",
      "specs": {
        "display_size": null,
        "disk_size": "64 GB",
        "ram": "4 GB",
        "operating_system": "Chrome OS"
      },
      "climate_pledge_friendly": false,
      "sustainability_features": [],
      "small_business": false,
      "top_rated": false
    }
  ],
  "html": ""
}

organic_position and sponsored_position are the pair worth building on, and they are the reason this endpoint is more useful than an array index: exactly one of them is populated per card, so organic_position gives you the real ranking with Amazon's ad auction filtered out. The nulls are shown on purpose. brand and manufacturer are null on this card because Amazon does not render a brand line on every search result, and sales_volume ("5K+ bought in past month") is present only on cards where Amazon chooses to show it. The html field comes back as an empty string on this endpoint. The more_buying_choices_link query string is truncated above where marked; it is complete in the sample file.

Runnable: amazon_scraper_api_codes/search.py


2. Product: full product data, pricing and Best Sellers Rank

One Amazon product by ASIN: pricing, Buy Box, seller, delivery, category ladder, Best Sellers Rank, star distribution, images and the spec table. 60 top-level fields, 39 of them populated on the sample below.

Param Type Required Default Description
query string yes unless url - A 10-character ASIN, e.g. 0143127748. Case-insensitive.
url string (URL) no - Alternative input: a full Amazon product URL. The ASIN is derived from /dp/<asin> or /gp/product/<asin>.
language string no marketplace default Locale to request, e.g. en_US, de_DE.
render_js boolean no false Render the page in a full browser before parsing. Costs 15 credits instead of 5.
screenshot boolean no false Capture a PNG of the rendered page. Implies render_js, and adds 10 credits.
add_html boolean no false Also return the raw upstream HTML in the html field alongside the parsed JSON. Adds ~3 MB to the response on this ASIN.
curl "https://api.chocodata.com/api/v1/amazon/product?api_key=YOUR_KEY&query=0143127748"

Real response. 23 of the 60 top-level fields, selected for length: images cut to 2 of 5 and product_details to 8 of 12. Of the 37 fields not shown, 21 are null or empty on this ASIN and 16 are populated but cut here, description, category and reviews among them. Every field and value below is verbatim (full sample):

{
  "asin": "0143127748",
  "parent_asin": "B00G3LLR0C",
  "title": "The Body Keeps the Score: Brain, Mind, and Body in the Healing of Trauma",
  "brand": "by Bessel van der Kolk M.D. (Author) Format: Paperback",
  "price": 14.15,
  "price_buybox": 14.15,
  "price_strikethrough": 19,
  "currency": "USD",
  "discount_percentage": 26,
  "stock": "In Stock",
  "is_prime": true,
  "max_quantity": 30,
  "pricing_str": "New & Used (140) from $4.06$4.06",
  "rating": 4.8,
  "reviews_count": 84491,
  "rating_stars_distribution": [
    { "rating": 5, "percentage": 86 },
    { "rating": 4, "percentage": 10 },
    { "rating": 3, "percentage": 3 },
    { "rating": 1, "percentage": 1 }
  ],
  "answered_questions_count": 0,
  "sales_rank": [
    { "ladder": [{ "name": "Post-Traumatic Stress", "url": null }], "rank": 1 }
  ],
  "featured_merchant": {
    "name": "Amazon.com",
    "seller_id": null,
    "link": null,
    "shipped_from": "Amazon.com",
    "is_amazon_fulfilled": true
  },
  "buybox": [
    {
      "price": 14.15,
      "seller_name": "Amazon.com",
      "seller_id": null,
      "seller_url": null,
      "ships_from_name": "Amazon.com",
      "stock": "In Stock",
      "returns": "Returns",
      "delivery_details": [
        { "date": { "by": "Saturday, July 25" }, "type": "FREE delivery" }
      ]
    }
  ],
  "images": [
    "https://m.media-amazon.com/images/I/71Ha3OShqSL._AC_SL1500_.jpg",
    "https://m.media-amazon.com/images/I/71Ha3OShqSL._AC_SL1500_.jpg"
  ],
  "product_details": {
    "publisher": "Penguin Books",
    "publication_date": "September 8, 2015",
    "edition": "Reprint",
    "language": "English",
    "print_length": "464 pages",
    "isbn_10": "0143127748",
    "isbn_13": "978-0143127741",
    "item_weight": "14.4 ounces"
  },
  "has_videos": true
}

sales_rank is the field most people come for: it is Amazon's Best Sellers Rank parsed into a category ladder plus an integer, which is the closest public proxy for sales velocity on a listing. price_strikethrough (19) against price (14.15) gives you the discount without recomputing it, and pricing_str carries the used-and-new floor ($4.06) that the Buy Box price hides. product_details is a flat key-value map whose keys vary by category: a book returns isbn_13 and print_length, an electronics listing returns different keys entirely, so treat it as a dictionary rather than a fixed schema. rating_stars_distribution covers all 84,491 reviews, and note it has 4 entries here rather than 5 because Amazon renders no row for 2 stars on this listing. The reviews array in the full sample carries per-review metadata (rating, date, verified flag, helpful count); on the three ASINs sampled its title and content were null.

The ASIN used here is a perennial bestseller, chosen so the example does not rot. A URL works identically: ?url=https://www.amazon.com/dp/0143127748.

One thing to guard against if your ASINs come from an untrusted source: Amazon does not reliably 404 an ASIN that does not exist. Of six invented ASINs we tried on 2026-07-20, four came back 404 product_not_found as expected, but ZZZZZZZZZZ and 0000000000 returned 200 carrying an unrelated book, because Amazon resolved those URLs to a real listing instead of an error page. The asin field echoes what you asked for either way, so compare the returned title against what you expected rather than treating a 200 as proof the ASIN was real.

Runnable: amazon_scraper_api_codes/product.py. Endpoint-only repo: amazon-product-scraper.


3. Best Sellers: top-ranked product listings by category

Amazon's ranked Best Sellers grid for a category, 30 rows per call.

Param Type Required Default Description
category string yes - Best Sellers category slug, as it appears in amazon.com/Best-Sellers/zgbs/<slug>. Verified against electronics, books, kitchen, beauty, videogames, home-garden, pet-supplies, office-products, automotive, music and toys-and-games.
curl "https://api.chocodata.com/api/v1/amazon/bestsellers?api_key=YOUR_KEY&category=electronics"

Real response. results cut to 1 of 30; the object is complete, all 10 fields verbatim (full sample):

{
  "category": "electronics",
  "results_count": 30,
  "results": [
    {
      "position": 1,
      "id": "B08JHCVHTY",
      "title": "blink plus plan with monthly auto-renewal",
      "url": "https://www.amazon.com/Blink-Plus-Plan-monthly-auto-renewal/dp/B08JHCVHTY/ref=zg_bs_g_electronics_d_sccl_1/134-7762665-8126303?psc=1",
      "price": 11.99,
      "currency": "USD",
      "rating": null,
      "reviews_count": null,
      "thumbnail": "https://images-na.ssl-images-amazon.com/images/I/31YHGbJsldL._AC_UL300_SR300,200_.png",
      "rank": 1
    }
  ]
}

rating and reviews_count came back null on all 30 rows, in every run we made, so treat this surface as rank, identity and price (one row of the 30 also had a null price, so guard for that too). Join on id (the ASIN) and call /amazon/product when you need ratings. position and rank are the same number here, and rank is the one Amazon printed on the badge.

Running it:

Amazon Best Sellers endpoint output

Runnable: amazon_scraper_api_codes/bestsellers.py


Track Amazon price changes

Competitor price tracking is the main reason people scrape Amazon, so that use case is in the repo end to end rather than as a snippet. price_monitor.py polls a search, stores every observation as a local dataset in SQLite (export to CSV with one sqlite3 command), and prints the diff since the last run:

python amazon_scraper_api_codes/price_monitor.py "gaming laptop"
# 16 cards this run (15 priced) | 0 price change(s) | 15 products tracked in amazon_prices.db
# No changes yet. Run it again in an hour, or schedule it (cron / GitHub Actions).

That is a real first run: the first pass has nothing to compare against, so it seeds the database and says so. Your card count will differ from the 16 above, for the sponsored-row reason in the block section; that is why the tracker keys on ASIN. Run it again after prices move and each change prints as a diff line (DOWN <title> 479.00 -> 429.00 (-10.4%)).

It keys on ASIN rather than array position, which matters for the reason in the table above: the sponsored rows come and go between runs, so position is not an identity.

One API call per run, per query.


Amazon's official APIs and what they require

Amazon does publish product-data APIs. Every one of them is gated behind a commercial relationship with Amazon, which is the thing worth knowing before you assume there is an official path to public product data.

The affiliate track. Product Advertising API 5.0 has closed to new customers in favour of the Creators API: Amazon's own documentation site now carries the banner "PA-API is no longer accepting new customers. Please onboard to Creators API." Both are governed by the same licence, whose usage requirements state, in full:

"You will use Product Advertising Content only in a lawful manner in accordance with and within the express scope of the terms of this License. You will not use Creators API, PA API, Data Feeds, or Product Advertising Content with any site or application, or in any other manner, that does not have the principal purpose of advertising and marketing an Amazon Site and driving sales of products and services on an Amazon Site."

"You will not store or cache Product Advertising Content consisting of an image, but you may store a link to Product Advertising Content consisting of an image for up to 24 hours. You may store other Product Advertising Content that does not consist of images for caching purposes for up to 24 hours, but if you do so you must immediately thereafter refresh and re-display the Product Advertising Content by making a call to Creators API, PA API or retrieving a new Data Feed... Unless otherwise notified by us, you may store individual Amazon Standard Identification Numbers (ASINs) for an indefinite period until the termination of this License."

Source: Amazon Associates Program Policies, affiliate-program.amazon.com/help/operating/policies, marked "Updated: April 14, 2026", read 2026-07-20.

Two things follow, and only these two. The licensed purpose is advertising that drives sales on Amazon, so a price-monitoring or catalogue-analytics application is not the purpose the licence describes. And non-image content may be cached for 24 hours, with ASINs themselves exempt and storable indefinitely.

Getting keys at all runs through the Associates programme, which has its own gate: "Once you have referred three qualifying sales, we'll evaluate your application within a day or two and reply to you with our decision." Under PA-API 5.0 the throughput then scaled with your shipped revenue, starting at "one request per second (one TPS) and a cumulative daily maximum of 8640 requests per day", with access lost after "a consecutive 30-day period" without qualifying sales. Those figures are the historical PA-API 5.0 regime; Amazon labels that documentation as no longer maintained, and the Creators API equivalents are not published behind an open URL.

The seller track. SP-API does expose catalog and competitor pricing for arbitrary ASINs, and its registration overview states: "Note that in Seller Central, you must have a professional selling account to develop SP-API private seller applications. Individual accounts are not eligible." Pricing calls such as getItemOffers and getCompetitivePricing are rated at 0.5 requests per second. Its Customer Feedback API returns "an item's ten most positive and ten most negative review topics" rather than review text.

robots.txt. Amazon's robots.txt carries no Disallow for User-agent: * on /s?k= search results or on bare /dp/ product pages (six narrower /dp/ sub-paths such as /dp/rate-this-item/ are disallowed). It is worth reading the rest of the file before drawing a conclusion from that: it also issues a blanket Disallow: / to 99 named agents, Scrapy and the major AI crawlers among them. Fetched 2026-07-20.

None of this is legal advice, and the licence terms above bind people who joined the Associates programme and accepted them rather than the public at large.


Measured latency

Real end-to-end wall-clock, measured from a laptop against the live API on 2026-07-20. This includes the upstream fetch, the anti-bot handling, and the parse:

Endpoint Median Range n
/amazon/search 3.1s 2.2 to 3.3s 5
/amazon/product 4.8s 4.4 to 7.6s 5
/amazon/bestsellers 1.8s 1.5 to 2.2s 5

Read the ranges, not just the medians. Small sample (n=5 per endpoint); reproduce it yourself with the scripts here.


License

MIT. See LICENSE.