To use the Amazon Deals API for Black Friday week, call /amazon-deals with marketplaceId on a schedule, not once: the endpoint has no event calendar and no history of its own, it only answers what is on the deals page right now, for up to 50 deals per call. To see what is new and what dropped out over the week, keep your own baseline keyed by ASIN across every scheduled run.
Key Takeaways
/amazon-dealsreturns the current deals page only; a week's worth of data means your own scheduled snapshots, not one call.countabove 50 is a billed 400 ("Count Max: 50"); clamp it to 50 locally before every call.category_idis never validated: an unknown department slug just returns fewer deals, billed the same as a correct one.- Track by ASIN, not
dealId:dealIdandasincan both be "N/A" on a given row, but ASIN is the only one usable as a baseline key often enough to be useful. - A page with no deal data at all is a retryable 500, refunded; an empty
dealslist on a page that does carry deal data is real and billed.
Why one /amazon-deals call is not enough for Black Friday week
A single call to /amazon-deals is a snapshot of right now: up to 50 deals, in Amazon's own "FEATURED" sort, for one marketplace and optionally one department. It carries no start or end date for the sale itself, and it does not remember what was there an hour ago. For a week where the deal list changes several times a day, that means running the same call on a schedule and keeping the history yourself: that is what the script below does.
One trimmed row of /amazon-deals' data.deals[] (illustrative data)
{
"dealId": "D1",
"asin": "B0CL61F39H",
"productTitle": "Wireless Earbuds Pro",
"dealPrice": {"priceTotal": 39.99, "priceCurrency": "EUR"},
"basisPrice": 59.99,
"savings_percentage": 33,
"dealStartTime": "2026-11-24T00:00:00Z",
"dealEndTime": "2026-11-30T23:59:00Z",
"isLightningDeal": false
}

How do I track new and dropped deals across the week in Python?
The script below snapshots /amazon-deals for one marketplace, clamps a caller-supplied count to the API's own ceiling of 50 before paying for the call, and merges the result into a baseline file keyed by ASIN. Rows with asin == "N/A" are counted but never tracked, since there is no stable key for them. Each run reports which ASINs are new since the last run and which ASINs it had seen before but are missing from today's page. It uses sellermagnet.py (ApiError, new_session, get) and retry.py (get_with_retry) from the Python quickstart.
black_friday_snapshot.py - track new and dropped deals across scheduled runs
"""Black Friday week deal tracking: snapshot /amazon-deals on a schedule and track first-seen / last-seen
per ASIN across the week, instead of reading one poll in isolation.
Behaviour this relies on (re-checked 2026-11-01):
- /amazon-deals params: marketplaceId (required), category_id (optional department slug, NOT validated - an
unknown value is passed straight to Amazon's own department filter and just returns fewer or no deals),
count (default 30, max 50; a count above 50 is a billed 400 "Count Max: 50", charged before the error is
returned).
- data.deals[]: dealId ("N/A" when the page gives none), asin ("N/A" sometimes too), productTitle,
dealPrice.priceTotal (0.0 when unreadable), basisPrice (number or null), savings_percentage (badge % or
computed) or null, dealStartTime/dealEndTime (ISO 8601 UTC or "N/A").
- An empty deals list on a page that still carries deal data is real data and billed; a page with no deal
data at all answers a retryable 5xx, not a billed empty list.
- No Amazon calendar dates are used here: "Black Friday week" means whatever 7-day window the caller is
running snapshots for, taken from the system clock, never hardcoded.
Helpers: sellermagnet.py and retry.py from https://sellermagnet-api.com/blog/amazon-product-api-python
"""
import json
import pathlib
import requests
from retry import get_with_retry
from sellermagnet import ApiError, new_session
MARKETPLACE = "A1PA6795UKMFR9" # amazon.de; swap for any marketplace ID
MAX_COUNT = 50 # the API's own ceiling: never send more, it would be a billed 400
BASELINE_PATH = pathlib.Path("deals_baseline.json")
def load_baseline(path):
if path.exists():
return json.loads(path.read_text())
return {}
def save_baseline(path, baseline):
path.write_text(json.dumps(baseline, indent=2, sort_keys=True))
def snapshot(session, marketplace_id, count=30, category_id=None):
"""One /amazon-deals call. count is clamped locally so a caller-supplied value can never trigger the
endpoint's own billed 400 for count > 50."""
params = {"marketplaceId": marketplace_id, "count": min(int(count), MAX_COUNT)}
if category_id:
params["category_id"] = category_id # not validated server-side: an unknown slug just thins the list
from sellermagnet import get # low-level helper: /amazon-deals is not wrapped by lookup()
return get_with_retry(session, "amazon-deals", **params)["deals"]
def apply_snapshot(baseline, deals, run_date):
"""Merge one day's deals into the running baseline. Returns (new_asins, untracked_count).
Dedupes by ASIN within the snapshot (the page can repeat an ASIN, or give asin == "N/A" for several
rows); "N/A" rows are counted but never tracked, since there is no key to track them by."""
seen_this_run = set()
new_asins = []
untracked = 0
for deal in deals:
asin = deal.get("asin", "N/A")
if asin == "N/A" or not asin:
untracked += 1
continue
if asin in seen_this_run:
continue # same ASIN twice on one page: count once per day
seen_this_run.add(asin)
price = (deal.get("dealPrice") or {}).get("priceTotal")
row = baseline.get(asin)
if row is None:
baseline[asin] = {
"title": deal.get("productTitle", "")[:60],
"first_seen": run_date,
"last_seen": run_date,
"lowest_price": price,
}
new_asins.append(asin)
else:
row["last_seen"] = run_date
if price is not None and (row["lowest_price"] is None or price < row["lowest_price"]):
row["lowest_price"] = price
return new_asins, untracked
def dropped_since(baseline, run_date, still_present):
"""ASINs tracked before today that did not appear in today's snapshot (dropped out of the deal list)."""
return sorted(asin for asin, row in baseline.items()
if row["last_seen"] != run_date and asin not in still_present)
def run_day(session, run_date, count=30, category_id=None, baseline_path=BASELINE_PATH):
baseline = load_baseline(baseline_path)
try:
deals = snapshot(session, MARKETPLACE, count=count, category_id=category_id)
except ApiError as err:
if err.status in (401, 403): # key or credits: every later call fails the same way too
print(f"{run_date}: stopped - {err}")
return
print(f"{run_date}: marketplace/category rejected - {err}") # free 400, nothing to merge
return
except (requests.ConnectionError, requests.Timeout) as err:
print(f"{run_date}: network error ({type(err).__name__}), baseline unchanged, retry next schedule")
return
new_asins, untracked = apply_snapshot(baseline, deals, run_date)
still_present = {d.get("asin") for d in deals}
dropped = dropped_since(baseline, run_date, still_present)
print(f"{run_date}: {len(deals)} deals seen, {len(new_asins)} new, {untracked} without an ASIN, "
f"{len(dropped)} dropped out")
for asin in new_asins:
print(f" NEW {asin} {baseline[asin]['title']}")
for asin in dropped:
print(f" GONE {asin} last seen {baseline[asin]['last_seen']}")
save_baseline(baseline_path, baseline)
def main():
session = new_session()
# Simulate two scheduled snapshots during the week: the baseline file carries first_seen across runs.
run_day(session, "2026-11-24", count=75) # a caller-mistake count, clamped to 50 before paying
run_day(session, "2026-11-26")
if __name__ == "__main__":
main()
Output (illustrative data: a caller-mistake count, a billed retry, a duplicated ASIN on the page, and a deal dropping out between the two runs)
2026-11-24: 4 deals seen, 2 new, 1 without an ASIN, 0 dropped out
NEW B0CL61F39H Wireless Earbuds Pro
NEW B0XMASGIFT Smart Plug 4-Pack
2026-11-26: 3 deals seen, 1 new, 0 without an ASIN, 1 dropped out
NEW B0NEWITEM1 4K Streaming Stick
GONE B0XMASGIFT last seen 2026-11-24
calls= 3
- count=75 in the first run is clamped to 50 before the request is sent, so the endpoint's own billed 400 for
count > 50never happens; the 75 never reaches the API. - the first attempt answers 502 and is retried once per
get_with_retry; both attempts are billed, which is why the example's total call count is higher than the number of scheduled runs. B0CL61F39Happears twice on one page (a dealId can repeat with a different ID for the same ASIN); the script counts it once per day, not once per row.B0XMASGIFTisGONEon the second run: it is still in the baseline with its last knownlast_seendate, it simply did not appear on that day's page - it may come back on a later run, or the deal may have ended.

