Back to blog

Build an Amazon Price Tracking Workflow

By Glade Engineering10 min read

A production-minded guide to tracking Amazon product and offer prices with marketplace context, freshness metadata, durable observations, bounded retries, and useful alerts.

Build an Amazon Price Tracking Workflow

Price tracking is an observation workflow, not one API call

An Amazon price tracker repeatedly observes a product in a specific marketplace, stores what it saw, compares the new observation with prior observations, and decides whether a change is meaningful enough to surface or alert on.

That distinction matters. A product page can contain a featured price, several seller offers, delivery charges, conditions, coupons, and variant-specific prices. Availability can change without the displayed amount changing. A request can also fail temporarily even though the product still exists. Treating each response as a single universal “Amazon price” produces noisy history and false alerts.

A durable workflow should answer five questions explicitly:

  1. Which product and marketplace are being observed?
  2. Which price concept is the tracker comparing?
  3. When was the underlying data fetched?
  4. Is the observation comparable with the previous one?
  5. Which changes should create an alert?

This guide builds that workflow with Glade API. Glade is an independent developer API, not an Amazon service. If your application is an affiliate experience or manages a seller account, review the official Amazon options near the end before choosing a data source.

Start with a stable tracking identity

Use the normalized ASIN and marketplace together as the minimum identity for a tracked product. An ASIN by itself is insufficient because price, currency, seller participation, availability, and catalog status can vary by storefront.

For offer-level history, add the offer or seller identity. For a specific variation, track the child ASIN instead of assuming every size or color shares the parent product's price.

A practical internal key looks like this:

product key:  AMAZON#US#B0D1XD1ZV3
offer key:    AMAZON#US#B0D1XD1ZV3#offer_example

Normalize identifiers before inserting them into a URL, cache key, or database:

function normalizeTrackingTarget({ asin, domain }) {
  const normalizedAsin = asin.trim().toUpperCase();
  const normalizedDomain = domain.trim().toUpperCase();

  if (!/^[A-Z0-9]{10}$/.test(normalizedAsin)) {
    throw new TypeError("ASIN must contain exactly 10 letters or numbers");
  }

  if (!/^[A-Z]{2}$/.test(normalizedDomain)) {
    throw new TypeError("Marketplace must be a supported two-letter code");
  }

  return { asin: normalizedAsin, domain: normalizedDomain };
}

Format validation catches obvious mistakes, but it cannot prove the product is listed in that marketplace. A well-formed target may still return 404, and that result should be recorded as a fetch outcome rather than converted into a zero price.

Choose the price concept before collecting history

Different product questions require different price series. Pick one and name it in your data model.

  • Product price is the normalized price shown on the product response. It is useful for a simple product-page tracker.
  • Buy-box or featured offer price follows the offer marked as the current winner when that signal is available.
  • Lowest visible new offer compares eligible new-condition offers after applying your delivery and availability rules.
  • Specific seller offer price follows one seller or offer identity across observations.
  • Landed price combines item price and delivery price when both are available and comparable.

Do not silently switch between these concepts. If yesterday's value was the featured offer and today's value is merely the lowest visible offer, the calculated difference does not describe one continuous series.

Store a priceType such as product, buybox, lowest_new, or seller_offer with every observation. Also store the currency instead of deriving it later from the marketplace.

Retrieve product and offer data

The product endpoint is the shortest path for product-level price and availability:

export GLADE_API_KEY="glade_test_tracking_your_secret"

curl --request GET \
  "https://gladeapi.com/api/amazon/product?asin=B0D1XD1ZV3&domain=US" \
  --header "Accept: application/json" \
  --header "API-KEY: $GLADE_API_KEY"

Use the product-offers endpoint when the workflow needs seller, fulfillment, condition, delivery, or buy-box context:

curl --request GET \
  "https://gladeapi.com/api/amazon/product/offers?asin=B0D1XD1ZV3&domain=US&page=1" \
  --header "Accept: application/json" \
  --header "API-KEY: $GLADE_API_KEY"

An offers response uses the same envelope as the other Glade operations. This example illustrates the contract, not a saved observation for the example ASIN:

{
  "data": {
    "amazonProduct": {
      "offersPaginated": {
        "offers": [
          {
            "id": "offer_example",
            "price": {
              "value": 79.99,
              "currency": "USD",
              "display": "$79.99"
            },
            "conditionIsNew": true,
            "isPrime": true,
            "buyboxWinner": true
          }
        ],
        "pageInfo": {
          "currentPage": 1,
          "hasNextPage": false
        }
      }
    }
  }
}

Price, delivery, seller, and buy-box fields can be absent. Missing data is not the same as zero, free delivery, or an unavailable product. Preserve null or an explicit unavailable state through normalization.

