Home / Blog / Amazon Deals API category_id: Why It Silently Returns Fewer Deals

Amazon Deals API category_id: Why It Silently Returns Fewer Deals

The category_id parameter on /amazon-deals is never validated: an unknown department slug is not rejected, it is passed straight to Amazon's filter and just returns fewer or no deals, billed like any other answer. Check category_id against the real category tree before you call, the same way /amazon-bestsellers is checked, because here there is no error to catch a typo after the fact.

October 27, 2026
4 min read
SellerMagnet Team
Share & Bookmark
Terminal window with the Python function that checks category_id against the category tree before calling the Amazon Deals API

category_id on /amazon-deals is an optional department slug (the documented example is electronics) that Amazon's deals-widget filter uses as-is, with no validation on the API side. Pass a value that is not a real department and you do not get an error: you get fewer deals, or none, billed exactly like a correct call. A monitor that reads a low or zero count as “no deals right now” can be reading a typo instead.

Key Takeaways

  • category_id on /amazon-deals is never checked - an unknown value just filters silently.
  • /amazon-bestsellers checks the same kind of parameter and answers a billed HTTP 400 instead.
  • A 0-deal result looks identical whether the department is genuinely quiet or the slug is wrong.
  • Check category_id against the marketplace's category tree before calling; the tree fetch is free.
  • An empty deals array is billed, real data; a page that could not be read is a retryable 5xx, never an empty list.

Why does my category_id filter return almost nothing?

Because the parameter has no gate. /amazon-deals takes marketplaceId (required), category_id (optional), count (default 30, max 50) and geo_location (best-effort). category_id is passed straight to Amazon's deals-widget department filter. A real department with few live deals and a misspelled or outdated slug produce the same shape of answer: a short deals array, success: true, billed. Nothing in the response tells you which one happened.

What does /amazon-bestsellers do differently?

/amazon-bestsellers takes the same kind of category_id and checks it against the catalogue: an unknown value answers a billed HTTP 400 Invalid CategoryId, caught the moment you make the call. That is worse for your wallet (you still pay the credit) but better for your code, because an exception is something you can catch and log. /amazon-deals has no equivalent check, so the same local-tree lookup that is an optimisation for bestsellers (skip a 400 you would get anyway) is the only defence here - there is no 400 to learn from afterwards. See the category ID guide for the full known_ids() pattern this reuses.

The same kind of filter parameter, three different behaviours
EndpointParameterUnknown valueWhat you see
/amazon-dealscategory_idaccepted as-is, no checkfewer or no deals, billed 1 credit
/amazon-bestsellerscategory_idchecked against the cataloguebilled HTTP 400 “Invalid CategoryId”
/amazon-product-statisticsasinchecked against the ASIN formatbilled HTTP 400 “Invalid product ASIN”

deals_category_guard.py - check locally, then call

"""Validate category_id against the real category tree before spending a credit on /amazon-deals.

/amazon-deals never rejects an unknown category_id (FACTS.md "Deals", verified 2026-09-20): it is an
optional department slug passed straight to Amazon's deals-widget filter as-is, so a typo or a stale
value just returns fewer or no deals - billed like any other answer, with no error to catch it. Contrast
with /amazon-bestsellers, which DOES reject an unknown category_id with a billed HTTP 400 "Invalid
CategoryId" (amazon-category-ids-bestsellers-api.py). The local-tree check that post treats as an
optimisation (skip a 400 you'd get anyway) is the ONLY defence on the deals side, because there is no
400 to learn from afterwards.
"""
from functools import lru_cache

import requests

from retry import get_with_retry
from sellermagnet import new_session

# Same tree the bestsellers endpoint is checked against: one JSON file per marketplace, top-level nodes
# carry a readable slug as CategoryId (FACTS.md "Deals": the deals category_id doc example is
# 'electronics', the same slug family).
TREE_URL = "https://sellermagnet-api.com/static/data/{code}.json"


def all_ids(nodes):
    for node in nodes:
        yield str(node["CategoryId"]).lower()
        yield from all_ids(node.get("Subcategories") or [])


@lru_cache(maxsize=None)  # one download per marketplace
def known_ids(tree_code: str) -> frozenset:
    url = TREE_URL.format(code=tree_code)
    tree = requests.get(url, timeout=60).json()
    return frozenset(all_ids(tree))


def deals_report(session, marketplace_id: str, category_id: str, tree_code: str, count: int = 50) -> dict:
    """Call /amazon-deals filtered and unfiltered, and flag an unrecognised category_id BEFORE its
    result count gets read as "this department has few deals right now"."""
    known = category_id.lower() in known_ids(tree_code)
    filtered = get_with_retry(session, "amazon-deals", marketplaceId=marketplace_id,
                               category_id=category_id, count=count)["deals"]
    baseline = get_with_retry(session, "amazon-deals", marketplaceId=marketplace_id, count=count)["deals"]
    report = {"category_id": category_id, "known": known, "filtered": len(filtered), "baseline": len(baseline)}
    if not known:
        report["warning"] = (
            f"{category_id!r} is not a department on the {tree_code} tree - the {len(filtered)} deals above "
            "are whatever Amazon's filter let through for an unrecognised value, not a department count")
    elif len(filtered) == 0 and len(baseline) > 0:
        report["warning"] = "0 deals in a real department: billed the same as any other answer, not a bug"
    return report


