# Amazon ASIN Lookup & Buy Box Monitor (`zucchini_gopher_m2v/amazon-asin-buybox-monitor`) Actor

Look up Amazon products by ASIN: price, stock, condition, rating, Best Sellers rank, variation family, and the Buy Box seller with ships-from and return policy. Tracks price and Buy Box changes between runs. No login needed.

- **URL**: https://apify.com/zucchini\_gopher\_m2v/amazon-asin-buybox-monitor.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/zucchini_gopher_m2v) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Amazon ASIN Lookup & Buy Box Monitor

Feed it a list of ASINs, get back product detail, the **Buy Box seller**,
stock, return policy and the full variation family — plus price and Buy Box
**changes since your last run**.

Built for retail arbitrage and dynamic-pricing sellers.

No login, no API key. HTTP-only — no browser.

### Read this before you buy: Amazon withholds the offer block

This is the single most important thing about scraping Amazon product pages
anonymously, and it decides whether this actor fits your use case.

Amazon serves logged-out visitors **two different versions** of a product
page, deterministically per ASIN:

| | Full page | Reduced page |
| --- | --- | --- |
| Title, brand, rating, reviews, BSR, images, variations | ✅ | ✅ |
| **Price** | ✅ | ❌ not served |
| **Buy Box seller / seller ID** | ✅ | ❌ not served |
| **Ships from, return policy** | ✅ | ❌ not served |

Measured over 10 successfully fetched ASINs: **6 full, 4 reduced.** Price and
seller always appear *together* — Amazon serves the whole offer block or none
of it.

The reduced page is not a transient failure. The same ASIN was refetched
**6 times across 4 TLS fingerprints and returned the reduced page every
time**. Warming a session first, reusing cookies, `?th=1&psc=1`,
`/gp/offer-listing/`, and the `aod` all-offers endpoints were all tested —
none of them make the offer block appear. It is not something a retry, a
proxy or a better fingerprint fixes.

**So every row carries `buyBoxDataAvailable`.** When it is `false`, the
seller fields are `null` because *Amazon did not serve them* — not because
Amazon is the seller. Never read a null seller as "sold by Amazon".

```
buyBoxDataAvailable = true   ->  price, seller, returns are real
buyBoxDataAvailable = false  ->  those fields are UNKNOWN, not empty
```

If your workflow needs seller data on 100% of ASINs, this actor will not
deliver it and neither will any other logged-out scraper — that data is
behind Amazon's SP-API.

### What you get

```json
{
  "asin": "B00XIKPUK4",
  "title": "NAKED Whey 2LB 100% Grass Fed Whey Protein Powder",
  "brand": "Naked Nutrition",
  "price": 62.99,
  "currency": "$",
  "inStock": true,
  "condition": "New",
  "rating": 4.3,
  "reviewCount": 10766,
  "bestSellersRank": 288,
  "bestSellersCategory": "Health & Household",
  "buyBoxDataAvailable": true,
  "buyBoxSellerId": "A2GB0T0Z517LEH",
  "buyBoxSellerName": "Naked Nutrition Inc",
  "buyBoxIsAmazon": false,
  "shipsFrom": "Amazon",
  "returnPolicy": "Non-returnable",
  "parentAsin": "B0CS4S2TWH",
  "variationCount": 19,
  "variationDimensions": ["Size", "Flavor Name"],
  "changeType": "PRICE_DECREASED",
  "previousPrice": 68.99,
  "priceDelta": -6.0,
  "priceDeltaPercent": -8.7
}
```

Field coverage over 10 fetched ASINs:

| Field | Coverage |
| --- | --- |
| title, rating, reviewCount, parentAsin | 10/10 |
| bestSellersRank, imageUrl, childAsins | 9/10 |
| brand, featureBullets | 8/10 |
| **price, currency, Buy Box seller** | **6/10** |
| shipsFrom, returnPolicy | 4/10 |

### Monitoring changes

Set `trackChanges: true` and the actor stores a snapshot in a named
key-value store, then labels every row on the next run:

```
NEW · UNCHANGED · PRICE_INCREASED · PRICE_DECREASED
BUYBOX_SELLER_CHANGED · BACK_IN_STOCK · OUT_OF_STOCK
```

Schedule it and you have a Buy Box watch on your ASIN portfolio.

**A withheld offer block never fires a false alert.** If Amazon served a
seller last run and withheld it this run, that is `UNCHANGED`, not
`BUYBOX_SELLER_CHANGED` — otherwise ~40% of your portfolio would alert on
every single run. The same guard applies to stock.

Use different `snapshotName` values to track separate portfolios.

### Input

```json
{
  "asins": ["B00XIKPUK4", "B06XX65GS1", "https://www.amazon.com/dp/B01MQQ4RFA"],
  "marketplace": "com",
  "trackChanges": true,
  "expandVariations": false
}
```

