How Often Should Product Pages Be Updated? A Plain-English Guide to Keeping Content Fresh

How Often Should Product Pages Be Updated? A Plain-English Guide to Keeping Content Fresh

How often product pages should be updated is one of the first questions a publisher faces after launching a comparison page, a deal roundup or a gift guide. Update too rarely and a page shows sold-out items and old prices. Update too often and the work repeats data that has not changed yet.

This guide gives a plain-English answer, backed by a look at how often product data actually refreshes at six retailers. It also includes one request you can copy and paste and a short script you can run against your own product list. Measurements were taken on October 6, 2026. Prices and times in the examples are snapshots, so confirm any price on the retailer's page before publishing it.

The short answer

Match your update schedule to how quickly the source changes. Affiliate.com's documentation describes product records as refreshed daily from merchant feeds, so for most pages a daily check of price and availability is the practical ceiling. Checking more often mostly returns the same data. Checking less often is fine for descriptions and images, which rarely change.

A workable starting point looks like this:

  • Price and availability: once a day, plus an extra check before a date you expect to bring heavy traffic.
  • Which products are featured: a weekly review.
  • Descriptions, images and categories: a monthly review.
  • Page copy and recommendations: whenever a featured product changes.

What actually changes on a product page

Not every part of a product page moves at the same speed. Separating them keeps the daily check small.

Page elementHow often it tends to changeWhat to do
Price and discountCan change at any feed refreshRead the price from the data at each check instead of typing it into the page
AvailabilityCan change at any feed refreshCheck daily and treat "unknown" as its own group
Whether the product existsA product can drop out of a feedTreat a product that is no longer returned as removed
Merchant and linkRarelyRecheck monthly
Title, description and imageRarelyReview monthly, or when a product is featured again

In practice, the daily check only needs three values per product: the final price, the availability and the updated time. Titles, images and links can stay on the monthly review. A smaller daily check is easier to automate, easier to review when something looks wrong, and uses fewer requests.

How often the data behind the page really refreshes

Every product record carries an updated_at value, which the documentation describes as the time the record was last refreshed from the merchant feed. To see how that plays out, we looked at six retailers on October 6, 2026. For each one we found the oldest and newest updated_at among in-stock products priced in US dollars. Times are in UTC.

Retailer typeOldest recordNewest recordSpread
Electronics retailerOct 5, 06:12Oct 5, 06:2917 minutes
Department store ASep 30, 08:53Sep 30, 09:1220 minutes
Department store BOct 5, 06:33Oct 5, 07:1441 minutes
Mass retailerOct 5, 04:11Oct 5, 09:21about 5 hours
General retailerOct 5, 05:53Oct 6, 06:15about 24 hours
Home improvement retailerOct 5, 07:26Oct 6, 06:06about 23 hours

Three things stand out.

Refreshes arrive in batches. For each retailer we also sampled 30 records, and every sample was stamped within about 80 seconds. A timestamp tells you when a retailer's feed was processed. It does not tell you when a price last changed. The same general-retailer product returned an updated time of Oct 5, 06:07 on one lookup and Oct 6, 06:13 on a lookup a few hours later, with the same price of $76.99 both times.

Rhythms differ by retailer. Five of the six had refreshed their newest records within about 40 hours of the check. One department store had not refreshed since September 30, more than six days earlier. A daily schedule suits most retailers, but it is worth reading the timestamps for the ones your pages depend on. The difference between this field and the one that records when a product first appeared is covered in this post on updated_at and added_at.

Windows are narrow. In this snapshot, every refresh window ended before 10:00 UTC. A daily check that runs later in the day will see the latest data, while a second check a few hours after the first will mostly see the same timestamps.

Match your checks to the source

Consider the electronics retailer in the table above. Its in-stock records were refreshed in a single 17-minute window on October 5, and nothing newer had appeared 39 hours later. Any check made in between would have returned the same timestamps, the same prices and the same availability, so the extra request added nothing. Because every request also counts toward a plan's usage, repeating a check that cannot return anything new has a cost. A better pattern is to run one check after a retailer's usual window, compare the result with what the page shows, and keep any extra requests for the days that matter most.

The table below suggests a starting rhythm by page type. These are recommendations, not measured rules, so adjust them to how your readers use each page.

Page typeSuggested checkWhy
Deal and price-drop pagesDaily, after the morning refreshPrice and stock are the content of the page
Gift guides and seasonal roundupsDaily in season, weekly otherwise, plus a check before key datesPopular items can sell out on peak days
Comparison pagesDaily for prices, weekly for the product listPrices move, but the set of products changes slowly
Evergreen reviewsWeekly availability check, monthly full reviewMost details stay the same

How the updated time and availability fields work together is explained in this guide to controlling product freshness.

Check every product on a page in one request

A page with 20 products does not need 20 requests. Product IDs can be sent together in one id value, separated by ||, up to 100 IDs per request. Send the body below as a POST request to https://api.affiliate.com/v1/products with the header Authorization: Bearer YOUR_API_TOKEN. You can create a token under Settings and API Keys, and the body works in any API client.

{
  "search": [
    {
      "field": "id",
      "operator": "=",
      "value": "1378383387732191393||1178444545855050513||8544194543518306461"
    }
  ],
  "fields": ["id", "name", "final_price", "regular_price", "availability", "updated_at"],
  "per_page": 100
}

The response, shortened to the data array:

