Blog/Product
12 min read

Extract Pricing and Free Trial Information From Websites

One endpoint returned four plans, numeric amounts and a free_trial_available flag. It also disclosed that a model read the page, which changes everything.

Extract Pricing and Free Trial Information From Websites

Copy this line to your agent to pull structured pricing from any SaaS page.

set up https://monid.ai/SKILL.md and use api.strale.io /x402/pricing-page-extract on a pricing page

Pricing pages are the worst-structured important pages on the web. Every one is a bespoke layout of cards and toggles, the numbers move when you switch to annual, and the tier that matters most says "Contact us". So the interesting question is not whether something can read one, it is what you get back and how much you can trust it. This guide measures three routes on 2026-09-02 and lands on a distinction most write-ups skip, running through Monid, the OpenRouter for agent tools.

What does a pricing extractor actually return?

More than a price list, and the extra fields are the ones that answer the question people are actually asking.

The plan array

api.strale.io/x402/pricing-page-extract pointed at a real SaaS pricing page returned four plans, each shaped like this:

{
  "name": "Plus",
  "price": "$10 per member / month",
  "price_amount": 10,
  "currency": "USD",
  "billing_period": "monthly",
  "features": ["Everything in Free", "Custom forms", "Unlimited charts", "..."],
  "highlighted": false
}

Both the formatted string and a numeric price_amount. That sounds minor and it removes the single most annoying step in this work, because pricing pages render money as text and every project that skips this ends up writing a regex for dollar signs and commas.

The fields that answer the actual query

Above the plan array, the response carried a set of page-level booleans:

{
  "free_trial_available": true,
  "free_tier_available": true,
  "money_back_guarantee": false,
  "annual_discount": "Save up to 20% with yearly",
  "pricing_model": "per-seat",
  "enterprise_cta": true
}

free_trial_available is the keyword, returned as a field. So is the distinction between a free trial and a free tier, which people conflate constantly and which matters a lot if you are building a comparison. pricing_model: "per-seat" is the field that tells you whether comparing two headline numbers is even meaningful.

What that means in practice

You can answer "which of these 200 tools offer a free trial without a credit card" without reading a single page yourself, and the shape of the answer is a filter rather than a research project.

📖 See also Pay-Per-Call Data API vs Subscription

Why does it matter that a model read the page?

Because the response told us it did, and that single field changes how you should treat every number in it.

The disclosure

Alongside the data, the response carried:

"_meta": {
  "capability": "pricing-page-extract",
  "latency_ms": 5729,
  "provenance": { "source": "claude-haiku", "fetched_at": "2026-09-02T21:41:28.878Z" }
}

source: "claude-haiku". The extraction was done by a language model reading the page, not by selectors matching a known layout.

Why that is the right design

Because pricing pages have no common structure. A selector-based parser needs a rule per site and breaks whenever a site redesigns, which is the maintenance treadmill described in the XPath guide. A model reads any layout, including ones nobody wrote a rule for, and that is the only approach that scales past a handful of tracked competitors.

Why it changes your verification

It is non-deterministic. The same page can produce slightly different output on two runs, a plan can be missed, and a footnote can be read as a feature. Nothing errors when that happens, because a plausible answer is exactly what the system is built to produce.

So verify against something the model cannot invent:

# a plan that lost its number is the failure to catch
suspicious = [p for p in plans if p.get("price_amount") is None
              and "contact" not in (p.get("price") or "").lower()]

# and a page that lost a whole tier
if len(plans) < last_seen_plan_count[url]:
    flag_for_review(url)

Plan count is the strongest cheap signal. Products add tiers occasionally and lose them rarely, so a drop from four plans to two is far more likely to be an extraction miss than a repricing.

The general point

An endpoint that tells you a model produced its output is doing you a favour. The ones to worry about are the ones doing the same thing without saying so, where you have no reason to add the check at all. We made the same argument about provenance metadata in the fundamentals guide: the field describing the data is often worth more than another field of data.

How do you build a pricing tracker?

Three steps. Discovery is free.

For agents

Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:

set up https://monid.ai/SKILL.md

It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.

For humans

npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main

Step 1. Extract the pricing page

What it does. Turns any pricing page into plans plus the page-level flags above.

The endpoints. api.strale.io/x402/pricing-page-extract, billed per call and the most expensive step here, because a model reads the page.

The call.

monid run -p api.strale.io -e /x402/pricing-page-extract --query '{"url": "https://example.com/pricing"}'

What comes back. The structure above, in about six seconds by the latency_ms field. That latency is worth planning around: this is not an endpoint you call inside a request handler.

What it costs. Per call, and materially more than a plain fetch. Current figures at monid.ai/tools.

Step 2. Cross-check against a review-site catalogue

What it does. Gives you a second, independently maintained view of the same product.

The endpoints. getapp/get_software_pricing and g2/get_product_pricing, per call.

The call.

monid run -p getapp -e /get_software_pricing --query '{"slug": "collaboration-software/a/monday-com"}'

The parameter is a category-and-product path, not a product name. Passing product returns a 422 whose message includes a worked example of the slug format, which is a small thing that saves a real amount of time.

What comes back. A different model of the same idea:

{
  "categories": ["free-trial", "free", "subscription"],
  "currency": "USD",
  "amount": "9",
  "periodicity": "month",
  "pricing_model": "Per User",
  "no_credit_card_required": true,
  "plans": [{ "name": "Free", "attributes": ["Up to 3 boards", "..."], "priceType": 1 }]
}

Note no_credit_card_required, which the pricing page itself often does not state, and priceType and paymentFrequency as enum integers rather than labels. Two sources, two vocabularies, same subject.

What it costs. A cent or two per call.

Step 3. Handle the block, because you will get one

What it does. Keeps a scheduled job honest.

The call. On 2026-09-02 the G2 endpoint returned:

{
  "error": "Service temporarily unavailable - site protection blocking all proxies",
  "block_type": "retryable_protection",
  "attempts": 3,
  "retry_after": 60,
  "status": "blocked"
}

This is the good kind of failure and worth pausing on. It says what happened, that it already retried three times on your behalf, and when to come back. Compare that with a 200 carrying an empty plan array, which reads as "this product has no pricing" and quietly writes a wrong row. Honour retry_after, and treat block_type: "retryable_protection" as "try later", not as "this product has no data".

Give this to your agent

$Set up https://monid.ai/SKILL.md, and then use Monid to pull pricing for these 25 competitor URLs, and give me a table of plan count, entry price, whether there is a free trial, and which ones failed.

📖 See also Datacenter Proxies vs Residential: What Each One Actually Fixes

Should you read the pricing page or a review site?

Different sources, different failure modes, and the answer depends on which way you would rather be wrong.

The pricing page is current and narrow

It is the vendor's own statement of today's prices, which makes it authoritative and immediate. It is also only what the vendor chose to publish, it changes without notice, and the tier that matters to an enterprise buyer says "Contact us".

The review site is stale and comparable

A catalogue entry is maintained by a third party and lags real changes, sometimes by months. What it adds is normalisation: the same vocabulary across thousands of products, plus fields the vendor does not publish, like whether a card is required.

Where they disagree

They will, and the disagreement is usually informative rather than a bug. A review site showing an older, lower entry price is a signal that the vendor raised prices recently, which is exactly the event a competitive tracker exists to catch. Store both with timestamps rather than picking a winner.

The practical split

Use the pricing page for anything time-sensitive: alerts, launch monitoring, "did they change the entry tier this week". Use the catalogue for breadth: building a market map across hundreds of products where per-page extraction would be slow and expensive. This is the same reasoning as the search-then-enrich pattern in the Amazon guide: use the cheap wide source to choose, and the expensive precise one on what survived.

Which endpoint should I use for which job?

EndpointWhat it doesInputOutputBest forBilling
api.strale.io/x402/pricing-page-extractReads any pricing pageA URLPlans, numeric amounts, trial and tier flagsCurrent prices, any sitePer call
getapp/get_software_pricingCatalogue entryCategory and product slugNormalised plans, card-required flagBreadth across a marketPer call
g2/get_product_pricingCatalogue entry, second sourceProduct slugPlans and featuresCross-checkingPer call
capterra/get_category_productsProducts in a categoryCategoryProduct listBuilding the target listPer call
context.dev/web/scrape/markdownThe raw pageA URLMarkdownDebugging what was servedPer call

