Skip to content
TrackPin developers

API reference

Product

GET/v1/product/{asin}

Price, normal price, verdict, listing claim and full history for one product.

1 request per call · 5 with live=1Try it

Parameters

  • asinrequiredpath
    string
    Example
    B01NBKTPTS

    The product's ASIN (10 characters).

  • history
    0 | 1
    Default
    1

    0 omits history[] for a smaller response.

  • live
    0 | 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.status ok count. Past that you get the stored data with live.reason limit, counted as 1. The reading is recorded into the price history. A live check takes about 1–2 seconds in our tests.

  • live_timeout_ms
    integer, 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: with live=1 you get the last price we recorded and live.status failed; for a product we don't track yet, tracking.status is failed with tracking.reason timeout (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 own live_timeout_ms, and the check runs within the first request's.

  • discover
    0 | 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 404 not found straight away. Either way the call counts as 1 request.

Response

A JSON object. Money is integer cents (USD) and times are milliseconds since the Unix epoch. Fields marked nullable can be null.

  • asinstring

    The product's ASIN.

  • titlestringnullable

    Product title as listed.

  • imagestringnullable

    Product image URL on Amazon's image CDN.

  • currencystring

    Always "USD". Money fields are integer cents.

  • categorystringnullable

    Amazon's category group, raw (e.g. "CE", "Kitchen").

  • category_labelstringnullable

    A readable name for category (e.g. "Electronics").

  • in_stockbooleannullable

    Availability at the last check. With live=1, what the live check found when it read it.

  • price.current_centsintegernullable

    The latest price we recorded. With a live price (price.source is live), the price the live check found.

  • price.reference_centsintegernullable

    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.discount_pctnumbernullable

    Percent below normal (positive) or above normal (negative). Two decimals.

  • price.is_dealboolean

    True when the current price is a deal by the state model.

  • price.observed_atintegernullable

    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.changed_atintegernullable

    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.checked_atintegernullable

    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.sourceone oflivestored

    live when price comes from a live check (live=1 and live.status is ok with a price). stored otherwise, including when a live check failed or couldn't run. Always present.

  • verdict.labelone ofgreatgoodnormalabovesteadyearly

    The same verdict the website shows. Computed without the price-error override; see verdict.suspect.

  • verdict.textstring

    Display text, e.g. "Great price".

  • verdict.pct_vs_normalnumbernullable

    Equal to price.discount_pct; null for steady and early.

  • verdict.evidenceone ofstrongmoderatethinnone

    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.staleboolean

    True when the price hasn't been seen for over 72 hours.

  • verdict.suspectboolean

    True for a likely listing error: under $1, or more than 90% below normal.

  • verdict.steady_sinceintegernullable

    For steady prices: when the single price was first seen.

  • verdict.sentencestring

    The plain-English verdict sentence.

  • listing_claimobjectnullable

    The crossed-out price on the listing, when there is one and the listing price equals the current price.

  • listing_claim.labelstring

    The listing's own label, verbatim (e.g. "List:", "Typical:").

  • listing_claim.listing_reference_centsinteger

    The crossed-out price.

  • listing_claim.claimed_pctinteger

    The discount the listing shows against its crossed-out price.

  • listing_claim.real_pctnumbernullable

    The discount against the normal price.

  • listing_claim.classone ofholds_upsmallernot_below_normalunchecked

    holds_up: real ≥ half the claim. not_below_normal: real under 2%. unchecked: under 30 days of history or stale.

  • listing_claim.observed_atintegernullable

    When the listing was read.

  • stats.min_centsintegernullable

    Lowest price in the history we hold.

  • stats.max_centsintegernullable

    Highest price in the history we hold.

  • stats.avg_centsintegernullable

    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.history_daysinteger

    Days between the first and last recorded price.

  • stats.price_levelsinteger

    Number of steps in history[] (kept under its original name).

  • stats.price_changesinteger

    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.distinct_pricesinteger

    Number of different prices seen (prices within 1% of each other count as one). 1 means the price has never moved.

  • stats.lowest_atintegernullable

    When the lowest price was most recently seen.

  • history[]array

    Price steps, oldest first. Omitted when history=0.

  • history[].price_centsinteger

    The price held during this step.

  • history[].frominteger

    When the step started (ms since epoch).

  • history[].tointeger

    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_seenintegernullable

    When we last saw this product.

  • liveobject

    When live=1 was sent, or when a request for a product we didn't track checked it on amazon.com: how the live check went.

  • live.statusone ofokfailedunavailable

    ok: the check read the listing. failed: it ran but didn't get a reading. unavailable: it didn't run. Unless it's ok, the rest of the response is the stored data and the call counts as 1 request.

  • live.reasonone oftimeoutupstreamnot_listederrorbusypauseddisabledlimitnullable

    Why the check failed or didn't run; null when live.status is ok. See Live checks in the guides.

  • live.checked_atintegernullable

    When amazon.com answered the check (ms since epoch). Earlier than your request when live.shared is true.

  • live.sharedboolean

    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.latency_msinteger

    How long this request waited for the live check.

  • live.price_centsintegernullable

    The price the check found. Null when the listing showed no single price or the check didn't get a reading; price then keeps the stored price.

  • live.in_stockbooleannullable

    Availability the check found.

  • live.previous_centsintegernullable

    The latest price we had recorded before this check.

  • live.previous_observed_atintegernullable

    When we last saw live.previous_cents.

  • live.changedbooleannullable

    True when live.price_cents is more than 1% away from live.previous_cents. Null when either is missing.

  • live.recordedboolean

    True when the reading was added to the price history, so later responses include it.

  • trackingobject

    Only 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.startedboolean

    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.source live) and, for a product we held no prices for, one price, no normal price yet and verdict early. A product we had prices for keeps its history. When amazon.com showed no single price, price.current_cents is null.

  • tracking.newboolean

    True for the one request that added the product. Other requests at the same moment share its check and get false.

  • tracking.statusone ofstartednot_listedfailedunavailableblockeddisabled

    started, or why tracking didn't start. See New products in the guides.

  • tracking.reasonone ofnot_listedtimeoutupstreamerrorbusypausedblockeddisablednullable

    The detail behind tracking.status; null when it's started.

  • tracking.sinceintegernullable

    When we started tracking the product (ms since epoch); null unless started.

  • tracking.sourceone ofapisite

    Where the request came from: api for API calls.

  • tracking.first_scan_eta_sintegernullable

    About how long until our first full check fills in the image, rating and category (about 25 minutes); null unless started.

  • tracking.verdict_after_daysintegernullable

    Days of prices we need before a verdict (14); null unless started.

  • tracking.retry_after_sintegernullable

    Seconds to wait before asking again, when that may work; also in the Retry-After header of a 404. Null otherwise.

