# Amazon Rank Tracker - Keyword Position, Share of Shelf & Delta (`doesaiknow/amazon-rank-tracker`) Actor

Track your ASINs' organic & sponsored positions for up to 100 keywords per run, plus share-of-shelf per brand and position delta vs your last run. Amazon search volume attached. Flat price per keyword - not per row. Schedule it daily; cheaper than a Helium 10 seat.

- **URL**: https://apify.com/doesaiknow/amazon-rank-tracker.md
- **Developed by:** [David S](https://apify.com/doesaiknow) (community)
- **Categories:** E-commerce, Automation, SEO tools
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 rank check (per keyword, per page scanned)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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 Rank Tracker — Keyword Position, Share of Shelf & Delta 📈

**Find out where your ASINs actually rank in Amazon search — for up to 100 keywords per run, up to 3
result pages deep — plus how much of the first page your brand owns and what moved since your last
run.** Flat price per keyword, never per row. Schedule it daily and every run tells you what
changed. Works with n8n, Make, Zapier and AI agents (Claude, ChatGPT) through the Apify API and MCP.

Most Amazon scrapers hand you a list of products and leave you to find yourself in it. This one
answers the question you actually had: **is my product on page one for this keyword, who is beating
me, and am I going up or down?**

---

### What you get per keyword

One dataset row per keyword. Here is a real one, trimmed for readability:

```json
{
  "keyword": "protein powder",
  "run_at": "2026-07-20T12:00:00Z",
  "observed_from": "New York 10001",
  "location_pinned": true,
  "requested_postal_code": "10001",
  "source": "primary",
  "samples_taken": 3,
  "position_noise": 1,
  "search_volume": 135000,
  "results_scanned": 50,
  "pages_scanned": 1,
  "sponsored_count": 7,

  "tracked": [
    {
      "asin": "B000QSNYGI",
      "found": true,
      "page": 1,
      "position_absolute": 4,
      "position_organic": 4,
      "sponsored": false,
      "is_amazon_choice": false,
      "is_best_seller": false,
      "price": 96.28,
      "stars": 4.6,
      "reviews": 99400,
      "bought_last_month": 20000,
      "brand": "Optimum Nutrition",
      "delta": {
        "position_organic": 3,
        "vs_run": "2026-07-19T12:00:00Z",
        "within_noise": false,
        "noise_floor": 1
      }
    },
    {
      "asin": "B0MISSING1",
      "found": false,
      "page": null,
      "position_organic": null,
      "not_in_top": 50
    }
  ],

  "share_of_shelf": {
    "top_n": 10,
    "top10_organic": [
      { "brand": "Optimum Nutrition", "slots": 5, "share": 0.5, "brand_source": "stated" },
      { "brand": "Premier Protein", "slots": 2, "share": 0.2, "brand_source": "clustered" }
    ],
    "top10_sponsored": [
      { "brand": "Transparent Labs", "slots": 4, "share": 0.571, "brand_source": "stated" }
    ],
    "full_organic": [ "… same shape, across everything scanned …" ],
    "tracked_set_share_top10": 0.1,
    "tracked_set_share_full": 0.04,
    "slots_counted": { "organic_top10": 10, "sponsored_top10": 7, "organic_full": 50 }
  },

  "new_in_top10": ["B0BJLBD427"],
  "dropped_from_top10": ["B00J074W7Q"],
  "vs_run": "2026-07-19T12:00:00Z"
}
````

Field by field:

| Field | What it means |
|---|---|
| `tracked[].position_organic` | Rank among **organic results only**, recomputed with ads removed |
| `tracked[].position_absolute` | Rank counting every slot on the page, ads included |
| `tracked[].found: false` + `not_in_top` | Your ASIN was not in the results scanned. **This is an answer**, and it is delivered and billed like any other |
| `tracked[].delta` | Change since your previous run of the same watchlist. **Positive = moved up** |
| `share_of_shelf.tracked_set_share_*` | Share of slots held by your ASIN set — matched by ASIN, **exact** |
| `share_of_shelf.*_organic` / `*_sponsored` | Share per brand, organic and sponsored kept separate |
| `results_scanned` | How many results were actually examined (Amazon varies this — see below) |
| `position_noise` | How much this keyword's own results shifted between samples |
| `samples_taken` | How many times the keyword was checked before taking the median |
| `source` | `primary` or `fallback` — which data path answered |

***

### Track changes, not snapshots

A position on its own is data. A position **compared with last week** is a decision.

Set a **Watchlist ID** — any label you like, such as `my-brand` or a client name — and schedule the
Actor daily or weekly. Every run after the first one compares itself with the previous run under the
same watchlist and reports:

- `delta.position_organic` per tracked ASIN — **positive means it moved up the page**
- `new_in_top10[]` — ASINs that entered the first ten organic slots
- `dropped_from_top10[]` — ASINs that fell out of them

Use a **separate watchlist ID per brand or per client**, and history stays separate too.

#### How to set it up

1. Run once with your keywords, ASINs and a watchlist ID. The first run has nothing to compare
   against, so it reports no movement — deliberately. Reporting everything as "new" on day one
   would tell you nothing.
2. Add an **Apify Schedule** (daily or weekly).
3. From the second run onward, every row carries its own movement.

#### When we refuse to report movement

Deltas are suppressed, rather than guessed, when two runs are not like-for-like:

- **The data path changed** between runs (`source_changed`)
- **The location pin changed** between runs (`location_pin_changed`)

Both produce genuinely different result sets, so diffing them would report *relocation* as *ranking
movement*. A tracker that invents a drop-out is worse than one that says "not comparable" — so this
one says it.

***

### Share of shelf — who owns the first page

For every keyword you get slot share at two depths (the top 10, and everything scanned), split
between organic and sponsored:

- **Per brand** — who occupies the shelf
- **For your tracked ASIN set** — how much of it is yours

Two things worth knowing, because they change how much weight to put on each number:

**Your own share is exact.** `tracked_set_share_*` is computed by matching ASINs you supplied. There
is no guesswork in it.

**Competitor brand shares are inferred.** Amazon does not publish a brand field on organic search
results, so brands are worked out from product titles: the page's own stated brands are matched
first, then titles that share a leading phrase are grouped (three "Cesar …" products are one brand
holding 30%, not three brands holding 10% each). Every entry carries `brand_source`:

| `brand_source` | Meaning |
|---|---|
| `stated` | Amazon stated this brand on the page |
| `clustered` | Inferred, and corroborated by other titles on the same page |
| `guessed` | Inferred from a single title with nothing to corroborate it |
| `none` | Could not be determined — the slot is still counted, under `brand: null` |

Unattributed slots are **counted, not dropped**. Dropping them would quietly inflate everyone
else's share.

***

### Pricing — flat per keyword, not per row

| Event | Price (FREE tier) | When it fires |
|---|---|---|
| Actor start | $0.05, once per run | Run starts; covers validation and the cache check |
| Rank check | $0.025 per keyword × pages scanned | Once per keyword delivered |

Tiered by your Apify Store tier: **FREE $0.025 · BRONZE $0.022 · SILVER $0.019 · GOLD $0.015**.

Everything is included in that one price — positions for every tracked ASIN, share of shelf, deltas,
and Amazon search volume. There is no separate enrichment fee and no per-row charge.

**You are never charged for:** keywords that failed upstream · runs served from the 24-hour cache ·
empty results. Those cost the start fee only.

**You are charged for** an ASIN that does not rank, because "you are not in these results" is the
answer you came for.

#### What it actually costs

All-in, including the $0.05 start fee on every run:

| Who | Setup | Rank checks/month | Monthly total |
|---|---|---|---|
| Seller | 25 keywords, daily, 1 page | 750 | **$20.25** |
| Agency | 5 clients × 30 keywords, weekly | 650 | **$14.51** (BRONZE) |
| Hobby | 10 keywords, weekly | 43 | **$1.30** |

#### Flat vs per-row — read this before comparing headline prices

Some trackers advertise a lower per-keyword price and then bill **per returned row**. An Amazon
result page holds roughly 50–65 products, so:

| Model | Headline | Real cost, one keyword, one page |
|---|---|---|
| Per-row rival | $0.01/keyword + $0.001/row | $0.01 + ~60 × $0.001 = **~$0.07** |
| **This Actor** | $0.025/keyword flat | **$0.025** — regardless of how many results came back |

That is **~2.8× cheaper in practice**, and it includes share of shelf, deltas and volume, which the
per-row model does not offer at all. More importantly, your bill is **predictable**: 25 keywords
daily is $20.25 a month, every month, whatever Amazon returns.

#### Start free

- **10-keyword preview per run** on a free Apify plan — real positions, no charge
- New Apify accounts include **$5 of platform credit** — roughly **200 keyword checks** here

The free preview returns genuine positions. Search volume and run-over-run tracking are the paid
features.

***

### How it compares

| | This Actor | Helium 10 | Jungle Scout | Per-row trackers |
|---|---|---|---|---|
| Price | **from $0.015/keyword** | $99/mo (Platinum) | $49–129/mo | $0.01/kw + $0.001/row |
| Keyword cap | 100/run, unlimited runs | **500 keywords** on Platinum | tier-capped | none |
| Share of shelf | **included** | Diamond, $279/mo+ | Cobalt (enterprise) | not offered |
| Run-over-run delta | **included** | included | included | not offered |
| Search volume attached | **included** | included | included | not offered |
| Seats | **none — API keyed to your account** | per seat | per seat | n/a |
| Multi-client / agency use | **unlimited watchlists** | per-seat cost | per-seat cost | manual |
| Output | JSON / CSV / API, schedulable | dashboard-first | dashboard-first | JSON |

If you live in a dashboard all day, a suite is a reasonable purchase. If you want rankings **in your
own systems** — a sheet, a database, a Slack alert, an LLM report — paying $99/month for a UI you
will not open is the expensive option.

***

### Use with AI agents & LLM workflows

The output is designed to be consumed by software, not just read.

**n8n / Make / Zapier**

1. Schedule trigger → run this Actor
2. Read the dataset
3. Branch on `dropped_from_top10.length > 0` → Slack alert, or append rows to Google Sheets

**AI agents (Claude, ChatGPT) via Apify MCP**

Connect the Apify MCP server and ask directly:

> "Run the Amazon Rank Tracker for my watchlist `my-brand` and tell me which keywords lost top-10
> positions since yesterday, ignoring anything inside the noise floor."

Because each row carries `position_noise` and `delta.within_noise`, an agent can separate real
movement from jitter **without you writing that logic**.

**LLM reporting**

Feed the dataset to a model and ask for a weekly summary: which keywords improved, which competitor
gained shelf share, which ASINs dropped off page one. The `delta` and `share_of_shelf` objects are
shaped for exactly that.

***

### How positions are measured (honest scope)

Amazon search is personalised and genuinely unstable. This section says what that means, because it
determines how much you should trust any Amazon rank number — from this Actor or any other.

#### Positions are observed, never estimated

Every position comes from a real Amazon search results page fetched at run time. Nothing is
modelled. `observed_from` records the location the page was rendered for, and `location_pinned`
tells you whether the postal code you asked for was actually applied.

#### Set a postal code

Amazon personalises results by delivery location. Tracking from a **fixed postal code** is what
makes two runs comparable. If a run cannot be pinned, the row says so via `location_pinned: false`
rather than silently reporting a different city's results as yours.

#### Each keyword is checked several times, then reduced to a median

This is the part most rank trackers do not talk about. **Amazon returns different orderings for
identical requests, seconds apart.** In our own measurements the same keyword's results moved by up
to 13 positions between two back-to-back checks.

So a single lookup is an anecdote, not a ranking. Each keyword is checked **three times per run**
and reduced to a **median** position, and an ASIN must appear in a **majority** of those checks to
be ranked at all. `position_noise` on every row tells you how much that keyword's own results
jittered, and `delta.within_noise` flags movement that falls inside it.

#### What is reliable, and what is directional

Measured across repeated pinned runs:

| Positions | Median movement between runs | Within ±2 positions |
|---|---|---|
| **Top 10** | **1** | **82–100%** |
| Top 20 | 1 | 67% |
| All depths scanned | 2–3 | 46–58% |

**Top-10 positions are stable enough to track and to act on.** Deeper positions are directional —
useful for "am I on page one at all" and for share-of-shelf totals, but a move from 38 to 44 is
mostly Amazon reshuffling, not your ranking changing. Treat deep numbers as a band, not a
measurement.

#### "Not found" is an answer

If a tracked ASIN is not in the results, the row says `found: false` and `not_in_top: <number
actually scanned>`. It is not an empty result and not an error — it is the answer, and it is billed
as one.

Note that `not_in_top` reflects what was **really examined**, not a round number. Amazon returns
roughly 50–65 organic results per page and varies it by keyword, so this Actor reports the actual
count rather than assuming one.

#### Sponsored placements

Sponsored products are identified and **excluded from organic rank**, so `position_organic` means
what it says. Ads are still reported: `sponsored_count`, sponsored share of shelf, and a `sponsored`
flag per row. Banner-style Sponsored Brands blocks are counted as advertising but kept **out of the
numbered ladder**, because they occupy no ranked slot — folding them in would shift every position
below them.

#### Scope limits in this version

- **amazon.com (US) only.** Other marketplaces are planned.
- Up to **100 keywords**, **20 tracked ASINs**, **50 competitor ASINs**, **3 pages** per run.
- Search volume is a monthly estimate, cached for 7 days.

***

### Input

| Field | Required | Default | Notes |
|---|---|---|---|
| `keywords` | ✅ | — | Up to 100. Case and whitespace variants are merged before anything is billed |
| `asins` | ✅ | — | Up to 20. 10 characters, e.g. `B002DYIZH6`. Invalid ones are reported and skipped |
| `competitorAsins` | | — | Up to 50. Tracked alongside yours, no extra charge |
| `marketplace` | | `com` | amazon.com only in this version |
| `postalCode` | | — | **Recommended.** Pins results to one location |
| `depth` | | `1` | Pages per keyword, 1–3. Also the billing multiplier |
| `watchlistId` | | — | Enables run-over-run tracking. Use one per brand or client |
| `includeVolume` | | `true` | Attaches Amazon search volume, cached 7 days |
| `bypassRunCache` | | `false` | Forces a fresh snapshot. **This will incur charges** |

Minimal input:

```json
{
  "keywords": ["protein powder", "whey protein isolate"],
  "asins": ["B002DYIZH6"],
  "postalCode": "10001",
  "watchlistId": "my-brand"
}
```

***

### Quick start

#### 1. Gather your ASINs

An ASIN is the 10-character identifier in any Amazon product URL:

```
https://www.amazon.com/dp/B002DYIZH6
                          ^^^^^^^^^^
```

Add your own products under **Your ASINs**, and anyone you benchmark against under **Competitor
ASINs**. Competitors cost nothing extra — they ride along on searches you are already paying for.

#### 2. Pick keywords the way a shopper would type them

Track the searches you want to win, not the ones that describe your product internally. Ten to
thirty well-chosen keywords beat a hundred vague ones, and a keyword list stays useful for months.

If you do not know where to start, pull the search terms your listing already gets impressions for
from Amazon Seller Central, or use the companion
**[Amazon Keyword Research](https://apify.com/doesaiknow/amazon-keyword-research-tool-volume-reverse-asin)**
Actor to expand a seed keyword and reverse-engineer a competitor's ASIN.

#### 3. Set a postal code

Use one and never change it. `10001` (New York) is a reasonable default. Amazon personalises
results by delivery location, so a fixed pin is what makes tomorrow's run comparable with today's.

#### 4. Set a watchlist ID

Any label: `my-brand`, `client-acme`, `supplements-q3`. This is what turns a snapshot into
tracking. One per brand or client.

#### 5. Run once, then schedule

The first run establishes the baseline and reports no movement. Add an **Apify Schedule** — daily
for active launches or PPC work, weekly for reporting — and every run from then on tells you what
changed.

***

### Reading the output

#### Which position number should I look at?

`position_organic` — that is the one that reflects your listing's own performance. It counts only
organic results, with ads removed, so it does not jump around when a competitor turns advertising on
or off.

Use `position_absolute` when you care about **visual** placement: where a shopper's eye actually
lands, ads included.

#### Is this move real?

Check `delta.within_noise`:

```js
const realMoves = row.tracked.filter(t => t.delta && !t.delta.within_noise);
```

`within_noise: true` means the change is smaller than the jitter measured on that keyword in this
very run — it is not evidence of anything. Filtering on this one field removes most of the false
alarms that make people distrust rank tracking.

#### Am I gaining or losing ground?

Compare `share_of_shelf.tracked_set_share_top10` across runs. This is the number to put in a client
report: it is exact, it is a percentage rather than a rank, and it moves slowly and meaningfully.
A single ASIN slipping from position 4 to 6 may be noise; your share of the top ten falling from 30%
to 10% is not.

#### Who is beating me?

`share_of_shelf.top10_organic` is ordered by share. Anyone above you with more slots is out-ranking
you on breadth, not just on one listing — usually a sign they have more variants indexed for the
keyword, which is actionable in a way a single position is not.

***

### Full output reference

#### Row level

| Field | Type | Notes |
|---|---|---|
| `keyword` | string | Normalised: lowercased, whitespace collapsed |
| `run_at` | ISO date | When this run started |
| `observed_from` | string | Location the results were rendered for |
| `location_pinned` | boolean | Whether your postal code was actually applied |
| `requested_postal_code` | string|null | What you asked for |
| `source` | `primary`|`fallback` | Which data path answered |
| `samples_taken` | number | Checks taken before the median (3 on paid plans) |
| `position_noise` | number|null | Measured jitter for this keyword, in positions |
| `search_volume` | number|null | Monthly Amazon search volume estimate |
| `results_scanned` | number | Results actually examined across all pages |
| `pages_scanned` | number | Pages fetched, 1–3 |
| `sponsored_count` | number | Ad slots seen, including banner placements |
| `new_in_top10` | string\[] | Present only when a comparable previous run exists |
| `dropped_from_top10` | string\[] | Same |
| `vs_run` | ISO date | The run this one was compared against |

#### `tracked[]` — one entry per ASIN you supplied

| Field | Type | Notes |
|---|---|---|
| `asin` | string | Uppercased |
| `found` | boolean | Whether it appeared at all |
| `page` | number|null | Which page it was found on |
| `position_absolute` | number|null | Rank including ad slots |
| `position_organic` | number|null | Rank among organic results only |
| `not_in_top` | number | Present when `found: false` — results actually scanned |
| `sponsored` | boolean | Whether this placement was an ad |
| `is_amazon_choice` / `is_best_seller` | boolean | Badges |
| `price` / `stars` / `reviews` | number|null | As displayed |
| `bought_last_month` | number|null | Purchase count, when Amazon shows one |
| `brand` / `brand_source` | string | See share-of-shelf notes |
| `delta` | object|null | `{ position_organic, vs_run, within_noise, noise_floor }` |

#### `share_of_shelf`

| Field | Notes |
|---|---|
| `top10_organic[]` / `top10_sponsored[]` | `{ brand, slots, share, brand_source }`, ordered by share |
| `full_organic[]` / `full_sponsored[]` | Same, across everything scanned |
| `tracked_set_share_top10` | Your set's share of the top 10, **exact** |
| `tracked_set_share_full` | Same across everything scanned |
| `slots_counted` | How many slots each figure was computed from |

Shares within a set always sum to 1.0. Slots whose brand could not be determined appear under
`brand: null` rather than being dropped.

***

### Recipes

#### Run it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/doesaiknow~amazon-rank-tracker/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "keywords": ["protein powder", "whey protein isolate"],
    "asins": ["B002DYIZH6"],
    "postalCode": "10001",
    "watchlistId": "my-brand"
  }'
```

Fetch the results:

```bash
curl "https://api.apify.com/v2/acts/doesaiknow~amazon-rank-tracker/runs/last/dataset/items?token=$APIFY_TOKEN&format=json"
```

Add `&format=csv` for a spreadsheet, or `&clean=true` to drop empty fields.

#### Alert when you fall out of the top 10

```js
const items = await fetch(datasetUrl).then(r => r.json());

const lost = items.flatMap(row =>
  row.tracked
    .filter(t => t.delta && !t.delta.within_noise && t.position_organic > 10)
    .map(t => `${t.asin} fell to ${t.position_organic} on "${row.keyword}"`)
);

if (lost.length) await notifySlack(lost.join('\n'));
```

Note the `!t.delta.within_noise` filter — without it you will alert on jitter and stop trusting the
alerts within a week.

#### Weekly share-of-shelf report

```python
import requests, statistics

items = requests.get(DATASET_URL).json()

for row in items:
    sos = row["share_of_shelf"]
    mine = sos["tracked_set_share_top10"]
    leader = sos["top10_organic"][0] if sos["top10_organic"] else None
    print(f'{row["keyword"]:30} mine {mine:.0%}'
          + (f'  leader {leader["brand"]} {leader["share"]:.0%}' if leader else ''))
```

#### n8n / Make / Zapier

1. **Schedule trigger** — daily at a fixed hour (consistency matters more than the hour)
2. **Apify node** — run this Actor with your saved input
3. **Filter** — keep rows where `dropped_from_top10` is non-empty
4. **Action** — Slack message, Google Sheets row, or an email digest

Running at the same time each day keeps your comparisons like-for-like.

***

### Choosing what to track

**Track the keywords you are trying to win, plus the ones you already own.** The second group is
your early-warning system: you find out you are slipping on a keyword that used to convert, which
is more valuable than watching a keyword you have never ranked for.

**Track competitor ASINs deliberately.** Two or three real rivals per category tell you more than
twenty. Their movement explains yours — if you dropped and they all rose, something changed in the
category, not in your listing.

**Pick a cadence that matches what you are doing.** Daily during a launch, a PPC push, or after a
listing change. Weekly for steady-state reporting. Rank tracking daily on a stable listing mostly
produces noise, and now costs money to produce.

**Depth is a cost multiplier.** Page 1 answers "am I visible". Pages 2–3 answer "how far off am I",
and cost 2× and 3× accordingly. Most tracking only needs page 1.

***

### Troubleshooting

**"None of the ASINs provided are valid"**
An ASIN is exactly 10 characters, letters and digits. Product URLs contain it after `/dp/`. If you
pasted a full URL or an SKU, the run stops before spending anything.

**A keyword is missing from the results**
It failed upstream after retries and was skipped rather than billed. The run's status message says
how many. Re-run to pick it up.

**Deltas are missing**
Either it is the first run for that watchlist ID, or the runs were not comparable — the location pin
or the data path differed. Check `location_pinned` and `source` on both runs.

**Positions look different from what I see in my browser**
Expected, and not a defect. Your browser carries your account history, Prime status and precise
location. This Actor reports what an unauthenticated shopper at your chosen postal code sees. It is
also why `position_noise` exists — check it before treating a small difference as real.

**Results came back cached and I wanted fresh ones**
Identical runs within 24 hours are served from cache at no per-keyword charge. Tick **Force a fresh
snapshot** to override — that will incur charges.

***

### Glossary

**ASIN** — Amazon Standard Identification Number, the 10-character product ID in every product URL.

**Organic position** — rank among unpaid results only. What your listing earned.

**Absolute position** — rank counting every slot including ads. What a shopper's eye sees.

**Share of shelf** — the percentage of visible slots held by a brand or a set of ASINs. Answers
"how much of this page is mine" rather than "where is my one product".

**Delta** — change since the previous run of the same watchlist. Positive means moved up.

**Noise floor** — how much a keyword's results shifted between checks within a single run. Movement
smaller than this is not evidence.

**Watchlist ID** — the label that groups runs into a history so deltas can be computed.

***

### FAQ

**How is this different from Helium 10 or Jungle Scout?**
Price model and delivery. They sell a monthly seat and a dashboard; this sells per keyword checked,
with output landing in your systems via API, JSON or CSV. Helium 10's Platinum plan is $99/month and
caps keyword tracking at 500; share of shelf starts at $279/month. Here, 25 keywords tracked daily
costs about $20.25/month with share of shelf included. If you want a dashboard and a full suite,
buy the suite — this is for people who want the data in a pipeline.

**Does it track sponsored positions?**
Yes, and separately from organic. `position_organic` excludes ads entirely, `position_absolute`
counts them, and share of shelf is reported for organic and sponsored independently, so you can see
who is buying the top of the page versus earning it.

**What happens if my ASIN is not in the results?**
You get `found: false` with `not_in_top` set to the number of results actually scanned. That is a
delivered answer and is billed like any other — knowing you are not on page one is usually the
reason you ran the check.

**Can I schedule it?**
Yes, and it is designed for that. Add an Apify Schedule and set a `watchlistId`. Each run then
reports what changed since the previous one. Daily is the common setup for sellers; weekly for
agency reporting.

**How much does 100 keywords daily cost?**
100 keywords × 1 page × 30 days = 3,000 rank-check events, plus the daily start fee. On the FREE
tier that is roughly $76.50/month; on GOLD about $46.50. Still below a single Helium 10 Platinum
seat, and without the 500-keyword cap.

**Does it work for agencies with multiple brands?**
Yes. Use a separate `watchlistId` per client, and each keeps its own history. There are no seats —
billing is per keyword checked, so a second client costs what their keywords cost and nothing more.

**How accurate are the positions?**
Top-10 positions are stable: in repeated pinned runs, the median movement between runs is 1
position and 82–100% of top-10 positions move by 2 or less. Deeper positions are noisier and should
be read as directional. Every row carries `position_noise`, so you can see the jitter rather than
take our word for it.

**Why is my delta missing on some rows?**
Either it is your first run for that watchlist, or the two runs were not comparable — the location
pin or the data path changed between them. In those cases movement is suppressed rather than
guessed, because comparing two differently-taken observations would report noise as a ranking
change.

**Why is there a "fallback" source, and does it cost me more?**
No — the price is the same either way. If the primary data path fails repeatedly during a run, a
secondary one takes over so you still get results instead of an error. Rows say which answered via
`source`, because the two are not identical: the fallback resolves location at country level rather
than to your postal code, which is also why deltas are suppressed across a source change.

**Can I track more than 100 keywords?**
Per run, no — that cap keeps runs inside a predictable time and cost envelope. Split larger lists
across several runs or several scheduled tasks, each with its own watchlist ID. There is no limit on
how many runs you make.

**Do competitor ASINs cost extra?**
No. They are read from searches you are already paying for, so tracking 20 of your own ASINs plus 50
competitors costs exactly the same as tracking one ASIN.

**Why does the first page sometimes have 50 results and sometimes 65?**
Because Amazon varies it by keyword and category. This Actor reports `results_scanned` — the number
actually examined — rather than assuming a fixed page size, which is why `not_in_top` is an honest
ceiling rather than a round number.

**Is search volume Amazon's own data?**
It is a monthly estimate for Amazon search, not a figure published by Amazon, and it is cached for
7 days. Use it to prioritise keywords, not as a precise traffic forecast.

**What happens if I change my keyword list mid-tracking?**
Nothing breaks. Keywords already in the watchlist keep their history; newly added ones start their
own and report no movement on their first run. Removing a keyword simply stops updating it.

**Can I export to CSV or Google Sheets?**
Yes. Every Apify dataset exports to CSV, JSON, Excel and more, through the UI or the API, and
integrates with n8n, Make and Zapier.

***

### Legal & compliance

This Actor reads **publicly available Amazon search results** — the same pages any visitor sees
without logging in.

- **No login, no account access, no gated content.** Nothing behind authentication is touched.
- **No personal data.** Output contains product listings: ASINs, titles, prices, ratings, review
  counts and positions. No customer names, reviewer identities or contact details are collected.
- **Public data only.** Search result pages are public web content.

You are responsible for using the output in line with Amazon's terms and any laws that apply to you.

***

### Related actors

- **[Amazon Bestseller Radar](https://apify.com/doesaiknow/amazon-bestseller-radar)** — which
  products entered, climbed or fell out of your categories' Best Sellers top 30 since your last
  run — rank deltas, prices, demand signals. Spot the mover, then track its keywords here.
- **[Amazon Keyword Research](https://apify.com/doesaiknow/amazon-keyword-research-tool-volume-reverse-asin)**
  — search volume and reverse-ASIN. Use it to find keywords worth tracking, then bring them here.
- **[Google Rank Checker](https://apify.com/doesaiknow/google-rank-checker)** — the same idea for
  Google: positions, competitors above you, SERP features.
- **[Keyword Metrics Pro](https://apify.com/doesaiknow/doesaiknow-keyword-metrics-apify)** —
  volume, CPC and competition in bulk.

***

### Support & feedback

Found a bug, need another marketplace, or want a field that is not here? Open an issue on the
Actor's page. Bug reports get fixed; feature requests shape the roadmap — non-`.com` marketplaces
and a buy-box module are the two most requested so far.

If the Actor is useful, a review or bookmark on the Store page helps other sellers find it.

# Actor input Schema

## `keywords` (type: `array`):

Amazon search terms to check, one per line. Each keyword costs one rank-check event per run. Duplicates and case variants are merged before anything is charged. FREE PLAN: 10-keyword preview per run, with volume and delta tracking disabled.

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

The ASINs you want positions for — 10 characters, e.g. B002DYIZH6. Every ASIN is reported for every keyword, including an explicit "not found" when it does not rank. That is a real answer and it is billed like any other. Invalid ASINs are rejected before any paid call.

## `competitorAsins` (type: `array`):

Extra ASINs to watch alongside your own. They appear in the tracked view and count toward your tracked-set share of shelf.

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

Amazon.com (US) only in this version. Other marketplaces are planned.

## `postalCode` (type: `string`):

Pins results to one location, e.g. 10001. Amazon personalizes search results, so tracking from a FIXED postal code is what makes two runs comparable — leaving this empty adds noise to your position deltas.

## `depth` (type: `integer`):

How many result pages to scan per keyword (roughly 50-65 results each — Amazon varies the exact count). Deeper scans cost proportionally more: 1 page = 1x, 2 pages = 2x, 3 pages = 3x rank-check events per keyword.

## `watchlistId` (type: `string`):

Any label you choose, e.g. "my-brand" or a client name. Runs sharing a watchlist ID are compared with each other, so every run reports position changes, new entrants and drop-outs. Leave empty for a one-off snapshot with no history. Use a separate ID per brand or client.

## `includeVolume` (type: `boolean`):

Adds monthly Amazon search volume per keyword so you can prioritize. Cached for 7 days, so repeat runs on the same keywords rarely pay for it again.

## `bypassRunCache` (type: `boolean`):

Identical runs within 24 hours normally return cached results with no per-keyword charge. Tick this to force fresh positions — this WILL incur charges.

## Actor input object example

```json
{
  "keywords": [
    "protein powder",
    "whey protein isolate"
  ],
  "asins": [
    "B002DYIZH6"
  ],
  "marketplace": "com",
  "postalCode": "10001",
  "depth": 1,
  "includeVolume": true,
  "bypassRunCache": false
}
```

# Actor output Schema

## `rankRows` (type: `string`):

Default dataset link. See dataset\_schema.json for per-field documentation.

# 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 = {
    "keywords": [
        "protein powder",
        "whey protein isolate"
    ],
    "asins": [
        "B002DYIZH6"
    ],
    "postalCode": "10001"
};

// Run the Actor and wait for it to finish
const run = await client.actor("doesaiknow/amazon-rank-tracker").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 = {
    "keywords": [
        "protein powder",
        "whey protein isolate",
    ],
    "asins": ["B002DYIZH6"],
    "postalCode": "10001",
}

# Run the Actor and wait for it to finish
run = client.actor("doesaiknow/amazon-rank-tracker").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "keywords": [
    "protein powder",
    "whey protein isolate"
  ],
  "asins": [
    "B002DYIZH6"
  ],
  "postalCode": "10001"
}' |
apify call doesaiknow/amazon-rank-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=doesaiknow/amazon-rank-tracker",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Amazon Rank Tracker - Keyword Position, Share of Shelf & Delta",
        "description": "Track your ASINs' organic & sponsored positions for up to 100 keywords per run, plus share-of-shelf per brand and position delta vs your last run. Amazon search volume attached. Flat price per keyword - not per row. Schedule it daily; cheaper than a Helium 10 seat.",
        "version": "0.1",
        "x-build-id": "5cnChwV92LhA4ZUWD"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/doesaiknow~amazon-rank-tracker/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-doesaiknow-amazon-rank-tracker",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/doesaiknow~amazon-rank-tracker/runs": {
            "post": {
                "operationId": "runs-sync-doesaiknow-amazon-rank-tracker",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/doesaiknow~amazon-rank-tracker/run-sync": {
            "post": {
                "operationId": "run-sync-doesaiknow-amazon-rank-tracker",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "keywords",
                    "asins"
                ],
                "properties": {
                    "keywords": {
                        "title": "Keywords to track",
                        "maxItems": 100,
                        "type": "array",
                        "description": "Amazon search terms to check, one per line. Each keyword costs one rank-check event per run. Duplicates and case variants are merged before anything is charged. FREE PLAN: 10-keyword preview per run, with volume and delta tracking disabled.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 250
                        }
                    },
                    "asins": {
                        "title": "Your ASINs",
                        "maxItems": 20,
                        "type": "array",
                        "description": "The ASINs you want positions for — 10 characters, e.g. B002DYIZH6. Every ASIN is reported for every keyword, including an explicit \"not found\" when it does not rank. That is a real answer and it is billed like any other. Invalid ASINs are rejected before any paid call.",
                        "items": {
                            "type": "string",
                            "maxLength": 250
                        }
                    },
                    "competitorAsins": {
                        "title": "Competitor ASINs (optional)",
                        "maxItems": 50,
                        "type": "array",
                        "description": "Extra ASINs to watch alongside your own. They appear in the tracked view and count toward your tracked-set share of shelf.",
                        "items": {
                            "type": "string",
                            "maxLength": 250
                        }
                    },
                    "marketplace": {
                        "title": "Amazon marketplace",
                        "enum": [
                            "com"
                        ],
                        "type": "string",
                        "description": "Amazon.com (US) only in this version. Other marketplaces are planned.",
                        "default": "com"
                    },
                    "postalCode": {
                        "title": "Postal code (recommended)",
                        "maxLength": 20,
                        "type": "string",
                        "description": "Pins results to one location, e.g. 10001. Amazon personalizes search results, so tracking from a FIXED postal code is what makes two runs comparable — leaving this empty adds noise to your position deltas."
                    },
                    "depth": {
                        "title": "Pages to scan",
                        "minimum": 1,
                        "maximum": 3,
                        "type": "integer",
                        "description": "How many result pages to scan per keyword (roughly 50-65 results each — Amazon varies the exact count). Deeper scans cost proportionally more: 1 page = 1x, 2 pages = 2x, 3 pages = 3x rank-check events per keyword.",
                        "default": 1
                    },
                    "watchlistId": {
                        "title": "Watchlist ID (enables position deltas)",
                        "maxLength": 40,
                        "type": "string",
                        "description": "Any label you choose, e.g. \"my-brand\" or a client name. Runs sharing a watchlist ID are compared with each other, so every run reports position changes, new entrants and drop-outs. Leave empty for a one-off snapshot with no history. Use a separate ID per brand or client."
                    },
                    "includeVolume": {
                        "title": "Attach Amazon search volume",
                        "type": "boolean",
                        "description": "Adds monthly Amazon search volume per keyword so you can prioritize. Cached for 7 days, so repeat runs on the same keywords rarely pay for it again.",
                        "default": true
                    },
                    "bypassRunCache": {
                        "title": "Force a fresh snapshot",
                        "type": "boolean",
                        "description": "Identical runs within 24 hours normally return cached results with no per-keyword charge. Tick this to force fresh positions — this WILL incur charges.",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
