Scraping API Documentation
Complete API reference for the ASINSpotlight Amazon Scraping API
Base URL
https://api.asinspotlight.com/v1
Authentication
All requests require an API key passed in the x-api-key header:
curl -H "x-api-key: YOUR_API_KEY" "https://api.asinspotlight.com/v1/product?asin=B0B3ZD8QXJ"
| Header | Required | Description |
|---|---|---|
x-api-key |
Yes | Your API key |
Response Metadata
Every successful response carries a meta object with per-request metadata. The meta.usage block lets you drive quota-aware loops without polling a separate endpoint:
"meta": {
"marketplace": "us",
"language": "en",
"timing_ms": 1842,
"request_url": "https://api.asinspotlight.com/v1/product?asin=B0B3ZD8QXJ&marketplace=us",
"timestamp": "2026-05-03T10:14:22.318Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"usage": { "requests_consumed": 1, "requests_remaining": 49999 }
}
requests_consumed is 1 for any successful scrape (HTTP 200) — including not-found results where found is false, since the page was still fetched and parsed; 0 for any failure. meta.usage is also included on 429 rate-limit and quota-exceeded errors so you can read remaining credits even when a request is rejected.
Found vs. Not Found
Status codes reflect our service health, not what's on Amazon. Every successful response carries a top-level found boolean:
found: true— the entity was found;dataholds the payload.found: false— the scrape succeeded but the entity isn't on Amazon (invalid ASIN/seller, removed listing).dataisnull, the HTTP status is still200, and the request still counts as one credit.
Branch on found, not on the HTTP status, to tell "found" from "not found". Reserve 4xx for your request errors and 5xx for our failures.
Display Language
Every endpoint accepts an optional language parameter:
| Value | Behavior |
|---|---|
en |
The English render of the page (default) |
native |
The marketplace's own language (currently supported: it, fr, de, es, more coming) |
With language=native, free-text fields (title, category and brand names, condition_text, delivery text) come back in the marketplace's language, while normalized fields (condition slugs, prices, dates, units) stay canonical, so condition filters and your parsing keep working unchanged. The requested language is echoed in meta.language.
Requesting native on a marketplace without native support returns HTTP 400 with error code NATIVE_LANGUAGE_NOT_SUPPORTED and is not billed.
Example (Italian product page in Italian)
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.asinspotlight.com/v1/product?asin=B0BWSCJ13H&marketplace=it&language=native"
Deals and Promotions
Product data and list-page entries carry a promotion object describing the discount state of the offer: the deal badge Amazon renders only while a promotion is live, and the struck-through reference prices the discount is measured against. One fetch answers "is this product on a deal right now, and how deep is the cut?", with no price history required.
promotion appears on /v1/product data and on every shallow_parts[] entry of list pages (search, category, bestseller). It is null when the page shows neither a deal badge nor a struck-through price, which simply means the product is selling at its normal price.
"promotion": {
"deal": {
"kind": "limited_time",
"label": "Limited time deal",
"deal_id": "amzn1.deal.b12c9bd2",
"ends_at": "2026-08-16T04:00:00Z",
"ends_on": "2026-08-16",
"ends_in_days": 15,
"ends_on_text": null
},
"list_price": {
"amount": 199.99,
"label": "List Price:",
"savings_amount": 50.99,
"savings_percent": 25.0
},
"lowest_price_30_days": null
}
The three parts are independent: an everyday markdown can be struck through against a list price with no badge at all, and a badged deal does not have to carry a reference price.
The deal badge
Presence is the signal: Amazon renders the badge only while the deal is running, so a non-null deal means the promotion is live at fetch time, and null means it is not. That makes "has the promotion ended?" answerable from a single request instead of a price time series.
| Field | Description |
|---|---|
kind |
Normalized deal type: limited_time, lightning, or prime_exclusive. null when the badge wording names no mechanic (countdown badges, seasonal badges like "Black Friday Deal", unrecognized locales); the raw wording is still in label |
label |
Verbatim badge text as rendered, in the page's language ("Limited time deal", "Oferta flash") |
deal_id |
Amazon's own promotion ID (amzn1.deal.…), stable for the lifetime of one deal and shared across the ASINs it covers. Compare it across scrapes to tell a continuing deal from a new one at a similar price. null on list-page entries |
ends_at |
The exact moment the deal expires, ISO 8601 in UTC (2026-08-16T04:00:00Z). Read from Amazon's machine-readable timestamp, so it is locale-independent; prefer it for anything time-sensitive |
ends_on |
The calendar day the deal ends, ISO YYYY-MM-DD: the UTC day of ends_at, or the day named in Amazon's deal-terms popover when the page carries no timestamp. A day-granularity summary, not a purchase deadline |
ends_in_days |
Whole days from the scrape date to ends_on; 0 means the deal ends today. null on list-page entries (derive it from ends_at there) |
ends_on_text |
Amazon's own end-date sentence, verbatim ("Questa offerta termina il 26 luglio 2026"). Absent on storefronts that render no deal-terms popover (amazon.com is one) |
Reference prices
list_price and lowest_price_30_days share one shape:
| Field | Description |
|---|---|
amount |
The struck-through price |
label |
Verbatim label Amazon rendered ("List Price:", "Typical:", "Prezzo consigliato:"). null when the struck price carries no label |
savings_amount |
amount minus the current price. Never negative: a reference at or below the current price is not a discount, so the field is null rather than zero or negative |
savings_percent |
The discount as a positive whole percent. Amazon's own rendered figure when it shows one (that is what the shopper sees), otherwise derived and rounded the way Amazon rounds |
list_price is the headline struck-through price. Usually that is the manufacturer's list price, but Amazon fills the same slot with a typical, median, or former price when it has no list price to show ("Typical:", "Prezzo mediano:", "Ancien prix :"); the label wording is the only thing that states which one you got. lowest_price_30_days is the lowest price of the last 30 days, which EU storefronts must display alongside a discount (the Omnibus Directive); it is absent outside the EU.
On list pages. A search, category, or bestseller entry is a summary tile, so its promotion is the shallow half of what a product page reports: the deal is named (kind, label) and the struck-through price is present, but there is no deal_id and no ends_on_text, ends_at appears only for countdown badges (which state their own target), and ends_in_days is always null. Call /v1/product on the ASIN when you need the deal's identity or exact deadline.
Coupons are separate. A clip-to-apply coupon is a different discount mechanism and stays in its own coupon field ({"unit": "percent", "value": 15} or {"unit": "currency", "value": 5}), on both product data and list-page entries, and it means the same thing in both places: value is what the coupon takes off, never the price left after it. A percent coupon carries the percentage, a currency coupon the amount in the marketplace's local currency. Coupons are not reflected in price, so the net price is yours to compute. A listing can carry a coupon and a deal at the same time.
Endpoints
GET /v1/product
Retrieve product details by ASIN.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
asin |
string | Yes | Amazon product ASIN (10-character pattern ^[A-Z0-9]{10}$) |
|
marketplace |
string | No | us |
Marketplace code (see below) |
language |
string | No | en |
en or native (see Display Language) |
Example
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.asinspotlight.com/v1/product?asin=B0B3ZD8QXJ&marketplace=us"
Response
{
"success": true,
"found": true,
"page_type": "product",
"data": {
"asin": "B0B3ZD8QXJ",
"title": "Product Title",
"buybox": true,
"bb_price": 29.99,
"rating": 4.5,
"reviews": 1234,
"bsr": 5678,
"in_stock": true,
"is_prime": true,
"promotion": {
"deal": {
"kind": "limited_time",
"label": "Limited time deal",
"deal_id": "amzn1.deal.b12c9bd2",
"ends_at": "2026-08-16T04:00:00Z",
"ends_on": "2026-08-16",
"ends_in_days": 15,
"ends_on_text": null
},
"list_price": {
"amount": 39.99,
"label": "List Price:",
"savings_amount": 10.00,
"savings_percent": 25.0
},
"lowest_price_30_days": null
},
"brand": { "name": "BrandName", "url": "/stores/BrandName" },
"category": { "name": "Electronics", "url": "/b?node=123" },
"image_url": "https://m.media-amazon.com/images/I/...",
"sellers_all": 5,
"bought_past_month": 1000
},
"meta": {
"marketplace": "us",
"timing_ms": 2340,
"request_url": "https://api.asinspotlight.com/v1/product?asin=B0B3ZD8QXJ&marketplace=us",
"timestamp": "2026-04-03T12:00:00.000Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"usage": { "requests_consumed": 1, "requests_remaining": 49999 }
}
}
bought_past_month is coerced from Amazon's Xk+ bought in past month label to an integer (e.g. 2K+ → 2000).
promotion reports whether the offer is on a live deal and the struck-through prices the discount is measured against; null means the product is selling at its normal price. See Deals and Promotions.
GET /v1/offers
Retrieve every seller offer for a product — the full Buy Box panel and All Offers Display — with each offer's condition, price, shipping, fulfillment method, seller rating, and stock.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
asin |
string | Yes | Amazon product ASIN | |
marketplace |
string | No | us |
Marketplace code |
condition |
string | No | all |
Filter offers by condition (see below) |
language |
string | No | en |
en or native (see Display Language) |
Filtering by condition
By default (condition=all) offers of every condition are returned. Pass condition to narrow the result to a single condition or used tier:
| Value | Returns |
|---|---|
all |
All conditions (default) |
new |
New offers only |
used |
Any used tier (Like New / Very Good / Good / Acceptable) |
used_like_new |
Used – Like New only |
used_very_good |
Used – Very Good only |
used_good |
Used – Good only |
used_acceptable |
Used – Acceptable only |
collectible |
Collectible offers only |
The filter is applied to each offer's resolved condition, so the response contains only offers of the requested condition, and fba_count / fbm_count / sold_by_amazon reflect the filtered set. Each request counts as one credit regardless of the filter.
Example
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.asinspotlight.com/v1/offers?asin=B0B3ZD8QXJ&marketplace=us"
Example (used offers only)
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.asinspotlight.com/v1/offers?asin=B0B3ZD8QXJ&marketplace=us&condition=used"
Response
Each entry in product_sellers_info carries a normalized condition (new, used_like_new, used_very_good, used_good, used_acceptable, or collectible) and the verbatim condition_text heading Amazon shows for that offer. condition is null when Amazon's heading is absent or not recognized (e.g. Renewed/Refurbished, or some non-English wordings) — the raw label is still available in condition_text. seller_id is null for offers sold directly by Amazon.
The featured (Buy Box) offer is flagged with is_pinned. At most one offer per response carries it, so you can read the Buy Box winner directly instead of inferring it from price. Position in the array is not a substitute: a response can list many offers and still have no pinned one, which means Amazon rendered no featured offer for that ASIN. A condition filter can also drop the pinned offer, because Amazon returns the featured offer whatever condition you ask for.
Each offer carries a delivery array with the promises Amazon displayed. kind is standard for the primary slot and fastest for the secondary one, which Amazon also uses for Prime upsells, so read it as "the other option shown" rather than a guaranteed faster one. date_min / date_max give the promised window as absolute dates, and days_min / days_max express the same promise in whole days from the request date, where 0 means delivery today. When a promise cannot be resolved to a date, every date and day field is null rather than a placeholder number, so treat null as unknown. Use is_free rather than shipping_price to tell free delivery apart from a cost we could not read. text is Amazon's wording verbatim and can embed a live countdown, so avoid using it as a cache key.
{
"success": true,
"found": true,
"page_type": "offers",
"data": {
"sold_by_amazon": true,
"fba_count": 3,
"fbm_count": 2,
"product_sellers_info": [
{
"name": "Amazon.com",
"price": 29.99,
"shipping_price": 0,
"is_fba": true,
"is_amazon": true,
"stock_qty": 100,
"rating_percent": 95,
"reviews_count": 50000,
"condition": "new",
"condition_text": "New",
"seller_id": null,
"domain": "com",
"is_just_launched": false,
"is_pinned": true,
"delivery": [
{
"kind": "standard",
"days_min": 8,
"days_max": 8,
"date_min": "2026-07-25",
"date_max": "2026-07-25",
"is_free": true,
"text": "FREE delivery Saturday, July 25."
},
{
"kind": "fastest",
"days_min": 5,
"days_max": 7,
"date_min": "2026-07-22",
"date_max": "2026-07-24",
"is_free": null,
"text": "Or fastest delivery July 22 - 24."
}
]
},
{
"name": "ThriftBooks",
"price": 18.50,
"shipping_price": 3.99,
"is_fba": false,
"is_amazon": false,
"stock_qty": 5,
"rating_percent": 97,
"reviews_count": 4200,
"condition": "used_like_new",
"condition_text": "Used - Like New",
"seller_id": "A3XYZ123456",
"domain": "com",
"is_just_launched": false,
"is_pinned": false,
"delivery": [
{
"kind": "standard",
"days_min": 6,
"days_max": 11,
"date_min": "2026-07-23",
"date_max": "2026-07-28",
"is_free": false,
"text": "$3.99 delivery July 23 - 28."
}
]
}
]
},
"meta": { "..." : "..." }
}
GET /v1/search
Search products by keyword.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
keyword |
string | Yes | Search keyword. URL-encode multi-word queries (e.g. wireless+headphones) |
|
marketplace |
string | No | us |
Marketplace code |
page |
integer | No | 1 |
Page number to retrieve (1-based). Amazon usually caps a keyword search at 20 pages |
language |
string | No | en |
en or native (see Display Language) |
Pagination
Pass page=N to fetch deeper pages of the same query. The response's data.current_page and data.last_page_number tell you where you are and how many pages Amazon exposes for that query. Each paginated request counts as one credit.
Amazon serves one of two result-page layouts, chosen per query, and they pair with different pagination walls: up to 48 entries per page stopping at 7 pages, or up to 16 entries per page stopping at 20. Both reach roughly the same depth overall. Read shallow_parts.length and data.last_page_number from the response rather than assuming fixed values, and don't page past last_page_number: total_results_count reports the whole match set (often tens of thousands), not what Amazon will actually paginate through.
Example
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.asinspotlight.com/v1/search?keyword=wireless+headphones&marketplace=us"
Example (page 2)
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.asinspotlight.com/v1/search?keyword=wireless+headphones&marketplace=us&page=2"
Response
{
"success": true,
"found": true,
"page_type": "search",
"data": {
"title": "wireless headphones",
"current_page": 1,
"last_page_number": 20,
"total_results_count": 10000,
"shallow_parts": [
{
"asin": "B0CQXMXJC5",
"title": "Soundcore Q20i Headphones",
"index_on_page": 1,
"page_number": 1,
"price": 39.99,
"shipping": 0,
"is_prime": true,
"rating": 4.6,
"reviews": 58800,
"bought_past_month": 20000,
"in_stock": true,
"coupon": null,
"image_url": "https://m.media-amazon.com/images/I/...",
"promotion": {
"deal": {
"kind": "limited_time",
"label": "Limited time deal",
"deal_id": null,
"ends_at": null,
"ends_on": null,
"ends_in_days": null,
"ends_on_text": null
},
"list_price": {
"amount": 49.99,
"label": "List:",
"savings_amount": 10.00,
"savings_percent": 20.0
},
"lowest_price_30_days": null
},
"has_variation_swatches": true,
"advertised_variation_count": 4,
"sampled_variation_asins": ["B0CQXMABC1", "B0CQXMABC2", "B0CQXMABC3"]
}
]
},
"meta": { "..." : "..." }
}
index_on_page and page_number place each entry in Amazon's own ranking, so merged pages stay ordered. shipping is the shipping cost the tile quotes, in the marketplace's local currency, and 0 means free shipping. is_prime is true when the tile carries the Prime badge and null when it does not. coupon holds any clip-to-apply coupon the tile advertises, in the shape described in Deals and Promotions, and is null on most listings.
Each list item also carries three best-effort variation signals from the result tile's color/size swatches: has_variation_swatches is true when the tile shows a swatch row — a positive-only signal that the product is one child of a variation family (false means there was no swatch row, not a guarantee the product has no variations); advertised_variation_count is the family size the tile advertises (visible swatches plus the +N more overflow), or null when there is no swatch row; and sampled_variation_asins lists the sibling child ASINs Amazon rendered as swatches — a partial set (typically ≤6 for large families, excluding the item's own ASIN), not the full family. The same three fields appear on shallow_parts[] for every list page (search, category, bestseller).
Each entry also carries the tile's promotion (deal badge and struck-through reference price) in the shallow list-page shape described in Deals and Promotions: no deal_id, ends_at only for countdown badges, ends_in_days always null. Like the variation signals, it appears on shallow_parts[] for every list page.
GET /v1/seller
Retrieve a seller storefront profile by seller ID.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sellerId |
string | Yes | Amazon seller (merchant) ID — the A… token from a storefront URL (/sp?seller=<sellerId>) |
|
marketplace |
string | No | us |
Marketplace code |
language |
string | No | en |
en or native (see Display Language) |
Example
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.asinspotlight.com/v1/seller?sellerId=A1PXYTJNWCR133&marketplace=us"
Response
Returns the seller name, feedback rating and positive-feedback percentage over the last 12 months, total rating count, the storefront link, and the Detailed Seller Information block (legal business name and address) when Amazon publishes it. An invalid seller ID returns 200 with found: false and data: null.
{
"success": true,
"found": true,
"page_type": "seller",
"data": {
"seller_id": "A1PXYTJNWCR133",
"name": "Lovinio",
"storefront_url": "/s?ie=UTF8&marketplaceID=ATVPDKIKX0DER&me=A1PXYTJNWCR133",
"rating": 3.4,
"positive_percent": 59,
"ratings_count": 217,
"business_name": "Lovinio Inc",
"business_address": ["4652 Eagle Falls Pl", "Tampa", "FL", "33619", "US"]
},
"meta": { "..." : "..." }
}
POST /v1/scrape
Scrape any Amazon URL. The page type is auto-detected.
Body (JSON)
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
url |
string | Yes | Full Amazon URL | |
marketplace |
string | No | us |
Marketplace code |
language |
string | No | en |
en or native (see Display Language) |
Example
curl -X POST -H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.amazon.com/s?k=laptop+stand", "marketplace": "us"}' \
"https://api.asinspotlight.com/v1/scrape"
Response
The data shape matches the detected page_type. For product, search, offers, and seller it matches the typed-endpoint payloads above. category, bestseller, storefront, and reviews return a structured payload tailored to the page. A not-found page returns found: false and data: null.
{
"success": true,
"found": true,
"page_type": "search",
"data": { "...": "..." },
"meta": { "...": "..." }
}
Supported Marketplaces
| Code | Country | Domain |
|---|---|---|
us |
United States | amazon.com |
uk |
United Kingdom | amazon.co.uk |
de |
Germany | amazon.de |
fr |
France | amazon.fr |
it |
Italy | amazon.it |
es |
Spain | amazon.es |
ca |
Canada | amazon.ca |
au |
Australia | amazon.com.au |
jp |
Japan | amazon.co.jp |
in |
India | amazon.in |
mx |
Mexico | amazon.com.mx |
br |
Brazil | amazon.com.br |
tr |
Turkey | amazon.com.tr |
sa |
Saudi Arabia | amazon.sa |
ae |
UAE | amazon.ae |
sg |
Singapore | amazon.sg |
nl |
Netherlands | amazon.nl |
pl |
Poland | amazon.pl |
se |
Sweden | amazon.se |
be |
Belgium | amazon.com.be |
Error Responses
All errors follow a consistent format:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description"
}
}
429 rate-limit and quota responses also include a top-level meta.usage block so you can read remaining credits even when the request is rejected.
A missing entity (invalid ASIN/seller, removed listing) is not an error — it returns 200 with found: false and data: null, and counts as one credit. See Found vs. Not Found.
| HTTP Status | Code | Description |
|---|---|---|
| 400 | validation | Missing required parameter, invalid marketplace or language code, or unrecognized field. Returned by the framework's validation pipe; payload shape may differ from the standard error envelope. |
| 400 | NATIVE_LANGUAGE_NOT_SUPPORTED |
language=native requested on a marketplace without native support (see Display Language). Not billed |
| 401 | MISSING_API_KEY |
No x-api-key header provided |
| 401 | INVALID_API_KEY |
API key is not valid |
| 429 | RATE_LIMIT_EXCEEDED |
Too many parallel in-flight requests for your API key |
| 429 | MONTHLY_QUOTA_EXCEEDED |
No active API request credits available |
| 500 | SCRAPE_FAILED |
Parser reached the page but could not extract structured data |
| 500 | INTERNAL_ERROR |
Internal service failure or transport error to the parser worker |
| 503 | CAPTCHA_DETECTED |
Amazon returned a captcha page. Retry after a short delay |
| 503 | API_USAGE_UNAVAILABLE |
Usage tracking is temporarily unavailable. Retry after a short delay |