# Pricing Page Change Monitor (`aibuildersuse/pricing-page-change-monitor`) Actor

Watch any SaaS pricing page and get a structured diff when plans, prices or limits change. Detects plan cards automatically, keeps state between runs, and can call your webhook (Slack, Discord, Zapier, Make, n8n).

- **URL**: https://apify.com/aibuildersuse/pricing-page-change-monitor.md
- **Developed by:** [AIBuildersUse](https://apify.com/aibuildersuse) (community)
- **Categories:** Developer tools, Automation, E-commerce
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 page checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Pricing Page Change Monitor: track SaaS price changes and get alerts

**Pricing Page Change Monitor** watches any list of SaaS pricing pages and tells you exactly what changed since the last run: a plan's price went up or down, a plan was added or removed, or the plan details (limits, credits, features) were rewritten. Each run compares the page with the snapshot saved by the previous run and outputs a clean dataset of changes, with an optional webhook alert to Slack, Discord, Zapier, Make or n8n.

It's built for competitor price tracking, SaaS market research, and procurement teams that need to know when a vendor changes its prices.

### What does Pricing Page Change Monitor do?

- **Extracts plans and prices** from pricing pages: plan cards, pricing tables, and comparison tables. It reads $, €, £, ¥ and ₹ prices and billing periods (per month, per user, per year, one-time).
- **Renders JavaScript pages automatically.** Most pages are fetched over fast plain HTTP. When a page only shows its prices after JavaScript runs (React, Next.js, Webflow and similar), a headless Chromium browser loads it instead.
- **Diffs against the previous run.** Snapshots are saved in a named key-value store, so scheduled runs compare against each other.
- **Outputs only what matters:** `price_changed` (with `increase`/`decrease`), `plan_added`, `plan_removed`, `plan_details_changed`, `page_text_changed`, plus clear error rows when a page can't be read.
- **Sends a webhook** with all changes in one POST. The payload works with Slack and Discord incoming webhooks as is.
- **Lets you scope the check with a CSS selector** when you only care about one part of a page.

### Use cases

- **Competitor price monitoring:** know the same day a competitor raises prices, launches a new tier, or drops a free plan.
- **Vendor and procurement watch:** track the tools your team pays for (AI tools, dev tools, SaaS seats) and catch price increases before renewal.
- **Market research and pricing strategy:** build a history of how a category's pricing moves over time.
- **Newsletters and analysts:** get a ready-made feed of pricing changes to write about.
- **Internal alerts:** pipe changes into Slack, a Google Sheet, or a CRM through Zapier, Make or n8n.

### How to use it

1. Add the pricing page URLs you want to watch. Optionally add a CSS selector, either for all pages or per URL.
2. Run the Actor once. The first run saves a **baseline** for each page (one row per plan detected), so you can check that it reads the plans correctly.
3. Create a **schedule** (for example daily or weekly) in Apify Console. Every later run outputs the changes, plus one `no_change` row per unchanged page.
4. Optional: add a **webhook URL** to get the changes pushed to you.

Keep the same **State store name** across runs. To track two independent lists, give each its own store name.

### Input

| Field | Description |
| --- | --- |
| `urls` | Pricing pages to monitor. Each item can carry its own `selector`. |
| `cssSelector` | Optional CSS selector used for all pages without their own selector, e.g. `#pricing`. |
| `renderMode` | `auto` (default: HTTP, browser only when needed), `http`, or `browser`. |
| `textDiffMode` | `pricing_lines` (default) also reports changed price-related lines outside the plan cards; `all` reports any text change; `off` reports plan and price changes only. |
| `ignoreTextPatterns` | Regular expressions for text to ignore, e.g. rotating banners, A/B test copy, dates. |
| `includeUnchanged` | On by default: each unchanged page gets one `no_change` row, so every run shows what was checked. Turn off to output only real changes. |
| `webhookUrl`, `webhookHeaders` | Where to POST the changes, with optional headers (e.g. `Authorization`). |
| `webhookOnBaseline` | Also call the webhook on the first (baseline) run. |
| `stateStoreName` | Name of the key-value store that keeps the snapshots. Default `pricing-page-monitor-state`. |
| `maxConcurrency` | Pages checked in parallel (1–10). |
| `proxyConfiguration` | Optional Apify Proxy for sites that block data-center IPs. |

Example input:

```json
{
  "urls": [
    { "url": "https://cursor.com/pricing" },
    { "url": "https://lovable.dev/pricing" },
    { "url": "https://www.coderabbit.ai/pricing", "selector": "main" }
  ],
  "renderMode": "auto",
  "textDiffMode": "pricing_lines",
  "webhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ"
}
```

### Output

Each dataset row is one change. Example of a price change:

```json
{
  "url": "https://cursor.com/pricing",
  "label": null,
  "selector": null,
  "checkedAt": "2026-11-04T15:50:02Z",
  "previousCheckedAt": "2026-11-03T15:49:18Z",
  "fetchMethod": "http",
  "plansDetected": 6,
  "changeType": "price_changed",
  "plan": "Plus",
  "oldPrice": "$10",
  "newPrice": "$8",
  "oldPrices": ["$10"],
  "newPrices": ["$8"],
  "addedPrices": ["$8"],
  "removedPrices": ["$10"],
  "direction": "decrease",
  "summary": "'Plus' price changed: $10 -> $8"
}
```

A new plan looks like `{"changeType": "plan_added", "plan": "Super", "newPrice": "$100", "summary": "New plan 'Super' at $100", ...}` and a removed one like `{"changeType": "plan_removed", "plan": "Legacy Pro", "oldPrice": "$20", ...}`. On the first run you get one `baseline` row per detected plan with its price. A summary of every run is saved as the `OUTPUT` record in the run's key-value store.

You can download the dataset as JSON, CSV, Excel, XML or HTML, or read it through the Apify API.

#### Change types

| `changeType` | Meaning |
| --- | --- |
| `baseline` | First run for this page: the plans and prices that were saved. |
| `price_changed` | A plan's main price changed (`direction` is `increase` or `decrease`). |
| `plan_added` / `plan_removed` | A plan appeared or disappeared. |
| `plan_details_changed` | A plan's limits, credits or feature text changed, but not its main price. |
| `page_text_changed` | Price-related text elsewhere on the page changed (added/removed lines are included). |
| `no_change` | The page was checked and nothing changed (turn off with `includeUnchanged`). |
| `plans_not_found` | The page loaded but no prices were found. The previous snapshot is kept, so a broken load can't create false "plan removed" alerts. |
| `selector_not_found` | The CSS selector matched nothing. |
| `fetch_error` | The page could not be loaded after retries. |

### Webhook payload

One POST per run when there are changes:

```json
{
  "event": "pricing_changes_detected",
  "runStartedAt": "2026-11-04T15:50:02Z",
  "runId": "abc123",
  "changeCount": 2,
  "text": "Pricing changes detected:\n- https://cursor.com/pricing: 'Plus' price changed: $10 -> $8\n- https://cursor.com/pricing: New plan 'Super' at $100",
  "content": "(same text, for Discord)",
  "changes": [ { "changeType": "price_changed", "plan": "Plus", "oldPrice": "$10", "newPrice": "$8" } ]
}
```

The `text` field makes it work with Slack incoming webhooks, and `content` makes it work with Discord. Zapier, Make and n8n can use the `changes` array.

### How much does it cost?

The Actor uses pay-per-event pricing: you pay a small fee per pricing page checked, plus Apify's standard Actor start fee. Pages that fail to load are not charged. Watching 20 pricing pages once a day costs roughly $3 a month. Set a maximum cost per run in Console and the Actor stops before going over it.

### Tips

- **Schedule it.** The value is in repeated runs. Daily is plenty for most pricing pages.
- **Noisy page?** Some vendors A/B test their copy. Add the changing phrase to `ignoreTextPatterns`, or set `textDiffMode` to `off` to get plan and price changes only.
- **Only one section matters?** Use a CSS selector such as `#pricing` or `main`.
- **Memory:** 1024 MB (default) is enough for any page. In `http` mode, 512 MB is enough.

### FAQ

**Does it work on pages that load prices with JavaScript?**
Yes. In `auto` mode the Actor first tries plain HTTP and switches to a headless browser when the page has no readable prices without JavaScript.

**Will it detect a change in the monthly vs annual toggle?**
It reads what the page shows by default, plus any prices present in the page text. If a page shows annual prices only after a click, use a CSS selector on the section that holds both, or monitor the page's annual pricing URL if it has one.

**What if the page layout changes?**
Plan names and prices are matched by name, not position, so reordering cards does not create false alerts. If a redesign renames plans, you'll see `plan_removed` and `plan_added` rows, which is usually what you want to know.

**What about pages that show no prices at all, like "Contact sales"?**
The text diff still reports changes to the plan names and price-related text. Use `textDiffMode: "all"` for any change at all.

**Can I reset the baseline?**
Change `stateStoreName` (or delete the store in Console) and the next run starts fresh.

**Is it legal to monitor pricing pages?**
The Actor only reads public pricing pages, as a visitor's browser would. Check each site's terms of use for your use case.

### Limitations

- Prices must be written with a currency symbol or code (for example `$20`, `€15`, `20 USD`). Pages that only say "Free" or "Contact us" are tracked through the text diff.
- Pages that also list per-token API prices or add-ons (for example model pricing tables) produce extra "plan" rows for those items. They stay stable between runs and still alert you when those prices change.
- Pages behind a login or a bot challenge can't be read; try `proxyConfiguration` with residential proxies.

### Feedback

Found a page it reads wrongly? Open an issue in the Actor's **Issues** tab with the URL, and it will be looked at.

# Actor input Schema

## `urls` (type: `array`):

Pricing or plans pages to watch. Each run compares every page with the snapshot saved by the previous run. Optional per-URL CSS selector: add an item as an object {"url": "...", "selector": "#pricing"} through the JSON editor.

## `cssSelector` (type: `string`):

Limit extraction to the part of the page matching this CSS selector, for example `#pricing` or `main section.plans`. Leave empty to auto-detect plan cards on the whole page. A per-URL selector overrides this one.

## `renderMode` (type: `string`):

`auto` loads the page over plain HTTP first and only starts a headless browser when no prices are visible in the HTML (JavaScript-rendered pages). `http` never uses a browser (cheapest, may miss JS-only pages). `browser` always renders with Chromium.

## `textDiffMode` (type: `string`):

Besides plan and price changes, also report changed text on the page. `pricing_lines` only compares lines that mention numbers, limits, credits, seats, billing or plans (recommended, low noise). `all` compares every text line. `off` reports plan and price changes only.

## `ignoreTextPatterns` (type: `array`):

Lines matching any of these regular expressions are ignored when comparing page text, for example rotating testimonials, GitHub star counts or dates.

## `includeUnchanged` (type: `boolean`):

When on, pages without changes get one `no_change` row, so every run shows what was checked. Turn off to output only real changes.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs one JSON payload with all detected changes after the run (only when something changed). The payload has a `text` field, so a Slack incoming-webhook URL works as is; it also has a Discord-compatible `content` field and the full `changes` array for Zapier, Make or n8n.

## `webhookHeaders` (type: `object`):

Extra HTTP headers to send with the webhook, for example an Authorization header.

## `webhookOnBaseline` (type: `boolean`):

By default the first run of a URL only saves a baseline and does not notify. Turn on to also send baseline rows to the webhook.

## `stateStoreName` (type: `string`):

Name of the key-value store in your account where page snapshots are kept between runs. Use a different name to keep separate watch lists apart. Letters, digits and hyphens only.

## `maxConcurrency` (type: `integer`):

How many pages to check at the same time. Keep it low (1-3) on small memory.

## `proxyConfiguration` (type: `object`):

Optional. Most pricing pages load without a proxy. Use Apify Proxy only if a site blocks the default IPs.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://cursor.com/pricing"
    },
    {
      "url": "https://lovable.dev/pricing"
    },
    {
      "url": "https://bolt.new/pricing"
    }
  ],
  "renderMode": "auto",
  "textDiffMode": "pricing_lines",
  "ignoreTextPatterns": [
    "\\d+(\\.\\d+)?k stars",
    "©\\s*\\d{4}"
  ],
  "includeUnchanged": true,
  "webhookHeaders": {},
  "webhookOnBaseline": false,
  "stateStoreName": "pricing-page-monitor-state",
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `changes` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        {
            "url": "https://cursor.com/pricing"
        },
        {
            "url": "https://lovable.dev/pricing"
        },
        {
            "url": "https://bolt.new/pricing"
        }
    ],
    "ignoreTextPatterns": [
        "\\d+(\\.\\d+)?k stars",
        "©\\s*\\d{4}"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("aibuildersuse/pricing-page-change-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        { "url": "https://cursor.com/pricing" },
        { "url": "https://lovable.dev/pricing" },
        { "url": "https://bolt.new/pricing" },
    ],
    "ignoreTextPatterns": [
        "\\d+(\\.\\d+)?k stars",
        "©\\s*\\d{4}",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("aibuildersuse/pricing-page-change-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    {
      "url": "https://cursor.com/pricing"
    },
    {
      "url": "https://lovable.dev/pricing"
    },
    {
      "url": "https://bolt.new/pricing"
    }
  ],
  "ignoreTextPatterns": [
    "\\\\d+(\\\\.\\\\d+)?k stars",
    "©\\\\s*\\\\d{4}"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call aibuildersuse/pricing-page-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aibuildersuse/pricing-page-change-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/wv1wEzLgrgN0hbdOR/builds/6i11FCSTykTquD5Wh/openapi.json
