Skip to the page

Vercel AI SDK

Give an AI SDK agent any list from the live web. Search tools hand an agent pages to read; SpecStream hands it the finished list. The @specstream/ai-sdk package adds two tools: the agent asks in plain words, like “dental clinics in Lisbon with online booking”, and gets back every matching row, each with the page it was read from. You set what one list may cost, in code. The model can’t raise it.

Install

Terminal
npm install @specstream/ai-sdk ai zod
export SPECSTREAM_API_KEY=ss_live_...

Create the key under Agents & API in your workspace (more on keys). A free account comes with $10 of work to use within 30 days and needs no card: sign up. The package works with AI SDK 6 and 7 and zod 3.25 or 4, on Node 18 or later.

An agent that finds lists

typescript
import { generateText, stepCountIs } from "ai";
import { specstreamTools } from "@specstream/ai-sdk";
const { text } = await generateText({
model: "openai/gpt-5-mini",
tools: specstreamTools({ maxPriceUsd: 5 }),
stopWhen: stepCountIs(5),
prompt: "Find US startups that raised $5M or more in a Form D filed after October 8, 2026. Which industries lead?",
});

The agent writes the request and picks how many rows. You decide what one list may cost. streamText and ToolLoopAgent take the same tools.

ToolThe model givesWhat happens
specstream_find_listrequest, maxResultsReads the request, prices it, starts it only within maxPriceUsd, waits up to seven minutes and returns every row. You pay only for rows that match.
specstream_get_list_resultslistIdEvery row found so far, for a list that was still running. Free.

When the price is above your maximum, nothing runs: the tool returns the price, so the agent can ask for fewer rows or tell the user. When a request is too vague to price (“clinics”), it returns how SpecStream read it, so the agent can add the place or the conditions.

Settings live in your code, never in the tool input. On a serverless function, keep waitSeconds below the time limit: a list still running when the wait ends comes back with its list_id, and the agent reads the rest with specstream_get_list_results.

typescript
specstreamTools({
maxPriceUsd: 20, // the most one list may cost
waitSeconds: 60, // below your function's time limit; the agent reads the rest later
locale: "en-GB", // the language the requests are written in
});

What comes back

One flat row per match: the list’s own columns, then where the row was read. This is a real row from the request above.

JSON
{
"list_id": "fnd_gk5y3d0r97bvyz0t08zzh75kr6",
"position": 26,
"Organization": "RapidCanvas Inc",
"Published address": "801 BARTON SPRINGS RD, AUSTIN, TEXAS, 78704",
"Source industry classification": "Other Technology",
"Amount sold (USD)": "28999988",
"Funding filing date": "2026-10-14",
"source_url": "https://www.sec.gov/Archives/edgar/data/2052031/000205203126000006/primary_doc.xml",
"checked_at": "2026-10-14T19:56:02.016693Z",
"list_link": "https://specstream.ai/workspace/collections/fnd_gk5y3d0r97bvyz0t08zzh75kr6"
}

Columns follow the request. Ask for breweries with their website and the rows carry the brewery and its website instead. A fact a source doesn’t publish comes back empty, never guessed. The list_link opens the same list in your workspace, where you can map it, export it or keep it current.

Without a model

The client the tools are built on is exported, for the same calls from your own code:

typescript
import { SpecStreamClient } from "@specstream/ai-sdk";
const client = new SpecStreamClient({ apiKey: process.env.SPECSTREAM_API_KEY });
const list = await client.findList("Breweries in Portland, Oregon that host events, with their website", {
maxResults: 25,
maxPriceUsd: 3,
});
if ("rows" in list) console.log(list.row_count, list.rows[0].source_url);
else console.log(list.message); // over the maximum or too vague: nothing ran

Money, plainly

  • maxPriceUsd lives in your code. The model never sees it and can’t change it.
  • You pay for rows that match. Candidates SpecStream couldn’t confirm are left out and aren’t billed.
  • A retried step asking for the same list within the hour gets the list it already started, not a second bill.
  • Rates and plans: pricing. How charges work: billing.

Every tool over MCP

The package covers finding a list and reading it. For everything else (schedules, exports, sharing, enrichment of your own list), load the whole MCP server with @ai-sdk/mcp:

typescript
import { createMCPClient } from "@ai-sdk/mcp";
const mcpClient = await createMCPClient({
transport: {
type: "http",
url: "https://api.specstream.ai/mcp",
headers: { Authorization: `Bearer ${process.env.SPECSTREAM_API_KEY}` },
},
});
const tools = await mcpClient.tools();

Source and issues: GitHub (opens in a new tab). Package: npm (opens in a new tab).