DEV Community

Cover image for How to Build an Amazon Product Search Tool with Node.js
Nexscope Team for Nexscope

Posted on Originally published at nexscope.ai

How to Build an Amazon Product Search Tool with Node.js

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 fetch API.

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
  }'
Enter fullscreen mode Exit fullscreen mode

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",
};
Enter fullscreen mode Exit fullscreen mode

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 : [],
  };
}
Enter fullscreen mode Exit fullscreen mode

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(),
  };
}
Enter fullscreen mode Exit fullscreen mode

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),
  }));
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Run it with:

node amazon-search.js
Enter fullscreen mode Exit fullscreen mode

The normalized records can feed a competitor table, price distribution, sponsored-share report, product-discovery queue, or scheduled rank monitor.

Amazon Search API workflow from keyword context through response validation and normalized product records

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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 products from 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)