---
title: Competitor Pricing Monitor | Tabstack
description: 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.

- [TypeScript](#tab-panel-6)
- [Python](#tab-panel-7)

```
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 os
import json
from 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

- **Consistent schema across URLs.** The same `json_schema` is 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_usd` to `["number", "null"]` handles “Contact us” or enterprise tiers without breaking the schema contract.
- **Parallel extraction.** The TypeScript version uses `Promise.all` to fetch all URLs concurrently. The Python version runs sequentially — wrap with `asyncio` or a thread pool if latency matters.
- **No cached data.** `nocache: true` forces a fresh fetch on every run. Drop it if you’re running this frequently and the underlying pages update slowly.

## Installation

- [TypeScript](#tab-panel-8)
- [Python](#tab-panel-9)

Terminal window

```
npm install @tabstack/sdk
```

Terminal window

```
pip install tabstack
```

Set your API key before running:

Terminal window

```
export TABSTACK_API_KEY=your_api_key
```
