To track an Amazon price, open the product page and click the price history link next to the price. Amazon shows this history to customers in the U.S., UK, Canada and India. Alexa for Shopping can also alert you when a price reaches a number you set. For many listings on a schedule, sellers can call the Selling Partner API, and affiliates with enough sales can use the Creators API. Everyone else builds a tracker. A tracker of your own fails more often by reading the wrong price than by being blocked.
We checked each part of this page on 16 September 2026. We read Amazon's own pages and API documents. We also sent amazon.com 102 requests from our own server in Germany, to see what a tracker really gets back.
Does Amazon have its own price tracker?
Yes, for shoppers in four countries. On a product page, click the price history link right next to the price. Amazon says price history is available to all customers in the U.S., UK, Canada and India. It covers 30 and 90 days, and 365-day history is rolling out in the U.S., UK and India.
Alexa for Shopping shows the same history. Amazon built it by bringing together Rufus and Alexa+. You can ask it for a price alert in plain words, such as "Set a price alert for when this face cream is $75".
The Was price on a deal is a different number. Amazon defines the Typical or Was price as the median price paid on Amazon in the last 90 days. Prices paid during limited-time deals are left out. So the Was price is a middle value, not the highest price in the history.
Which other price trackers can you use?
Two trackers outside Amazon are CamelCamelCamel and Keepa. CamelCamelCamel describes itself as an Amazon US price tracker with price history, price watch and price drop alerts. Keepa says its API delivers complete price histories for Amazon marketplaces to your own code, on a subscription plan.
One shopper asked on Quora for a good tracker "if camelcamelcamel is often incorrect". Before you buy, compare the tracker's number with the price on the product page.
Can you use Amazon's API to track prices?
Only if you are an affiliate with sales or a seller.
The Product Advertising API 5.0 is closed. Amazon's notice says that applications that still call it "receive an HTTP 403 Forbidden response". The old documentation pages now redirect to that notice. The successor is the Creators API.
To use the Creators API, you must be in the Amazon Associates program for the marketplace. You also need "at least 10 qualifying sales within the past 30 days". Its GetItems call returns the buying price of an offer, for up to 10 ASINs per request. Access tokens last one hour, and it answers too many requests with HTTP 429. The pages we read give no number of requests per second.
Sellers have their own route. The Selling Partner API call getItemOffers "returns the lowest priced offers for a single item based on ASIN". Its limit is 0.5 requests per second with a burst of 1, which allows at most 1,800 calls an hour.
Keepa's API is the way to a ready history. Each product in a request costs one token, and tokens left unused for 60 minutes expire.
| Way to track | Who can use it | What you get | The limit |
|---|---|---|---|
| Price history link on the product page | Shoppers in the U.S., UK, Canada and India | 30- and 90-day history, 365 days rolling out | One product at a time, by hand |
| Alexa for Shopping | Amazon shoppers | Price history and a price alert at your target | Set per product |
| CamelCamelCamel, Keepa | Anyone; Keepa's API on a plan | Price history; CamelCamelCamel lists drop alerts | You rely on their data and their timing |
| Creators API | Associates with 10 qualifying sales in 30 days | Offer prices for up to 10 ASINs per call | Eligibility, and HTTP 429 when you ask too fast |
| SP-API getItemOffers | Sellers | The lowest priced offers for one ASIN | 0.5 requests per second |
| Your own tracker | Anyone who can run a script | Any listing, on your schedule | Amazon's terms, the delivery location, robot checks |
Is it allowed to scrape Amazon prices?
Amazon's terms say no. Its Conditions of Use were last updated on 14 August 2026. They grant a license "to access and make personal and non-commercial use of the Amazon Services". The license then leaves out "any collection and use of any product listings, descriptions, or prices". It also leaves out "any use of data mining, robots, or similar data gathering and extraction tools".
Amazon's robots.txt points the same way. On 16 September 2026, it barred 99 named crawlers from the whole site, among them GPTBot, ClaudeBot and Scrapy. A new Scrapy project calls itself Scrapy and obeys robots.txt by default. Robots rules match that name without regard to case, so such a project fetches no Amazon page until you change those settings.
What this means for you depends on where you are, what you collect and what you do with it. This page does not give legal advice. It explains how each route works and what it costs you.
What breaks when you build your own tracker
We sent amazon.com 102 requests on 16 September 2026, between 11:56 and 12:06 UTC. They came from our own server in Germany and used only Python's standard library. The script asked for six listings, with Python's default headers and with the headers of a desktop browser.
Blocks were not the first problem
95 of the 102 requests came back with the page or file. At one request every eight seconds, nothing was challenged. A burst of 60 requests to one listing passed as well. The challenge came later, in a new session that kept cookies. The home page came back with status 202 and a page of 2,010 bytes. After that, two product pages came back as a robot check.
The robot check arrives with status 200
The robot check said "Click the button below to continue shopping". Its status code was 200, and the page was 3,781 bytes long. A tracker that checks only the status code saves this page as a normal answer. Check the page itself: the robot check is a form that posts to /errors_page/validateCaptcha.
The price box was empty for our location
We never chose a delivery location, yet the pages said "Deliver to France". For that location, no product page showed its own price. The Stanley listings said "No featured offers available". The Kindle page said "This item cannot be shipped to your selected delivery location". With browser headers, the other prices on those pages were in euros. With Python's default headers, they were in dollars.
The first price on the page belongs to another product
A product page carries many prices: carousels, other colors and "customers also bought". On the Stanley Quencher page (B0BD76BTSN), the first price in the HTML was $44.54. That price sits on the carousel card of another listing, B0CRMXG22M. A reader that falls back to the first span.a-offscreen on the page saves $44.54 as the Stanley price.
What the empty price box needs
Amazon's help pages say the delivery location decides the product availability a page shows. The Kindle page asked for the same thing: "Please choose a different delivery location." So a tracker for amazon.com needs a US delivery location. There are two ways to get one. You can set a US location in the session, which is what the "Deliver to" selector does. Or you can send the requests from a US address, such as a server in the US or a proxy with a US exit.
We could not confirm either way on the day. Our attempt to set the ZIP code 10001 was stopped by the challenge, and we did not test a US exit. Check the "Deliver to" line on every page you save: the tracker below prints it. Our residential proxies include country targeting on every plan and are billed by the gigabyte. Our Amazon proxies page compares rotating residential, static ISP and datacenter proxies for Amazon.
Pace decides when the challenge comes
From one address, the robot check came after about 100 requests in 11 minutes. It did not go away quickly. 44 minutes later, the home page still came back as a page of 3,781 bytes, the same size as the robot check. If a tracker needs more checks per hour than that, spread them over more time or over more addresses.
Build a tracker that reads the right price
The tracker below reads the price only from the product's own price box. When there is no price, it says why, and it prints the "Deliver to" line of each page. It saves every check in a SQLite file and prints a drop below the lowest price it has seen.
You need Python 3 and two packages. We ran it with Python 3.13, requests 2.34.2 and beautifulsoup4 4.15.0.
python -m pip install requests beautifulsoup4
#!/usr/bin/env python3
"""A small Amazon price tracker: one listing at a time, history in SQLite.
It reads the price only from the product's own price box, and it names the
answer when there is no price:
ok a price was read from the price box
robot_check Amazon's "continue shopping" check, which arrives with status 200
error_page Amazon's error page, which points automated traffic to its API team
cannot_ship the listing does not ship to the delivery location Amazon picked
no_offer "No featured offers available": Amazon shows no main offer here
no_price none of the above, and no price in the price box
Every check also prints Amazon's "Deliver to" line, so you can see which
delivery location a price belongs to.
Usage:
python amazon_price_tracker.py B0BD76BTSN B0CTS9WK49
python amazon_price_tracker.py --proxy http://USER:PASS@HOST:PORT B0BD76BTSN
"""
import argparse
import random
import sqlite3
import time
from datetime import datetime, timezone
import requests
from bs4 import BeautifulSoup
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
# The product's own price lives in one of these boxes. Any other price on the
# page belongs to a different product (carousels, "customers also bought").
PRICE_BOXES = [
"#corePriceDisplay_desktop_feature_div",
"#corePrice_feature_div",
"#apex_desktop",
]
def read_page(html):
"""Return (price_text, state, deliver_to) for one product page."""
if "/errors_page/validateCaptcha" in html:
return None, "robot_check", ""
if "automated access to Amazon data" in html:
return None, "error_page", ""
soup = BeautifulSoup(html, "html.parser")
line = soup.select_one("#glow-ingress-line2")
deliver_to = line.get_text(" ", strip=True) if line else ""
if "cannot be shipped to your selected delivery location" in html:
return None, "cannot_ship", deliver_to
for box in PRICE_BOXES:
tag = soup.select_one(f"{box} span.a-offscreen")
if tag and tag.get_text(strip=True):
return tag.get_text(strip=True), "ok", deliver_to
if "No featured offers available" in html:
return None, "no_offer", deliver_to
return None, "no_price", deliver_to
def to_number(price_text):
"""'$1,249.99' -> 1249.99 (US listings only)."""
return float(price_text.replace("$", "").replace(",", ""))
def check(session, asin, proxies=None):
response = session.get(
f"https://www.amazon.com/dp/{asin}",
headers=HEADERS,
proxies=proxies,
timeout=30,
)
price, state, deliver_to = read_page(response.text)
if state == "no_price" and response.status_code != 200:
state = f"http_{response.status_code}"
return price, state, deliver_to, response.status_code
def open_db(path):
db = sqlite3.connect(path)
db.execute(
"CREATE TABLE IF NOT EXISTS prices "
"(asin TEXT, checked_at TEXT, price REAL, state TEXT, http INTEGER)"
)
return db
def save(db, asin, price, state, status):
db.execute(
"INSERT INTO prices VALUES (?, ?, ?, ?, ?)",
(
asin,
datetime.now(timezone.utc).isoformat(timespec="seconds"),
to_number(price) if price else None,
state,
status,
),
)
db.commit()
def lowest_before(db, asin):
row = db.execute(
"SELECT MIN(price) FROM prices WHERE asin = ? AND state = 'ok'", (asin,)
).fetchone()
return row[0] if row else None
def main():
parser = argparse.ArgumentParser()
parser.add_argument("asins", nargs="+")
parser.add_argument("--proxy", help="http://USER:PASS@HOST:PORT, with an exit in the marketplace's country")
parser.add_argument("--db", default="prices.db")
args = parser.parse_args()
proxies = {"http": args.proxy, "https": args.proxy} if args.proxy else None
db = open_db(args.db)
try:
track(db, args.asins, proxies)
finally:
db.close()
def track(db, asins, proxies):
with requests.Session() as session:
for n, asin in enumerate(asins):
if n:
time.sleep(random.uniform(20, 40))
before = lowest_before(db, asin)
price, state, deliver_to, status = check(session, asin, proxies)
save(db, asin, price, state, status)
print(f"{asin} {state:12} {price or '-':10} deliver to: {deliver_to or '?'}")
if state == "ok" and before is not None and to_number(price) < before:
print(f" price drop: {price}, lowest before was ${before:,.2f}")
if state == "robot_check":
print(" robot check: stop here and try again later, more slowly")
break
if state in ("cannot_ship", "no_offer"):
print(f" no price for this delivery location ({deliver_to or 'unknown'}): amazon.com prices need a US one")
if __name__ == "__main__":
main()
Save it as amazon_price_tracker.py and pass the ASINs you want to watch. The ASIN is the ten-character code that appears after /dp/ in a product link. To send the checks through a US exit, add --proxy:
python amazon_price_tracker.py B0BD76BTSN B0CTS9WK49
python amazon_price_tracker.py --proxy http://USER:PASS@HOST:PORT B0BD76BTSN
The tracker waits a random 20 to 40 seconds between listings. That is about 90 to 180 listings an hour from one address. Run it on a schedule, such as a cron job every few hours, and the SQLite file becomes your price history.
What each state means
| State | What Amazon sent | What to do |
|---|---|---|
ok | A price inside the price box | Save it |
robot_check | The continue-shopping page, with status 200 | Stop and try again later, more slowly |
error_page | An error page that names automated access | Check the ASIN and your pace |
cannot_ship | "This item cannot be shipped to your selected delivery location" | Get a US delivery location first |
no_offer | "No featured offers available" | Amazon shows no main offer for this location: get a US delivery location first |
no_price | No price and none of the above | Open the saved page and look |
We tested the tracker on Amazon's real answers
We ran read_page on the 11 product answers we saved from our test, without network access. It returned a named state for all 11 and a price for none, because none of them had a filled price box. Where the page carried a "Deliver to" line, it said France. The first span.a-offscreen on those pages gave a number 7 times, and none of the 7 was the listing's own price. On a test file that copies Amazon's price markup, the reader returned $44.99 from the price box. The first price on that file was $12.49, from a carousel card.
We also ran the whole script offline, with saved answers in place of the network. It saved each answer to SQLite with its state, and it stopped at the robot check before the next listing. When the test file came back at $39.99, it printed a price drop from $44.99.

