Developer guide · reviewed September 2026

How to build a competitor price monitoring workflow

Separate extraction, comparison, storage, and alerting

Price monitoring is a small data pipeline, not a single scraping call. First observe a public product page, then normalize the observation, compare it with history you own, and decide whether a notification is appropriate. ExtractAPI handles page extraction; your application owns the other decisions.

Define a small target set

Start with a few public product URLs and record stable identifiers. Use synthetic URLs while developing this example, and check the target’s terms and robots guidance before a recurring job.

// targets.json — synthetic examples only
[
  {"id":"demo-widget","name":"Widget Pro","source":"Demo Store","url":"https://demo.example/products/widget"},
  {"id":"demo-gadget","name":"Super Gadget","source":"Demo Store","url":"https://demo.example/products/gadget"}
]

Extract the current observation

Ask ExtractAPI for a narrow selector when the page has a stable price element. The selected text appears in data.custom; JSON-LD, if present and valid, appears in data.structuredData. Neither field guarantees that the value is the final payable price.

const key = process.env.EXTRACTAPI_KEY;
if (!key || /^(YOUR_API_KEY|ext_your)/i.test(key)) {
  throw new Error('Set EXTRACTAPI_KEY before making a request');
}

async function extractPrice(target) {
  const response = await fetch('https://extractapi.app/v1/extract', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${key}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: target.url,
      javascript: true,
      waitFor: '[data-ready="true"]',
      selector: '[data-price]'
    })
  });
  const body = await response.json();
  if (!response.ok || body.status !== 'ok') {
    throw new Error(body.error || `HTTP ${response.status}`);
  }
  const text = body.data?.custom?.[0] || '';
  const match = text.match(/[0-9]+(?:[.,][0-9]{1,2})?/);
  return {
    ...target,
    observedText: text,
    price: match ? Number(match[0].replace(',', '.')) : null,
    checkedAt: body.fetchedAt || new Date().toISOString()
  };
}
Field reminder: the documented API response uses data.custom for selected text and data.structuredData for valid JSON-LD. Inspect your actual target before writing a parser.

Compare in storage you control

The extraction response is a point-in-time observation. Store a normalized value and its timestamp in your own database or file, then compare it with the previous valid observation. This sample deliberately uses a local JSON file; ExtractAPI does not provide price-history storage.

import { readFile, writeFile } from 'node:fs/promises';

async function comparePrice(observation) {
  let history = {};
  try { history = JSON.parse(await readFile('price-history.json', 'utf8')); }
  catch (error) { if (error.code !== 'ENOENT') throw error; }

  const previous = history[observation.id];
  const change = previous && observation.price !== null && previous.price !== observation.price
    ? {
        direction: observation.price < previous.price ? 'decrease' : 'increase',
        from: previous.price,
        to: observation.price
      }
    : null;

  history[observation.id] = observation;
  await writeFile('price-history.json', JSON.stringify(history, null, 2) + '\n');
  return { observation, change };
}

Choose scheduling and notifications separately

Your scheduler can invoke the extraction function on a cadence appropriate for the target and your plan. Your application can then apply a threshold, write an audit record, and send an authorized notification. Keep these responsibilities separate so a failed extraction cannot be mistaken for a price increase.

// Pseudocode for your own job runner
for (const target of targets) {
  try {
    const observation = await extractPrice(target);
    const result = await comparePrice(observation);
    if (result.change) await notifyYourTeam(result); // your transport and policy
  } catch (error) {
    console.error({ id: target.id, category: 'extraction_failed', message: error.message });
  }
}

ExtractAPI does not schedule jobs, store price histories, send alerts, solve CAPTCHAs, or guarantee access to protected sites.

Handle the hard cases

FAQ

Can I run this as a cron job?

You can schedule your own script or job runner if the target permits recurring access. The scheduler, history, and alerting are your responsibility; ExtractAPI only handles the extraction request.

Is a rendered selector more reliable than JSON-LD?

They answer different questions. A selector can capture visible rendered text; JSON-LD can contain structured Offer fields. Validate both when price correctness matters because they may disagree.

What if the page requires a CAPTCHA or login?

Do not attempt to bypass it. Ask for an approved API or feed, obtain permission, or exclude the target from the workflow.

Get a free API key