An Amazon negative feedback alert needs one API call per check: /amazon-seller-review returns your seller feedback counts and the five most recent feedback entries. Save both as a baseline, compare each later check with it, and e-mail every new 1- or 2-star entry with its text. The lifetime 1-star count catches ratings that already scrolled out of the five-entry list, and a failed check never touches the baseline.
Key Takeaways
/amazon-seller-reviewreturns positive, neutral and negative feedback as counts for 30, 90 and 365 days and lifetime, plus up to five recent entries with text and star rating.- The API files 2 stars under neutral and only 1 star under negative, while Amazon's feedback page calls both negative, so the script reads the star rating of each entry.
- The recent list holds five entries, so the lifetime 1-star count is the backstop for ratings that scrolled out between two checks.
- The first run only saves a baseline: feedback that existed before you started is not news.
- A daily check costs 1 credit, about 30 a month; a 404 for a wrong seller ID is billed, a 401 is free.
What does the API count as negative feedback?
positiveRating, neutralRating and negativeRating are counts, not percentages, each for 30d, 90d, 365d and lifetime. The API counts 4 and 5 stars as positive, 2 and 3 stars as neutral and 1 star as negative. Amazon's own feedback page calls 1 and 2 stars negative, so a 2-star rating shows up in neutralRating, and only the star rating of an entry in recentFeedback tells it apart from a 3-star one. Each entry has text, rating (1 to 5, or "N/A" when the star text could not be read), starRatingText and dateRated, the rater line with name and date. The seller feedback API guide covers every field.
| Rating | Counted in | Amazon's feedback page | Alert |
|---|---|---|---|
| 5 or 4 stars | positiveRating | positive | no |
| 3 stars | neutralRating | neutral | no |
| 2 stars | neutralRating | negative | yes, from the recent list |
| 1 star | negativeRating | negative | yes, list and lifetime count |
How does the Amazon negative feedback alert find new ratings?
read_feedback() makes one call and keeps two things: the lifetime 1-star count and the recent entries. Entries have no ID, so entry_key() hashes the rater line and the text. find_alerts() reports every unseen entry rated 1 or 2, then compares the lifetime 1-star count with the baseline: if it rose by more than the new 1-star entries explain, the rest scrolled out of the list and the e-mail says how many. get_with_retry comes from retry.py and ApiError and new_session from sellermagnet.py, both from the Python quickstart. The baseline is only saved after the e-mail went out, so an alert that could not be sent comes back on the next run.
feedback_alert.py - one feedback call, baseline compare, one e-mail per run
import hashlib
import json
import os
import smtplib
import sys
from email.message import EmailMessage
import requests
from retry import get_with_retry
from sellermagnet import ApiError, new_session
MARKETPLACE_ID = "ATVPDKIKX0DER" # amazon.com; feedback is counted per store
SELLER_ID = os.environ["MY_SELLER_ID"] # your own seller ID; no default on purpose
NEGATIVE_MAX = 2 # Amazon's feedback page calls 1 and 2 stars negative
STATE_FILE = "feedback_state.json"
def entry_key(entry: dict) -> str:
"""recentFeedback entries carry no ID: the rater line (name and date) plus the text identify one."""
raw = f"{entry.get('dateRated')}|{entry.get('text')}"
return hashlib.sha1(raw.encode("utf-8")).hexdigest()[:16]
def read_feedback(session) -> dict:
data = get_with_retry(session, "amazon-seller-review", sellerId=SELLER_ID, marketplaceId=MARKETPLACE_ID)
fb = data.get("feedback") or {}
return {"one_star": int((fb.get("negativeRating") or {}).get("lifetime") or 0), # 1-star count, not a %
"recent": fb.get("recentFeedback") or []} # the last 5 entries at most
def find_alerts(state: dict, now: dict) -> list[str]:
seen, lines, new_one_star = set(state["seen"]), [], 0
for entry in now["recent"]:
rating = entry.get("rating") # 1-5, or "N/A" when unreadable
if entry_key(entry) in seen or not isinstance(rating, int) or rating > NEGATIVE_MAX:
continue
new_one_star += rating == 1
lines.append(f"{rating}-star: \"{entry.get('text', '')[:300]}\" {entry.get('dateRated', '')}")
# the list shows 5 entries; the lifetime count catches 1-star ratings that already scrolled out of it
extra = now["one_star"] - state["one_star"] - new_one_star
if extra > 0:
lines.append(f"{extra} more 1-star rating(s) than the recent list shows")
return lines
def send_mail(lines: list[str]) -> bool:
msg = EmailMessage()
msg["Subject"] = f"Amazon feedback alert: {len(lines)} new negative item(s)"
msg["From"], msg["To"] = os.environ["SMTP_USER"], os.environ["ALERT_TO"]
msg.set_content("\n".join(lines) + f"\n\nSeller {SELLER_ID} on marketplace {MARKETPLACE_ID}\n")
try:
with smtplib.SMTP_SSL(os.environ["SMTP_HOST"], int(os.environ.get("SMTP_PORT", "465")), timeout=30) as smtp:
smtp.login(os.environ["SMTP_USER"], os.environ["SMTP_PASSWORD"])
smtp.send_message(msg)
except (smtplib.SMTPException, OSError) as err:
print(f"mail not sent ({type(err).__name__}): the alert is sent again on the next run")
return False
return True
def load_state() -> dict:
try:
with open(STATE_FILE, encoding="utf-8") as fh:
return json.load(fh)
except FileNotFoundError:
return {}
def save_state(state: dict) -> None:
tmp = STATE_FILE + ".tmp"
with open(tmp, "w", encoding="utf-8") as fh:
json.dump(state, fh, indent=1)
os.replace(tmp, STATE_FILE) # a crash mid-write keeps the last good baseline
def main() -> int:
state = load_state()
try:
now = read_feedback(new_session())
except ApiError as err: # 404 = wrong seller ID for this store (billed)
print(f"check failed: {err}; baseline kept")
return 1
except requests.RequestException as err:
print(f"check failed: {type(err).__name__} after retries; baseline kept")
return 1
keys = [entry_key(e) for e in now["recent"]]
if not state: # first run: today's feedback is the baseline, not news
save_state({"one_star": now["one_star"], "seen": keys})
print(f"baseline saved: {now['one_star']} 1-star rating(s) lifetime, {len(keys)} recent entries")
return 0
lines = find_alerts(state, now)
if lines and not send_mail(lines):
return 1 # baseline unchanged, so the same alert comes back
one_star = now["one_star"]
if one_star < state["one_star"]: # removed feedback or a partly read page: keep the bar
print(f"1-star count fell from {state['one_star']} to {one_star}; baseline kept")
one_star = state["one_star"]
save_state({"one_star": one_star, "seen": list(dict.fromkeys(keys + state["seen"]))[:50]})
print(f"{len(lines)} new negative item(s){', mailed' if lines else ''}")
for line in lines:
print(" " + line)
return 0
if __name__ == "__main__":
sys.exit(main())
First run and the next day (illustrative feedback; [mail] = the message the SMTP server got)
$ python feedback_alert.py # first run (illustrative feedback)
baseline saved: 12 1-star rating(s) lifetime, 5 recent entries
$ python feedback_alert.py # next day: 1 one-star and 1 two-star in the list, 2 new one-stars counted
[mail] Subject: Amazon feedback alert: 3 new negative item(s)
3 new negative item(s), mailed
2-star: "Not as described, the charger was missing." By K. Example on October 22, 2026.
1-star: "Arrived two weeks late and the box was crushed." By J. Example on October 22, 2026.
1 more 1-star rating(s) than the recent list shows
Trimmed /amazon-seller-review response (example values)
{
"success": true,
"data": {
"sellerId": "A1MYSELLER0001",
"marketplaceId": "ATVPDKIKX0DER",
"sellerFullName": "My Store",
"feedback": {
"negativeRating": {"30d": 1, "90d": 2, "365d": 5, "lifetime": 14},
"neutralRating": {"30d": 1, "90d": 2, "365d": 4, "lifetime": 20},
"recentFeedback": [
{"text": "Not as described, the charger was missing.", "rating": 2,
"starRatingText": "2 out of 5 stars", "dateRated": "By K. Example on October 22, 2026."}
]
}
}
}