{
  "data": [
    {
      "id": "1378383387732191393",
      "name": "Patagonia Black Hole Duffel 55 L Gray",
      "final_price": 88.83,
      "regular_price": 179,
      "availability": "InStock",
      "updated_at": "2026-10-05 04:18:23"
    },
    {
      "id": "1178444545855050513",
      "name": "Byredo Gypsy Water Absolu De Parfum Frargance Collection",
      "final_price": 290,
      "regular_price": 290,
      "availability": "InStock",
      "updated_at": "2026-09-30 08:53:03"
    },
    {
      "id": "8544194543518306461",
      "name": "Melissa & Doug Deluxe Band Set With Wooden Musical Instruments and Storage Case",
      "final_price": 76.99,
      "regular_price": 76.99,
      "availability": "InStock",
      "updated_at": "2026-10-06 06:13:17"
    }
  ]
}

Three details are worth knowing. Results do not come back in the order the IDs were sent, so match them by id. An ID that is not returned is a product no longer in the data. And the discount can be read straight from the two price fields: the duffel's final price of 88.83 against a regular price of 179 is about half off, with no typing involved.

Find out which retailers are falling behind

The same kind of request can show how fresh a retailer's data is. Sort the results by updated_at in ascending order, filter to in-stock products and add a cutoff such as updated_at less than a date in YYYY-MM-DD format. The first results are the products that have waited longest for a refresh. For the general retailer above, the two oldest in-stock records carried an updated time of October 5 at 05:53, which matches the table.

Sorting in descending order does the opposite and lists what was refreshed most recently. That is useful for deciding which records to recheck after a refresh, but a refreshed record is not necessarily a changed one, so treat the list as worth rechecking and compare the price against what your page shows. Search totals also stop counting at 10,000. If you need the exact number of matching products, add "exact_total": true to the request body, which the documentation describes as a precise count that takes a little longer to return.

A simple routine for the retailers your pages rely on is to read the newest updated_at once a week. If it is more than three days old, check the retailer's page for the products you feature before relying on the data.

A script that checks your saved list

The script below applies the whole idea. It takes the products a page features and the prices the page currently shows, requests them in batches of up to 100, and prints a status for each one. It needs Node 18 or newer. Store your token in an environment variable rather than in the file, and treat the timestamps as UTC.

// freshness-check.js  (run with: AFFILIATE_API_TOKEN=your_token node freshness-check.js)
const MAX_AGE_DAYS = 3;

// The products your page features, with the price your page currently shows
const saved = [
  { id: "1378383387732191393", price: 99.0 },
  { id: "1178444545855050513", price: 290.0 },
  { id: "8544194543518306461", price: 76.99 },
  { id: "1111111111111111111", price: 19.99 }, // an ID that does not exist
];

async function fetchBatch(ids) {
  const response = await fetch("https://api.affiliate.com/v1/products", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AFFILIATE_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      search: [{ field: "id", operator: "=", value: ids.join("||") }],
      fields: ["id", "final_price", "availability", "updated_at"],
      per_page: 100,
    }),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return (await response.json()).data;
}

async function main() {
  const found = new Map();
  for (let i = 0; i < saved.length; i += 100) {
    const ids = saved.slice(i, i + 100).map((item) => item.id);
    for (const product of await fetchBatch(ids)) found.set(product.id, product);
  }

  for (const item of saved) {
    const product = found.get(item.id);
    let status = "missing: remove from page";
    if (product) {
      const days = (Date.now() - Date.parse(product.updated_at.replace(" ", "T") + "Z")) / 86400000;
      if (product.availability !== "InStock") status = `not in stock (${product.availability})`;
      else if (product.final_price !== item.price) status = `price changed: ${item.price} -> ${product.final_price}`;
      else if (days > MAX_AGE_DAYS) status = `stale: ${days.toFixed(1)} days old`;
      else status = "ok";
    }
    console.log(item.id, status);
  }
}

main().catch(console.error);

Run on October 6, with the first price set to 99 on purpose, the script reported:

ProductStatus reported
Duffel bag (page showed 99.00)price changed: 99 -> 88.83
Fragrance (record from September 30)stale: 6.6 days old
Toy band setok
Invented IDmissing: remove from page

If a request returns 429, the account has exceeded its per-minute limit, and the documentation advises waiting before retrying.

What to do with each result

StatusWhat it meansWhat to do
okThe product is in stock, the price matches and the record is recentLeave it as it is
price changedThe current final price differs from the price on your pageUpdate the displayed price and recheck any discount wording
not in stockThe product is out of stock or its availability is unknownReplace it or hide it, and do not feature it
staleThe record has not been refreshed within your limitConfirm on the retailer's page before keeping it
missingThe product is no longer returnedRemove it or replace it with a similar product

Wording about discounts depends on both price fields, which is covered in this article on comparing regular and final prices.

A refresh checklist

  • Is every featured product still returned, and is it in stock today?
  • Does each displayed price come from the data and not from typed text?
  • Is each record newer than the age limit you set for that page type?
  • Do the retailers on the page refresh on a rhythm that matches your schedule?
  • Did you run the check after the retailers' usual refresh window?
  • Is there an extra check planned before your busiest dates?
  • Do descriptions, images and links get a monthly review?
  • Does the page tell readers to confirm the final price on the retailer's site?

Try it with your own pages

Start with one page. List the products it features, send the request in the batch section with your token, and read the availability, final_price and updated_at values. The same filters can be built without code in the Affiliate.com Query Builder. Keep the list of saved products and prices in a spreadsheet or a small file, so the script always has a current starting point, and update the saved price after each run so the same change is not reported twice. Once the script works for one page, schedule it to run once a day after the morning refresh, and extend it to your other pages starting with the ones that earn the most. To track changes across a whole catalog instead of a single page, see this overview of monitoring catalog changes with structured data.