Where to go from here
For the choice of proxy type on Amazon, read proxies for Amazon. For price checks across many shops, see proxies for price monitoring. The Python requests guide covers the proxies setting, timeouts and errors in depth. For a larger crawler, how to avoid IP bans is the checklist.
What this page could not check
Every product page in our test came from a server in Germany, and Amazon hid the product's own price for the location it showed us, France. So the success path of the reader is tested on a file that copies Amazon's price markup, not on a live US page. Our attempt to set a US ZIP code was stopped by the challenge, and we did not test a residential or ISP exit. Our test covers one day, one address, 102 requests and six listings. Where Amazon starts to challenge depends on the address, cookies, headers and pace, so treat our numbers as one sample. The Creators API pages we read publish no request rate, and Amazon's articles on price history carry no date. Amazon's page markup, its terms and its API rules change, so we will check this page again by 15 December 2026.
Sources
- Amazon, Conditions of Use, last updated 14 August 2026, read 16 September 2026: amazon.com Conditions of Use.
- Amazon, robots.txt, as served on 16 September 2026: amazon.com/robots.txt.
- Amazon Associates, PA-API 5 deprecation notice, read 16 September 2026: Creators API documentation.
- Amazon Associates, Creators API introduction, read 16 September 2026: Creators API documentation.
- Amazon Associates, Creators API GetItems reference, read 16 September 2026: Creators API documentation.
- Amazon Associates, Creators API error codes and messages, read 16 September 2026: Creators API documentation.
- Amazon Selling Partner API, getItemOffers reference, updated 10 September 2026: SP-API documentation.
- Amazon, "What is Amazon's price history feature and how does it work?", read 16 September 2026: aboutamazon.com.
- Amazon, how to use Alexa for Shopping, read 16 September 2026: aboutamazon.com.
- Amazon, Meet Alexa for Shopping, read 16 September 2026: aboutamazon.com.
- Amazon, how Amazon offers genuine deals to customers, read 16 September 2026: aboutamazon.com.
- Keepa, API overview, and plans and tokens, read 16 September 2026: Keepa API documentation.
- Scrapy documentation, settings ROBOTSTXT_OBEY and USER_AGENT, read 16 September 2026: docs.scrapy.org.
- RFC 9110, HTTP Semantics, June 2022: rfc-editor.org/rfc/rfc9110.
- RFC 9309, Robots Exclusion Protocol, September 2022: rfc-editor.org/rfc/rfc9309.
- Amazon, "Understanding ASIN numbers", 4 June 2025: sell.amazon.com.
- CamelCamelCamel, home page title, read 16 September 2026 (camelcamelcamel.com).
- Our own measurement: 102 requests to amazon.com from our server on 16 September 2026, and an offline test of the reader on the saved answers, logged in the research folder for this page.