How do I run the feedback check every day?
Put the key, your seller ID and the mail settings into an environment file and start the script from cron. MY_SELLER_ID has no default on purpose: the script refuses to start without it instead of checking the wrong seller. If you do not know the ID, the seller ID lookup guide shows where to find it. Feedback is counted per store, so one run checks one marketplace; copy the MARKETPLACE_ID line into a second script for a second store.
Environment file and a daily cron entry
# .env (keep it out of git: it holds the API key and the mail password)
export SELLERMAGNET_API_KEY="your-key-here"
export MY_SELLER_ID="A1MYSELLER0001" # your seller ID on this store
export SMTP_HOST="smtp.example.com" # your mail provider's SMTP server
export SMTP_PORT="465" # SMTP submission over implicit TLS
export SMTP_USER="alerts@example.com"
export SMTP_PASSWORD="app-password-here"
export ALERT_TO="you@example.com"
# crontab -e: once a day at 07:00 = 30 credits a month
0 7 * * * cd /opt/feedback && . ./.env && .venv/bin/python feedback_alert.py >> feedback.log 2>&1
smtplib.SMTP_SSL opens the connection with TLS from the first byte, and its default port is 465 (Python documentation, checked 2026-10-23); RFC 8314 registers port 465 for message submission over implicit TLS. Use the host, port and app password your mail provider documents. A login or connection error is reported without the password, the baseline stays unchanged, and the same alert goes out on the next run.
How many credits does a feedback alert cost?
One /amazon-seller-review call per check, whatever the number of entries. Billing follows the usual rules: a 404 is billed once and not retried, a 502 or 503 is billed on every attempt of get_with_retry, a 500 is refunded, and the free gate errors (400 for a missing parameter, 401, 403, 429) cost nothing. Plans are on Pricing.
| Check interval | Credits per day | Credits per 30 days |
|---|---|---|
| Once a day | 1 | 30 |
| Twice a day | 2 | 60 |
| Every 6 hours | 4 | 120 |
| Every hour | 24 | 720 |

