Search vs Fetch vs Agent vs Browser: TinyFish Guide

Divya Lath
Share
Search vs Fetch vs Agent vs Browser: TinyFish Guide

TLDR

Step 1: what is your agent doing right now? Pick a capability.

If you need to…UseCost
Find where the info livesSearchFree
Read the content off a known URL (no clicking)FetchFree
Get a task done on a page, described in plain EnglishAgentCredits
Drive a real browser yourself with Playwright/CDPBrowserCredits

Step 2: what does that run need to actually succeed? Add modifiers.

If the run…Reach for
Should stay logged in across runsBrowser Context Profile (use_profile)
Needs to log in now, safelyVault (use_vault)
Is getting blocked / hitting captchasStealth (browser_profile: "stealth")
Needs a specific countryProxy (proxy_config)
Feeds a database or pipelineoutput_schema
Is long, batched, or user-facingPick the right endpoint (/run, /run-async, /run-sse)

So which one do I use?

If you're building agents on TinyFish, this question shows up almost immediately. You've got a task that involves a website, and Search, Fetch, Agent, and Browser all sound like they could handle it.

They feel overlapping, but they're really four stages of the same job. Each one does a different amount of work, and the cost (in speed, credits, and the number of ways a run can break) goes up as you move across them. The goal is to use the lightest one that gets the task done, and only move up when a page gives you a reason to.

The short version