Notes

  • 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.status and live.reason say why. Read price.source to 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.source live; for a product we held no prices for, verdict early: we need 14 days of prices before we judge it) and a tracking object. When that doesn't work you get 404 not found with tracking.status saying why, or a 200 with the prices we hold, and tracking, when we hold some. discover=0 turns this off. live=1 on 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 exceeded and 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 with live=1 counts 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 with live=1 on a plan that has live checks.

Errors

  • 400invalid asin

    The path isn't a 10-character ASIN. The request still counts against your quota (1 request, also with live=1).

  • 401missing api key

    No key sent.

  • 401invalid api key

    Unknown or revoked key.

  • 403live checks need a paid plan

    live=1 with a key on a plan without live checks (Pro and Business have them). Not counted against your quota.

  • 404not found

    We don't track this ASIN and couldn't start tracking it: tracking.status says why, with Retry-After when asking again later may work. Without tracking (with discover=0), we didn't try. When we hold older prices for an ASIN we don't track, you get a 200 with them and tracking instead. The request still counts against your quota (1 request, also with live=1).

  • 405method not allowed

    The data endpoints are GET only. The Allow header lists the methods an endpoint answers: GET, HEAD and OPTIONS (POST and OPTIONS for /v1/keys/rotate).

  • 429rate limit exceeded

    More than your plan's per-minute ceiling; wait Retry-After seconds. Not charged. The body adds limit, window_s and retry_after_s.

  • 429daily quota exceeded

    Over today's quota. The body adds limit and used.

  • 500internal error

    Something failed on our side, not in your request.

  • 503temporarily unavailable

    Our database can't be reached right now. Retry after the number of seconds in the Retry-After header.

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.

Try it

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.

The demo covers these 5 products. It needs no key and uses no quota. It returns the prices we recorded; live checks need your key.

Request

GET /api/demo/product/B01NBKTPTS

Same response as /v1/product, without a key.

Response

Send a request to see the status, headers and body here.

Early access.

Free keys are emailed straight away; Pro and Business are bought by card through Stripe. Request an API key