Fetch from a trusted server runtime

Keep the API key in a server environment. The following Node.js helper validates the target, applies a timeout, preserves response metadata, and distinguishes an API failure from an observed product state:

const GLADE_ORIGIN = "https://gladeapi.com";

async function fetchGlade(path, params) {
  const url = new URL(path, GLADE_ORIGIN);
  for (const [name, value] of Object.entries(params)) {
    url.searchParams.set(name, String(value));
  }

  const response = await fetch(url, {
    headers: {
      Accept: "application/json",
      "API-KEY": process.env.GLADE_API_KEY,
    },
    signal: AbortSignal.timeout(10_000),
  });

  const body = await response.json();
  const metadata = {
    requestId: response.headers.get("x-request-id"),
    cache: response.headers.get("x-glade-cache"),
    fetchedAt: response.headers.get("x-glade-data-fetched-at"),
    rateRemaining: Number(response.headers.get("x-ratelimit-remaining")),
    rateReset: Number(response.headers.get("x-ratelimit-reset")),
    retryAfter: Number(response.headers.get("retry-after")),
  };

  if (!response.ok) {
    const problem = body.errors?.[0];
    const error = new Error(problem?.message ?? `Glade returned ${response.status}`);
    error.status = response.status;
    error.metadata = metadata;
    throw error;
  }

  return { data: body.data, metadata };
}

export async function fetchPriceTarget(input) {
  const target = normalizeTrackingTarget(input);
  const params = { ...target, page: 1 };

  const [productResult, offersResult] = await Promise.all([
    fetchGlade("/api/amazon/product", target),
    fetchGlade("/api/amazon/product/offers", params),
  ]);

  return { target, productResult, offersResult };
}

Run this code in a backend, queue worker, scheduled job, server action, or serverless function. Do not place the key in browser JavaScript, a public mobile bundle, logs, or alert payloads.

Normalize a comparable observation

Convert provider output into a small record that represents the price concept your application selected. Keep raw payloads separately and only for as long as the product requires.

For a featured-offer series:

function toMinorUnits(value) {
  if (!Number.isFinite(value)) return null;
  return Math.round(value * 100);
}

function featuredOfferObservation({ target, offersResult }) {
  const product = offersResult.data.amazonProduct;
  const offers = product?.offersPaginated?.offers ?? [];
  const offer = offers.find((item) => item.buyboxWinner === true);

  if (!offer?.price || typeof offer.price.value !== "number") {
    return {
      ...target,
      priceType: "buybox",
      state: "price_unavailable",
      observedAt: offersResult.metadata.fetchedAt ?? new Date().toISOString(),
      requestId: offersResult.metadata.requestId,
    };
  }

  return {
    ...target,
    priceType: "buybox",
    state: "observed",
    offerId: offer.id ?? null,
    amountMinor: toMinorUnits(offer.price.value),
    currency: offer.price.currency,
    isPrime: offer.isPrime ?? null,
    observedAt: offersResult.metadata.fetchedAt ?? new Date().toISOString(),
    requestId: offersResult.metadata.requestId,
  };
}

Store money as integer minor units when the currency uses two decimal places. If your product expands to currencies with different minor-unit rules, use a decimal library and an ISO 4217-aware conversion instead of assuming every currency uses cents.

The data fetch timestamp is more useful for comparison than the moment your worker received the response. Keeping both can help diagnose queues and latency:

observedAt  = when the underlying data was fetched
recordedAt  = when your system persisted the observation

Design the observation table for idempotency

A minimal durable record should contain:

targetKey
asin
marketplace
priceType
state
amountMinor
currency
offerId
availability
observedAt
recordedAt
requestId
payloadHash

Make the write idempotent. A unique key derived from the target, price type, fetch timestamp, and normalized payload hash prevents retried jobs from inserting duplicate observations. Keep fetch failures in a separate job-attempt or status table so an outage does not become a price observation.

Do not update a single “current price” row and discard history. Insert the observation first, then update a compact current-state projection in the same transaction. That gives the application a fast read path without losing the audit trail used to explain alerts.

Compare changes without creating noise

Only compare observations when all of these match:

  • ASIN and marketplace.
  • Price concept.
  • Currency.
  • Product variation or child ASIN.
  • Any seller or condition rule required by the tracker.

Then apply a meaningful-change policy. An alert rule can combine absolute and percentage thresholds:

function priceChange(previous, current) {
  if (previous.state !== "observed" || current.state !== "observed") return null;
  if (previous.currency !== current.currency) return null;
  if (previous.priceType !== current.priceType) return null;

  const deltaMinor = current.amountMinor - previous.amountMinor;
  const percent = previous.amountMinor === 0
    ? null
    : (deltaMinor / previous.amountMinor) * 100;

  return { deltaMinor, percent };
}

