Home / Blog / Amazon Listing Change Monitor: Catch Title, Image and Variation Changes via API

Amazon Listing Change Monitor: Catch Title, Image and Variation Changes via API

Monitor Amazon listings for changes with one lookup per ASIN: snapshot title, bullets, images, variations and A+ content, ignore cosmetic noise such as image size variants, and report real content changes apart from price moves.

September 25, 2026
4 min read
SellerMagnet Team
Share & Bookmark
Blueprint flow from a list of ASINs through daily lookups and normalised snapshots to change alerts

To monitor an Amazon listing for changes, look it up once a day with /api/amazon-product-lookup, keep a normalised snapshot of the title, bullet points, description, images, variations and A+ flag, and compare it with the previous run. Normalising is what makes it usable: image URLs change size suffixes and whitespace shifts without anything real happening. The script below stores snapshots in a JSON file and reports content changes apart from price and Buy Box moves.

Key Takeaways

  • One lookup per ASIN and run returns the fields a shopper sees first: title, bullets, description, images, variations, A+ flag.
  • Compare image IDs, not URLs: the same photo appears under different size suffixes such as _SL500_ and _SL1000_.
  • Keep content changes (title, images, variations) apart from offer changes (price, Buy Box seller), which move all day.
  • Never overwrite a good snapshot with a failed or title-only lookup, and alert on content only when two runs agree.
  • Daily checks cost 30 credits per ASIN a month; a hundred listings need 3,000.

What can change on an Amazon listing?

Several sellers can contribute to one detail page, Amazon decides which contribution is shown and makes catalog edits of its own, so the page you wrote is not guaranteed to stay that way. The fields worth watching are the ones a shopper reads first - title, bullets, main image - plus the structure around them: the variation family and whether A+ content is still shown.

Fields in the snapshot and how each is normalised before comparing.
FieldNormalised asAlert class
titleWhitespace collapsedContent
bulletsEach bullet trimmed, order keptContent
descriptionHash of the trimmed textContent
imagesImage IDs, size modifiers droppedContent
variationsSorted set of child ASINsContent
aplusTrue / falseContent
price · buybox_sellerAs returnedOffer

How do I detect listing changes in Python?

snapshot() reduces a lookup to comparable values, check() compares each ASIN with its stored snapshot and saves the new one. The first run only records a baseline. A failed lookup - a 404, a 502, a timeout, or a page that returned a title but no body - is skipped, and a content change is reported only when two runs in a row agree, because a page can occasionally render its bullets from a different block. get(), new_session() and ApiError are the helpers from the Python quickstart.

listing_watch.py - snapshot, compare, report

"""Detect changes to Amazon listings between two runs of this script."""
import hashlib
import json
import re
import sys
from pathlib import Path

import requests

from sellermagnet import ApiError, get, new_session

STATE = Path("listing_state.json")
IMAGE_ID = re.compile(r"/images/I/([^./]+)")        # 71abcDEF+L in .../images/I/71abcDEF+L._SL1000_.jpg
CONTENT = ("title", "bullets", "description", "images", "variations", "aplus")   # the seller's content
OFFER = ("price", "buybox_seller")                  # changes many times a day - report separately


def _norm(text) -> str:
    return " ".join(str(text or "").split())


def snapshot(asin: str, marketplace_id: str) -> dict | None:
    """The comparable parts of a listing, or None when the lookup gave nothing usable."""
    try:
        p = get(new_session(), "amazon-product-lookup", asin=asin, marketplaceId=marketplace_id)["productInfo"]
    except (ApiError, requests.RequestException) as err:
        print(f"{asin}: skipped ({getattr(err, 'status', type(err).__name__)})", file=sys.stderr)
        return None                                   # keep the last good snapshot
    title = _norm(p.get("title"))
    if title in ("", "N/A"):
        return None
    if not any(p.get(f) for f in ("categories", "details", "bulletPoints")):
        return None                                   # title only: the page body did not load
    price = (p.get("buyBoxInfo") or {}).get("price")
    return {
        "title": title,
        "bullets": [_norm(b) for b in p.get("bulletPoints") or []],
        "description": hashlib.sha256(_norm(" ".join(p.get("description") or [])).encode()).hexdigest()[:16],
        "images": [m.group(1) for m in map(IMAGE_ID.search, p.get("images") or []) if m],
        "variations": sorted({v.get("asin") for v in p.get("variations") or [] if v.get("asin")}),
        "aplus": bool(p.get("hasAPlusContent")),
        "price": price if isinstance(price, (int, float)) else None,
        "buybox_seller": (p.get("buyBoxInfo") or {}).get("sellerId"),
    }


def describe(field: str, old, new) -> str:
    if field == "description":
        return "description: text changed"          # stored as a hash, the text itself is long
    if isinstance(old, list) and isinstance(new, list):
        added, removed = [x for x in new if x not in old], [x for x in old if x not in new]
        if not added and not removed:
            return f"{field}: same items, new order"
        return f"{field}: +{len(added)} / -{len(removed)} ({', '.join(map(str, (added + removed)[:3]))})"
    return f"{field}: {old!r} -> {new!r}"