A website is built for a person, so an agent working through one usually has to do some mix of three things: find the right pages, read what's on them, and operate the page when reading isn't enough.

  • Search handles the finding.
  • Fetch handles the reading.
  • Agent (or Browser, if you're scripting it yourself) handles the operating.

There's also a small set of run settings underneath all of this for logins, blocked sites, and output shape, which we'll get to once the four basics are clear.

The four, with a real task

Let's make it concrete. Say you want to track how a few competitors price their plans, and pull their plan names and prices into a table you can check each week. We'll build it up one tool at a time.

1. Search — finding the pages

You don't want to hardcode competitor pricing URLs, since those pages move. Search takes a query and returns ranked results with titles, snippets, and links, so your agent has solid starting points instead of guesses.

const results = await client.search.query({
  query: "project management SaaS pricing pages",
  location: "US",
  language: "en",
});

What comes back is a list of URLs to work from, not the data itself. Use Search for discovery, source-gathering, and any research-style workflow where step one is "where do I even look." It runs in a couple of seconds and costs nothing.

2. Fetch — reading a page, fast

Once you have a URL, the next question is what's actually on it. For a plain pricing page (some text, a few plan cards, nothing that needs clicking) you don't need anything heavy. Fetch renders the page in a real browser, so JS-heavy sites still come back readable, and hands you clean content with the nav and clutter stripped out. You can pass up to ten URLs at once.

const pages = await client.fetch.getContents({
  urls: ["<https://acme-pm.com/pricing>", "<https://bolttasks.io/plans>"],
});

This is your default whenever you just need to read something: no interaction, no login, and you care about speed and cost. Like Search, it's free. A large share of "I need data off the web" work never goes past Search and Fetch, so if your task fits here, stop here.

3. Agent — describe the goal, let TinyFish do it

Now suppose one competitor hides their real numbers behind a monthly/annual toggle, or a button that only loads prices after a click. Fetch will return the page, but not the state you care about, because it reads what's there rather than interacting.

That's where the Agent comes in. You give it a URL and a goal in plain language, and it works out the steps to get there.

const run = await client.agent.run({
  url: "<https://acme-pm.com/pricing>",
  goal: "Switch to annual billing, then return every plan's name and per-user monthly price as JSON.",
});

The goal is the part that decides whether this works. "Get the pricing" is the kind of instruction that passes a demo and fails in production. Name the exact fields, the format you want, and what to do with the odd cases, like a plan that just says "Contact Sales." One more thing that catches people out: a run finishing isn't the same as a run succeeding, since it can complete and still have quietly hit a block, so check the output before trusting it.

A note on the question we get most: Agent and Browser both use a real browser, so what's the difference? It's about who decides the steps. With the Agent you describe the outcome and let it figure out the clicks. With Browser you write the clicks yourself. Start with the Agent, and drop to Browser when you specifically want that control.

4. Browser — driving it yourself

Sometimes you don't want TinyFish choosing the steps. You've already got Playwright code, or the flow is fiddly enough that you want to script it precisely. The Browser API gives you a remote browser session and connection details, and you drive it directly.

curl -X POST <https://api.browser.tinyfish.ai> \
  -H "X-API-Key: $TINYFISH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "<https://acme-pm.com/pricing>"}'

You get back a cdp_url, connect your own client to it, and from there it's your code: goto, click, waitForSelector, whatever you need, running against a scalable remote browser you don't have to host. Use it when you want full control and you're happy writing the automation by hand.

The settings underneath

The four above are what your agent does. There's a second layer for how a run behaves, and most of it lives on Agent (and Browser) runs. They don't change what you're doing, they change whether it works on the page in front of you. You don't turn these on by default; you add one when a page gives you a reason to.

Endpoints — how you call Agent. Same capability, three delivery styles:

  • /run — synchronous, simplest, blocks until done. Can't be cancelled. Great for quick one-offs.
  • /run-async — returns a run_id, you poll. For long tasks and batches. Cancellable.
  • /run-sse — streams live events plus a URL you can embed to watch the browser work. For user-facing apps and debugging. Cancellable.

Browser Context Profiles — stay logged in. Save cookies / local / session storage once, reuse across runs so recurring automations start already authenticated. use_profile: true (default profile), or add a profile_id. Not the same as browser_profile.

Vault — log in safely. Connect 1Password or Bitwarden; use_vault: true lets TinyFish fill credentials when a login form appears. The AI agent never sees the actual values, they're injected at the browser layer and discarded. Handles TOTP codes too. (Currently driven via the REST API; SDK support is on the way.)

Browser Profiles — lite vs stealth. lite is the standard Chromium default. stealth adds anti-fingerprinting for sites behind Cloudflare / DataDome or throwing "access denied." Note: stealth still can't solve captchas.

Proxies — geo and unblocking. proxy_config: { enabled: true, country_code: "US" } routes the run through a specific country (US, GB, CA, DE, FR, JP, AU). For geo-restricted content or regional blocks.

output_schema — lock the shape. Pass a (constrained) JSON Schema and TinyFish validates the result against it before returning, so what lands in your pipeline is exactly the shape you expect. (Feature-gated; returns 403 if your account isn't enabled.)

The combinations that actually matter

This is the real answer to "what should I use." Single calls are rare in production; here are the stacks worth memorizing.

1. Search + Fetch → free, fast read-only intelligence

Scenario: monitoring competitors' blogs, tracking job postings, gathering research sources — anything that's "find pages, read pages," no login or interaction.

Stack: Search to discover URLs → Fetch (batched, up to 10) to pull clean content → filter or LLM-summarize the results.

Why it's good: both are free and fast, and you never spin up a heavyweight run. This handles a huge share of "web data" use cases on its own.

2. Search + Fetch + Agent → the escalation loop

Scenario: same as above, but a few of the pages hide what you need behind a toggle, a "load more," or a login.

Stack: Search → Fetch the easy ones → escalate only the stubborn pages to Agent.

Why it's good: you do the cheap thing by default and pay for the smart thing only where the page actually demands interaction. This is the canonical TinyFish loop.

3. Agent + Browser Context Profile + Vault → authenticated recurring workflows

Scenario: "every morning, log into our internal dashboard or a SaaS portal and pull the latest numbers." Sessions expire, MFA shows up, and logging in cold every run is fragile.

Stack: set up a Browser Context Profile once → run with use_profile: true so you start already signed in → add use_vault: true so when the session goes stale, TinyFish re-logs-in and refreshes the profile instead of failing.

{
  "url": "<https://app.example.com/dashboard>",
  "goal": "Open the dashboard and summarize anything urgent",
  "use_profile": true,
  "use_vault": true
}

Why it's good: this is the most reliable pattern for logged-in automation. Profiles give persistence; Vault gives self-healing. Don't make the agent log in from scratch every time.

4. Agent + Stealth + Proxy → protected and geo-locked sites

Scenario: scraping an e-commerce site behind Cloudflare, or content that's only served to a specific country.

Stack: Agent + browser_profile: "stealth" + proxy_config.

const run = await client.agent.run({
  url: "<https://protected-site.com>",
  goal: "Extract the current pricing for all plans. Return as JSON.",
  browser_profile: "stealth",
  proxy_config: { enabled: true, country_code: "US" },
});

Why it's good: stealth handles the fingerprinting, the proxy handles the geo / IP-reputation side. The honest caveat: neither solves a captcha. If a site hard-gates with reCAPTCHA, no flag gets you past it.

5. Agent + output_schema (+ /run-async) → clean data at scale

Scenario: you're extracting structured records across many URLs and dumping them straight into a DB or sheet.

Stack: Agent with an output_schema so every result is validated to the same shape → fire them through /run-async and poll, so a batch of 50 doesn't block on each other.

Why it's good: schema = predictable downstream code; async = throughput. Together they turn one-off extraction into a pipeline.

6. Agent + /run-sse → user-facing apps with live progress

Scenario: your product lets users kick off a web task, and staring at a spinner for 40s feels broken.

Stack: /run-sse streams PROGRESS events for each browser action plus a STREAMING_URL you can drop in an iframe so users literally watch it work.

Why it's good: turns an opaque wait into a live demo, and you can cancel mid-run. Great for trust and debugging.

One example, end to end

Back to the pricing tracker. Say it runs every morning and reports how your plans stack up. A single run might touch almost the whole stack:

  1. Search → find each competitor's pricing / blog / news URLs without hardcoding them. (free)
  2. Fetch → batch-read the straightforward public pages and filter out the ones with nothing useful. (free)
  3. Agent + output_schema → for the competitor whose pricing hides behind a billing toggle, extract plan names and prices validated to your schema.
  4. Agent + Stealth + US Proxy → for the one competitor behind Cloudflare and US-geo'd, the same extraction with anti-detection on.
  5. Agent + Browser Context Profile + Vault → log into your own analytics dashboard to grab your live numbers for the comparison, reusing the saved session.
  6. /run-async → because it's a batch across competitors, fire the runs and poll rather than blocking. (Swap to /run-sse if a human's watching it in your app.)

Notice the shape: lean on the free, lightweight capabilities for most of the work, escalate to the Agent only where the page fights back, and add modifiers exactly where a given page needs them.

Rule of thumb

The teams that build reliable web agents converge on the same instinct: do as much as possible with the lightest tool, and only level up when the page forces you to.

  • Read-only and public? → Search + Fetch. (Free. Most of your volume lives here.)
  • Needs interaction? → Agent. Describe the goal well.
  • Needs to be logged in, repeatedly? → Profile + Vault.
  • Getting blocked or geo-gated? → Stealth + Proxy.
  • Feeding a pipeline? → output_schema + /run-async.
  • A user is watching? → /run-sse.
  • Want to script it yourself? → Browser (optionally + a Profile).

Reaching for a full Agent run (or a stealth proxy) on every task works, but it's slower, pricier, and harder to debug. Match the tool to the moment.

Get started

When you're not sure, ask what the agent is doing right now. Finding pages, reading one, operating one, or being driven by your own code. That answers which of the four you want, and the run settings follow from whatever the page throws at you.

The full reference and copy-paste examples are at docs.tinyfish.ai. If you're working inside Claude Code, Cursor, or another MCP host, you can connect over MCP and skip managing API keys entirely.

Get started

Start building.

No credit card. No setup. Run your first operation in under a minute.

Get 500 free creditsRead the docs