What can the alert not see?
Three limits come from the data. The recent list shows five entries, so a 2-star rating that scrolls out of it between two checks is missed, because its count sits in neutralRating next to the 3-star ones. A falling count (removed feedback, or a page that was only partly read) never lowers the baseline, so it cannot trigger a false alarm later; the script prints the drop instead. And an entry whose rating is "N/A" is only caught through the 1-star count. If you also want to know when an unauthorised seller shows up on your listings, the hijacker alert watches the offer list for that.
Frequently Asked Questions
Does Amazon count 2-star feedback as negative?
Amazon's feedback page does: it calls 1 and 2 stars negative and 3 stars neutral. The API's negativeRating counts only 1-star ratings and files 2 stars under neutralRating, so the script reads the star rating of each recent entry.
How often should I check my seller feedback?
Once a day costs about 30 credits a month. Check more often if more than five new feedback entries can arrive between two checks, because the recent list only shows the last five.
Why did the first run send no e-mail?
The first run saves the current counts and the recent entries as the baseline. Feedback that existed before you started is not new, so only later checks can send an alert.
What does a 404 from the feedback endpoint mean?
The seller ID has no feedback page on that store: a typo, the wrong marketplace, or Amazon's own seller ID. A 404 is billed, so the script reports it, keeps the baseline and does not retry.
Does the alert cover product reviews?
No. Seller feedback rates the seller, product reviews rate the item, and they live on different pages. This endpoint reads the seller feedback page only.
Bottom line: one feedback call a day, a baseline of the lifetime 1-star count and the recent entries, and an e-mail only for new 1- and 2-star ratings is a complete negative feedback alert for about 30 credits a month. A free account includes 150 credits to try it.