To monitor Amazon competitors, run one report a day: for each competitor ASIN, read /api/amazon-product-lookup for rating and sales rank and /api/amazon-product-offers for the Buy Box price, its holder and the number of New sellers on the first offer page. Write the rows to a dated CSV and compare them with yesterday's, so you read only what changed. Two credits per competitor per day - about 60 a month - and the script below needs only Python, requests and the helper from the Python quickstart.
Key Takeaways
- Two calls per competitor cover the essentials: lookup for rating and rank, offers for Buy Box price, holder and sellers.
- A dated CSV per day keeps the history; the change list keeps the reading short.
- Compare against the last successful row, so a failed call never shows up as a price change.
- Two credits a day is about 60 per competitor a month; lookup alone halves that and still names the holder's seller ID, but not the landed price, seller name or seller count.
- For deeper questions - stock-outs, repricers, listing edits - the same list feeds the specialised monitors.
What should a competitor report contain?
Five columns answer most daily questions: what the featured offer costs, who holds it, how many sellers compete, how the product is rated and how well it sells. Each comes from one of two calls; the offers call reads the first page of the offer listing, which the offer listing guide explains.
| Column | Source | Answers |
|---|---|---|
buybox_price | offers: buyBox.totalPrice | Landed price of the featured offer |
buybox_seller | offers: buyBox.sellerName | Who holds the Buy Box |
new_sellers | offers: New offers | How many compete (first page) |
| rating · ratings | lookup: reviews | Stars and ratings count |
rank | lookup: bestsellerRanks | Main-category sales rank |
How do I build a daily competitor report in Python?
row() makes the two calls for one competitor and returns a flat dict; a failed call becomes a row with an error instead of stopping the run. changes() compares today's rows with the last successful ones and lists what moved. The report runs four competitors at a time and stores the baseline in a JSON file, keeping yesterday's row when today's call failed. get_with_retry() retries a 429 or 5xx a few times before a row counts as failed; it, new_session() and ApiError come from the Python quickstart.
competitor_report.py - daily CSV and change list
"""Daily competitor report: price, Buy Box, sellers, rating and rank per ASIN, and what changed since yesterday."""
import csv
import json
import threading
from concurrent.futures import ThreadPoolExecutor
from datetime import date
from pathlib import Path
import requests
from sellermagnet import ApiError, get_with_retry, new_session
COMPETITORS = [("B0CL61F39H", "ATVPDKIKX0DER"), ("B0CLTBHXWQ", "APJ6JRA9NG5V4")] # (asin, marketplaceId)
LAST = Path("last_report.json")
WATCH = {"buybox_price": "price", "buybox_seller": "Buy Box", "new_sellers": "New sellers", "rank": "rank"}
_local = threading.local()
def session():
if not hasattr(_local, "session"):
_local.session = new_session()
return _local.session
def row(asin: str, marketplace_id: str) -> dict:
base = {"asin": asin, "marketplace": marketplace_id, "error": ""}
try:
p = get_with_retry(session(), "amazon-product-lookup", asin=asin, marketplaceId=marketplace_id)["productInfo"]
o = get_with_retry(session(), "amazon-product-offers", asin=asin, marketplaceId=marketplace_id)
except ApiError as err:
return {**base, "error": err.status}
except requests.RequestException as err:
return {**base, "error": type(err).__name__}
box = o.get("buyBox") or {}
price = box.get("totalPrice")
new = {x.get("sellerId") for x in o.get("offers") or [] if x.get("condition") == "New"}
reviews = p.get("reviews") or {}
return {**base,
"title": (p.get("title") or "")[:50],
"buybox_price": round(price, 2) if isinstance(price, (int, float)) and price > 0 else None,
"currency": (o.get("currency") or {}).get("code"),
"buybox_seller": box.get("sellerName") or None, # None: no featured offer
"new_sellers": len(new),
"rating": reviews.get("averageRating"),
"ratings": reviews.get("totalReviews"),
"rank": ((p.get("bestsellerRanks") or {}).get("main_category") or {}).get("rank") or None}
def changes(today: list[dict], yesterday: dict) -> list[str]:
lines = []
for r in today:
old = yesterday.get(f"{r['marketplace']}:{r['asin']}")
if not old or r["error"] or old.get("error"):
continue # no baseline, or one side failed
for field, name in WATCH.items():
if old.get(field) != r.get(field):
lines.append(f"{r['marketplace']} {r['asin']} {name}: {old.get(field)} -> {r.get(field)}")
return lines
if __name__ == "__main__":
with ThreadPoolExecutor(max_workers=4) as pool:
rows = list(pool.map(lambda c: row(*c), COMPETITORS))
yesterday = json.loads(LAST.read_text()) if LAST.exists() else {}
for line in changes(rows, yesterday):
print(line)
with open(f"competitors-{date.today()}.csv", "w", newline="", encoding="utf-8") as fh:
fields = ["asin", "marketplace", "title", "buybox_price", "currency", "buybox_seller", "new_sellers",
"rating", "ratings", "rank", "error"]
writer = csv.DictWriter(fh, fieldnames=fields, restval="")
writer.writeheader()
writer.writerows(rows)
keep = {f"{r['marketplace']}:{r['asin']}": r for r in rows if not r["error"]}
LAST.write_text(json.dumps({**yesterday, **keep}, indent=1)) # a failed row keeps yesterday's baseline
Example change list on a busy day (illustrative values)
ATVPDKIKX0DER B0CL61F39H price: 444.98 -> 419.83
ATVPDKIKX0DER B0CL61F39H Buy Box: Amazon -> Example Shop
ATVPDKIKX0DER B0CL61F39H New sellers: 2 -> 3
ATVPDKIKX0DER B0CL61F39H rank: 26 -> 21
price: ... -> None together with Buy Box: ... -> None means there is no featured offer today: a suppressed Buy Box, a stock-out or a page that could not be read. Confirm it on the next run before acting - the out-of-stock alert linked below waits for two checks in a row.