What does a category_id filter change on this schedule?
Passing category_id restricts the snapshot to one department, but the parameter is never checked against a real category tree: an unrecognised or misspelled slug is passed to Amazon's own filter as-is and simply returns fewer or no deals, billed the same as a correct value. For a tracking schedule that difference matters more than for a one-off call, because a silently-wrong filter looks exactly like a quiet department every single run. See the category_id post for how to validate it against the real tree first.
What does a week of Black Friday deal snapshots cost?
1 credit per scheduled run, independent of how many deals come back, plus 1 credit for every billed retry on a 502/503. A 500 with no Retry-After is refunded and retried once; a second 500 in a row is reported instead of retried again. An invalid marketplaceId is free. Running the snapshot every few hours for a week adds up fast, so pick a schedule that matches how often the deals page actually changes rather than polling continuously; for the base endpoint and its countdown/history-verdict uses, see the Deals API overview and the Lightning Deals countdown post.
| Scenario | Result |
|---|---|
| count > 50 requested | clamped to 50 before the call |
| Invalid marketplaceId | free 400, no retry |
| 500, no deal data on page | refunded, retried once |
| 502/503 | retried up to 4x, each billed |
| Deal with asin == "N/A" | counted, not tracked |
| ASIN missing from today's page | flagged GONE, baseline kept |
Frequently Asked Questions
Does the Amazon Deals API tell me when Black Friday starts?
No. /amazon-deals has no event calendar; it returns whatever is on the deals page right now. Track it yourself by snapshotting the endpoint on a schedule through the week, which is what the script below does.
Is an empty deals list during the week an error?
Not by itself. An empty deals array on a page that still carries the deal data structure is real and billed. Only a page with no deal data at all is a retryable 500, which SellerMagnet retries once automatically before you see it.
Can I filter the snapshot to one department?
Yes, with category_id, but it is never validated: an unknown or misspelled slug is not rejected, it just returns fewer or no deals, billed the same as a correct one. See the category_id post before relying on a filtered count.
Why track by ASIN instead of dealId?
dealId can be "N/A" for several rows on the same page, so it cannot key a baseline. ASIN is missing on some rows too (also "N/A"), but it is present often enough to track first-seen and last-seen per product across days; rows without an ASIN are counted but not tracked.
What does a week of snapshots cost?
1 credit per scheduled run (plus a billed retry on a 502/503), independent of how many deals came back. Seven runs a day for a week is 7 credits a day at minimum; a 500 is refunded, so it does not add to that.
Bottom line: treat /amazon-deals as a feed you poll, not a history you can query, keep a small ASIN-keyed baseline file across runs, and clamp count to 50 yourself so a typo never turns into a billed 400. A free account includes 150 credits to try the schedule end to end; see pricing for what a full week of polling costs at scale.