@tiny-fish/sdk
v0.1.0
Published
TinyFish TypeScript SDK — State-of-the-art web agents in an API
Readme
TinyFish TypeScript SDK
Typed access to TinyFish web agents, fetch extraction, remote browser sessions, run history, and search.
Use the SDK when you want to:
- automate a page with a natural-language goal
- stream live progress events and a browser preview URL
- queue jobs and poll them later
- fetch clean page content from up to 10 URLs at once
- create a remote browser session for direct CDP control
- query TinyFish Search from the same client
Installation
npm install @tiny-fish/sdkRequirements:
- Node.js 18+
- a TinyFish API key from agent.tinyfish.ai/api-keys
Authenticate with either the constructor or TINYFISH_API_KEY:
import { TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish({
apiKey: process.env.TINYFISH_API_KEY,
});Quickstart
agent.stream() is the best default for product integrations because it gives you progress updates while the run is happening.
import { TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish();
const stream = await client.agent.stream(
{
goal: "Extract the top 5 headlines",
url: "https://news.ycombinator.com",
},
{
onStarted: (event) => console.log(`Run: ${event.run_id}`),
onStreamingUrl: (event) => console.log(`Watch live: ${event.streaming_url}`),
onProgress: (event) => console.log(`> ${event.purpose}`),
onComplete: (event) => console.log(event.result),
},
);
for await (const event of stream) {
if (event.type === "COMPLETE") {
console.log(`Finished with status ${event.status}`);
}
}Need request-scoped cancellation without changing the client-wide timeout? Pass an AbortSignal as the optional second argument:
const controller = new AbortController();
const stream = await client.agent.stream(
{
goal: "Extract the top 5 headlines",
url: "https://news.ycombinator.com",
},
{
signal: controller.signal,
},
);
controller.abort();Choose an API
| Method | Use it when | Returns |
| --- | --- | --- |
| client.agent.stream() | You want live events and a browser preview URL | AgentStream |
| client.agent.run() | You want one blocking request that waits for the final result | AgentRunResponse |
| client.agent.queue() | You want to enqueue work and check back later | AgentRunAsyncResponse |
| client.fetch.getContents() | You want extracted page content without running a browser agent | FetchResponse |
| client.browser.sessions.create() | You want a remote browser session and CDP connection info | BrowserSession |
| client.runs.get() | You already have a run_id and need the latest state | Run |
| client.runs.list() | You want to list or filter historical runs | RunListResponse |
| client.search.query() | You want TinyFish Search results | SearchQueryResponse |
Core workflows
Run and wait
Use agent.run() for scripts, cron jobs, and backend tasks that should block until the result is ready.
import {
BrowserProfile,
ProxyCountryCode,
RunStatus,
TinyFish,
} from "@tiny-fish/sdk";
const client = new TinyFish();
const response = await client.agent.run({
goal: "Find the price of the latest MacBook Pro",
url: "https://www.apple.com/shop/buy-mac/macbook-pro",
browser_profile: BrowserProfile.STEALTH,
proxy_config: {
enabled: true,
country_code: ProxyCountryCode.US,
},
output_schema: {
type: "object",
properties: { price: { type: "string" } },
required: ["price"],
},
});
if (response.status === RunStatus.COMPLETED) {
console.log(response.result);
} else {
console.error(response.error?.message);
}output_schema must be a top-level object. The SDK validates that outer shape locally, and the API validates the
supported structured-output subset before execution.
Structured output is supported on client.agent.run(), client.agent.queue(), and client.agent.stream().
The TypeScript SDK mirrors the API's snake_case contract, so pass output_schema in request params and read
run.output_schema back from stored runs.
run() also accepts an optional second argument with signal:
const controller = new AbortController();
const response = await client.agent.run(
{
goal: "Extract the page title",
url: "https://example.com",
},
{
signal: controller.signal,
},
);Queue and poll
Use agent.queue() when you do not want to keep the request open.
import { RunStatus, TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish();
const queued = await client.agent.queue({
goal: "Extract all job titles from the careers page",
url: "https://example.com/careers",
});
if (queued.error) {
throw new Error(queued.error.message);
}
let run = await client.runs.get(queued.run_id);
while (run.status === RunStatus.PENDING || run.status === RunStatus.RUNNING) {
await new Promise((resolve) => setTimeout(resolve, 5000));
run = await client.runs.get(run.run_id);
}
if (run.status === RunStatus.COMPLETED) {
console.log(run.result);
} else {
console.error(run.error?.message ?? `Run ended with status: ${run.status}`);
}queue() also accepts an optional second argument with signal for cancelling just the enqueue request.
Fetch clean content
Use fetch.getContents() when you want extracted content from URLs without a browser-agent run.
import { FetchFormat, TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish();
const response = await client.fetch.getContents({
urls: ["https://example.com", "https://example.org"],
format: FetchFormat.Markdown,
links: true,
image_links: false,
per_url_timeout_ms: 45_000,
});
console.log(response.results);
console.log(response.errors);fetch.getContents() accepts 1 to 10 URLs. FetchResult.text is:
stringforFetchFormat.MarkdownandFetchFormat.HtmlRecord<string, unknown>forFetchFormat.Jsonnullif extraction failed for that item
Set per_url_timeout_ms to apply an independent timeout budget to each URL in
the batch; slow URLs return in errors with timeout while siblings can still
complete.
Create a browser session
Use browser.sessions.create() when you want connection details for direct browser control.
import { TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish();
const session = await client.browser.sessions.create({
url: "https://example.com",
});
console.log(session.session_id);
console.log(session.cdp_url);
console.log(session.base_url);Inspect and list runs
Fetch a single run when you already know its run_id:
const run = await client.runs.get("run_abc123");
console.log(run.status);
console.log(run.result);
console.log(run.output_schema);
console.log(run.streaming_url);Use runs.list() for filtering and pagination:
import { RunStatus, SortDirection, TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish();
const response = await client.runs.list({
status: RunStatus.COMPLETED,
goal: "headlines",
sort_direction: SortDirection.DESC,
limit: 10,
});
for (const run of response.data) {
console.log(`${run.run_id} | ${run.status} | ${run.goal}`);
}
if (response.pagination.has_more) {
const nextPage = await client.runs.list({
cursor: response.pagination.next_cursor ?? undefined,
});
console.log(`Fetched ${nextPage.data.length} more runs`);
}Query search
Returns ranked web search results with titles, snippets, and URLs.
import { TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish();
const response = await client.search.query({ query: "FIFA" });
console.log(response.query);
console.log(response.total_results);
console.log(response.results[0]?.title);Optional parameters:
location— country code for geo-targeted results (e.g."US","GB")language— language code (e.g."en","fr")page— page number, 0-indexed, max10recency_minutes— freshness window in minutes (1to5256000)after_date/before_date— calendar date range inYYYY-MM-DDdomain_type— result category:"web"(default),"news", or"research_paper"pub_year_min/pub_year_max— publication-year range, inclusive (0to9999). Only supported fordomain_type: "research_paper"
// geo-targeted
const geo = await client.search.query({ query: "FIFA", location: "US", language: "en" });
// freshness window
const fresh = await client.search.query({ query: "FIFA", recency_minutes: 60 });
// calendar date range
const archived = await client.search.query({
query: "FIFA",
after_date: "2026-06-01",
before_date: "2026-06-18",
});
// domain type
const news = await client.search.query({ query: "FIFA", domain_type: "news" });
const papers = await client.search.query({ query: "machine learning", domain_type: "research_paper" });
// publication-year range (research_paper only)
const byYear = await client.search.query({
query: "transformer architecture",
domain_type: "research_paper",
pub_year_min: 2019,
pub_year_max: 2022,
});Filter validation rules:
recency_minutesmust be an integer from1to5256000after_dateandbefore_datemust useYYYY-MM-DDrecency_minutescannot be combined withafter_dateorbefore_date- if both dates are present,
after_datemust be less than or equal tobefore_date domain_typemust be one of"web","news", or"research_paper"pub_year_minandpub_year_maxmust be integers from0to9999- if both are present,
pub_year_minmust be less than or equal topub_year_max
Streaming events
agent.stream() guarantees this event order:
STARTEDSTREAMING_URLPROGRESSrepeated zero or more timesCOMPLETE
HEARTBEAT events may also appear, but they are keepalive events rather than part of the guaranteed ordered sequence.
You can consume the stream in two ways:
- callbacks like
onProgressandonComplete - direct iteration with
for await...of
To stop a stream early, call await stream.close().
Configuration
import { TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish({
apiKey: process.env.TINYFISH_API_KEY,
baseURL: "https://agent.tinyfish.ai",
timeout: 600_000,
maxRetries: 2,
});Defaults:
baseURL:https://agent.tinyfish.aitimeout:600000msmaxRetries:2
The SDK automatically retries 408, 429, and 5xx responses with exponential backoff. Authentication, validation, and not-found errors fail immediately.
Browser profiles and proxies
agent.run(), agent.queue(), and agent.stream() all accept the same execution parameters:
goalandurlare requiredbrowser_profilecan beBrowserProfile.LITEorBrowserProfile.STEALTHproxy_configcan enable a proxy and optionally pin a countryuse_profile: truestarts from your default Browser Context Profileprofile_idselects a specific Browser Context Profile and requiresuse_profile: true
Example Browser Context Profile run:
const response = await client.agent.run({
goal: "Summarize the dashboard",
url: "https://app.example.com/dashboard",
use_profile: true,
profile_id: "prof_abc123def4567890",
use_vault: true,
});Supported proxy country codes:
USGBCADEFRJPAU
Error handling
import {
AuthenticationError,
RateLimitError,
SDKError,
TinyFish,
} from "@tiny-fish/sdk";
const client = new TinyFish();
try {
await client.agent.run({
goal: "Extract the page title",
url: "https://example.com",
});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error("Invalid API key");
} else if (error instanceof RateLimitError) {
console.error("Rate limited");
} else if (error instanceof SDKError) {
console.error(error.message);
} else {
throw error;
}
}