How much does monitoring competitors cost?
One credit per call. Lookup alone is 30 credits per competitor a month and still shows the featured offer's item price (without shipping), its seller ID, rating and rank; adding offers doubles it and adds the landed price, the seller's name and the New-seller count. A weekly statistics call on top adds recorded price and rank history for about 4 more (statistics covers 11 marketplaces; others get a free 400). Twenty competitors with lookup and offers cost about 1,200 credits a month - see pricing for plans.

What else can the same competitor list feed?
- Stock-outs and a disappearing Amazon offer: the out-of-stock alert guide.
- Which competitors reprice automatically: the repricer detection guide.
- Title, image and variation edits: the listing change monitor.
- The change list on your phone: the Telegram price bot guide.
Run the report every morning from cron (server clock)
# crontab -e: cron has no login environment, so set the key in the crontab
# SELLERMAGNET_API_KEY=your-key-here
# 0 7 * * * cd /opt/competitors && .venv/bin/python competitor_report.py >> changes.log 2>&1
cd /opt/competitors && .venv/bin/python competitor_report.py && ls competitors-*.csv | tail -3
Frequently Asked Questions
How can I monitor my Amazon competitors automatically?
Run a daily script that looks up each competitor ASIN and reads its offers, saves the rows to CSV and lists what changed since the day before.
Which data should a competitor report include?
At least the Buy Box price and holder, the number of sellers, rating and sales rank. Stock-outs and listing edits need their own checks.
How much does it cost per competitor?
Two credits a day with lookup and offers, about 60 a month. Lookup alone is about 30; it gives the holder's seller ID and item price, but no landed price, seller name or seller count.
What happens when a call fails?
The row gets an error and the last successful row stays the baseline, so a failed call is never reported as a price or rank change.
Can I monitor competitors on several marketplaces?
Yes. Each competitor is an ASIN plus a marketplaceId; list the same ASIN once per marketplace you care about.
Bottom line: two calls per competitor, one CSV a day and a short list of changes turn competitor monitoring into a five-minute morning read. A free account includes 150 credits to try it on your own list.