DEV Community

Cover image for Benchmark Amazon Product APIs Without Losing Missing Data
Nexscope Team for Nexscope

Posted on Originally published at nexscope.ai AI-assisted

Benchmark Amazon Product APIs Without Losing Missing Data

Amazon product API comparisons often collapse into feature lists. A useful benchmark asks a stricter question: how reliably can each provider return the fields a specific application needs for the same ASINs, marketplaces, and observation window?

This Node.js design preserves missing values, separates transport failures from empty fields, and avoids publishing invented benchmark results.

Define the Benchmark Contract

Choose representative test cases before integrating providers:

const cases = [
  { asin: "B000000001", marketplace: "US", expectedCategory: "home" },
  { asin: "B000000002", marketplace: "US", expectedCategory: "beauty" },
  { asin: "B000000003", marketplace: "GB", expectedCategory: "electronics" },
];

const requiredFields = [
  "asin",
  "title",
  "price",
  "currency",
  "availability",
  "rating",
  "reviewCount",
  "imageUrl",
];
Enter fullscreen mode Exit fullscreen mode

Use real test ASINs in an actual run. The placeholders above are deliberately synthetic.

Create a Provider Adapter

Every provider should return the same result envelope:

/**
 * @typedef {Object} Result
 * @property {string} provider
 * @property {string} asin
 * @property {string} marketplace
 * @property {number} latencyMs
 * @property {number|null} httpStatus
 * @property {'ok'|'empty'|'auth_error'|'rate_limited'|'upstream_error'|'schema_error'} status
 * @property {Record<string, unknown>} data
 * @property {string[]} missingFields
 * @property {string|null} error
 * @property {number|null} estimatedCost
 */
Enter fullscreen mode Exit fullscreen mode
function findMissing(data, fields) {
  return fields.filter(field => data[field] == null);
}

class ProviderAdapter {
  constructor(name, fetchProduct, estimateCost = () => null) {
    this.name = name;
    this.fetchProduct = fetchProduct;
    this.estimateCost = estimateCost;
  }

  async run(testCase) {
    const started = performance.now();
    try {
      const data = await this.fetchProduct(testCase);
      const missingFields = findMissing(data ?? {}, requiredFields);
      return {
        provider: this.name,
        ...testCase,
        latencyMs: Math.round(performance.now() - started),
        httpStatus: 200,
        status: data ? "ok" : "empty",
        data: data ?? {},
        missingFields,
        error: null,
        estimatedCost: this.estimateCost(testCase),
      };
    } catch (error) {
      return {
        provider: this.name,
        ...testCase,
        latencyMs: Math.round(performance.now() - started),
        httpStatus: error.status ?? null,
        status: classifyError(error),
        data: {},
        missingFields: [...requiredFields],
        error: String(error.message ?? error),
        estimatedCost: this.estimateCost(testCase),
      };
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Classify Errors Explicitly

function classifyError(error) {
  if (error.status === 401 || error.status === 403) return "auth_error";
  if (error.status === 429) return "rate_limited";
  if (error.status >= 500) return "upstream_error";
  if (error.name === "SchemaError") return "schema_error";
  return "upstream_error";
}
Enter fullscreen mode Exit fullscreen mode

Do not mix rate_limited with a valid response that lacks rating. These failures require different engineering decisions.

Add Retry Accounting

Retry transient failures, but include the retry cost and final latency in the benchmark:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

async function withRetry(task, maxAttempts = 3) {
  const attempts = [];

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const started = Date.now();
    try {
      const value = await task();
      attempts.push({ attempt, ok: true, elapsedMs: Date.now() - started });
      return { value, attempts };
    } catch (error) {
      attempts.push({ attempt, ok: false, status: error.status ?? null, elapsedMs: Date.now() - started });
      const retryable = error.status === 429 || error.status >= 500;
      if (!retryable || attempt === maxAttempts) throw Object.assign(error, { attempts });
      await sleep(500 * 2 ** (attempt - 1));
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Report both first-attempt success and retry-adjusted success. A provider that succeeds after three calls may have acceptable coverage but a different latency and cost profile.

Run and Summarize

async function benchmark(adapters) {
  const results = [];
  for (const adapter of adapters) {
    for (const testCase of cases) results.push(await adapter.run(testCase));
  }
  return results;
}

function summarize(results) {
  return results.reduce((groups, result) => {
    (groups[result.provider] ??= []).push(result);
    return groups;
  }, {});
}
Enter fullscreen mode Exit fullscreen mode

For each provider, calculate:

  • First-attempt and retry-adjusted success rate
  • Median and p95 latency
  • Missing rate for every required field
  • Marketplace-specific failures
  • Error distribution
  • Estimated request cost and retry-adjusted cost
  • Terms and storage constraints reviewed separately

Run the benchmark on the same date and with the same ASIN set. Historical depth should be evaluated with a separate test because a current-product lookup cannot prove backfill coverage.

Preserve the Evidence

Store raw responses beside normalized results. Include the request date, provider version, endpoint, marketplace, and benchmark configuration. Redact credentials and avoid logging authorization headers.

Publish real results only after the benchmark has actually run. Until then, code examples and schemas should be labeled as methodology, not performance evidence.

Next Step

The current Nexscope Amazon Product Detail API can be implemented as one adapter in the same benchmark, then evaluated against the identical cases and missing-field rules.

Open the Amazon Product Detail API →

Disclosure: This article was prepared with AI-assisted editing using current published documentation. No benchmark results in this article are claimed as observed production measurements.

Top comments (0)