| Field | Description |
| --- | --- |
| `asins` | ASINs to look up. Product URLs work too — the ASIN is extracted |
| `marketplace` | com, co.uk, de, fr, it, es, ca, com.au, co.jp, in |
| `includeVariations` | Capture parent/child ASINs and dimensions (default on) |
| `expandVariations` | Also scrape each child as its own row, for per-variant prices |
| `maxVariationsPerAsin` | Cap children fetched per product (default 5) |
| `trackChanges` | Compare against the previous run |
| `snapshotName` | Named store for the snapshot — one per portfolio |
| `delaySeconds` | Pacing between products |
| `proxyConfiguration` | Residential **strongly recommended** |

### Things worth knowing

**Prices are pinned to the marketplace currency.** Amazon prices from the
request's geo-IP — an Indonesian exit returned `IDR 336,939.57` for a $19.99
item. The actor sends the cookie that forces the marketplace's currency, so
figures stay comparable wherever the run exits from. `currency` is on every
row regardless, so never assume USD.

**A null price is a null price.** The reduced page still contains dozens of
`a-offscreen` price spans belonging to *related products and other variants*.
Reading the first one gave one $25 protein powder a price of **$2489.00**.
The actor only accepts a price from Amazon's canonical price containers, so
a product whose price was not served reports `null` rather than a plausible
wrong number. For a repricing tool that distinction is the whole ballgame.

**`inStock` is tri-state.** `null` means Amazon served no stock signal —
not "out of stock". A cart button on a reduced page is not counted as proof
of stock, because that button is rendered even when no offer exists.

**Blocked ASINs are reported, never silently dropped.** A bot wall emits a
`CAPTCHA_ESCALATION` row so a short run is never mistaken for a short
catalogue. CAPTCHAs are never solved. Expect some blocking without a
residential proxy — 2 of 12 ASINs were blocked from a home IP in testing.

### Known limits

- **Buy Box / seller / returns on ~60% of ASINs**, for the reason above.
- **Only the Buy Box offer**, not the full offer list. The all-offers panel
  (`aod`) returns 404 to logged-out clients, so competing-seller counts and
  used/refurbished prices are not obtainable — `condition` reflects the New
  offer shown.
- **No sales estimates or bid data** — no public surface exposes them.
- Variation children are only expanded one level, from the ASINs you supply.

See [CRAWLING\_METHOD.md](CRAWLING_METHOD.md) for the fingerprint matrix, the
full-vs-reduced page investigation, and every endpoint that was ruled out.

# Actor input Schema

## `asins` (type: `array`):

Amazon ASINs to look up. Full product URLs are accepted too — the ASIN is extracted from them.

## `marketplace` (type: `string`):

Amazon domain to query. Prices are pinned to this marketplace's currency regardless of where the run exits from.

## `includeVariations` (type: `boolean`):

Capture the parent ASIN, child ASINs and variation dimensions (size, colour, flavour) for each product.

## `expandVariations` (type: `boolean`):

Fetch each child ASIN as its own row, giving per-variant prices. Costs one extra request per child.

## `buyBoxRetries` (type: `integer`):

Amazon withholds the offer block from some exit IPs. When the Buy Box is missing, refetch this many times on a new proxy session. Raises seller coverage substantially; costs extra requests only on the ASINs that need it. Set 0 to disable.

## `maxVariationsPerAsin` (type: `integer`):

Cap on child ASINs fetched per product when expanding variations.

## `trackChanges` (type: `boolean`):

Compare against the previous run and label each row NEW / UNCHANGED / PRICE\_INCREASED / PRICE\_DECREASED / BUYBOX\_SELLER\_CHANGED / BACK\_IN\_STOCK / OUT\_OF\_STOCK.

## `snapshotName` (type: `string`):

Named key-value store holding the previous run's state. Use separate names to track separate ASIN portfolios.

## `delaySeconds` (type: `integer`):

Pacing between product pages. Amazon blocks fast anonymous crawling of /dp/ quickly.

## `proxyConfiguration` (type: `object`):

Residential proxy is strongly recommended — Amazon blocks datacentre IPs on product pages within a few requests.

## Actor input object example

```json
{
  "asins": [
    "B00XIKPUK4",
    "B06XX65GS1",
    "B0BJLBD427"
  ],
  "marketplace": "com",
  "includeVariations": true,
  "expandVariations": false,
  "buyBoxRetries": 2,
  "maxVariationsPerAsin": 5,
  "trackChanges": false,
  "snapshotName": "amazon-buybox-snapshots",
  "delaySeconds": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per ASIN: product detail, Buy Box seller and change verdict.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "asins": [
        "B00XIKPUK4",
        "B06XX65GS1",
        "B0BJLBD427"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("zucchini_gopher_m2v/amazon-asin-buybox-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "asins": [
        "B00XIKPUK4",
        "B06XX65GS1",
        "B0BJLBD427",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("zucchini_gopher_m2v/amazon-asin-buybox-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "asins": [
    "B00XIKPUK4",
    "B06XX65GS1",
    "B0BJLBD427"
  ]
}' |
apify call zucchini_gopher_m2v/amazon-asin-buybox-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zucchini_gopher_m2v/amazon-asin-buybox-monitor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/X121lmwXWpeW4DjKD/builds/Gxnsm5OwovDH62AKa/openapi.json
