Competitor Briefing Agent
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
Section titled “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/pricingwith your competitor’s real pricing page.
Complete example
Section titled “Complete example”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, timezonefrom 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
Section titled “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.runResearchconsumes the stream and resolves on thecompleteevent, 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
Section titled “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
Section titled “Installation”npm install @tabstack/sdk # TypeScriptpip install tabstack # Pythonexport TABSTACK_API_KEY="your-key-here"