def check(listings: list[tuple[str, str]]) -> list[str]:
    state = json.loads(STATE.read_text()) if STATE.exists() else {}
    alerts = []
    for asin, marketplace_id in listings:
        key, snap = f"{marketplace_id}:{asin}", snapshot(asin, marketplace_id)
        if snap is None:
            continue
        old, pending = state.get(key), state.pop(key + "?", None)
        content = {f: snap[f] for f in CONTENT}
        if old and any(old.get(f) != content[f] for f in CONTENT) and pending != content:
            state[key + "?"] = content                # seen once: alert only if the next run agrees
            continue
        for field in (CONTENT + OFFER) if old else ():
            if old.get(field) != snap[field]:
                kind = "CONTENT" if field in CONTENT else "offer"
                alerts.append(f"[{kind}] {asin} {describe(field, old.get(field), snap[field])}")
        state[key] = snap
    STATE.write_text(json.dumps(state, indent=1))
    return alerts


if __name__ == "__main__":
    for line in check([("B0CL61F39H", "ATVPDKIKX0DER"), ("B0CLTBHXWQ", "APJ6JRA9NG5V4")]):
        print(line)

Example output once an edit has been seen on two runs

[CONTENT] B0CL61F39H title: 'PS5 Console slim' -> 'PS5 Console slim - Digital'
[CONTENT] B0CL61F39H bullets: +1 / -0 (New bullet)
[CONTENT] B0CL61F39H description: text changed
[CONTENT] B0CL61F39H images: same items, new order
[CONTENT] B0CL61F39H variations: +0 / -1 (B0CL5KNB9M)
[CONTENT] B0CL61F39H aplus: True -> False
[offer] B0CL61F39H price: 444.99 -> 419.0
[offer] B0CL61F39H buybox_seller: 'Amazon' -> 'A2I59UVTUWUFH0'
Blueprint table of the listing fields in a snapshot, how each is normalised and whether it is content or offer
Normalise first, then compare - a new size suffix or image host would otherwise read as a new photo.

Why image IDs, not URLs

Amazon serves the same photo under many URLs: the ._SL1000_ part is only a size instruction. The ID before the first dot identifies the image, so comparing IDs catches a new photo and ignores a new size. The image download guide explains the suffixes.

What does each kind of change usually mean?

A report is only useful with a next step. The causes below are the common ones, not the only ones - check the live page before acting. Variation changes are covered in depth in the variations guide, and Buy Box moves belong to the polling guide.

Blueprint table of listing changes, their usual causes and the first thing to check
From alert to action.

How often should the monitor run?

Once a day is enough for content: it changes far less often than prices. Each run costs one credit per ASIN - 30 a month per listing - so watch your own listings and the handful of competitors that matter rather than a whole category. Price and Buy Box lines change almost daily, so the wrapper below keeps them in a file and posts only content changes to a chat webhook; most chat webhooks accept a JSON body with a text field.

run.sh - daily run from cron, alerts to a chat webhook

#!/usr/bin/env bash
# run.sh - started by cron once a day:  30 7 * * * /opt/listing-watch/run.sh
set -euo pipefail
cd /opt/listing-watch
export SELLERMAGNET_API_KEY="your-key-here"            # cron has no login environment
CHAT_WEBHOOK_URL="https://hooks.example.com/your-webhook"

.venv/bin/python listing_watch.py > changes.txt
grep '^\[CONTENT\]' changes.txt > alerts.txt || true    # price and Buy Box moves stay in changes.txt
if [ -s alerts.txt ]; then                              # only post when content changed
  curl -fsS -X POST -H "Content-Type: application/json" \
    --data "$(jq -Rs '{text: .}' alerts.txt)" "$CHAT_WEBHOOK_URL"
fi

Frequently Asked Questions

Can I get an alert when someone changes my Amazon listing?

Yes. Look the ASIN up once a day, compare a normalised snapshot with the previous one, and alert on differences in title, bullets, images, variations or A+ content.

Why do image URLs change when the image did not?

The URL carries a size instruction such as ._SL1000_. Compare the image ID before the first dot instead of the full URL.

How much does monitoring cost?

One credit per ASIN per run. A daily check is about 30 credits per listing a month.

What happens when a lookup fails?

The script skips that ASIN and keeps its last good snapshot, so the next successful run still compares against real data.

Does this detect hijackers?

It shows changed content and a new Buy Box seller, which are the usual signs. The offers endpoint lists the featured offer and the first page of other offers.

Bottom line: one lookup a day, a normalised snapshot and a plain comparison catch the listing changes that matter without drowning you in URL noise. Every lookup field is on the product lookup page, and a free account includes 150 credits.

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