API reference
Product
GET/v1/product/{asin}Price, normal price, verdict, listing claim and full history for one product.
asinrequiredpathstring- Example
- B01NBKTPTS
The product's ASIN (10 characters).
history0 | 1- Default
- 1
0 omits
history[]for a smaller response.live0 | 1- Default
- 0
1 = check the price on amazon.com at request time instead of returning the last price we recorded. Pro and Business only. Uses 5 requests, or 1 if the live check fails or can't run. Live checks per key per day: 4,000 on Pro and 20,000 on Business; only checks with
live.statusokcount. Past that you get the stored data withlive.reasonlimit, counted as 1. The reading is recorded into the price history. A live check takes about 1–2 seconds in our tests.live_timeout_msinteger, range 1000–15000- Default
- 8000
With
live=1, or for a product we don't track yet: how long to wait for the live check, in milliseconds, counted from when we receive the request. Values outside 1,000–15,000 are clamped. If the check doesn't finish in time: withlive=1you get the last price we recorded andlive.statusfailed; for a product we don't track yet,tracking.statusisfailedwithtracking.reasontimeout(a 404, or a 200 with the prices we hold when we hold some). Requests for the same product at the same moment share one check: each waits at most its ownlive_timeout_ms, and the check runs within the first request's.discover0 | 1- Default
- 1
For a product we don't track yet: 1 checks it on amazon.com and, if amazon.com lists it, starts tracking it and returns its price (see
tracking). 0 skips that and returns 404not foundstraight away. Either way the call counts as 1 request.
A JSON object. Money is integer cents (USD) and times are milliseconds since the Unix epoch. Fields marked nullable can be null.
asinstringThe product's ASIN.
titlestringnullableProduct title as listed.
imagestringnullableProduct image URL on Amazon's image CDN.
currencystringAlways "USD". Money fields are integer cents.
categorystringnullableAmazon's category group, raw (e.g. "CE", "Kitchen").
category_labelstringnullableA readable name for
category(e.g. "Electronics").in_stockbooleannullableAvailability at the last check. With
live=1, what the live check found when it read it.price.integernullablecurrent_cents The latest price we recorded. With a live price (
price.sourceislive), the price the live check found.price.integernullablereference_cents The normal price: the lowest price level the product held for at least 15% of the last 45 days (about a week). Prices within 3% of each other count as one level, and we use the lowest price in that level.
price.numbernullablediscount_pct Percent below normal (positive) or above normal (negative). Two decimals.
price.booleanis_deal True when the current price is a deal by the state model.
price.integernullableobserved_at When we last saw the current price (ms since epoch). For when the price last changed, use
price.changed_at. For a live price, when amazon.com answered the check, or a later sighting of the same price we already held.price.integernullablechanged_at When the price became the current price (ms since epoch): the start of the current step in
history[]. With a live price that differs from the stored one, when amazon.com answered the check.price.integernullablechecked_at The latest check of this product we have on record (ms since epoch), whatever it found. Never earlier than
price.observed_at; later when the price wasn't on the listing at that check (out of stock or no offer).price.one oflivestoredsource livewhenpricecomes from a live check (live=1andlive.statusisokwith a price).storedotherwise, including when a live check failed or couldn't run. Always present.verdict.one ofgreatgoodnormalabovesteadyearlylabel The same verdict the website shows. Computed without the price-error override; see
verdict.suspect.verdict.stringtext Display text, e.g. "Great price".
verdict.numbernullablepct_vs_normal Equal to
price.discount_pct; null for steady and early.verdict.one ofstrongmoderatethinnoneevidence How much history backs the verdict: strong 60+ days, moderate 30–59, thin 14–29, none under 14. A stale price caps it at thin.
verdict.booleanstale True when the price hasn't been seen for over 72 hours.
verdict.booleansuspect True for a likely listing error: under $1, or more than 90% below normal.
verdict.integernullablesteady_since For steady prices: when the single price was first seen.
verdict.stringsentence The plain-English verdict sentence.
listing_claimobjectnullableThe crossed-out price on the listing, when there is one and the listing price equals the current price.
listing_claim.stringlabel The listing's own label, verbatim (e.g. "List:", "Typical:").
listing_claim.integerlisting_reference_cents The crossed-out price.
listing_claim.integerclaimed_pct The discount the listing shows against its crossed-out price.
listing_claim.numbernullablereal_pct The discount against the normal price.
listing_claim.one ofholds_upsmallernot_below_normaluncheckedclass holds_up: real ≥ half the claim. not_below_normal: real under 2%. unchecked: under 30 days of history or stale.
listing_claim.integernullableobserved_at When the listing was read.
stats.integernullablemin_cents Lowest price in the history we hold.
stats.integernullablemax_cents Highest price in the history we hold.
stats.integernullableavg_cents Time-weighted average price: each price weighed by how long it held (until the price changed), with time out of stock and our known data outages left out.
stats.integerhistory_days Days between the first and last recorded price.
stats.integerprice_levels Number of steps in
history[](kept under its original name).stats.integerprice_changes Number of price changes: steps whose price is more than 1% from the step before (a product back in stock at its old price starts a new step but is no change).
stats.integerdistinct_prices Number of different prices seen (prices within 1% of each other count as one). 1 means the price has never moved.
stats.integernullablelowest_at When the lowest price was most recently seen.
history[]arrayPrice steps, oldest first. Omitted when
history=0.history[].integerprice_cents The price held during this step.
history[].integerfrom When the step started (ms since epoch).
history[].integerto When the price was last seen at this level. The price held until the next step's
from(or until the product went out of stock, when that came first). Draw a step chart; don't interpolate.last_seenintegernullableWhen we last saw this product.
liveobjectWhen
live=1was sent, or when a request for a product we didn't track checked it on amazon.com: how the live check went.live.one ofokfailedunavailablestatus ok: the check read the listing.failed: it ran but didn't get a reading.unavailable: it didn't run. Unless it'sok, the rest of the response is the stored data and the call counts as 1 request.live.one oftimeoutupstreamnot_listederrorbusypauseddisabledlimitnullablereason Why the check failed or didn't run; null when
live.statusisok. See Live checks in the guides.live.integernullablechecked_at When amazon.com answered the check (ms since epoch). Earlier than your request when
live.sharedis true.live.booleanshared True when the reading wasn't made for this request: amazon.com was read within the last 60 seconds, by another request's live check or by our regular tracking.
live.integerlatency_ms How long this request waited for the live check.
live.integernullableprice_cents The price the check found. Null when the listing showed no single price or the check didn't get a reading;
pricethen keeps the stored price.live.booleannullablein_stock Availability the check found.
live.integernullableprevious_cents The latest price we had recorded before this check.
live.integernullableprevious_observed_at When we last saw
live.previous_cents.live.booleannullablechanged True when
live.price_centsis more than 1% away fromlive.previous_cents. Null when either is missing.live.booleanrecorded True when the reading was added to the price history, so later responses include it.
trackingobjectOnly when we didn't track the product and this request checked it on amazon.com to start tracking it (or tried to). Also on the 404 when that didn't work.
tracking.booleanstarted True when we track the product now. The rest of the response is the product as the check found it: the price amazon.com showed (
price.sourcelive) and, for a product we held no prices for, one price, no normal price yet and verdictearly. A product we had prices for keeps its history. When amazon.com showed no single price,price.current_centsis null.tracking.booleannew True for the one request that added the product. Other requests at the same moment share its check and get false.
tracking.one ofstartednot_listedfailedunavailableblockeddisabledstatus started, or why tracking didn't start. See New products in the guides.tracking.one ofnot_listedtimeoutupstreamerrorbusypausedblockeddisablednullablereason The detail behind
tracking.status; null when it'sstarted.tracking.integernullablesince When we started tracking the product (ms since epoch); null unless
started.tracking.one ofapisitesource Where the request came from:
apifor API calls.tracking.integernullablefirst_scan_eta_s About how long until our first full check fills in the image, rating and category (about 25 minutes); null unless
started.tracking.integernullableverdict_after_days Days of prices we need before a verdict (14); null unless
started.tracking.integernullableretry_after_s Seconds to wait before asking again, when that may work; also in the
Retry-Afterheader of a 404. Null otherwise.
- History covers everything we hold for the product, as steps with real start and end times.
- The normal price uses only the last 45 days, so it follows genuine long-term price changes.
live=1(Pro and Business) checks the price on amazon.com at request time. The price, verdict, listing claim, stats and history then describe the price it found, and the reading is recorded into the price history.- When a live check fails or can't run, the answer is still a 200 with the stored data;
live.statusandlive.reasonsay why. Readprice.sourceto tell a live price from a stored one. - A product we don't track yet is checked on amazon.com and, when amazon.com lists it, added to the products we track, on every plan and without
live=1. You get a 200 with today's price (price.sourcelive; for a product we held no prices for, verdictearly: we need 14 days of prices before we judge it) and atrackingobject. When that doesn't work you get 404not foundwithtracking.statussaying why, or a 200 with the prices we hold, andtracking, when we hold some.discover=0turns this off.live=1on a plan without live checks is still a 403, and nothing is checked. - Calls count toward your key's per-minute ceiling (Free 60, Pro 300, Business 1,200 calls a minute). A call over it gets 429
rate limit exceededand isn't charged. - Each call with a valid key counts as one request against your daily quota, even if the request then fails. A call turned away by a per-minute ceiling (429
rate limit exceeded) doesn't count. A call withlive=1counts as 5, or 1 when the live check fails or can't run. A call for a product we don't track yet counts as 1, whatever the outcome, also withlive=1on a plan that has live checks.
- 400
invalid asinThe path isn't a 10-character ASIN. The request still counts against your quota (1 request, also with
live=1). - 401
missing api keyNo key sent.
- 401
invalid api keyUnknown or revoked key.
- 403
live checks need a paid planlive=1with a key on a plan without live checks (Pro and Business have them). Not counted against your quota. - 404
not foundWe don't track this ASIN and couldn't start tracking it:
tracking.statussays why, withRetry-Afterwhen asking again later may work. Withouttracking(withdiscover=0), we didn't try. When we hold older prices for an ASIN we don't track, you get a 200 with them andtrackinginstead. The request still counts against your quota (1 request, also withlive=1). - 405
method not allowedThe data endpoints are GET only. The
Allowheader lists the methods an endpoint answers: GET, HEAD and OPTIONS (POST and OPTIONS for/v1/keys/rotate). - 429
rate limit exceededMore than your plan's per-minute ceiling; wait
Retry-Afterseconds. Not charged. The body addslimit,window_sandretry_after_s. - 429
daily quota exceededOver today's quota. The body adds
limitandused. - 500
internal errorSomething failed on our side, not in your request.
- 503
temporarily unavailableOur database can't be reached right now. Retry after the number of seconds in the
Retry-Afterheader.
Error bodies are {"error": string}. A daily-quota 429 adds limit and used; a per-minute 429 adds limit, window_s and retry_after_s, with a Retry-After header. See Errors and Quotas.
Send a real request from this page. The demo needs no key and covers 5 products; with your key you can ask for any ASIN, and one we don't track yet is checked on amazon.com and added.
Send a request to see the status, headers and body here.
Early access.