SaaS Pricing Page Monitor
Pricing
from $3.00 / 1,000 results
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
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$/USD→USD,€→EUR,£→GBP,¥→JPY,₹→INR, and more - Billing period detection —
/mo,/month,/yr,/year→monthly/yearly - Plan CTA links —
planUrlcaptures 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
pricingPlanrecord withisCustomPricing: trueand nopricefield, 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 apriceChangerecord 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,pricingUrlplanName(omitted if it couldn't be reliably identified)price,currencySymbol,currencyCode,billingPeriod(omitted for custom-pricing tiers, which have no listed price)priceUnit—perUser/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 badgeisCustomPricing(bool) — true when the tier has no listed price and instead shows a "Contact Sales"-style CTA (e.g. Enterprise tiers); such records omitprice/currencySymbol/currencyCode/billingPeriodsince there is no fixed price to reportfeatures[](up to 10 bullet points, ifincludeFeaturesis on)planUrl— the plan's own signup/CTA link ("Get started", "Buy now"), when the page has one near that pricectaText— 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,changeType—new/removed/priceIncreased/priceDecreased/currencyChanged/billingPeriodChangedplanNameoldPrice,newPrice,priceDelta— for price changesoldCurrencyCode,newCurrencyCode— for currency changesoldBillingPeriod,newBillingPeriod— for billing-period changessourceUrl,recordType: "priceChange",scrapedAt
Input
| Field | Type | Default | Description |
|---|---|---|---|
companyUrls | array | 3 example companies | Homepage or pricing-page URLs |
minPrice / maxPrice | number | – | Price bounds (in the plan's own currency) |
currency | string | any | Restrict to USD/EUR/GBP/JPY/INR/CAD/AUD |
billingPeriod | string | any | Restrict to monthly or yearly plans |
keyword | string | – | Filter on plan name / features |
includeFeatures | bool | true | Include extracted feature bullet points |
includeCompanySummary | bool | true | Emit a summary record per company |
enableChangeDetection | bool | false | Emit priceChange records vs. the previous run's snapshot (persisted per Apify account) |
maxPlansPerCompany | int | 20 | Cap distinct plans per company |
maxItems | int | 200 | Hard 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.