function shouldAlert(change, rule) {
  if (!change || change.deltaMinor >= 0) return false;

  return Math.abs(change.deltaMinor) >= rule.minimumDropMinor &&
    Math.abs(change.percent ?? 0) >= rule.minimumDropPercent;
}

Add a confirmation policy for high-noise products. For example, require the new value to appear in two observations before sending an alert, or suppress a reversal that occurs within a short window. Deduplicate notifications by tracker, observation, and rule version so a retried delivery cannot send the same email twice.

An unavailable price should normally update status rather than trigger a “price dropped to zero” notification. Likewise, a seller change may be important even when the amount is unchanged, but it should be modeled as its own event.

Schedule requests around freshness and limits

Polling every product every minute is usually wasteful. Choose an interval based on the user promise, the volatility of the series, and your request budget.

A scheduler can place each target into a queue with a nextCheckAt time. Workers claim bounded batches, coalesce duplicate targets, and limit concurrency. After each run, compute the next time from the tracker policy and current system capacity.

Use Glade's response metadata as part of that decision:

  • X-Glade-Data-Fetched-At identifies the age of the underlying observation.
  • X-Glade-Cache tells you whether the response came from Glade's cache path.
  • X-RateLimit-Remaining and X-RateLimit-Reset help background workers pace requests.
  • Retry-After, when present, tells a throttled worker when it may try again.
  • X-Request-Id lets support and logs connect a failed job with the API request.

Your application cache and Glade's cache solve different problems. Your cache protects your read path and prevents duplicate work inside your system. Glade's metadata tells you about the API response. Keep both layers explicit instead of guessing freshness from HTTP arrival time.

Retry temporary failures without corrupting history

Categorize failures before retrying:

  • 400 indicates invalid parameters. Mark the tracker as needing correction.
  • 401 indicates a missing, malformed, or revoked key. Stop the worker and alert the operator.
  • 402 indicates that the current entitlement does not permit the request.
  • 404 means the resource was not found in the selected marketplace. Record resource status, not a zero price.
  • 429 indicates a rate, concurrency, or quota control. Respect Retry-After and reduce pressure.
  • 502, 503, and 504 are temporary provider, service, or timeout conditions.

Retry only idempotent reads and temporary failures. Use exponential backoff with jitter, limit attempts, and carry an idempotency key into the persistence step. Keep the last successful observation separate from lastFetchStatus, so the UI can show both “last observed at $79.99” and “latest refresh failed” without inventing a price change.

Handle variants and offer pagination deliberately

A parent product can have size, color, storage, or package variations with different ASINs and prices. If a user expects an alert for one selection, resolve and store that child ASIN. Do not compare a parent-level price from one run with a child-variation price from another.

Offer results can also be paginated. Page one may be sufficient for a featured-offer tracker, but a lowest-offer or seller-coverage workflow must define how many pages it inspects and whether delivery charges are part of the comparison. Persist that rule with the tracker so historical results remain explainable after code changes.

Choose the data source that matches the product

Glade is useful when a workflow needs public Amazon product and offer observations behind one normalized API key. It does not provide private seller data, change listings, manage inventory, or grant affiliate rights.

For an Amazon Associates shopping experience, review the official Amazon Creators API introduction and its applicable license and display requirements. For software acting on behalf of sellers or vendors, review the Selling Partner API onboarding guide. Use the official API whenever your workflow depends on account-authorized data or actions.

Whichever provider you select, review its policies and third-party rights before storing, displaying, or redistributing product content. A technically successful response does not grant additional usage rights.

Production checklist

Before enabling a price-tracking workflow:

  1. Keep credentials in a trusted server environment.
  2. Key each tracker by ASIN, marketplace, and price concept.
  3. Track the specific child ASIN when variation price matters.
  4. Store amount, currency, state, and fetch timestamp together.
  5. Treat missing price data as unavailable, never zero.
  6. Separate last successful observation from latest fetch status.
  7. Make observation writes and notification delivery idempotent.
  8. Pace workers with concurrency limits and rate metadata.
  9. Retry only temporary failures with bounded backoff and jitter.
  10. Define absolute, percentage, and confirmation thresholds for alerts.
  11. Preserve the request ID needed to diagnose failures.
  12. Test seller changes, unavailable items, coupons, delivery costs, pagination, variants, currency changes, and rapid reversals.

The complete Glade API documentation describes the product, offer, variant, stock, search, seller, and category operations that can extend this workflow. Start with one precisely defined price series, make every observation explainable, and broaden the tracker only after its change and failure behavior is reliable.

References

Topics

#amazon-price-tracking#offer-monitoring#price-alerts#api-workflows

Related posts

AI agent or LLM? Read this page as Markdown