Amazon Rank Tracking Guide: Monitor Keyword Positions with an API
Track Amazon keyword rank over time with the Canopy search API: fetch results by page, skip sponsored rows, compute the organic position, and store a row per check on a schedule you run.

First published May 2026.
This guide is for sellers and developers who want to know where an ASIN ranks for a keyword every day, not just once. It shows how to fetch Amazon search results from the Canopy API, compute the organic position in code, and store one row per check so you can see movement over time.
Two things to be clear about up front. Canopy does not return a field called "rank"; you compute it from the order of the results, exactly as the free tool does. And Canopy does not store your history; the schedule and the table are yours. If you only need a single lookup, use the free keyword rank checker or read the shorter how to check your Amazon keyword rank tutorial.
What "rank" means on Amazon
A search results page is an ordered list of listings. Some are sponsored placements, the rest are organic. Three numbers describe where an ASIN sits:
- Page: the results page it appears on.
- Position on page: its row on that page, counting sponsored rows too. This is what a shopper scrolls past.
- Organic position: its row counting only non-sponsored listings, across all pages scanned. Sponsored slots move with advertising budgets, so this is the number worth tracking.
Amazon personalizes results by account, delivery location, and device, so no two people see exactly the same page. The API runs a plain search with the default FEATURED sort, which is the order an anonymous shopper sees. Treat the numbers as a consistent measurement of that view, not as what one specific customer saw.
Run a single check first
Before writing any code, confirm the ASIN shows up for the keyword at all. The checker below searches amazon.com, scans the first three pages, and reports the page, position, organic position, and whether the spot is sponsored.
The free tool checks one keyword and one ASIN at a time, on amazon.com only, and reuses a result for about 30 days. The API below has none of those limits.
Fetch a page of search results
The endpoint is the Amazon search API. One request returns one page of results. Send your key in the API-KEY header.
REST:
curl -G "https://rest.canopyapi.co/api/amazon/search" \
-H "API-KEY: YOUR_API_KEY" \
--data-urlencode "searchTerm=wireless earbuds" \
--data-urlencode "domain=US" \
--data-urlencode "page=1"
GraphQL, posted to https://graphql.canopyapi.co/ with the same header:
query RankPage($searchTerm: String!, $page: BigInt!) {
amazonProductSearchResults(input: { searchTerm: $searchTerm, domain: US }) {
productResults(input: { page: $page }) {
results {
asin
title
sponsored
}
pageInfo {
currentPage
totalPages
hasNextPage
}
}
}
}
Both return the same shape. Every row in productResults.results has an asin and a sponsored flag, and pageInfo.hasNextPage tells you whether to ask for the next page. Leave sort unset so results come back in the default FEATURED order. The domain parameter accepts US, UK, DE, FR, IT, ES, CA, MX, BR, IN, JP, AU, AE, NL, PL, SE, and SG, and defaults to US.
Compute the organic position
Walk the pages in order. Count every row for the position on the page, and only non-sponsored rows for the organic position. Stop at the first match, or when a page says there is no next page. This is the same logic the free tool runs.
const API_KEY = process.env.CANOPY_API_KEY;
async function fetchPage(searchTerm, page, domain = 'US') {
const params = new URLSearchParams({ searchTerm, domain, page: String(page) });
const res = await fetch(`https://rest.canopyapi.co/api/amazon/search?${params}`, {
headers: { 'API-KEY': API_KEY }
});
if (!res.ok) throw new Error(`search failed: ${res.status}`);
const body = await res.json();
return body.data.amazonProductSearchResults.productResults;
}
async function findRank(searchTerm, asin, { maxPages = 3, domain = 'US' } = {}) {
let organic = 0;
for (let page = 1; page <= maxPages; page++) {
const { results = [], pageInfo } = await fetchPage(searchTerm, page, domain);
for (let i = 0; i < results.length; i++) {
const row = results[i];
if (!row.sponsored) organic += 1;
if (row.asin === asin) {
return {
page,
positionOnPage: i + 1,
organicPosition: row.sponsored ? null : organic,
sponsored: Boolean(row.sponsored)
};
}
}
if (!pageInfo?.hasNextPage) break;
}
return null; // not in the pages scanned
}
null means the ASIN was not in the pages you scanned. It does not mean Amazon has not indexed the product for the keyword. Raise maxPages if you need to see further back, keeping in mind that each page is one request.
Store one row per check
Tracking is just this function on a timer plus a table. A minimal schema:
| Column | Example | Notes |
|---|---|---|
keyword | wireless earbuds | Lower-case it so the same term is one series |
asin | B0BSHF7WHW | Upper-case |
domain | US | One series per marketplace |
page | 1 | null when not found |
position_on_page | 7 | Counts sponsored rows |
organic_position | 4 | null when not found or sponsored |
sponsored | false | Whether the first appearance was an ad |
pages_scanned | 3 | So a null is comparable later |
observed_at | 2026-11-07T14:00:00Z | When you ran it |
Run the loop once a day from wherever you already run scheduled jobs: a cron entry, a GitHub Actions schedule, or a Cloudflare Worker cron trigger. Plot organic_position by observed_at and you have a rank tracker for your own catalog.
What it costs
Each page of results is one request. Every account includes 100 free requests a month, then requests are $0.01 each with automatic volume discounts. For example, 20 keywords checked daily at up to 3 pages each is at most 60 requests a day, or about 1,800 a month, which comes to roughly $17 a month after the free 100. Checking fewer pages or fewer days costs proportionally less.
Practical notes
- Compare organic positions, not positions on page. Sponsored rows change between searches and push everything else around.
- Record the pages scanned with every row. "Not found in 3 pages" and "not found in 5 pages" are different facts.
- Expect movement. Amazon reorders results continuously. Look at a week of rows before deciding that a listing change worked or did not.
- Only the first appearance counts in the code above. If an ASIN appears both as an ad and organically, you will see the ad first. Keep walking the rows if you want the organic row as well.
FAQs
What is an Amazon ASIN, and where do I find it?
An ASIN is Amazon's 10-character product identifier. It appears in the product URL after /dp/ and in the Product Information section of the listing.
Does Canopy return a rank or position field?
No. The search API returns each page's rows in the order Amazon shows them, with a sponsored flag on each row. You compute the position by counting rows, as the code above does.
Does Canopy store my rank history?
No. The API returns the current page of results when you ask for it. Storing one row per check in your own database is what turns the API into a tracker, and this guide shows the schema for that.
How accurate are the results?
Each API call runs a live Amazon search at that moment, so the order is what an anonymous shopper would see with the default sort. Amazon personalizes results by account, location, and device, so an individual shopper's page may differ. The free tool is different: it reuses a result for about 30 days and shows the date it was fetched.
Can I check rankings for many products at once?
The free keyword rank checker takes one keyword and one ASIN per check. With the API you loop over as many keyword and ASIN pairs as you like. Each page you fetch is one request.
Does the API report keyword search volume?
No. The search API returns the listings for a search term. It does not estimate how often shoppers search for it.