SaaS Pricing Page Monitor avatar

SaaS Pricing Page Monitor

Pricing

from $3.00 / 1,000 results

Go to Apify Store
SaaS Pricing Page Monitor

SaaS Pricing Page Monitor

Extract structured pricing-plan data (name, price, currency, billing period, features) from any list of SaaS company URLs. Auto-discovers the pricing page from a homepage URL.

Pricing

from $3.00 / 1,000 results

Rating

0.0

(0)

Developer

Crawler Bros

Crawler Bros

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 days ago

Last modified

Share

Extract structured pricing-plan data — plan name, price, currency, billing period, and features — from any list of SaaS company URLs. Give a homepage (https://asana.com) or a direct pricing-page URL (https://github.com/pricing); the actor auto-discovers the pricing page and parses it. No auth, no proxy, works on any company site.

What this actor does

  • Auto-discovery — give a homepage URL and the actor tries common pricing paths (/pricing, /plans, /price, etc.) until one is found
  • Generic DOM extraction — no site-specific scraping code; works across arbitrary SaaS pricing pages by detecting price-shaped text and the nearest plan-name heading and feature list
  • Currency normalization$/US$/USDUSD, EUR, £GBP, ¥JPY, INR, and more
  • Billing period detection/mo, /month, /yr, /yearmonthly / yearly
  • Plan CTA linksplanUrl captures each plan's own signup/upgrade link when the page has one near that price (e.g. /create-account?tier=starter), resolved to an absolute URL
  • Custom-pricing tier detection — tiers with no listed price (e.g. an "Enterprise" card whose only CTA is "Contact Sales" / "Talk to Sales" / "Book a demo") are still emitted as a pricingPlan record with isCustomPricing: true and no price field, instead of being silently skipped
  • Company summaries — one record per company confirming whether a pricing page was found, even if zero plans were extracted
  • Price-change detection (enableChangeDetection) — persists a snapshot of each company's plans across runs and emits a priceChange record whenever a plan is new, removed, or has a different price/currency/billing period since the previous run — true "monitor" behavior when scheduled
  • Filters: price range, currency, billing period, keyword (plan name / features)
  • Empty fields are omitted — a plan with an undetectable name still includes its price rather than being dropped

Output records

Two recordTypes in the same dataset:

pricingPlan (one per detected plan)

  • companyDomain, pricingUrl
  • planName (omitted if it couldn't be reliably identified)
  • price, currencySymbol, currencyCode, billingPeriod (omitted for custom-pricing tiers, which have no listed price)
  • priceUnitperUser / perSeat / perMember / perAgent / perDevice / perHost / perLicense / perEditor, when the page states the price is per-user/seat/etc. (e.g. "$4 per user/month"); omitted for flat-rate plans since there's no reliable signal to positively assert "flat"
  • isPopular (bool) — true when the plan's card carries a "Most popular" / "Recommended" / "Best value" style badge
  • isCustomPricing (bool) — true when the tier has no listed price and instead shows a "Contact Sales"-style CTA (e.g. Enterprise tiers); such records omit price/currencySymbol/currencyCode/billingPeriod since there is no fixed price to report
  • features[] (up to 10 bullet points, if includeFeatures is on)
  • planUrl — the plan's own signup/CTA link ("Get started", "Buy now"), when the page has one near that price
  • ctaText — the visible text of that same signup/CTA button/link (e.g. "Start free trial", "Contact sales", "Buy now") — useful for spotting custom-pricing tiers ("Contact sales") vs. self-serve tiers ("Sign up")
  • sourceUrl, recordType: "pricingPlan", scrapedAt

companySummary (one per company checked)

  • companyDomain, checkedUrl, pricingUrl (if found)
  • pricingPageFound (bool), plansFound (count)
  • annualDiscountText / annualDiscountPercent — when the page carries a monthly/annual billing-toggle badge (e.g. "Save up to 18%"), the raw badge text and parsed percentage; omitted when the page has no such toggle (most pages don't)
  • sourceUrl, recordType: "companySummary", scrapedAt

priceChange (only when enableChangeDetection is on, and only after a baseline run exists)

  • companyDomain, changeTypenew / removed / priceIncreased / priceDecreased / currencyChanged / billingPeriodChanged
  • planName
  • oldPrice, newPrice, priceDelta — for price changes
  • oldCurrencyCode, newCurrencyCode — for currency changes
  • oldBillingPeriod, newBillingPeriod — for billing-period changes
  • sourceUrl, recordType: "priceChange", scrapedAt

Input

FieldTypeDefaultDescription
companyUrlsarray3 example companiesHomepage or pricing-page URLs
minPrice / maxPricenumberPrice bounds (in the plan's own currency)
currencystringanyRestrict to USD/EUR/GBP/JPY/INR/CAD/AUD
billingPeriodstringanyRestrict to monthly or yearly plans
keywordstringFilter on plan name / features
includeFeaturesbooltrueInclude extracted feature bullet points
includeCompanySummarybooltrueEmit a summary record per company
enableChangeDetectionboolfalseEmit priceChange records vs. the previous run's snapshot (persisted per Apify account)
maxPlansPerCompanyint20Cap distinct plans per company
maxItemsint200Hard cap on total records

Example: monitor competitor pricing

{
"companyUrls": [
"https://asana.com",
"https://github.com/pricing",
"https://www.dropbox.com/plans"
],
"maxPlansPerCompany": 10
}

Example: find all plans under $20/mo across a watchlist

{
"companyUrls": ["https://asana.com", "https://slack.com/pricing"],
"maxPrice": 20,
"currency": "USD"
}

Use cases

  • Competitive pricing intelligence — track how competitors price their plans over time (run daily/weekly)
  • Market research — build a comparison dataset of pricing across a category of SaaS tools
  • Sales enablement — quickly pull competitor plan/feature breakdowns
  • Investment/analyst research — monitor pricing changes across a portfolio of companies

FAQ

Does this need login or API keys? No — it fetches each company's public pricing page directly.

What if a company's pricing page isn't at /pricing? The actor tries /pricing, /pricing/, /plans, /plans/, /price, /en/pricing, and /pricing.html. If none of these are found, a companySummary record with pricingPageFound: false is still emitted so you know the company was checked.

Why does a company with a real pricing page sometimes show plansFound: 0? A minority of pricing pages mark up their plan titles with plain <div>/<span> text instead of a semantic heading tag (<h1><h4>), so the plan-name heading isn't identifiable from the server-rendered HTML alone. Since the actor never guesses or fabricates a plan name, it skips a price it can't confidently attach to a plan rather than emitting a nameless/misleading record — the companySummary record still confirms the pricing page itself was found.

Why do some features entries include stray words like "check icon"? Feature lists are extracted from <li> elements as visible text, which occasionally includes an icon's alt/label text prepended by the site's own markup. This is the real text present in that list item — the actor never invents feature text — but it isn't always perfectly clean.

Is this reliable across arbitrary company sites? The extractor is a generic heuristic (price-shaped text + nearest heading + nearby list items), not a per-site scraper, so quality varies by how a given site structures its pricing page. It's designed to fail soft — a mismatched page always produces a companySummary record, and per-plan records never contain fabricated data.

Why doesn't a company's "Enterprise / Contact Sales" tier always show up? The custom-pricing extraction pass looks for a "Contact Sales"-style CTA sitting right next to a short plan-name heading with no price nearby. A minority of sites serve a slightly reduced page (missing that section entirely) to non-browser/datacenter network origins even though they return a normal 200 — that's upstream content variance, not a parsing failure; when the section is present in the fetched HTML, it's extracted correctly.

Why does a company I know has a pricing page still show pricingPageFound: false? Two cases: (1) the site returns its pricing content only via client-side JavaScript with no plan/price markup in the initial HTML the actor fetches, or (2) the site's anti-bot layer returns a 403/challenge page to non-browser requests. Both are upstream site behavior the actor can't override without a full browser + proxy (which this actor intentionally avoids to keep runs fast and free of proxy costs) — the companySummary record still tells you definitively that the company was checked and found nothing.

How current is the data? Real-time — every run fetches each pricing page live.

How does enableChangeDetection work across runs? Each run saves a snapshot of every plan it extracted (price, currency, billing period) to a persistent, named key-value store scoped to your Apify account — it isn't tied to a single run or schedule. The next time you run the actor (manually or on a schedule) with enableChangeDetection: true, it loads that snapshot, diffs it against the freshly extracted plans, and emits a priceChange record for anything that's different. The first run for a given company only writes the baseline; there's nothing to compare yet, so no priceChange records are emitted that first time.