# Competitor Change Tracker: Pricing, Terms, Careers & Changelog (`fairquery/competitor-change-tracker`) Actor

Track competitors' public pricing, terms, careers, changelog and sitemap pages on a schedule. Get only meaningful changes: plan and price changes, changed legal sections, new pages and roles, with summaries, severity and Slack/Discord alerts. Pay per page checked, from $0.00049.

- **URL**: https://apify.com/fairquery/competitor-change-tracker.md
- **Developed by:** [Fairquery](https://apify.com/fairquery) (community)
- **Categories:** Automation, Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 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

## Competitor Change Tracker: Pricing, Terms, Careers & Changelog

**Track your competitors' public pricing, terms and privacy, careers, changelog and sitemap pages on a schedule, and get only the changes that matter, each with a plain-English summary and a severity.** Prices are parsed per plan, legal pages are compared section by section, job boards (Greenhouse, Lever, Ashby) are compared role by role, and sitemaps show new and removed pages. Timestamps, rotating tokens, cookie banners, nav and footers are ignored.

Pricing (pay per event, no start fee): **$0.00049 per page checked, $0.0049 per change reported**. The first run (baseline) is charged per page only. Example: 20 competitor pages checked every hour for a month is 14,400 checks = **about $7**, plus $0.0049 per actual change.

> **How fresh is it?** Exactly as fresh as your schedule. Each run is a check; the Actor does not watch pages continuously. Run it hourly, daily or weekly with an Apify Schedule.

### When to use this Actor (LLM / tool selection)

Use it when a person, agent or workflow needs to:

- Know when a competitor **changes a price, adds or removes a plan**, or switches monthly/annual billing
- See **which sections of a Terms of Service or Privacy Policy changed**, with the removed and added sentences
- Spot **new or removed product, feature or integration pages** (sitemap diff)
- Follow **hiring signals**: new and closed roles by department from Greenhouse, Lever or Ashby boards, or keyword counts on a plain careers page
- Get **new changelog or blog entries** with their links
- Send alerts to **Slack, Discord or any HTTPS webhook**, filtered by severity

Do **not** use it for: pages behind a login, paywall or CAPTCHA (never accessed), visual/pixel comparison of designs, minute-by-minute monitoring, or sites whose robots.txt disallows the page (skipped and reported).

### What counts as a change

| Page type | What is compared | Example summary (from test runs) |
| --- | --- | --- |
| `pricing` | Plans: name, price, currency, period (month/year/hour), per-user units, billed annually | `"Pro" price changed from $79/month to $99/month (up 25.3%).` · `New plan "Studio" ($59/month).` |
| `terms`, `privacy` | Sections (by heading), sentence-level diff, renamed sections | `Terms of service: 2 sections changed: "Terms of Service" (modified: +1/−1 sentences), "Linear’s Ownership" (added). Removed: “…” Added: “…”` |
| `careers` | Job board (Greenhouse/Lever/Ashby) roles by id, or careers-page text and keyword counts | `3 new roles (3 Product); 1 role closed (1 Engineering). Open roles: 27 → 29 (+7%).` |
| `changelog`, `blog` | Entry headings and links | `2 new changelog entries: “Stacked pull requests”, …` |
| `product` (site root) | sitemap.xml URLs (index files and .gz supported) | `2 pages removed from the sitemap (2 product/feature-like), e.g. /features/…` |
| `generic` | Page text by section | `Page: 1 section changed …` |

Severities: **high** (price/plan changes, substantive legal edits about liability, data, AI, arbitration, fees, termination and similar), **medium** (larger text changes, product pages added/removed, notable hiring shifts, watched keywords), **low** (small edits, routine sitemap churn, a page switching back to a version seen before).

#### Noise filtering

- Strips scripts, styles, nav, footer, cookie/consent banners and hidden templates; reads the main content.
- Masks timestamps, dates, "x minutes ago", session tokens, UUIDs, hashes, cache-busting parameters, copyright years and live counters. Numbers are masked except on pricing and legal pages, where they matter.
- Ignores reordered sentences, small edits under `minChangeChars`, and your own `ignorePatterns`.
- **Confirms each change with a second read** (after the polite delay; not charged) so A/B variants that come and go within seconds are not reported.
- **Remembers recent versions:** a page alternating between two versions across runs alerts once, then only as one low-severity "switched back" item.
- Small edits accumulate against the old baseline until they cross the threshold, so a slow drip of edits is not lost.

### Input

You can give exact pages, plain URLs (type guessed from the path), or domains plus the page types to find.

| Field | Default | What it does |
| --- | --- | --- |
| `pages` | | List of `{ "url": "...", "type": "pricing" }` (types: pricing, product, terms, privacy, careers, changelog, blog, generic) |
| `urls` | | Plain URL list; type guessed from the path (`/pricing` → pricing, `/terms` → terms, …) |
| `domains` | | Competitor domains; the Actor finds the requested page types via homepage links and a few common paths (cached 30 days, discovery requests are free) |
| `pageTypes` | pricing, terms, privacy, careers, changelog, product | Page types to find for `domains` |
| `watchlistName` | `default` | Name of this watchlist. Baselines are stored per watchlist, so different lists don't interfere |
| `maxPages` | 50 (≤ 200) | Cap per run |
| `minSeverity` | `low` | Changes below it are not output or charged |
| `careersKeywords` / `watchKeywords` | | Words to count on careers pages / anywhere (e.g. "AI", "SOC 2", "enterprise") |
| `ignorePatterns` | | Extra regexes to ignore (ReDoS-checked) |
| `minChangeChars` | 20 | Smallest text change worth reporting (legal pages use a lower floor) |
| `includeDiff` | true | Include removed/added sentences in `details` |
| `webhookUrl`, `webhookFormat`, `webhookMinSeverity` | , `json`, `medium` | HTTPS webhook for alerts: `json`, `slack` or `discord` (stored as a secret input) |
| `renderJs` | `never` | `auto`: render with a headless browser only when plain HTML has no readable prices or almost no text; `always`: render all. Needs ≥ 1024 MB memory; robots.txt still applies |
| `confirmChanges` | true | Re-read a page once before reporting a change |
| `sitemapInclude` / `sitemapExclude` / `maxSitemapFiles` | / / 10 | Filter sitemap URLs by regex; cap sitemap files read |
| `requestDelaySecs` | 1.5 (1–30) | Minimum gap between requests to the same host (a larger robots.txt Crawl-delay wins) |
| `stateStoreName` | `fairquery-competitor-tracker` | Named key-value store in your account holding baselines |
| `resetBaseline` | false | Start over: this run becomes the new baseline |

#### Example input

```json
{
  "watchlistName": "saas-competitors",
  "pages": [
    { "url": "https://basecamp.com/pricing", "type": "pricing" },
    { "url": "https://linear.app/pricing", "type": "pricing" },
    { "url": "https://github.com/site/terms", "type": "terms" },
    { "url": "https://jobs.ashbyhq.com/linear", "type": "careers" }
  ],
  "domains": ["cal.com"],
  "careersKeywords": ["AI", "enterprise"],
  "webhookUrl": "https://hooks.slack.com/services/…",
  "webhookFormat": "slack"
}
```

Then add an **Apify Schedule** (for example hourly or daily) with the same input. The first run stores baselines; every later run reports changes.

### Output

One dataset row per change (view **Changes**), plus `baseline`, `skipped` and `error` rows so you can see what happened to every page. A summary goes to the `OUTPUT` record.

#### Change row (from a test run; the old price was simulated by editing the stored baseline)

```json
{
  "type": "change", "watchlist": "fq-test-main", "checked_at": "2026-10-18T02:15:41.208Z",
  "url": "https://basecamp.com/pricing", "page_type": "pricing", "domain": "basecamp.com",
  "severity": "high", "change_kind": "price_changed",
  "summary": "\"Pro\" price changed from $79/month to $99/month (up 25.3%).",
  "details": { "plan": "Pro",
    "from": { "amount": 79, "currency": "USD", "period": "month", "raw": "$79" },
    "to":   { "amount": 99, "currency": "USD", "period": "month", "raw": "$99" },
    "change_pct": 25.3 },
  "previous_checked_at": "2026-10-18T02:10:52.136Z"
}
```

`change_kind` values: `price_changed`, `plan_prices_changed`, `plan_added`, `plan_removed`, `pricing_restructured`, `pricing_unparsed`, `pricing_text_changed`, `legal_text_changed`, `content_changed`, `pages_added`, `pages_removed`, `jobs_changed`, `careers_keywords_changed`, `new_entries`, `keyword_changed`, `reverted_to_seen_version`.

#### Run summary (`OUTPUT`)

`status`, `pages_checked`, `baselines`, `changes`, `changes_by_severity`, `unchanged`, `skipped`, `errors`, `requests`, `bytes_downloaded`, `browser_renders`, `events`, `webhook`, `duration_secs` and a per-page list with notes such as `304 Not Modified` or `ROBOTS_DISALLOWED`.

### Pricing (pay per event)

| Event | Price |
| --- | --- |
| Page checked (each page, job-board read or sitemap file fetched) | $0.00049 |
| Change reported (at or above `minSeverity`) | $0.0049 |
| Page rendered with the headless browser (optional) | $0.0059 |

Free: baselines beyond the page check, robots-blocked/skipped/error rows, discovery requests, confirmation re-reads, filtered A/B flips below `minSeverity`. Set a maximum cost per run in the run options and the Actor stops before exceeding it (baselines are not saved for pages it could not pay for, so nothing is lost).

| Example | Checks per month | Cost |
| --- | --- | --- |
| 10 pages, daily | 300 | ≈ $0.16 |
| 20 pages, hourly | 14,400 | ≈ $7.06 + changes |
| 100 pages, hourly | 72,000 | ≈ $35.28 + changes |

### How it behaves (politeness and safety)

- **Public, no-login pages only.** No cookies or sessions are kept, no logins, no CAPTCHA solving, no proxies to get around blocks.
- **robots.txt is respected** (RFC 9309, longest match, `Crawl-delay` honoured; a host asking for more than 30 s is skipped). Disallowed pages are skipped and reported. 5xx/unreachable robots.txt = not crawled. Every redirect hop is re-checked.
- **Honest user agent:** `FairqueryChangeTracker/1.0 (+https://getfairquery.com; fairquery@proton.me)`.
- **Polite rate:** one request at a time per host, at least 1.5 s apart (configurable, never below 1 s), at most 4 hosts in parallel. Conditional requests (ETag / If-Modified-Since) so unchanged pages often cost the site nothing.
- **Kill switch:** an HTTP 403 or 429 stops all further requests to that host for the run.
- **Plain HTTP first.** The headless browser is opt-in, only for JavaScript-rendered pages, still checks robots.txt, blocks images/fonts/media and keeps no state between pages.
- Job boards are read through their documented public APIs (Greenhouse Job Board API, Lever Postings API, Ashby Public Job Posting API).
- Private/internal addresses are refused (SSRF protection with DNS pinning; ports 80/443 only).

### Memory / runtime

256 MB is enough for plain HTTP checks (a 24-page rerun took about 7–30 s in tests). Browser rendering needs 1024 MB or more. Default timeout 30 minutes.

### Limitations

- **Plan parsing is heuristic.** It works on most server-rendered pricing pages (tested on Basecamp, Linear, GitHub, GitLab, Zapier, Slack, Notion, Dropbox, Netlify, Tailscale and more), but usage-based pricing pages and calculators can produce odd plan names. Even then, changes are still caught by the section text comparison, and unchanged pages produce no false alerts.
- Prices may vary by country, currency and A/B tests. Runs use Apify datacenter IPs (usually US). The confirmation re-read and version memory reduce, but cannot fully remove, variant noise.
- JavaScript-only pricing pages need `renderJs` and 1 GB memory. Sites that block automated access are skipped, never worked around.
- Freshness equals your schedule. Changes made and reverted between two runs are not seen.
- If the same watchlist is run twice at the same time, the runs can overwrite each other's baselines. Schedule one run per watchlist at a time.
- Baselines live in a named key-value store in your account until you delete it.

### FAQ

**How is this different from Visualping or Distill?** Those compare screenshots or text and alert on any difference. This Actor understands page types: it tells you which plan's price moved and by how much, which legal section changed, which roles opened, and it ignores dates, tokens and banners. It runs on your Apify schedule and is billed per check, with no monthly plan.

**Does the first run report anything?** No. It stores a baseline per page and outputs one `baseline` row per page (charged as a page check only).

**Can I get alerts in Slack?** Yes: set `webhookUrl` to a Slack incoming webhook and `webhookFormat` to `slack`. Only changes at or above `webhookMinSeverity` (default medium) are sent.

**Will it get my IP banned?** It sends roughly one request per page per run, with honest identification, robots.txt and delays. If a site answers 403 or 429, the Actor stops contacting it for the rest of the run.

**Is it real-time?** No. It checks when it runs. Use an Apify Schedule for the frequency you need.

### Fairquery family

Other Fairquery tools on the Apify Store. Each one returns structured rows with machine-readable error codes, and error rows are never charged:

- [Email Validator for Agents (MX, Disposable, Typo)](https://apify.com/fairquery/email-validator-mx-disposable): syntax, MX, disposable, role and typo checks without sending mail; $0.79 per 1,000 emails
- [Website Contact & Company Enricher](https://apify.com/fairquery/website-contact-company-enricher): domain in, public business emails, phones, socials and company details out; $3 per 1,000 domains
- [Website Tech, SEO & llms.txt Check for Agents](https://apify.com/fairquery/website-tech-seo-ai-readiness): tech stack, SEO score, AI-crawler rules and llms.txt per site; $1.50 per 1,000 sites
- [Sanctions & Watchlist Screener (OFAC, EU, UK, UN)](https://apify.com/fairquery/sanctions-watchlist-screener): fuzzy name screening against official sanctions lists; $3 per 1,000 names + $0.007 per run
- [LLM Data Guard: PII & Secret Redactor](https://apify.com/fairquery/llm-data-guard-pii-secrets): redact PII and secrets before text reaches an LLM; $1.50 per 1,000 documents + $0.20 per 1,000 findings
- [Dependency Vulnerability Check for Agents](https://apify.com/fairquery/osv-dependency-vulnerability-check): packages and lockfiles against OSV.dev; $0.50 per 1,000 packages + $1.50 per 1,000 vulnerabilities
- [EDGAR Company Facts & Filings for Agents](https://apify.com/fairquery/edgar-company-facts-filings): SEC filings and XBRL facts by ticker, CIK or name; from $1.50 per 1,000 companies
- [Company Jobs Feed: Greenhouse, Lever & Ashby](https://apify.com/fairquery/company-jobs-feed): live jobs from employers' own ATS boards with age and repost signals; $0.99 per 1,000 jobs + $0.0005 per board
- [AI Exposure Scanner: Public Secrets, Files & AI-Crawler Policy](https://apify.com/fairquery/ai-exposure-scanner): passive, scored scan of your own domains and public GitHub repos for leaked secrets (masked), exposed files, AI-crawler policy, security headers and SPF/DMARC; $0.039 per domain + $0.0039 per repo + $0.0019 per finding
- **MCP server** [`@fairquery/agent-trust-mcp`](https://www.npmjs.com/package/@fairquery/agent-trust-mcp): one MCP server for Claude Desktop, Cursor and VS Code (`npx -y @fairquery/agent-trust-mcp`). It has 7 free tools that need no token, plus paid tools that call the PII, email, sanctions and OSV Actors with your own Apify token.

All Fairquery Actors: https://apify.com/fairquery

### Disclaimer

Fairquery is not affiliated with any of the sites mentioned. Page content belongs to its owners. Use the Actor only for public pages and in line with the sites' terms.

# Actor input Schema

## `pages` (type: `array`):

Competitor pages to track. Each item: {"url": "https://example.com/pricing", "type": "pricing", "label": "Acme pricing"}. type is optional (auto-detected from the URL): pricing, terms, privacy, careers, changelog, blog, product (a sitemap URL or a site root = new/removed pages via sitemap.xml) or generic. A plain URL string also works.

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

Optional plain list of URLs. The page type is guessed from the path (/pricing, /terms, /privacy, /careers, /changelog, /blog, sitemap.xml); anything else is tracked as a generic page.

## `domains` (type: `array`):

Optional. For each domain (e.g. acme.com) the Actor reads the homepage links once (robots.txt respected) to find the page types below, falling back to at most 2 common paths per type. Results are cached for 30 days in your state store. Discovery requests are free.

## `pageTypes` (type: `array`):

Which pages to look for on each domain. product = new or removed pages in the site's sitemap.xml.

## `watchlistName` (type: `string`):

Snapshots are stored per watchlist. Use one name per scheduled task (e.g. 'saas-competitors'). The first run of a watchlist saves a baseline; later runs report changes against it.

## `maxPages` (type: `integer`):

Safety cap on pages checked in one run (explicit + discovered). Extra pages are listed as skipped, not charged.

## `minSeverity` (type: `string`):

Only changes at or above this level are written to the dataset (and charged). high = price changes, plans added/removed, sensitive legal edits; medium = notable text, product-page, hiring or keyword changes; low = minor edits, routine new posts.

## `careersKeywords` (type: `array`):

Words to count in job titles (e.g. 'machine learning', 'Kubernetes', 'Germany'). Count changes are reported; new roles matching a keyword raise severity to medium.

## `watchKeywords` (type: `array`):

Alert (medium) when one of these words appears on or disappears from a page, e.g. 'free trial', 'SOC 2', 'AI', 'per seat'. Whole-word, case-insensitive.

## `ignorePatterns` (type: `array`):

Optional regular expressions (max 20, 200 chars each) removed from text before comparing, for page-specific noise such as rotating promo banners. Timestamps, dates, relative times, tokens, cookie banners, navigation and footers are already ignored.

## `minChangeChars` (type: `integer`):

Text edits smaller than this (after noise filtering) are not reported on generic, product, careers and blog pages; small edits accumulate against the old snapshot until they cross it. Legal pages use 10, pricing uses structured price comparison.

## `includeDiff` (type: `boolean`):

Add the added/removed sentences per changed section to each change row.

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

Public HTTPS URL that receives one POST per run when there are changes at or above the webhook severity (Slack/Discord incoming webhooks, n8n, Make, Zapier, your API). Stored as a secret.

## `webhookFormat` (type: `string`):

json = {watchlist, count, text, changes\[]}; slack = {text}; discord = {content}.

## `webhookMinSeverity` (type: `string`):

Only changes at or above this level are sent to the webhook.

## `renderJs` (type: `string`):

never = plain HTTP only (cheapest). auto = render with a headless browser only when plain HTML has no readable prices (pricing pages) or almost no text (JavaScript-only pages); robots.txt still applies. always = render every page. Browser rendering needs a run with at least 1024 MB memory and is charged per rendered page.

## `confirmChanges` (type: `boolean`):

When a change is found on a plain HTML page, read the page once more (after the polite delay) and report only changes seen on both reads. Filters out A/B tests and rotating page variants. The extra read is not charged.

## `sitemapInclude` (type: `array`):

For product (sitemap) tracking: only URLs matching one of these patterns are tracked, e.g. '/features/', '/integrations/'. Empty = all URLs.

## `sitemapExclude` (type: `array`):

For product (sitemap) tracking: URLs matching these are ignored, e.g. '/blog/', '/de/'.

## `maxSitemapFiles` (type: `integer`):

Sitemap indexes can list many files; each file read counts as one page check. If the cap is hit, removed pages are not reported for that run.

## `requestDelaySecs` (type: `number`):

Politeness delay between requests to the same host (minimum 1 s). A longer robots.txt Crawl-delay always wins; sites asking for more than 30 s are skipped.

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

Named key-value store in your Apify account that keeps snapshots between runs (one store can hold many watchlists).

## `resetBaseline` (type: `boolean`):

Ignore stored snapshots and save a fresh baseline for every page in this run (no changes are reported).

## Actor input object example

```json
{
  "pages": [
    {
      "url": "https://basecamp.com/pricing",
      "type": "pricing",
      "label": "Basecamp pricing"
    },
    {
      "url": "https://linear.app/pricing",
      "type": "pricing",
      "label": "Linear pricing"
    },
    {
      "url": "https://github.com/site/terms",
      "type": "terms",
      "label": "GitHub terms"
    },
    {
      "url": "https://jobs.ashbyhq.com/linear",
      "type": "careers",
      "label": "Linear careers"
    }
  ],
  "pageTypes": [
    "pricing",
    "terms",
    "privacy",
    "careers",
    "changelog",
    "product"
  ],
  "watchlistName": "default",
  "maxPages": 50,
  "minSeverity": "low",
  "minChangeChars": 20,
  "includeDiff": true,
  "webhookFormat": "json",
  "webhookMinSeverity": "medium",
  "renderJs": "never",
  "confirmChanges": true,
  "maxSitemapFiles": 10,
  "requestDelaySecs": 1.5,
  "stateStoreName": "fairquery-competitor-tracker",
  "resetBaseline": 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 = {
    "pages": [
        {
            "url": "https://basecamp.com/pricing",
            "type": "pricing",
            "label": "Basecamp pricing"
        },
        {
            "url": "https://linear.app/pricing",
            "type": "pricing",
            "label": "Linear pricing"
        },
        {
            "url": "https://github.com/site/terms",
            "type": "terms",
            "label": "GitHub terms"
        },
        {
            "url": "https://jobs.ashbyhq.com/linear",
            "type": "careers",
            "label": "Linear careers"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fairquery/competitor-change-tracker").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 = { "pages": [
        {
            "url": "https://basecamp.com/pricing",
            "type": "pricing",
            "label": "Basecamp pricing",
        },
        {
            "url": "https://linear.app/pricing",
            "type": "pricing",
            "label": "Linear pricing",
        },
        {
            "url": "https://github.com/site/terms",
            "type": "terms",
            "label": "GitHub terms",
        },
        {
            "url": "https://jobs.ashbyhq.com/linear",
            "type": "careers",
            "label": "Linear careers",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("fairquery/competitor-change-tracker").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 '{
  "pages": [
    {
      "url": "https://basecamp.com/pricing",
      "type": "pricing",
      "label": "Basecamp pricing"
    },
    {
      "url": "https://linear.app/pricing",
      "type": "pricing",
      "label": "Linear pricing"
    },
    {
      "url": "https://github.com/site/terms",
      "type": "terms",
      "label": "GitHub terms"
    },
    {
      "url": "https://jobs.ashbyhq.com/linear",
      "type": "careers",
      "label": "Linear careers"
    }
  ]
}' |
apify call fairquery/competitor-change-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fairquery/competitor-change-tracker"
        }
    }
}
```

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/8mMQ8SURJyiRPjQ7R/builds/uTeury6wUDFoXdaPE/openapi.json
