Structured Data Extraction LLM: Building a Price-Monitoring Agent
A practical walkthrough for building a price-monitoring agent with structured data extraction llm calls — schema design, scheduling, diffing, and alerting.
Published September 14, 2026
Tracking ten competitors' pricing pages by hand means someone opens ten tabs, on some cadence, and eyeballs whether anything changed since last time — a task that's tedious enough that it gets skipped the week things are busy, which is exactly when a competitor is most likely to have quietly changed a price. A structured data extraction llm call replaces the eyeballing with a repeatable pull of the specific fields that matter, on a schedule, whether or not anyone remembers to check. This is a practical walkthrough of what that actually looks like built end to end: the schema, the extraction call, the diffing logic that turns a raw result into an alert, and where the edges of this approach are.
Why a structured data extraction llm call is the right building block here
The core building block this whole system rests on is a single, repeatable call: point a structured data extraction llm at a URL and a schema, and get back the specific fields you asked for, mapped consistently regardless of how that particular page happens to be built. That consistency is what makes the rest of this system possible to build at all — a diffing step needs a stable shape to compare, and a stable shape is exactly what a schema-driven extraction call guarantees, page after page, competitor after competitor.
What the agent actually needs to do
Break the task down into what it's really asking for, separate from any specific tool: for each competitor, on some schedule, pull the same set of fields — plan name, price, billing period, included limits — from that competitor's pricing page, compare the result against what was pulled last time, and flag anything that changed. Framed this way, there are three genuinely separate pieces: the extraction step that turns a page into structured fields, the storage step that remembers what was pulled last time, and the diffing step that decides whether a difference is worth surfacing. Conflating these three into one undifferentiated "scrape the page" step is where most home-grown monitoring scripts get fragile — each piece has a different failure mode, and separating them makes each one easier to get right.
Designing the extraction schema
The schema is the part of this system worth spending the most thought on, because it's what turns an arbitrary page into something comparable across runs and across competitors. A reasonable starting schema for this use case:
{
"plans": [
{
"plan_name": "string",
"price": "number",
"billing_period": "string",
"included_limits": "string"
}
]
}
This shape holds up across competitors with very different page designs — one might list plans as cards in a row, another as rows in a table, another as a single scrolling page with anchor links — because a structured data extraction llm call maps content to the fields you asked for based on what it means, not where it sits in the page's markup. The schema doesn't need to change per competitor; only the URL does. This is the same property covered in more architectural depth in why meaning-based extraction survives a layout change — the relevant point for this build specifically is that it lets one schema serve every competitor in the list, rather than needing bespoke field-mapping logic per page.
The extraction call itself
With NeuralVerge's AI extraction capability, pulling one competitor's page against the schema above looks like this:
curl https://api.neuralverge.ai/v1/extract \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://competitor.example.com/pricing",
"schema": {
"plans": [
{
"plan_name": "string",
"price": "number",
"billing_period": "string",
"included_limits": "string"
}
]
}
}'
The response comes back as structured JSON matching the schema — the same shape regardless of which competitor's page was the target, ready to hand to a diffing step without any per-page parsing logic. Run once per competitor per scheduled interval, this single call is the entire extraction half of the system; the rest of the build is what happens to the result afterward.
Storage: remembering what changed
A single extraction result is only useful relative to the previous one — "plan X costs $49/month" means nothing to a monitoring system without something to compare it against. The storage piece needs to keep, at minimum, the most recent successful extraction result per competitor, keyed by URL or competitor name, so each new run has something to diff against. This doesn't need to be elaborate: a row per competitor in a database, holding the last extracted JSON and a timestamp, is enough for most implementations of this pattern. What matters is that storage and extraction stay separate concerns — the extraction call doesn't know or care what happened last time, and shouldn't; that comparison belongs entirely to the diffing step.
Diffing: turning a result into a decision
Diffing is where most of the actual judgment in this system lives, and it's also where a naive implementation generates the most noise. Comparing the raw extracted JSON field by field, and flagging any difference, catches genuine price changes — but it also catches a plan's included_limits text being reworded without the actual limit changing, or a plan_name capitalization tweak that means nothing. The fix isn't a smarter extraction call; it's diffing logic that's deliberate about which fields matter enough to alert on. A price change is almost always worth flagging. A included_limits change is worth flagging if the underlying number changed, but not if it's the same limit described in slightly different words — which argues for normalizing that field (extracting a number where possible, not just free text) rather than diffing it as an opaque string.
A second, quieter source of noise: a competitor reordering plans on the page, which changes array order without changing anything about any individual plan. Diffing by array position instead of by plan_name as a key turns a harmless reorder into a wall of false-positive changes. Keying the comparison by plan name, and diffing each plan's fields independently, avoids this specific trap.
Alerting: what actually deserves a notification
Not every detected change deserves the same response. A price increase on a competitor's flagship plan is worth an immediate notification to whoever owns competitive positioning. A wording change in a limits description, even a genuine one, might be worth logging but not paging anyone. Structuring the alerting step around severity — rather than firing the same notification for every detected diff — keeps the system useful instead of becoming another feed that gets muted after the second false alarm. A reasonable default: alert immediately on any price field change, batch and summarize everything else into a periodic digest.
Scheduling: how often is often enough
Most B2B pricing pages change infrequently — a handful of times a year, not daily — which makes a daily run a reasonable default for most competitors in a monitoring list. Running more frequently than prices actually change doesn't catch anything sooner in expectation; it mostly adds cost without adding signal. The exception is a competitor known to run frequent promotions or time-limited pricing, where an hourly or even more frequent cadence for that specific competitor, while keeping the rest of the list on a slower schedule, matches effort to how often each individual source actually changes rather than applying one blanket interval to a list of sources with very different real change rates.
Where a fixed pipeline hits its limit
Everything described so far is a fixed pipeline: a known list of competitor URLs, run on a schedule, against a schema that doesn't change. This is the right shape for the common case — a stable list of known competitors — but it has a specific limit: it can't discover a new pricing page a competitor just published, or a new competitor that entered the list's blind spot, because nothing in a fixed pipeline is looking for pages it doesn't already know about. Handling that case is a genuinely different problem — either a periodic crawl of each competitor's site looking for new pricing-shaped pages, or an agent with its own reasoning about what to check next, reaching for the same underlying extraction capability as an MCP tool when it decides a new page is worth pulling. The distinction between a fixed pipeline calling a capability directly and an agent discovering when to reach for it applies directly here: the extraction call itself doesn't change between the two, only who's deciding when a new URL gets added to the list.
What this actually costs to run
Cost is worth pricing out concretely rather than assuming it's negligible or assuming it's prohibitive — both instincts are common and both are usually wrong without doing the arithmetic. A single AI extraction call costs 5 points, which works out to $0.005 per call on NeuralVerge's flat $0.001-per-point pricing across every plan tier. Monitoring twenty competitors on a daily schedule is twenty calls a day, or roughly six hundred a month — about $3 in extraction cost for the entire month, before counting the (typically much larger) engineering time saved by not having anyone check pages by hand. At this cost, the constraint on how many competitors to track or how often to check them is rarely the per-call price; it's more often how much alerting noise a team can actually absorb, which is the reason the diffing and severity logic above matters more to get right than the raw call volume.
This also means the temptation to run every competitor hourly "just in case" is usually not worth the marginal cost it avoids — the cost stays low either way, but an hourly cadence on a page that only changes a few times a year multiplies the number of near-identical extraction results storage has to hold and compare against, for no additional signal. Matching cadence to how often a specific source actually changes, as covered above, is a better lever than uniformly increasing frequency across the whole list.
A worked example: one price change, start to finish
Take a concrete, illustrative case: a competitor quietly raises their mid-tier plan from $49/month to $59/month, with no announcement.
The next scheduled run extracts the current page against the standing schema and gets back {"plan_name": "Growth", "price": 59, ...} alongside the unchanged fields for the competitor's other plans. The diffing step compares this against the stored result from the previous run, finds price changed from 49 to 59 on the plan keyed "Growth", and — because a price field change is configured to alert immediately — fires a notification naming the specific plan, the old price, and the new one. The new result is written to storage, becoming the baseline the next run will diff against. Nobody had to notice the change by eye, and the alert fired the same day the price actually changed rather than whenever someone next happened to check that competitor's page manually.
Where teams use this pattern
- —Competitive pricing intelligence. The exact use case above — tracking a known list of competitors' pricing pages for changes that affect positioning or sales conversations.
- —Feature and plan-limit tracking. The same schema-and-diff pattern applied to feature lists or usage limits instead of price, catching a competitor quietly tightening or loosening what a plan includes.
- —Job posting monitoring. Applying the same pattern to a competitor's careers page — tracking new postings as a signal of hiring focus or expansion plans.
- —Inventory or availability tracking. The same shape again, applied to a product or SKU's listed availability rather than a subscription plan's price.
What to check before building this
- —Is the field you're diffing normalized, or is it free text? A number extracted as a number diffs cleanly; the same value embedded in a sentence invites false positives from rewording alone.
- —Does the extraction call return an honest empty result when a field genuinely isn't there, or does it guess? Verify this directly — a schema-driven call that fabricates a plausible-looking value on a page that changed structurally will corrupt the diff silently.
- —Is diffing keyed by a stable identifier, like plan name, rather than array position? A reorder without a real change shouldn't read as N changes.
- —Does alerting distinguish severity, or does every diff fire the same notification? An undifferentiated alert stream gets muted after the first few false alarms, defeating the point of building this at all.
- —Is the competitor list itself static, or does it need to grow over time? A fixed list is simplest to build; a list that needs to discover new competitors or new pages is a genuinely different, harder problem layered on top.
Running this against two or three real competitor pages for a couple of scheduled cycles — before wiring up alerting for the full list — is the fastest way to see whether the schema and diffing logic actually hold up against how those specific pages are built.
Frequently asked questions
Do I need a separate schema for every competitor's pricing page?
No — a schema-driven structured data extraction llm call maps to the fields you ask for regardless of how a specific page is laid out, so one schema (plan name, price, billing period, included limits) applies across pages with completely different designs.
How often should a price-monitoring agent actually run?
Daily is a reasonable default for most B2B pricing pages, which change infrequently. A faster-moving market, or a competitor known to run frequent promotions, can justify running more often — the right cadence is set by how often prices actually change, not by how cheap it is to run more frequently.
What's the biggest source of false-positive alerts in a system like this?
Cosmetic page changes that don't touch the fields you actually care about — a redesign, a new testimonial section, reordered plan cards. Diffing on the extracted fields specifically, rather than the page's raw content, avoids alerting on changes that don't matter.
Can this agent discover a new pricing page on its own, without being told about it?
Not by default — a fixed list of URLs is a pipeline decision, made once. Discovering a new page a competitor just published is a different, harder problem that needs either a periodic crawl of the competitor's site or an agent reasoning over what pages exist, not the extraction call itself.
What happens if a competitor blocks or restructures a page entirely?
A well-behaved extraction call returns an empty or partial result rather than a plausible guess — which is itself a signal worth alerting on, since a page returning nothing usually means something changed on the source side that's worth a human looking at directly.
About NeuralVerge
NeuralVerge gives developers and AI builders a single API for AI deep research, AI extraction, and autonomous agents — powered by 29 data sources under the hood.
AI Extraction on the NeuralVerge blog.
Try it on your own data
One request format across research, extraction, and enrichment.