Every row was verified with monid inspect on 2026-09-02. The table gives billing shape rather than figures; shape drives design and current numbers live on monid.ai/tools.

The last row is the debugging tool. When an extraction returns two plans and you expected four, fetching the raw page tells you in one call whether the missing tiers are behind a toggle, rendered by script, or genuinely gone. Those have different fixes and guessing between them wastes an afternoon.

When is this the wrong approach?

Three cases.

The number you need is negotiated. Enterprise pricing is not on the page, and enterprise_cta: true is the endpoint telling you so honestly. No extractor produces a figure that only exists in a quote.

You need historical prices you did not collect. Nothing reconstructs what a page said last quarter. If price history matters, the answer is to start recording today rather than to find a source, and the value of that archive compounds from the first run.

One competitor, watched closely. For a handful of pages, a change-detection service and your own eyes are cheaper and better than a pipeline. This approach earns its keep at tens or hundreds of products, and the crossover arithmetic is the same one worked through in the phone validation cost breakdown.

And the disclosure: this is Monid's blog and we sell per-call access to all of the endpoints above, including the one that was blocked today. We also do not publish our own figures in articles because they change; the live numbers are on the tools pages, which is the honest place for a number that moves.

Conclusion

The good news is that extracting pricing is a solved problem: one call returned four plans with numeric amounts, and answered the free-trial half of the question with a literal boolean.

The thing to actually take away is the provenance field saying a model read the page. That makes the endpoint work on layouts nobody wrote a rule for, and it makes the output non-deterministic in a way that produces plausible wrong answers rather than errors. So track plan count per URL and alert when it drops. That check costs nothing and it is the difference between a tracker you trust and a table nobody checks.

And when a source blocks you, read the shape of the block. Today's G2 response said it retried three times and to come back in sixty seconds. That is a source being straight with you, and it deserves a retry rather than a null row.

Free next step: run monid discover -q "pricing page plans free trial" and read the field lists. Discovery is free, and seeing free_trial_available sitting there as a boolean usually shortens the plan you had in mind. Start at monid.ai.

FAQ

Is it legal to monitor competitor pricing?

Reading published prices is ordinary competitive research and prices are among the most public facts a company produces. The care needed is in how you collect rather than whether you may look: respect the site's terms, do not hammer a page on a tight loop, and remember that a pricing page can carry terms distinct from the site's general ones. The genuinely risky move is coordinating prices with a competitor, which is an antitrust problem and has nothing to do with scraping. Observing a public price is not that.

How do you handle enterprise tiers that say contact us?

Treat the absence as data rather than a gap. The response above carried enterprise_cta: true, which lets you record that a hidden tier exists without inventing a number for it. A common mistake is defaulting such tiers to null and then averaging across plans, which silently biases every comparison towards vendors who publish everything. Model it as its own state: published, hidden, or absent. If you need actual enterprise figures, procurement benchmarks and customer conversations are the source, not the web.

How do you deal with regional pricing and currency?

The same page frequently serves different prices depending on where the request came from, which is the exit-geography effect described in the proxy guide. The response carried an explicit currency field for exactly this reason, so store it alongside every amount and never compare bare numbers across records. If you need a specific market's prices, that is a real reason to control exit location, and if you do not, at least record which currency you were shown so a mixed dataset is detectable later.

How often should you re-check a pricing page?

Weekly is right for most competitive tracking, because pricing changes are rare, announced, and rarely urgent to know within hours. Daily is justified when a launch or a repricing is expected, and for a small watchlist rather than a whole market. What matters more than cadence is running every product on the same schedule, since a mixed refresh produces a comparison table where some rows are current and others are two months old, and nothing on the table shows you which is which. Stamp every row with the time it was fetched, as in the enrichment refresh guidance.

Last updated September 2026.

pricing page extractioncompetitor pricingfree trial datasaas pricingllm extraction