Competitor Pricing Monitor
Extract structured pricing from multiple competitor pages in parallel. Shows how to use /extract/json with a consistent schema across a list of URLs.
Pull current pricing from a list of competitor URLs and normalize it into a consistent schema. Useful for agents that make decisions based on market positioning, or pipelines that track pricing drift over time. Using nocache: true ensures you’re always fetching live data, not a cached snapshot.
import Tabstack from "@tabstack/sdk";
const client = new Tabstack();
const competitors = [ "https://competitor-a.com/pricing", "https://competitor-b.com/pricing", "https://competitor-c.com/pricing",];
type Plan = { name: string; price_usd: number | null; billing_cycle: "monthly" | "annual" | "one-time" | "unknown"; has_free_tier: boolean;};
const pricingSchema = { type: "object", properties: { plans: { type: "array", items: { type: "object", properties: { name: { type: "string" }, price_usd: { type: ["number", "null"], description: "Monthly price in USD. Null if custom or contact-only.", }, billing_cycle: { type: "string", enum: ["monthly", "annual", "one-time", "unknown"], }, has_free_tier: { type: "boolean" }, }, required: ["name", "price_usd", "billing_cycle", "has_free_tier"], }, }, }, required: ["plans"],};
async function monitorPricing() { const results = await Promise.all( competitors.map(async (url) => { try { // The SDK returns `Record<string, unknown>` for extract.json -- cast // to the shape declared by your schema so downstream code is typed. const data = (await client.extract.json({ url, json_schema: pricingSchema, effort: "standard", nocache: true, })) as { plans?: Plan[] }; return { url, plans: data.plans ?? [] }; } catch (err) { console.error(`Failed to extract ${url}:`, err); return { url, plans: null, error: String(err) }; } }) );
console.log(JSON.stringify(results, null, 2));}
monitorPricing();import osimport jsonfrom tabstack import Tabstack
client = Tabstack(api_key=os.environ["TABSTACK_API_KEY"])
competitors = [ "https://competitor-a.com/pricing", "https://competitor-b.com/pricing", "https://competitor-c.com/pricing",]
pricing_schema = { "type": "object", "properties": { "plans": { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string"}, "price_usd": { "type": ["number", "null"], "description": "Monthly price in USD. Null if custom or contact-only.", }, "billing_cycle": { "type": "string", "enum": ["monthly", "annual", "one-time", "unknown"], }, "has_free_tier": {"type": "boolean"}, }, "required": ["name", "price_usd", "billing_cycle", "has_free_tier"], }, } }, "required": ["plans"],}
results = []for url in competitors: try: data = client.extract.json( url=url, json_schema=pricing_schema, effort="standard", nocache=True, ) results.append({"url": url, "plans": data["plans"]}) except Exception as err: print(f"Failed to extract {url}: {err}") results.append({"url": url, "plans": None, "error": str(err)})
print(json.dumps(results, indent=2))How it works
Section titled “How it works”- Consistent schema across URLs. The same
json_schemais applied to every competitor page. Tabstack normalizes the output regardless of how each site structures its pricing table. - Nullable prices for custom plans. Setting
price_usdto["number", "null"]handles “Contact us” or enterprise tiers without breaking the schema contract. - Parallel extraction. The TypeScript version uses
Promise.allto fetch all URLs concurrently. The Python version runs sequentially — wrap withasyncioor a thread pool if latency matters. - No cached data.
nocache: trueforces a fresh fetch on every run. Drop it if you’re running this frequently and the underlying pages update slowly.
Installation
Section titled “Installation”npm install @tabstack/sdkpip install tabstackSet your API key before running:
export TABSTACK_API_KEY=your_api_key