if __name__ == "__main__":
    session = new_session()
    for category_id in ("electronics", "eletcronics", "pet-supplies"):
        row = deals_report(session, "ATVPDKIKX0DER", category_id, "US")
        flag = f"  <- {row['warning']}" if "warning" in row else ""
        print(f"{row['category_id']:<14} known={str(row['known']):<5} filtered={row['filtered']:>2}  "
              f"baseline={row['baseline']:>2}{flag}")

Same check from the shell: known slug vs a typo

# known department: billed, real data either way
curl -sG "https://sellermagnet-api.com/api/amazon-deals" \
  --data-urlencode "marketplaceId=ATVPDKIKX0DER" \
  --data-urlencode "category_id=electronics" \
  --data-urlencode "api_key=$SELLERMAGNET_API_KEY" | jq ".data.deals | length"

# the category tree, free, no api_key needed - confirm the slug before you pay for the call above
curl -s "https://sellermagnet-api.com/static/data/US.json" \
  | jq -r '.[] | .CategoryId' | grep -ix electronics

Output (illustrative category_ids and counts)

electronics    known=True  filtered=12  baseline=41
eletcronics    known=False filtered= 3  baseline=41  <- 'eletcronics' is not a department on the US tree - the 3 deals above are whatever Amazon's filter let through for an unrecognised value, not a department count
pet-supplies   known=True  filtered= 0  baseline=41  <- 0 deals in a real department: billed the same as any other answer, not a bug
Blueprint table of three category_id checks: a known department with deals, an unknown slug that leaked a few deals through, and a known but quiet department
Same shape of answer, three different causes - only the tree check tells them apart.

How do I tell a quiet department from a typo?

Load the marketplace's category tree once from /static/data/<CC>.json - a plain static file, not a billed call - and keep every top-level CategoryId slug in a set. Before spending a credit, confirm category_id is in that set. If it is not, you already know the low count is not a department size, whatever Amazon's filter happened to let through. If it is, a 0 result is real: a billed, honest answer that this department has nothing live right now, not a bug. Compare the filtered call with an unfiltered baseline call (no category_id) when you can afford the extra credit: a baseline of 0 too means the whole marketplace is quiet, not the department.

Blueprint flow diagram: category_id goes through a local tree check before the billed Amazon Deals API call, splitting into a known-department path and an unrecognised-value warning
The tree check happens before the credit is spent, not after reading the count.

What this costs in credits

The deals call is 1 credit whether category_id is valid, unknown, or omitted - the tree check itself costs nothing. Comparing a filtered call with a baseline call, as the example script does, doubles the credit cost of that one lookup; reserve it for a one-off audit of a category_id you do not trust, not a routine poll. count above 50 is a billed HTTP 400 Count Max: 50, and 502/503 are retried and billed on every attempt - the same retry and error handling rules as every other endpoint. For the deal fields themselves (price, savings, start/end time) see the Amazon Deals API guide; for /amazon-bestsellers category IDs follow the same catalogue, documented in the docs.

Frequently Asked Questions

Does /amazon-deals reject an unknown category_id?

No. It is an optional department slug passed straight to Amazon's deals-widget filter with no validation. An unrecognised value does not error; it just returns fewer or no deals, billed like any other answer.

So how do I tell a typo from a department that is genuinely quiet right now?

You can't from the response alone - 0 deals looks the same either way. Check category_id against the marketplace's category tree before calling, the same tree the bestsellers endpoint is checked against, and only trust a 0 result for a value you confirmed is a real department.

Why does /amazon-bestsellers behave differently for the same kind of parameter?

It validates category_id against the catalogue and answers a billed HTTP 400 “Invalid CategoryId” for an unknown value. /amazon-deals has no such gate, so there is no error to catch a typo after the fact.

Does checking the category tree cost a credit?

No. The tree is a static JSON file per marketplace at /static/data/<CC>.json, not a billed API call; cache it with lru_cache or on disk and reuse it for every check.

Is an empty deals array ever a sign of a real error?

No. A real error is a retryable 5xx, not an empty list - since the 2026-09-19 Lambda deploy, a page that could not be read answers 502/503 instead of a billed empty result.

Bottom line: /amazon-deals will never tell you category_id was wrong. Check it against the category tree before you call, treat a 0 result from a confirmed department as real, and treat a 0 result from an unconfirmed slug as meaningless.

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