Amazon product search data becomes much more useful when an application can preserve the context behind every result: the keyword, marketplace, language, page, device, delivery location, and collection time.
This tutorial builds a small server-side Amazon product search workflow with Node.js. It sends a request to the Nexscope Amazon Search API, validates the response, normalizes product records, keeps sponsored and organic placements separate, and adds the reliability controls needed for a production integration.
The finished workflow can support:
- Keyword rank checks for a known ASIN
- Competitor and new-product discovery
- Price and rating comparisons
- Sponsored-placement analysis
- Search-result snapshots for later comparison
The examples require Node.js 18 or newer because they use the built-in
fetchAPI.
What the API returns
An Amazon storefront search API is different from a catalog API.
Amazon's official Catalog Items API is designed for authorized catalog search and item retrieval. The Amazon Creators API SearchItems operation serves approved affiliate and creator experiences.
The Nexscope Amazon Search API focuses on observed storefront search results. Its documented product fields can include:
- ASIN, title, brand, and product URL
- Displayed and extracted prices
- Rating and rating count
- Search position
- Sponsored status
- Image, delivery, fulfillment, badges, and offers
- Additional research signals when returned by the provider
The response is a direct payload. The products array is at the top level rather than inside data or result.
Send the first request
The production endpoint is:
POST https://api.nexscope.ai/api/skill-api/v1/skills/amazon-search/run
Create an API key from the Nexscope API access page, then store it in a server-side environment variable:
export NEXSCOPE_API_KEY="nk_your_key_here"
Never place the key in browser JavaScript, commit it to a repository, or include it in application logs.
A minimal request needs a keyword and page:
curl -X POST \
"https://api.nexscope.ai/api/skill-api/v1/skills/amazon-search/run" \
-H "Authorization: Bearer $NEXSCOPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"keyword": "phone case",
"page": 1
}'
For repeatable comparisons, include the storefront context:
const searchContext = {
keyword: "phone case",
amazonDomain: "amazon.com",
language: "en_US",
sort: "relevanceblender",
page: 1,
deliveryZip: "10001",
device: "desktop",
};
Amazon search results can change with each of these inputs. A rank without its marketplace, device, ZIP code, sort order, page, and collection time is incomplete data.
Build the Node.js client
Create amazon-search.js:
const ENDPOINT =
"https://api.nexscope.ai/api/skill-api/v1/skills/amazon-search/run";
function requireApiKey() {
const apiKey = process.env.NEXSCOPE_API_KEY;
if (!apiKey) {
throw new Error("NEXSCOPE_API_KEY is not set");
}
return apiKey;
}
async function searchAmazon(context) {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: {
Authorization: `Bearer ${requireApiKey()}`,
"Content-Type": "application/json",
},
body: JSON.stringify(context),
});
const responseText = await response.text();
if (!response.ok) {
throw new Error(
`Amazon search failed: ${response.status} ${responseText}`
);
}
let payload;
try {
payload = JSON.parse(responseText);
} catch {
throw new Error("Amazon search returned invalid JSON");
}
if (!payload || typeof payload !== "object") {
throw new Error("Amazon search returned an invalid payload");
}
return {
...payload,
products: Array.isArray(payload.products) ? payload.products : [],
};
}
The client validates the HTTP status before parsing the payload. It also protects the rest of the application from a missing or malformed products field.
Normalize product records
Upstream fields are optional, so production code should never assume that every product contains a price, rating, position, or sponsored flag.
function normalizeProduct(product, context) {
return {
asin: product.asin ?? null,
title: product.title ?? null,
brand: product.brand ?? null,
price: product.extractedPrice ?? product.price ?? null,
oldPrice:
product.extractedOldPrice ?? product.oldPrice ?? null,
currency: product.currency ?? null,
rating: product.rating ?? null,
ratingCount: product.ratings ?? null,
position: product.position ?? null,
sponsored: product.sponsored ?? null,
imageUrl: product.imageUrl ?? null,
productUrl: product.asinUrl ?? null,
keyword: product.keyword ?? context.keyword,
marketplace: context.amazonDomain,
language: context.language,
page: context.page,
deliveryZip: context.deliveryZip,
device: context.device,
sort: context.sort,
collectedAt: new Date().toISOString(),
};
}
Missing values should remain null or unknown. Converting a missing price to 0 creates a fake price. Converting an absent sponsored value to false can incorrectly classify an unknown placement as organic.
Keep paid and organic results separate
Sponsored and organic positions answer different questions. Combining them into one rank metric can hide the difference between advertising visibility and organic search visibility.
function classifyPlacement(product) {
if (product.sponsored === true) return "sponsored";
if (product.sponsored === false) return "organic";
return "unknown";
}
function buildSearchObservations(payload, context) {
return payload.products.map((product) => ({
...normalizeProduct(product, context),
placementType: classifyPlacement(product),
}));
}
For a keyword rank check, match the target ASIN and store at least:
- Absolute position
- Placement type
- Page number
- Marketplace and language
- Delivery ZIP and device
- Sort order
- Collection timestamp
A single run is a snapshot. Historical rank tracking requires repeated collection with the same context.
Run the complete workflow
Add the execution code:
const context = {
keyword: "phone case",
amazonDomain: "amazon.com",
language: "en_US",
sort: "relevanceblender",
page: 1,
deliveryZip: "10001",
device: "desktop",
};
try {
const payload = await searchAmazon(context);
const observations = buildSearchObservations(payload, context);
const summary = {
keyword: payload.keyword ?? context.keyword,
returnedProducts: observations.length,
sponsored: observations.filter(
(item) => item.placementType === "sponsored"
).length,
organic: observations.filter(
(item) => item.placementType === "organic"
).length,
unknown: observations.filter(
(item) => item.placementType === "unknown"
).length,
};
console.log(summary);
console.log(observations.slice(0, 3));
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
Run it with:
node amazon-search.js
The normalized records can feed a competitor table, price distribution, sponsored-share report, product-discovery queue, or scheduled rank monitor.
Add production safeguards
A working HTTP request is only the first step. A reliable integration needs retry, caching, schema validation, and observability.
Retry only transient failures
Requests that return 400, 401, or 403 normally need a corrected request, credential, or access setting. Repeating them unchanged wastes time and credits.
Use capped exponential backoff with jitter for 429 and selected 5xx responses:
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function withRetry(task, maxAttempts = 4) {
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
return await task();
} catch (error) {
lastError = error;
const status = Number(error.message.match(/failed: (\d{3})/)?.[1]);
const retryable = status === 429 || status >= 500;
if (!retryable || attempt === maxAttempts) throw error;
const baseDelay = 500 * 2 ** (attempt - 1);
const jitter = Math.floor(Math.random() * 250);
await sleep(baseDelay + jitter);
}
}
throw lastError;
}
In a larger application, use a typed error object instead of extracting the status code from an error message.
Cache by complete context
A cache key based only on the keyword can mix incompatible searches. Include every parameter that can affect the storefront view:
function createCacheKey(context) {
const stableContext = {
keyword: context.keyword,
amazonDomain: context.amazonDomain,
language: context.language,
node: context.node ?? null,
sort: context.sort,
page: context.page,
deliveryZip: context.deliveryZip,
device: context.device,
};
return JSON.stringify(stableContext);
}
The cache duration should follow the business decision. A product-research dashboard can usually tolerate a longer cache than a short-interval rank monitor.
Record useful telemetry
Track:
- Request duration
- HTTP status
- Returned product count
- Retry count
- Credit cost when present in the response
- A non-secret request identifier
Never log the bearer token or complete authorization headers.
Common mistakes
-
Expecting a response wrapper: Read
productsfrom the top-level payload. - Dropping the search context: Store marketplace, language, ZIP, device, sort, page, and collection time with every observation.
- Combining paid and organic results: Keep sponsored, organic, and unknown placements separate.
- Treating missing as zero: Optional response fields should remain unknown when absent.
- Exposing the API key: Call the API from a trusted backend, not directly from browser code.
- Assuming built-in history: Live search results become historical data only after an application stores consistent snapshots.
- Overcalling the endpoint: Cache identical request contexts and collect only as often as the decision requires.
Next steps
The complete request parameters, response schema, error behavior, and MCP example are maintained in the Amazon Search API documentation.
Start with one keyword and one marketplace. Preserve the full request context, inspect the raw payload, normalize only the fields the application needs, and add scheduled collection after the single-request workflow is stable.
Disclosure: This article was prepared with AI-assisted editing using the current published API documentation as its technical source of truth.

Top comments (0)