---
title: Competitor Briefing Agent | Tabstack
description: Extract structured pricing from a competitor's page and research their recent announcements. A complete working example combining /extract/json and /research.
---

Pull a complete competitor profile: schema-validated pricing extracted directly from their page, plus recent signals synthesized from across the web. In TypeScript, both calls run in parallel.

---

## What you get back

```
{
  pricing: {
    plans: [
      { name: 'Starter', price: 29, features: ['5 seats', '10k API calls/mo'] },
      { name: 'Pro', price: 99, features: ['25 seats', '100k API calls/mo', 'priority support'] },
      { name: 'Enterprise', price: null, features: ['custom seats', 'SLA'] },
    ]
  },
  recentSignals: "Acme Corp launched a batch processing API in Q1 2026...",
  sources: [
    { title: "Acme Corp Blog: Q1 Updates", url: "https://blog.acmecorp.example/q1-2026" },
    // ...
  ],
  retrievedAt: "2026-04-15T12:00:00.000Z"
}
```

`pricing` comes from schema-validated extraction on the pricing page. `recentSignals` and `sources` come from multi-source research. Neither requires prompt engineering or manual parsing.

> **Note:** Replace `https://acmecorp.example/pricing` with your competitor’s real pricing page.

---

## Complete example

- [TypeScript](#tab-panel-0)
- [Python](#tab-panel-1)

```
import Tabstack, { RateLimitError, AuthenticationError } from '@tabstack/sdk'


const client = new Tabstack()


interface PricingPlan {
  name: string
  price: number | null
  features: string[]
}


interface CompetitorBriefing {
  pricing: { plans: PricingPlan[] }
  recentSignals: string
  sources: Array<{ title: string; url: string }>
  retrievedAt: string
}


async function runResearch(query: string) {
  const stream = await client.agent.research({ query, mode: 'fast' })


  for await (const event of stream) {
    if (event.event === 'error') {
      throw new Error(event.data.error?.message ?? 'unknown error')
    }
    if (event.event === 'complete') {
      return {
        answer: event.data.report,
        sources: (event.data.metadata.citedPages ?? []).map((p) => ({
          title: p.title ?? '',
          url: p.url,
        })),
      }
    }
  }


  throw new Error('Stream ended without a complete event')
}


async function buildCompetitorBriefing(
  company: string,
  pricingUrl: string,
): Promise<CompetitorBriefing> {
  const [pricing, research] = await Promise.all([
    client.extract.json({
      url: pricingUrl,
      json_schema: {
        type: 'object',
        properties: {
          plans: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                name: { type: 'string', description: 'Plan name' },
                price: {
                  type: ['number', 'null'],
                  description: 'Monthly price in USD, null if custom or contact-us',
                },
                features: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Key included features',
                },
              },
            },
          },
        },
      },
    }),
    runResearch(
      `What has ${company} shipped, announced, or changed in the last 90 days? ` +
      `Include pricing changes, product launches, and API updates.`
    ),
  ])


  return {
    pricing: pricing as { plans: PricingPlan[] },
    recentSignals: research.answer,
    sources: research.sources,
    retrievedAt: new Date().toISOString(),
  }
}


try {
  const briefing = await buildCompetitorBriefing(
    'Acme Corp',
    'https://acmecorp.example/pricing',
  )


  console.log('Pricing plans:')
  for (const plan of briefing.pricing.plans) {
    console.log(`  ${plan.name}: $${plan.price ?? 'custom'}/mo`)
  }


  console.log('\nRecent signals:')
  console.log(briefing.recentSignals)


  console.log(`\nSources (${briefing.sources.length}):`)
  for (const s of briefing.sources) {
    console.log(`  ${s.title}: ${s.url}`)
  }
} catch (err) {
  if (err instanceof RateLimitError) {
    console.error('Rate limit -- retry after a pause')
  } else if (err instanceof AuthenticationError) {
    console.error('Invalid API key -- check TABSTACK_API_KEY')
  } else {
    throw err
  }
}
```

```
from datetime import datetime, timezone
from tabstack import Tabstack, RateLimitError, AuthenticationError


client = Tabstack()


PRICING_SCHEMA = {
    "type": "object",
    "properties": {
        "plans": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Plan name"},
                    "price": {
                        "type": ["number", "null"],
                        "description": "Monthly price in USD, null if custom or contact-us",
                    },
                    "features": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "Key included features",
                    },
                },
            },
        },
    },
}




def run_research(query: str) -> dict:
    for event in client.agent.research(query=query, mode="fast"):
        if event.event == "error":
            msg = getattr(event.data.error, "message", None) or "unknown error"
            raise RuntimeError(msg)
        if event.event == "complete":
            cited = event.data.metadata.cited_pages or []
            return {
                "answer": event.data.report,
                "sources": [{"title": p.title, "url": p.url} for p in cited],
            }
    raise RuntimeError("Stream ended without a complete event")




def build_competitor_briefing(company: str, pricing_url: str) -> dict:
    pricing = client.extract.json(url=pricing_url, json_schema=PRICING_SCHEMA)
    research = run_research(
        f"What has {company} shipped, announced, or changed in the last 90 days? "
        "Include pricing changes, product launches, and API updates."
    )
    return {
        "pricing": pricing,
        "recent_signals": research["answer"],
        "sources": research["sources"],
        "retrieved_at": datetime.now(timezone.utc).isoformat(),
    }




try:
    briefing = build_competitor_briefing("Acme Corp", "https://acmecorp.example/pricing")


    print("Pricing plans:")
    for plan in briefing["pricing"].get("plans", []):
        price = plan.get("price")
        print(f"  {plan['name']}: ${price if price is not None else 'custom'}/mo")


    print("\nRecent signals:")
    print(briefing["recent_signals"])


    sources = briefing["sources"]
    print(f"\nSources ({len(sources)}):")
    for s in sources:
        print(f"  {s['title']}: {s['url']}")


except RateLimitError:
    print("Rate limit -- retry after a pause")
except AuthenticationError:
    print("Invalid API key -- check TABSTACK_API_KEY")
```

---

## How it works

`buildCompetitorBriefing` runs two calls in parallel via `Promise.all`:

- `client.extract.json()` fetches the pricing page and returns a schema-validated object. The schema defines exactly what fields to pull. No HTML parsing, no post-processing.
- `client.agent.research()` runs multi-source research across the web and streams events. `runResearch` consumes the stream and resolves on the `complete` event, returning the synthesized answer and cited sources.

Total latency is the slower of the two, not their sum.

The Python version runs them sequentially rather than concurrently — use `concurrent.futures.ThreadPoolExecutor` if parallel execution matters in your context.

---

## Extending this pattern

The same structure applies anywhere you need both a structured snapshot and broader context:

- **Prospect research**: extract a company’s job postings schema + research their recent funding news
- **Market monitoring**: extract a product’s changelog entries + research developer sentiment
- **Due diligence**: extract financials from a known filing URL + research recent press coverage

Swap the schema and query. The agent structure stays the same.

---

## Installation

Terminal window

```
npm install @tabstack/sdk   # TypeScript
pip install tabstack         # Python
export TABSTACK_API_KEY="your-key-here"
```
