ax-check.com
by Gauge
T
TinyFish
tinyfish.ai
ax-check.com
Checked 17 October 2026
B
84/100
Agents need your login
TinyFish's quickstart and pricing are usable with no login wall.
20 of 23 checklist items passed: install commands, code samples, and a static rate table are all readable without signing in first.
2 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no tinyfish.ai account. Recorded 17 October 2026 on Pi.
3 recorded · 1m 7s
1m 7s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
TinyFish — it's a web automation agent company (tinyfish.io). Is there an SDK? Let me check whether the repository exists and whether I have network access.
Let me take a look at the sandbox.
pwd; ls -la; env | grep -i -E "tiny|fish|api|key" | sed 's/=.*/=<set>/'
/sandbox/repo
/sandbox/repo total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Oct 17 02:14 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore PI_GATEWAY_API_KEY=<set> GIT_CONFIG_KEY_0=<set>
Recorded tool call
{
"type": "tool_call",
"seq": 3,
"ts": "2026-10-17T02:14:45.721Z",
"name": "bash",
"turn": 1,
"input": {
"command": "pwd; ls -la; env | grep -i -E \"tiny|fish|api|key\" | sed 's/=.*/=<set>/'"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-a60b1bf3483be5ff",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 7,
"ts": "2026-10-17T02:14:45.721Z",
"turn": 1,
"output": {
"content": [
{
"text": "/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Oct 17 02:14 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\nPI_GATEWAY_API_KEY=<set>\nGIT_CONFIG_KEY_0=<set>\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-a60b1bf3483be5ff",
"is_error": false,
"tool_kind": "shell"
}curl -s -m 8 -o /dev/null -w "%{http_code}\n" https://pypi.org/simple/ ; curl -s -m 8 -o /dev/null -w "%{http_code}\n" https://registry.npmjs.org/200
200 200
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-10-17T02:14:45.721Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -m 8 -o /dev/null -w \"%{http_code}\\n\" https://pypi.org/simple/ ; curl -s -m 8 -o /dev/null -w \"%{http_code}\\n\" https://registry.npmjs.org/"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-8476755c277f2355",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 8,
"ts": "2026-10-17T02:14:54.066Z",
"turn": 1,
"output": {
"content": [
{
"text": "200\n200\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-8476755c277f2355",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
There's network access. Let's investigate TinyFish: docs, SDK (pypi's "tinyfish"? npm?). Let me check.
curl -s -m 10 https://pypi.org/pypi/tinyfish/json | head -c 2000; echo; echo ---; curl -s -m 10 "https://registry.npmjs.org/-/v1/search?text=tinyfish&size=10" | python3 -c "import json,sys; d=json.load(sys.stdin); [print(o['package']['name'], o['package']['version'], o['package'].get('description','')[:80]) for o in d['objects']]"{"info":{"author":null,"author_email":null,"bugtrack_url":null,"classifiers":[],"description":"# TinyFish Python SDK\n\nThe official Python SDK for [TinyFish](h
{"info":{"author":null,"author_email":null,"bugtrack_url":null,"classifiers":[],"description":"# TinyFish Python SDK\n\nThe official Python SDK for [TinyFish](https://agent.tinyfish.ai)\n\n## Installation\n\n```bash\npip install tinyfish\n```\n\nRequires Python 3.11+.\n\n## Get your API key\n\nSign up and grab your key at [agent.tinyfish.ai/api-keys](https://agent.tinyfish.ai/api-keys).\n\n## Quickstart\n\n```python\nfrom tinyfish import TinyFish\n\nclient = TinyFish(api_key=\"your-api-key\")\n\nresponse = client.agent.run(\n goal=\"What is the current Bitcoin price?\",\n url=\"https://www.coinbase.com/price/bitcoin\",\n)\nprint(response.result)\n```\n\nOr set the `TINYFISH_API_KEY` environment variable and omit `api_key`:\n\n```python\nclient = TinyFish()\n```\n\n## Methods\n\nEvery method below is available on both `TinyFish` (sync) and `AsyncTinyFish` (async). Async versions have the same signatures — just `await` them.\n\n| Method | Description | Returns | Blocks? |\n|--------|-------------|---------|---------|\n| [`agent.run()`](#agentrun--block-until-done) | Run an automation, wait for the result | `AgentRunResponse` | Yes |\n| [`agent.queue()`](#agentqueue--fire-and-forget) | Start an automation, return immediately | `AgentRunAsyncResponse` | No |\n| [`agent.stream()`](#agentstream--real-time-events) | Stream live SSE events as the agent works | `AgentStream` | No |\n| [`fetch.get_contents()`](#fetchget_contents--clean-content) | Fetch extracted page content without running a browser agent | `FetchResponse` | Yes |\n| [`runs.get()`](#runsget--retrieve-a-single-run) | Retrieve a single run by ID | `Run` | — |\n| [`runs.list()`](#runslist--list-and-filter-runs) | List runs with filtering, sorting, pagination | `RunListResponse` | — |\n| [`search.query()`](#searchquery--search-the-web) | Search the web for ranked results | `SearchQueryResponse` | Yes |\n| [`wallet.get()`](#walletget--get-your-balance) | Get the caller's wallet balance, auto-reload, a
---
tinyfish 0.0.0 TinyFish
dsh-tinyfish-search 0.16.0 TinyFish-backed web search provider for DeepSeek Harness (ctx.web): makes the bu
@tiny-fish/cli 0.50.1 TinyFish CLI — run web automations from your terminal
@ownweb-me/dsh-web-search-tinyfish 0.1.7 pnpm: `@ownweb-me/dsh-web-search-tinyfish`
@tiny-fish/sdk 0.8.0 TinyFish TypeScript SDK — State-of-the-art web agents in an API
dsh-tinyfish 0.12.1 TinyFish-backed search and fetch providers for the DeepSeek Harness web capabili
@ownweb-me/dsh-web-fetch-tinyfish 0.1.7 pnpm: `@ownweb-me/dsh-web-fetch-tinyfish`
@viztor/dsh-tinyfish 0.12.1 TinyFish-backed search and fetch providers for the DeepSeek Harness web capabili
@tiny-fish/hermes 0.2.2 TinyFish web provider plugin for Hermes Agent, delivered via npm for the TinyFis
pi-web-access 0.41.0 Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video und
Recorded tool call
{
"type": "tool_call",
"seq": 11,
"ts": "2026-10-17T02:14:56.352Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -m 10 https://pypi.org/pypi/tinyfish/json | head -c 2000; echo; echo ---; curl -s -m 10 \"https://registry.npmjs.org/-/v1/search?text=tinyfish&size=10\" | python3 -c \"import json,sys; d=json.load(sys.stdin); [print(o['package']['name'], o['package']['version'], o['package'].get('description','')[:80]) for o in d['objects']]\""
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-b5726c9ca365e9c2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 16,
"ts": "2026-10-17T02:14:58.076Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\"info\":{\"author\":null,\"author_email\":null,\"bugtrack_url\":null,\"classifiers\":[],\"description\":\"# TinyFish Python SDK\\n\\nThe official Python SDK for [TinyFish](https://agent.tinyfish.ai)\\n\\n## Installation\\n\\n```bash\\npip install tinyfish\\n```\\n\\nRequires Python 3.11+.\\n\\n## Get your API key\\n\\nSign up and grab your key at [agent.tinyfish.ai/api-keys](https://agent.tinyfish.ai/api-keys).\\n\\n## Quickstart\\n\\n```python\\nfrom tinyfish import TinyFish\\n\\nclient = TinyFish(api_key=\\\"your-api-key\\\")\\n\\nresponse = client.agent.run(\\n goal=\\\"What is the current Bitcoin price?\\\",\\n url=\\\"https://www.coinbase.com/price/bitcoin\\\",\\n)\\nprint(response.result)\\n```\\n\\nOr set the `TINYFISH_API_KEY` environment variable and omit `api_key`:\\n\\n```python\\nclient = TinyFish()\\n```\\n\\n## Methods\\n\\nEvery method below is available on both `TinyFish` (sync) and `AsyncTinyFish` (async). Async versions have the same signatures — just `await` them.\\n\\n| Method | Description | Returns | Blocks? |\\n|--------|-------------|---------|---------|\\n| [`agent.run()`](#agentrun--block-until-done) | Run an automation, wait for the result | `AgentRunResponse` | Yes |\\n| [`agent.queue()`](#agentqueue--fire-and-forget) | Start an automation, return immediately | `AgentRunAsyncResponse` | No |\\n| [`agent.stream()`](#agentstream--real-time-events) | Stream live SSE events as the agent works | `AgentStream` | No |\\n| [`fetch.get_contents()`](#fetchget_contents--clean-content) | Fetch extracted page content without running a browser agent | `FetchResponse` | Yes |\\n| [`runs.get()`](#runsget--retrieve-a-single-run) | Retrieve a single run by ID | `Run` | — |\\n| [`runs.list()`](#runslist--list-and-filter-runs) | List runs with filtering, sorting, pagination | `RunListResponse` | — |\\n| [`search.query()`](#searchquery--search-the-web) | Search the web for ranked results | `SearchQueryResponse` | Yes |\\n| [`wallet.get()`](#walletget--get-your-balance) | Get the caller's wallet balance, auto-reload, a\n---\ntinyfish 0.0.0 TinyFish\ndsh-tinyfish-search 0.16.0 TinyFish-backed web search provider for DeepSeek Harness (ctx.web): makes the bu\n@tiny-fish/cli 0.50.1 TinyFish CLI — run web automations from your terminal\n@ownweb-me/dsh-web-search-tinyfish 0.1.7 pnpm: `@ownweb-me/dsh-web-search-tinyfish`\n@tiny-fish/sdk 0.8.0 TinyFish TypeScript SDK — State-of-the-art web agents in an API\ndsh-tinyfish 0.12.1 TinyFish-backed search and fetch providers for the DeepSeek Harness web capabili\n@ownweb-me/dsh-web-fetch-tinyfish 0.1.7 pnpm: `@ownweb-me/dsh-web-fetch-tinyfish`\n@viztor/dsh-tinyfish 0.12.1 TinyFish-backed search and fetch providers for the DeepSeek Harness web capabili\n@tiny-fish/hermes 0.2.2 TinyFish web provider plugin for Hermes Agent, delivered via npm for the TinyFis\npi-web-access 0.41.0 Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video und\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-b5726c9ca365e9c2",
"is_error": false,
"tool_kind": "shell"
}curl -sL -m 15 -o /tmp/tinyfish.html -w "%{http_code} %{url_effective}\n" https://www.tinyfish.io/; python3 - <<'EOF'
import re,html
t=open('/tmp/tinyfish.html',encoding='utf-8',errors='ignore').read()
t=re.sub(r'<script.*?</script>','',t,flags=re.S)
t=re.sub(r'<style.*?</style>','',t,flags=re.S)
t=re.sub(r'<[^>]+>',' ',t)
print(html.unescape(re.sub(r'\s+',' ',t))[:3000])
EOF200 https://www.tinyfish.ai/
200 https://www.tinyfish.ai/
TinyFish — Web Infrastructure for AI Agents Products Mako Web-native model for live workflows Search Fast, structured web search Fetch Any URL to clean content Agent Multi-step web automation Browser Cloud browser sessions All products share one API key View docs → Resources Documentation API reference and guides Partners The Data Partner Alliance Integrations Connect with your stack Compare TinyFish vs alternatives Benchmarks How we measure up Blog Product updates and insights Pulse What we've shipped Cookbook Open-source examples Pricing Enterprise Overview Web execution for enterprise teams Solutions What we run, and where it runs in production Customers See who builds with TinyFish Contact Log In Sign up Sign up Products Mako Web-native model for live workflows Search Fast, structured web search Fetch Any URL to clean content Agent Multi-step web automation Browser Cloud browser sessions Resources Documentation API reference and guides Partners The Data Partner Alliance Integrations Connect with your stack Compare TinyFish vs alternatives Benchmarks How we measure up Blog Product updates and insights Pulse What we've shipped Cookbook Open-source examples Pricing Rates, Wallet, and billing Enterprise Overview Web execution for enterprise teams Solutions What we run, and where it runs in production Customers See who builds with TinyFish Contact Log In The web stack for AI agents. Search, extract, browse, and act on the web with one platform. Connect TinyFish + 9 Works with any AI Trusted by Trusted by What TinyFish can do. Four jobs people hand their agents every day, on the live web. Monitor web pages Get an alert when a page or topic changes. Prices, filings, job posts. acme.com/pricing Monitoring Starter $19 Pro $59 Team $129 TinyFish now Acme raised the Pro plan $49 → $59 a month Enrich records Check contacts and accounts against the live web and fill the gaps. crm.yourco.com/leads Company Headcount HQ Globex 1,240 Denver Initech 310 Austin Northwind 86 Kestrel Umbrella Checking northwind.com, kestrel.com 2 of 5 Research anything Ask a question, get an answer that links to every source it used. Which suppliers changed owner this year? TinyFish read 214 pages Two changed owner: Northwind and Kestrel . companieshouse.gov.uk sec.gov bloomberg.com reuters.com ft.com Automate portals Sign in, fill forms and pull files from other companies' portals. supplier.acme.com/onboarding Filling Supplier onboarding Step 3 of 10 Legal name Yourco Inc. Tax ID 84-2917305 Contact email Street address Next And much more. All solutions Use with ChatGPT , OpenClaw , Claude , Cursor , Hermes Grok or in your {code}. Connect TinyFish View API docs Give your agent the web. TinySearch Free Fresh results from the live web. Never cached. Real browser-rendered search that returns structured JSON. Try it TinyFetch Free From rendered page to clean data in one call. Markdown, JSON or HTML, token-efficient. Try it TinyBrowser Access the web that search can't reach. A real c
Recorded tool call
{
"type": "tool_call",
"seq": 14,
"ts": "2026-10-17T02:14:56.352Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -sL -m 15 -o /tmp/tinyfish.html -w \"%{http_code} %{url_effective}\\n\" https://www.tinyfish.io/; python3 - <<'EOF'\nimport re,html\nt=open('/tmp/tinyfish.html',encoding='utf-8',errors='ignore').read()\nt=re.sub(r'<script.*?</script>','',t,flags=re.S)\nt=re.sub(r'<style.*?</style>','',t,flags=re.S)\nt=re.sub(r'<[^>]+>',' ',t)\nprint(html.unescape(re.sub(r'\\s+',' ',t))[:3000])\nEOF"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-bd027eb312db3be2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 17,
"ts": "2026-10-17T02:14:58.814Z",
"turn": 1,
"output": {
"content": [
{
"text": "200 https://www.tinyfish.ai/\n TinyFish — Web Infrastructure for AI Agents Products Mako Web-native model for live workflows Search Fast, structured web search Fetch Any URL to clean content Agent Multi-step web automation Browser Cloud browser sessions All products share one API key View docs → Resources Documentation API reference and guides Partners The Data Partner Alliance Integrations Connect with your stack Compare TinyFish vs alternatives Benchmarks How we measure up Blog Product updates and insights Pulse What we've shipped Cookbook Open-source examples Pricing Enterprise Overview Web execution for enterprise teams Solutions What we run, and where it runs in production Customers See who builds with TinyFish Contact Log In Sign up Sign up Products Mako Web-native model for live workflows Search Fast, structured web search Fetch Any URL to clean content Agent Multi-step web automation Browser Cloud browser sessions Resources Documentation API reference and guides Partners The Data Partner Alliance Integrations Connect with your stack Compare TinyFish vs alternatives Benchmarks How we measure up Blog Product updates and insights Pulse What we've shipped Cookbook Open-source examples Pricing Rates, Wallet, and billing Enterprise Overview Web execution for enterprise teams Solutions What we run, and where it runs in production Customers See who builds with TinyFish Contact Log In The web stack for AI agents. Search, extract, browse, and act on the web with one platform. Connect TinyFish + 9 Works with any AI Trusted by Trusted by What TinyFish can do. Four jobs people hand their agents every day, on the live web. Monitor web pages Get an alert when a page or topic changes. Prices, filings, job posts. acme.com/pricing Monitoring Starter $19 Pro $59 Team $129 TinyFish now Acme raised the Pro plan $49 → $59 a month Enrich records Check contacts and accounts against the live web and fill the gaps. crm.yourco.com/leads Company Headcount HQ Globex 1,240 Denver Initech 310 Austin Northwind 86 Kestrel Umbrella Checking northwind.com, kestrel.com 2 of 5 Research anything Ask a question, get an answer that links to every source it used. Which suppliers changed owner this year? TinyFish read 214 pages Two changed owner: Northwind and Kestrel . companieshouse.gov.uk sec.gov bloomberg.com reuters.com ft.com Automate portals Sign in, fill forms and pull files from other companies' portals. supplier.acme.com/onboarding Filling Supplier onboarding Step 3 of 10 Legal name Yourco Inc. Tax ID 84-2917305 Contact email Street address Next And much more. All solutions Use with ChatGPT , OpenClaw , Claude , Cursor , Hermes Grok or in your {code}. Connect TinyFish View API docs Give your agent the web. TinySearch Free Fresh results from the live web. Never cached. Real browser-rendered search that returns structured JSON. Try it TinyFetch Free From rendered page to clean data in one call. Markdown, JSON or HTML, token-efficient. Try it TinyBrowser Access the web that search can't reach. A real c\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-bd027eb312db3be2",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Alright. I have the Python SDK. I need an API key (TINYFISH_API_KEY). Let me check if it's in the environment variables — it wasn't included in the env grep output (only PI_GATEWAY_API_KEY and GIT_CONFIG_KEY_0 matched). So there's no TinyFish API key. Which means I can't actually execute any calls. Let me check the docs for pricing, build an example project, and then report back that I'm blocked due to the missing API key.
Let me fetch the pricing page and the rest of the SDK README.
curl -s -m 10 https://pypi.org/pypi/tinyfish/json | python3 -c "import json,sys;print(json.load(sys.stdin)['info']['description'])" > /tmp/tinyfish_readme.md; wc -l /tmp/tinyfish_readme.md; grep -n -i -E "price|pricing|credit|cost|wallet|\\$" /tmp/tinyfish_readme.md | head -50
543 /tmp/tinyfish_readme.md
543 /tmp/tinyfish_readme.md
25: goal="What is the current Bitcoin price?",
26: url="https://www.coinbase.com/price/bitcoin",
50:| [`wallet.get()`](#walletget--get-your-balance) | Get the caller's wallet balance, auto-reload, and rates | `WalletResponse` | — |
205:Pass `highlights={"query": "pricing tiers"}` (beta, markdown format only) to get query-ranked `HighlightSnippet` lists on each result instead of the full `text`; set `include_full_page_text=True` to keep both. Accounts not enrolled in the beta get `PermissionDeniedError`.
349:### `wallet.get()` — get your balance
354:from tinyfish import TinyFish, WalletAutoReloadState
358:wallet = client.wallet.get()
360:print(f"{wallet.available_balance} {wallet.currency}")
362:if wallet.auto_reload and wallet.auto_reload.state == WalletAutoReloadState.ON:
363: print(f"Auto-reload at {wallet.auto_reload.threshold}, tops up to {wallet.auto_reload.recharge_to}")
365:if wallet.pending_top_up:
366: print(f"Pending top-up: {wallet.pending_top_up.amount}")
369:**Returns `WalletResponse`:**
373:| `available_balance` | `str` | Current spendable balance. Negative means the wallet is overdrawn. |
375:| `as_of` | `str` | ISO timestamp the wallet was read |
376:| `auto_reload` | `WalletAutoReload \| None` | `None` means the auto-reload read failed — not the same as `state == "unconfigured"`, which means the read succeeded and nothing is set |
377:| `pending_top_up` | `WalletPendingTopUp \| None` | `None` means no in-flight top-up, or the ledger read failed |
378:| `rates` | `WalletRates \| None` | `None` means the rates read failed, or the wallet has no rate contract yet |
380:`auto_reload.state` is one of `WalletAutoReloadState.UNCONFIGURED`, `ON`, `OFF`, `PAUSED_PAYMENT_FAILED`, or `NEEDS_PAYMENT_METHOD`. `threshold` and `recharge_to` are only present for the four configured states.
382:Accounts are on either wallet billing or a legacy subscription/credits plan. `wallet.get()` raises `NotFoundError` (404) with `.code` of `"FEATURE_NOT_AVAILABLE"` when the account is on the legacy plan. A wallet account that hasn't added money yet is not an error — it returns normally with an `available_balance` of `"0"`.
388: wallet = client.wallet.get()
391: print("Account is on the legacy subscription/credits plan, not a wallet.")
420:All eight methods (`agent.run()`, `agent.queue()`, `agent.stream()`, `fetch.get_contents()`, `runs.get()`, `runs.list()`, `search.query()`, `wallet.get()`) work the same way — same parameters, just `await`-ed.
Recorded tool call
{
"type": "tool_call",
"seq": 20,
"ts": "2026-10-17T02:15:01.055Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -m 10 https://pypi.org/pypi/tinyfish/json | python3 -c \"import json,sys;print(json.load(sys.stdin)['info']['description'])\" > /tmp/tinyfish_readme.md; wc -l /tmp/tinyfish_readme.md; grep -n -i -E \"price|pricing|credit|cost|wallet|\\\\$\" /tmp/tinyfish_readme.md | head -50"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-8c40d438b4f0cded",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 24,
"ts": "2026-10-17T02:15:01.402Z",
"turn": 1,
"output": {
"content": [
{
"text": "543 /tmp/tinyfish_readme.md\n25: goal=\"What is the current Bitcoin price?\",\n26: url=\"https://www.coinbase.com/price/bitcoin\",\n50:| [`wallet.get()`](#walletget--get-your-balance) | Get the caller's wallet balance, auto-reload, and rates | `WalletResponse` | — |\n205:Pass `highlights={\"query\": \"pricing tiers\"}` (beta, markdown format only) to get query-ranked `HighlightSnippet` lists on each result instead of the full `text`; set `include_full_page_text=True` to keep both. Accounts not enrolled in the beta get `PermissionDeniedError`.\n349:### `wallet.get()` — get your balance\n354:from tinyfish import TinyFish, WalletAutoReloadState\n358:wallet = client.wallet.get()\n360:print(f\"{wallet.available_balance} {wallet.currency}\")\n362:if wallet.auto_reload and wallet.auto_reload.state == WalletAutoReloadState.ON:\n363: print(f\"Auto-reload at {wallet.auto_reload.threshold}, tops up to {wallet.auto_reload.recharge_to}\")\n365:if wallet.pending_top_up:\n366: print(f\"Pending top-up: {wallet.pending_top_up.amount}\")\n369:**Returns `WalletResponse`:**\n373:| `available_balance` | `str` | Current spendable balance. Negative means the wallet is overdrawn. |\n375:| `as_of` | `str` | ISO timestamp the wallet was read |\n376:| `auto_reload` | `WalletAutoReload \\| None` | `None` means the auto-reload read failed — not the same as `state == \"unconfigured\"`, which means the read succeeded and nothing is set |\n377:| `pending_top_up` | `WalletPendingTopUp \\| None` | `None` means no in-flight top-up, or the ledger read failed |\n378:| `rates` | `WalletRates \\| None` | `None` means the rates read failed, or the wallet has no rate contract yet |\n380:`auto_reload.state` is one of `WalletAutoReloadState.UNCONFIGURED`, `ON`, `OFF`, `PAUSED_PAYMENT_FAILED`, or `NEEDS_PAYMENT_METHOD`. `threshold` and `recharge_to` are only present for the four configured states.\n382:Accounts are on either wallet billing or a legacy subscription/credits plan. `wallet.get()` raises `NotFoundError` (404) with `.code` of `\"FEATURE_NOT_AVAILABLE\"` when the account is on the legacy plan. A wallet account that hasn't added money yet is not an error — it returns normally with an `available_balance` of `\"0\"`.\n388: wallet = client.wallet.get()\n391: print(\"Account is on the legacy subscription/credits plan, not a wallet.\")\n420:All eight methods (`agent.run()`, `agent.queue()`, `agent.stream()`, `fetch.get_contents()`, `runs.get()`, `runs.list()`, `search.query()`, `wallet.get()`) work the same way — same parameters, just `await`-ed.\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-8c40d438b4f0cded",
"is_error": false,
"tool_kind": "shell"
}curl -sL -m 15 -o /tmp/pricing.html -w "%{http_code} %{url_effective}\n" https://www.tinyfish.ai/pricing; python3 - <<'EOF'
import re,html
t=open('/tmp/pricing.html',encoding='utf-8',errors='ignore').read()
t=re.sub(r'<script.*?</script>','',t,flags=re.S)
t=re.sub(r'<style.*?</style>','',t,flags=re.S)
t=re.sub(r'<[^>]+>','|',t)
t=html.unescape(re.sub(r'\|+','|',t))
print(re.sub(r'\s+',' ',t)[:6000])
EOF200 https://www.tinyfish.ai/pricing
200 https://www.tinyfish.ai/pricing |Pricing | TinyFish|Products|Mako|Web-native model for live workflows|Search|Fast, structured web search|Fetch|Any URL to clean content|Agent|Multi-step web automation|Browser|Cloud browser sessions|All products share one API key|View docs| →|Resources|Documentation|API reference and guides|Partners|The Data Partner Alliance|Integrations|Connect with your stack|Compare|TinyFish vs alternatives|Benchmarks|How we measure up|Blog|Product updates and insights|Pulse|What we've shipped|Cookbook|Open-source examples|Pricing|Enterprise|Overview|Web execution for enterprise teams|Solutions|What we run, and where it runs in production|Customers|See who builds with TinyFish|Contact|Log In|Sign up|Sign up|Products|Mako|Web-native model for live workflows|Search|Fast, structured web search|Fetch|Any URL to clean content|Agent|Multi-step web automation|Browser|Cloud browser sessions|Resources|Documentation|API reference and guides|Partners|The Data Partner Alliance|Integrations|Connect with your stack|Compare|TinyFish vs alternatives|Benchmarks|How we measure up|Blog|Product updates and insights|Pulse|What we've shipped|Cookbook|Open-source examples|Pricing|Rates, Wallet, and billing|Enterprise|Overview|Web execution for enterprise teams|Solutions|What we run, and where it runs in production|Customers|See who builds with TinyFish|Contact|Log In|Pricing|Pay only for what you run. No plans, no minimums, and a contract when you need more.|Get started for free|Product|Price|Search|Free|Limits: 30 requests / min · 500 requests / hour|$0.00|per request|Fetch|Free|Limits: 150 urls / min · 1,000 urls / day|$0.00|per url|Agent|Limit: 2 concurrent runs|$0.016|per step|Browser|Limit: 5 concurrent sessions|$0.002|per minute|Monitor|New|Watches a page or a search topic on a schedule. Alerts by email or webhook when something changes.|$0.005|per check|Pick your path|Pay as you go|Start free with Search and Fetch. Add Wallet funds when you need Agent or Browser.|Get started for free|No card required|Includes|–|Search and Fetch free at any Wallet balance|–|Agent and Browser metered against your Wallet|–|No subscription and no monthly minimum|–|Vault + Profile|–|Auto-reload|–|SDKs, CLI, and MCP|Enterprise|For teams that need limits set to their workload, a contract, and a security review.|Talk to sales|Adds|–|Rate limits and concurrency set to your workload|–|ISO 27001|–|Enterprise SSO|–|Audit logs|–|VPC deployment|–|99.99% uptime SLA|Agent and Browser|Everything a run needs|LLM inference|Agent|The model the agent reasons with. No key to bring, no provider bill on top.|Residential proxies|Requests route through real residential IPs, so sites serve the page they would serve a person.|Anti-bot handling|Bot checks are handled at the infrastructure layer, before the run reaches them.|Storage|Screenshots, logs and source URLs from every run, kept and addressable.|Common questions|What is the Wallet?|+|What is the Wallet?|A prepaid balance on your account. Agent and Browser draw from it as they run; Search and Fetch never do. There is no subscription and no seats. You add funds and spend them on what you run.|How do I add funds?|+|How do I add funds?|Add a card, then add funds to your Wallet — $10.00 minimum. After your first deposit you can turn on auto-reload so the balance refills itself. New accounts start with $8 in Wallet funds, so you can run Agent and Browser before you add a card.|What happens when my Wallet reaches $0?|+|What happens when my Wallet reaches $0?|Nothing breaks. Search and Fetch keep running within their limits, since neither ever draws from the Wallet. An Agent run that is already under way will finish. Agent and Browser need funds to start, so make a deposit when you want to run them again.|Is there a monthly plan?|+|Is there a monthly plan?|No. There are no recurring Wallet plans. You fund the Wallet and pay for the Agent and Browser usage you actually run.|Are Search and Fetch really free?|+|Are Search and Fetch really free?|Yes, Search at 30 requests / min and 500 requests / hour, Fetch at 150 urls / min and 1,000 urls / day. No charge, no Wallet draw, and they keep working at a $0 balance.|Can I raise concurrency or rate limits?|+|Can I raise concurrency or rate limits?|Yes. Accounts start at 2 concurrent runs on Agent and 5 concurrent sessions on Browser, and both go up. Talk to sales and we will set them to your workload. Limits live on your account rather than being bought with Wallet funds, so raising them is a conversation, not a purchase.|I'm on a legacy credit plan. Where do I see my pricing?|+|I'm on a legacy credit plan. Where do I see my pricing?|Your credit rates and balance live in your account dashboard. Sign in and they are all there.|Start building|Search and Fetch are free. Fund the Wallet when you need Agent or Browser.|Get started for free|Talk to sales|Web APIs built for agents.|Product|Mako|Enterprise|Solutions|Customers|Pricing|Partners|Integrations|Compare|Docs|Trust|Resources|Benchmarks|Cookbook|Blog|Pulse|Current|Accelerator|Students|Ambassadors|Connect|X/Twitter|LinkedIn|Discord|GitHub|Contact Us|© |2026| TinyFish|·|Privacy|·|Cookies|·|Terms|
Recorded tool call
{
"type": "tool_call",
"seq": 22,
"ts": "2026-10-17T02:15:01.055Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -sL -m 15 -o /tmp/pricing.html -w \"%{http_code} %{url_effective}\\n\" https://www.tinyfish.ai/pricing; python3 - <<'EOF'\nimport re,html\nt=open('/tmp/pricing.html',encoding='utf-8',errors='ignore').read()\nt=re.sub(r'<script.*?</script>','',t,flags=re.S)\nt=re.sub(r'<style.*?</style>','',t,flags=re.S)\nt=re.sub(r'<[^>]+>','|',t)\nt=html.unescape(re.sub(r'\\|+','|',t))\nprint(re.sub(r'\\s+',' ',t)[:6000])\nEOF"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-90b1f6ca43c1a989",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 25,
"ts": "2026-10-17T02:15:03.037Z",
"turn": 1,
"output": {
"content": [
{
"text": "200 https://www.tinyfish.ai/pricing\n|Pricing | TinyFish|Products|Mako|Web-native model for live workflows|Search|Fast, structured web search|Fetch|Any URL to clean content|Agent|Multi-step web automation|Browser|Cloud browser sessions|All products share one API key|View docs| →|Resources|Documentation|API reference and guides|Partners|The Data Partner Alliance|Integrations|Connect with your stack|Compare|TinyFish vs alternatives|Benchmarks|How we measure up|Blog|Product updates and insights|Pulse|What we've shipped|Cookbook|Open-source examples|Pricing|Enterprise|Overview|Web execution for enterprise teams|Solutions|What we run, and where it runs in production|Customers|See who builds with TinyFish|Contact|Log In|Sign up|Sign up|Products|Mako|Web-native model for live workflows|Search|Fast, structured web search|Fetch|Any URL to clean content|Agent|Multi-step web automation|Browser|Cloud browser sessions|Resources|Documentation|API reference and guides|Partners|The Data Partner Alliance|Integrations|Connect with your stack|Compare|TinyFish vs alternatives|Benchmarks|How we measure up|Blog|Product updates and insights|Pulse|What we've shipped|Cookbook|Open-source examples|Pricing|Rates, Wallet, and billing|Enterprise|Overview|Web execution for enterprise teams|Solutions|What we run, and where it runs in production|Customers|See who builds with TinyFish|Contact|Log In|Pricing|Pay only for what you run. No plans, no minimums, and a contract when you need more.|Get started for free|Product|Price|Search|Free|Limits: 30 requests / min · 500 requests / hour|$0.00|per request|Fetch|Free|Limits: 150 urls / min · 1,000 urls / day|$0.00|per url|Agent|Limit: 2 concurrent runs|$0.016|per step|Browser|Limit: 5 concurrent sessions|$0.002|per minute|Monitor|New|Watches a page or a search topic on a schedule. Alerts by email or webhook when something changes.|$0.005|per check|Pick your path|Pay as you go|Start free with Search and Fetch. Add Wallet funds when you need Agent or Browser.|Get started for free|No card required|Includes|–|Search and Fetch free at any Wallet balance|–|Agent and Browser metered against your Wallet|–|No subscription and no monthly minimum|–|Vault + Profile|–|Auto-reload|–|SDKs, CLI, and MCP|Enterprise|For teams that need limits set to their workload, a contract, and a security review.|Talk to sales|Adds|–|Rate limits and concurrency set to your workload|–|ISO 27001|–|Enterprise SSO|–|Audit logs|–|VPC deployment|–|99.99% uptime SLA|Agent and Browser|Everything a run needs|LLM inference|Agent|The model the agent reasons with. No key to bring, no provider bill on top.|Residential proxies|Requests route through real residential IPs, so sites serve the page they would serve a person.|Anti-bot handling|Bot checks are handled at the infrastructure layer, before the run reaches them.|Storage|Screenshots, logs and source URLs from every run, kept and addressable.|Common questions|What is the Wallet?|+|What is the Wallet?|A prepaid balance on your account. Agent and Browser draw from it as they run; Search and Fetch never do. There is no subscription and no seats. You add funds and spend them on what you run.|How do I add funds?|+|How do I add funds?|Add a card, then add funds to your Wallet — $10.00 minimum. After your first deposit you can turn on auto-reload so the balance refills itself. New accounts start with $8 in Wallet funds, so you can run Agent and Browser before you add a card.|What happens when my Wallet reaches $0?|+|What happens when my Wallet reaches $0?|Nothing breaks. Search and Fetch keep running within their limits, since neither ever draws from the Wallet. An Agent run that is already under way will finish. Agent and Browser need funds to start, so make a deposit when you want to run them again.|Is there a monthly plan?|+|Is there a monthly plan?|No. There are no recurring Wallet plans. You fund the Wallet and pay for the Agent and Browser usage you actually run.|Are Search and Fetch really free?|+|Are Search and Fetch really free?|Yes, Search at 30 requests / min and 500 requests / hour, Fetch at 150 urls / min and 1,000 urls / day. No charge, no Wallet draw, and they keep working at a $0 balance.|Can I raise concurrency or rate limits?|+|Can I raise concurrency or rate limits?|Yes. Accounts start at 2 concurrent runs on Agent and 5 concurrent sessions on Browser, and both go up. Talk to sales and we will set them to your workload. Limits live on your account rather than being bought with Wallet funds, so raising them is a conversation, not a purchase.|I'm on a legacy credit plan. Where do I see my pricing?|+|I'm on a legacy credit plan. Where do I see my pricing?|Your credit rates and balance live in your account dashboard. Sign in and they are all there.|Start building|Search and Fetch are free. Fund the Wallet when you need Agent or Browser.|Get started for free|Talk to sales|Web APIs built for agents.|Product|Mako|Enterprise|Solutions|Customers|Pricing|Partners|Integrations|Compare|Docs|Trust|Resources|Benchmarks|Cookbook|Blog|Pulse|Current|Accelerator|Students|Ambassadors|Connect|X/Twitter|LinkedIn|Discord|GitHub|Contact Us|© |2026| TinyFish|·|Privacy|·|Cookies|·|Terms|\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-90b1f6ca43c1a989",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Pricing has already been obtained. Next, create an example. Need to check whether the API key exists in the environment variables — it doesn't exist. Should I check the documentation for the quickstart requirements? The SDK quickstart only needs TINYFISH_API_KEY. Since it's not in the environment variables, I can't execute live calls. But it's still possible to create the example, install the SDK, and verify that the import works (without any API calls). After that, report the blocked situation: without the key, live execution is impossible.
Let me read more of the README to check the details of the search.query and fetch examples.
✓read /tmp/tinyfish_readme.md✓└{"content":[{"text":"```python\nfrom tinyfish import TinyFish, CompleteEvent, ProgressEvent\n\nclient = TinyFish()\n\nwith client.agent.stream(\n goal=\"Extr…ctrl+o
{"content":[{"text":"```python\nfrom tinyfish import TinyFish, CompleteEvent, ProgressEvent\n\nclient = TinyFish()\n\nwith client.agent.stream(\n goal=\"Extract the top 5 headlines\", # required — what to do on the page\n url=\"https://news.ycombinator.com\", # required — URL to open\n browser_profile=None, # optional — \"lite\" (default) or \"stealth\"\n proxy_config=None, # optional — proxy settings\n on_started=lambda e: print(f\"Started: {e.run_id}\"), # optional — called when run starts\n on_streaming_url=lambda e: print(f\"Watch: {e.streaming_url}\"), # optional — called with live browser URL\n on_progress=lambda e: print(f\" > {e.purpose}\"), # optional — called on each step\n on_heartbeat=lambda e: None, # optional — called on keepalive pings\n on_complete=lambda e: print(f\"Done: {e.status}\"), # optional — called when run finishes\n) as stream:\n for event in stream:\n # Callbacks fire automatically during iteration.\n # You can also inspect events directly:\n if isinstance(event, CompleteEvent):\n print(event.result_json)\n```\n\n**Returns `AgentStream`** — a context manager you iterate over. Events arrive in order: `STARTED` → `STREAMING_URL` → `PROGRESS` (repeated) → `COMPLETE`.\n\nSee the [Streaming Guide](docs/streaming-guide.md) for the full event lifecycle, event types, and advanced patterns.\n\n---\n\n### `fetch.get_contents()` — clean content\n\nFetch extracted content from one or more URLs without a browser-agent run.\n\n```python\nfrom tinyfish import TinyFish\n\nclient = TinyFish()\n\nresponse = client.fetch.get_contents(\n [\n \"https://example.com\",\n \"https://example.org\",\n ],\n format=\"markdown\",\n links=True,\n image_links=False,\n per_url_timeout_ms=45_000,\n)\n\nprint(response.results)\nprint(response.errors)\n```\n\n`fetch.get_contents()` accepts 1 to 10 URLs. 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.\n\nPass `highlights={\"query\": \"pricing tiers\"}` (beta, markdown format only) to get query-ranked `HighlightSnippet` lists on each result instead of the full `text`; set `include_full_page_text=True` to keep both. Accounts not enrolled in the beta get `PermissionDeniedError`.\n\n**Returns `FetchResponse`:**\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `results` | `list[FetchResult]` | Successfully fetched URLs |\n| `errors` | `list[FetchError]` | URLs that failed to fetch or extract |\n\n---\n\n### `runs.get()` — retrieve a single run\n\nFetch the full details of a run by its ID.\n\n```python\nrun = client.runs.get(\n \"run_abc123\", # required — the run ID\n)\n\nprint(run.status) # PENDING, RUNNING, COMPLETED, FAILED, CANCELLED\nprint(run.result)\nprint(run.goal)\nprint(run.output_schema) # requested structured-output contract, if one was provided\nprint(run.streaming_url) # live browser URL (while RUNNING)\nprint(run.browser_config) # proxy/browser settings that were used\n```\n\n**Returns `Run`:**\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `run_id` | `str` | Unique identifier |\n| `status` | `RunStatus` | `PENDING`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED` |\n| `goal` | `str` | The goal that was given |\n| `result` | `dict \\| None` | Extracted data (`None` if not completed) |\n| `output_schema` | `dict \\| None` | JSON Schema contract originally requested for the run |\n| `error` | `RunError \\| None` | Error details (`None` if succeeded) |\n| `streaming_url` | `str \\| None` | Live browser URL (available while running) |\n| `browser_config` | `BrowserConfig \\| None` | Proxy/browser settings used |\n| `created_at` | `datetime` | When the run was created |\n| `started_at` | `datetime \\| None` | When execution started |\n| `finished_at` | `datetime \\| None` | When execution finished |\n\n**Raises:** `ValueError` if `run_id` is empty. `NotFoundError` if no run exists with that ID.\n\n---\n\n### `runs.list()` — list and filter runs\n\nList runs with optional filtering, sorting, and cursor-based pagination. All parameters are optional.\n\n```python\nfrom tinyfish import RunStatus, SortDirection\n\nresponse = client.runs.list(\n status=RunStatus.COMPLETED, # optional — filter by status\n goal=\"headlines\", # optional — filter by goal text\n created_after=\"2025-01-01T00:00:00Z\", # optional — ISO 8601 lower bound\n created_before=\"2025-12-31T23:59:59Z\", # optional — ISO 8601 upper bound\n sort_direction=SortDirection.DESC, # optional — \"asc\" or \"desc\"\n limit=10, # optional — max runs per page\n cursor=None, # optional — pagination cursor from previous response\n)\n\nfor run in response.data:\n print(f\"{run.run_id} | {run.goal}\")\n\n# Pagination\nif response.pagination.has_more:\n next_page = client.runs.list(cursor=response.pagination.next_cursor)\n```\n\n**Returns `RunListResponse`:**\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `data` | `list[Run]` | List of runs |\n| `pagination.total` | `int` | Total runs matching filters |\n| `pagination.has_more` | `bool` | Whether more pages exist |\n| `pagination.next_cursor` | `str \\| None` | Pass to `cursor=` for the next page |\n\nSee the [Pagination Guide](docs/pagination-guide.md) for full pagination loop examples.\n\n---\n\n### `search.query()` — search the web\n\nReturns ranked web search results with titles, snippets, and URLs.\n\n```python\nfrom tinyfish import TinyFish\n\nclient = TinyFish()\n\nresponse = client.search.query(\"FIFA\")\n\nprint(response.query)\nprint(response.total_results)\nprint(response.results[0].title if response.results else \"No results\")\n```\n\nOptional parameters:\n\n- `location` — country code for geo-targeted results (e.g. `\"US\"`, `\"GB\"`)\n- `language` — language code (e.g. `\"en\"`, `\"fr\"`)\n- `page` — page number, 0-indexed, max `10`\n- `recency_minutes` — freshness window in minutes (`1` to `5256000`)\n- `after_date` / `before_date` — calendar date range in `YYYY-MM-DD`\n- `domain_type` — result category: `\"web\"` (default), `\"news\"`, or `\"research_paper\"`\n- `pub_year_min` / `pub_year_max` — publication-year range, inclusive (`0` to `9999`). Only supported for `domain_type=\"research_paper\"`\n\n```python\n# geo-targeted\nresponse = client.search.query(\"FIFA\", location=\"US\", language=\"en\")\n\n# freshness window\nresponse = client.search.query(\"FIFA\", recency_minutes=60)\n\n# calendar date range\nresponse = client.search.query(\"FIFA\", after_date=\"2026-06-01\", before_date=\"2026-06-18\")\n\n# domain type\nresponse = client.search.query(\"FIFA\", domain_type=\"news\")\nresponse = client.search.query(\"machine learning\", domain_type=\"research_paper\")\n\n# publication-year range (research_paper only)\nresponse = client.search.query(\n \"transformer architecture\", domain_type=\"research_paper\", pub_year_min=2019, pub_year_max=2022\n)\n```\n\nFilter validation rules:\n\n- `recency_minutes` must be an integer from `1` to `5256000`\n- `after_date` and `before_date` must use `YYYY-MM-DD`\n- `recency_minutes` cannot be combined with `after_date` or `before_date`\n- if both dates are present, `after_date` must be less than or equal to `before_date`\n- `domain_type` must be one of `\"web\"`, `\"news\"`, or `\"research_paper\"`\n- `pub_year_min` and `pub_year_max` must be integers from `0` to `9999`\n- if both are present, `pub_year_min` must be less than or equal to `pub_year_max`\n\n---\n\n### `wallet.get()` — get your balance\n\n[195 more lines in file. Use offset=350 to continue.]","type":"text"}]}cd /sandbox/repo && python3 -V && pip install -q tinyfish 2>&1 | tail -3; python3 -c "import tinyfish,inspect;print(tinyfish.__version__ if hasattr(tinyfish,'__version__') else 'n/a'); from tinyfish import TinyFish; print([m for m in dir(TinyFish) if not m.startswith('_')])"Python 3.12.3
Python 3.12.3 0.9.0 ['close', 'product_url']
Recorded tool call
{
"type": "tool_call",
"seq": 29,
"ts": "2026-10-17T02:15:04.831Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && python3 -V && pip install -q tinyfish 2>&1 | tail -3; python3 -c \"import tinyfish,inspect;print(tinyfish.__version__ if hasattr(tinyfish,'__version__') else 'n/a'); from tinyfish import TinyFish; print([m for m in dir(TinyFish) if not m.startswith('_')])\""
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-ab6dd2054f8a8ce4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 32,
"ts": "2026-10-17T02:15:09.397Z",
"turn": 1,
"output": {
"content": [
{
"text": "Python 3.12.3\n0.9.0\n['close', 'product_url']\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-ab6dd2054f8a8ce4",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
SDK is already installed (0.9.0). Check the API key in the environment variables — it doesn't exist. Should I try running without the key? Let me quickly verify whether an error is raised on initialization when TINYFISH_API_KEY is not set. Let's build a sample project: small demo scripts (free search + fetch, optional agent run), a README noting the pricing, and execution of a smoke test that only verifies the imports and shows that a live call requires a key.
Just in case, check for .env or credentials in the repository.
cd /sandbox/repo && ls -a; git log --oneline -3; find / -maxdepth 3 -name "*tinyfish*" -not -path "*/node_modules/*" 2>/dev/null; env | grep -i TINYFISH || echo "no TINYFISH_API_KEY"
.
. .. .git .gitignore 9ebf5fb Initial workspace /tmp/tinyfish_readme.md /tmp/tinyfish.html no TINYFISH_API_KEY
Recorded tool call
{
"type": "tool_call",
"seq": 35,
"ts": "2026-10-17T02:15:11.171Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && ls -a; git log --oneline -3; find / -maxdepth 3 -name \"*tinyfish*\" -not -path \"*/node_modules/*\" 2>/dev/null; env | grep -i TINYFISH || echo \"no TINYFISH_API_KEY\""
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9f1f8a14e17e8cf8",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 38,
"ts": "2026-10-17T02:15:11.514Z",
"turn": 1,
"output": {
"content": [
{
"text": ".\n..\n.git\n.gitignore\n9ebf5fb Initial workspace\n/tmp/tinyfish_readme.md\n/tmp/tinyfish.html\nno TINYFISH_API_KEY\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9f1f8a14e17e8cf8",
"is_error": false,
"tool_kind": "shell"
}✓read /tmp/tinyfish_readme.md✓└{"content":[{"text":" if e.code == \"FEATURE_NOT_AVAILABLE\":\n print(\"Account is on the legacy subscription/credits plan, not a wallet.\")\n else…ctrl+o
{"content":[{"text":" if e.code == \"FEATURE_NOT_AVAILABLE\":\n print(\"Account is on the legacy subscription/credits plan, not a wallet.\")\n else:\n raise\n```\n\n---\n\n## Sync vs Async\n\nUse `AsyncTinyFish` when you're in an async context (FastAPI, aiohttp, etc.):\n\n**Sync:**\n\n```python\nfrom tinyfish import TinyFish\n\nclient = TinyFish()\nresponse = client.agent.run(goal=\"...\", url=\"...\")\n```\n\n**Async:**\n\n```python\nfrom tinyfish import AsyncTinyFish\n\nclient = AsyncTinyFish()\nresponse = await client.agent.run(goal=\"...\", url=\"...\")\n```\n\nAll eight methods (`agent.run()`, `agent.queue()`, `agent.stream()`, `fetch.get_contents()`, `runs.get()`, `runs.list()`, `search.query()`, `wallet.get()`) work the same way — same parameters, just `await`-ed.\n\n## Configuration\n\n### Client options\n\n```python\nclient = TinyFish(\n api_key=\"your-api-key\", # optional — or set TINYFISH_API_KEY env var\n base_url=None, # optional — omit it: Search and Fetch then use\n # https://api.search.tinyfish.ai and\n # https://api.fetch.tinyfish.ai; any explicit value\n # keeps every product on it. Do not pass a product host\n # here — it serves at the root, so /v1/search would 404\n timeout=600.0, # optional — seconds (default: 600)\n max_retries=2, # optional — retry attempts (default: 2)\n)\n```\n\nThe SDK retries `408`, `429`, and `5xx` errors automatically with exponential backoff (0.5s multiplier, max 8s wait).\n\n### Browser profiles\n\nControl the browser environment with `browser_profile`:\n\n- **`lite`** (default) — fast, lightweight. Good for most sites.\n- **`stealth`** — anti-detection mode. Use for sites with bot protection.\n\n```python\nfrom tinyfish import BrowserProfile\n\nresponse = client.agent.run(\n goal=\"...\",\n url=\"...\",\n browser_profile=BrowserProfile.STEALTH,\n)\n```\n\n### Agent controls and capture\n\n`agent.run()`, `agent.queue()`, and `agent.stream()` also accept `agent_config`, `capture_config`, `webhook_url`, `use_profile`, and `profile_id`:\n\n```python\nfrom tinyfish import AgentConfig, CaptureConfig\n\nresponse = client.agent.run(\n goal=\"...\",\n url=\"...\",\n agent_config=AgentConfig(mode=\"strict\", cursor_style=\"standard\", max_duration_seconds=300),\n capture_config=CaptureConfig(screenshots=True, html=True),\n webhook_url=\"https://example.com/tinyfish-webhook\",\n use_profile=True,\n profile_id=\"prof_abc123def4567890\",\n)\n```\n\n- `agent_config` (`AgentConfig`) — `mode`, `cursor_style`, `max_steps`, `max_duration_seconds`\n- `capture_config` (`CaptureConfig`) — `elements`, `snapshots`, `screenshots`, `recording`, `html`\n- `webhook_url` — HTTPS URL notified on run lifecycle events\n- `profile_id` — a specific Browser Context Profile; requires `use_profile=True`\n\n`elements` and `recording` are gated capabilities: an account without them enabled gets a 403 saying the capability is not enabled.\n\n### Browser Context Profiles\n\nUse Browser Context Profiles when a run should start from saved logged-in state. Pass `use_profile=True` for your default profile, or add `profile_id` for a specific profile. Pair with `use_vault=True` when TinyFish should repair stale sessions with saved credentials.\n\n```python\nresponse = client.agent.run(\n goal=\"Summarize the dashboard\",\n url=\"https://app.example.com/dashboard\",\n use_profile=True,\n profile_id=\"prof_abc123def4567890\",\n use_vault=True,\n)\n```\n\n### Proxy configuration\n\nRoute requests through a proxy, optionally pinned to a country:\n\n```python\nfrom tinyfish import ProxyConfig, ProxyCountryCode\n\nresponse = client.agent.run(\n goal=\"...\",\n url=\"...\",\n proxy_config=ProxyConfig(enabled=True, country_code=ProxyCountryCode.US),\n)\n```\n\nAvailable countries: `US`, `GB`, `CA`, `DE`, `FR`, `JP`, `AU`.\n\nSee the [Proxy & Browser Profiles Guide](docs/proxy-and-browser-profiles.md) for more details.\n\n## Error handling\n\n```python\nfrom tinyfish import TinyFish, AuthenticationError, RateLimitError, SDKError\n\nclient = TinyFish()\n\ntry:\n response = client.agent.run(goal=\"...\", url=\"...\")\nexcept AuthenticationError:\n print(\"Invalid API key\")\nexcept RateLimitError:\n print(\"Rate limited (retries exhausted)\")\nexcept SDKError:\n print(\"Something else went wrong\")\n```\n\nThe SDK automatically retries transient errors (`408`, `429`, `5xx`) up to `max_retries` times with exponential backoff. Non-retryable errors (`401`, `400`, `404`) raise immediately.\n\nFor the full exception hierarchy and internal architecture, see [docs/internal/exceptions-and-errors-guide.md](docs/internal/exceptions-and-errors-guide.md).\n\n## Guides\n\n- [Streaming Guide](docs/streaming-guide.md) — event lifecycle, callbacks vs iteration, event type reference\n- [Proxy & Browser Profiles](docs/proxy-and-browser-profiles.md) — stealth mode, proxy countries\n- [Pagination Guide](docs/pagination-guide.md) — filtering, sorting, cursor-based pagination\n- [Exceptions & Error Handling (internal)](docs/internal/exceptions-and-errors-guide.md) — layer-by-layer architecture\n- [Testing Guide](tests/testing_guide.md) — running and writing tests\n\n","type":"text"}]}›Thinking…
Alright, let me build an example project. Small, lightweight: 3 scripts + README. No local stack. Since there's no API key, I can't run it live. Verify with a dry run (constructing the client without making network calls) and display the error behavior when the key is missing.
Let me check how TinyFish() behaves without a key — a quick test.
cd /tmp && python3 -c "
from tinyfish import TinyFish
try:
c=TinyFish(); print('constructed OK')
except Exception as e: print(type(e).__name__, e)
"ValueError No API key provided. Pass api_key= to the client or set the TINYFISH_API_KEY environment variable.
ValueError No API key provided. Pass api_key= to the client or set the TINYFISH_API_KEY environment variable.
Recorded tool call
{
"type": "tool_call",
"seq": 41,
"ts": "2026-10-17T02:15:13.078Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && python3 -c \"\nfrom tinyfish import TinyFish\ntry:\n c=TinyFish(); print('constructed OK')\nexcept Exception as e: print(type(e).__name__, e)\n\""
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-818d1130771c2ee6",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 42,
"ts": "2026-10-17T02:15:13.407Z",
"turn": 1,
"output": {
"content": [
{
"text": "ValueError No API key provided. Pass api_key= to the client or set the TINYFISH_API_KEY environment variable.\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-818d1130771c2ee6",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Alright, I'll write the example files to /sandbox/repo.
File edits
1 file
Writtentinyfish_example/README.mdView changes
/sandbox/repo/tinyfish_example/README.md
Written content · previous contents were not recorded
# TinyFish exampleA minimal example using the **hosted** TinyFish API through the official Python SDK(`pip install tinyfish`, v0.9.0). No local services, no browsers to run — every callgoes to TinyFish's cloud.TinyFish exposes four products on one API key: **Search**, **Fetch** (both free),**Agent** (multi-step web automation), and **Browser**. This example touches Search,Fetch, and Agent.## Setup```bashpip install -r requirements.txt # tinyfish>=0.9export TINYFISH_API_KEY="..." # from https://agent.tinyfish.ai/api-keys```## Run```bashpython search_and_fetch.py # free tier: web search + clean page contentpython agent_run.py # paid: one blocking agent run (~$0.016/step)python wallet_check.py # paid-tier hygiene: show balance before spending```All three scripts are single-file, single-call examples with error handling for`AuthenticationError` / `NotFoundError` / `RateLimitError`.## Pricing (as published on tinyfish.ai/pricing)| Product | Price | Notes ||---|---|---|| Search | **$0.00**/request | Free. 30 req/min, 500 req/hr || Fetch | **$0.00**/url | Free. 150 urls/min, 1,000 urls/day || Agent | **$0.016**/step | Draws from Wallet; 2 concurrent runs by default || Browser | **$0.002**/minute | Draws from Wallet; 5 concurrent sessions || Monitor | **$0.005**/check | Scheduled page/topic watch with alerts |- No subscription, no seats, no monthly minimum. Pay-as-you-go.- **Wallet** = prepaid balance. Agent/Browser spend from it; Search/Fetch never do andkeep working at a $0 balance. New accounts start with ~$8 of Wallet funds; top-upsare $10 minimum, with optional auto-reload.- LLM inference, residential proxies, anti-bot handling, and run storage are includedin the Agent per-step price — no separate model bill.- Higher rate limits / concurrency are a sales conversation, not a purchase.- `wallet.get()` in the SDK returns balance, auto-reload state, and rates(raises `NotFoundError` with code `FEATURE_NOT_AVAILABLE` on legacy credit plans).
Source: write · chatcmpl-tool-9026e389e6567dfb
Recorded tool call
{
"type": "tool_call",
"seq": 45,
"ts": "2026-10-17T02:15:22.353Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/tinyfish_example/README.md",
"content": "# TinyFish example\n\nA minimal example using the **hosted** TinyFish API through the official Python SDK\n(`pip install tinyfish`, v0.9.0). No local services, no browsers to run — every call\ngoes to TinyFish's cloud.\n\nTinyFish exposes four products on one API key: **Search**, **Fetch** (both free),\n**Agent** (multi-step web automation), and **Browser**. This example touches Search,\nFetch, and Agent.\n\n## Setup\n\n```bash\npip install -r requirements.txt # tinyfish>=0.9\nexport TINYFISH_API_KEY=\"...\" # from https://agent.tinyfish.ai/api-keys\n```\n\n## Run\n\n```bash\npython search_and_fetch.py # free tier: web search + clean page content\npython agent_run.py # paid: one blocking agent run (~$0.016/step)\npython wallet_check.py # paid-tier hygiene: show balance before spending\n```\n\nAll three scripts are single-file, single-call examples with error handling for\n`AuthenticationError` / `NotFoundError` / `RateLimitError`.\n\n## Pricing (as published on tinyfish.ai/pricing)\n\n| Product | Price | Notes |\n|---|---|---|\n| Search | **$0.00**/request | Free. 30 req/min, 500 req/hr |\n| Fetch | **$0.00**/url | Free. 150 urls/min, 1,000 urls/day |\n| Agent | **$0.016**/step | Draws from Wallet; 2 concurrent runs by default |\n| Browser | **$0.002**/minute | Draws from Wallet; 5 concurrent sessions |\n| Monitor | **$0.005**/check | Scheduled page/topic watch with alerts |\n\n- No subscription, no seats, no monthly minimum. Pay-as-you-go.\n- **Wallet** = prepaid balance. Agent/Browser spend from it; Search/Fetch never do and\n keep working at a $0 balance. New accounts start with ~$8 of Wallet funds; top-ups\n are $10 minimum, with optional auto-reload.\n- LLM inference, residential proxies, anti-bot handling, and run storage are included\n in the Agent per-step price — no separate model bill.\n- Higher rate limits / concurrency are a sales conversation, not a purchase.\n- `wallet.get()` in the SDK returns balance, auto-reload state, and rates\n (raises `NotFoundError` with code `FEATURE_NOT_AVAILABLE` on legacy credit plans).\n"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9026e389e6567dfb",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 50,
"ts": "2026-10-17T02:15:22.353Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 2064 bytes to /sandbox/repo/tinyfish_example/README.md",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9026e389e6567dfb",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtentinyfish_example/search_and_fetch.pyView changes
/sandbox/repo/tinyfish_example/search_and_fetch.py
Written content · previous contents were not recorded
"""Free-tier example: TinyFish Search + Fetch.Both products are $0.00 and never draw from the Wallet, so this script is safe torun repeatedly (within rate limits).export TINYFISH_API_KEY="..."python search_and_fetch.py"""from tinyfish import AuthenticationError, RateLimitError, SDKError, TinyFishclient = TinyFish() # reads TINYFISH_API_KEYtry:# 1) Structured web search — returns ranked titles/snippets/URLs as JSON.search = client.search.query("TinyFish web agent pricing", location="US", language="en")print(f"Search: {search.total_results} results for {search.query!r}")for r in search.results[:3]:print(f" - {r.title}\n {r.url}")# 2) Fetch: rendered page -> clean markdown, no browser agent involved.# Accepts 1-10 URLs per call; failures land in response.errors.top_url = search.results[0].url if search.results else "https://www.tinyfish.ai/pricing"fetched = client.fetch.get_contents([top_url], format="markdown", links=False)for result in fetched.results:print(f"\nFetch OK: {result.url} ({len(result.text)} chars)")print(result.text[:500])for err in fetched.errors:print(f"\nFetch failed: {err}")except AuthenticationError:raise SystemExit("Invalid API key — check TINYFISH_API_KEY")except RateLimitError:raise SystemExit("Rate limited after SDK retries — slow down")except SDKError as e:raise SystemExit(f"TinyFish API error: {e}")
Source: write · chatcmpl-tool-82c2b5f73e3e4d57
Recorded tool call
{
"type": "tool_call",
"seq": 46,
"ts": "2026-10-17T02:15:22.353Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/tinyfish_example/search_and_fetch.py",
"content": "\"\"\"Free-tier example: TinyFish Search + Fetch.\n\nBoth products are $0.00 and never draw from the Wallet, so this script is safe to\nrun repeatedly (within rate limits).\n\n export TINYFISH_API_KEY=\"...\"\n python search_and_fetch.py\n\"\"\"\n\nfrom tinyfish import AuthenticationError, RateLimitError, SDKError, TinyFish\n\nclient = TinyFish() # reads TINYFISH_API_KEY\n\ntry:\n # 1) Structured web search — returns ranked titles/snippets/URLs as JSON.\n search = client.search.query(\"TinyFish web agent pricing\", location=\"US\", language=\"en\")\n print(f\"Search: {search.total_results} results for {search.query!r}\")\n for r in search.results[:3]:\n print(f\" - {r.title}\\n {r.url}\")\n\n # 2) Fetch: rendered page -> clean markdown, no browser agent involved.\n # Accepts 1-10 URLs per call; failures land in response.errors.\n top_url = search.results[0].url if search.results else \"https://www.tinyfish.ai/pricing\"\n fetched = client.fetch.get_contents([top_url], format=\"markdown\", links=False)\n\n for result in fetched.results:\n print(f\"\\nFetch OK: {result.url} ({len(result.text)} chars)\")\n print(result.text[:500])\n for err in fetched.errors:\n print(f\"\\nFetch failed: {err}\")\n\nexcept AuthenticationError:\n raise SystemExit(\"Invalid API key — check TINYFISH_API_KEY\")\nexcept RateLimitError:\n raise SystemExit(\"Rate limited after SDK retries — slow down\")\nexcept SDKError as e:\n raise SystemExit(f\"TinyFish API error: {e}\")\n"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-82c2b5f73e3e4d57",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 51,
"ts": "2026-10-17T02:15:22.353Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 1480 bytes to /sandbox/repo/tinyfish_example/search_and_fetch.py",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-82c2b5f73e3e4d57",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtentinyfish_example/agent_run.pyView changes
/sandbox/repo/tinyfish_example/agent_run.py
Written content · previous contents were not recorded
"""Paid-tier example: one blocking TinyFish Agent run ($0.016 per step, from Wallet).The agent opens the URL in TinyFish's cloud browser (residential proxies andanti-bot handling included), works toward `goal`, and returns extracted data.export TINYFISH_API_KEY="..."python agent_run.py"""from tinyfish import (AuthenticationError,BrowserProfile,CaptureConfig,NotFoundError,SDKError,TinyFish,)client = TinyFish()try:# Blocking run — for fire-and-forget use client.agent.queue(),# for live progress events use client.agent.stream().response = client.agent.run(goal="Extract the price table: product name and price per unit",url="https://www.tinyfish.ai/pricing",browser_profile=BrowserProfile.LITE, # "stealth" for bot-protected sitescapture_config=CaptureConfig(screenshots=True),)print(f"Run {response.run_id}: {response.status}")print(response.result)# Runs are stored and addressable — you can re-fetch any run later.run = client.runs.get(response.run_id)print(f"Created {run.created_at}, finished {run.finished_at}")except AuthenticationError:raise SystemExit("Invalid API key — check TINYFISH_API_KEY")except NotFoundError:raise SystemExit("Run not found")except SDKError as e:raise SystemExit(f"TinyFish API error: {e}")
Source: write · chatcmpl-tool-8859d586baada539
Recorded tool call
{
"type": "tool_call",
"seq": 47,
"ts": "2026-10-17T02:15:22.353Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/tinyfish_example/agent_run.py",
"content": "\"\"\"Paid-tier example: one blocking TinyFish Agent run ($0.016 per step, from Wallet).\n\nThe agent opens the URL in TinyFish's cloud browser (residential proxies and\nanti-bot handling included), works toward `goal`, and returns extracted data.\n\n export TINYFISH_API_KEY=\"...\"\n python agent_run.py\n\"\"\"\n\nfrom tinyfish import (\n AuthenticationError,\n BrowserProfile,\n CaptureConfig,\n NotFoundError,\n SDKError,\n TinyFish,\n)\n\nclient = TinyFish()\n\ntry:\n # Blocking run — for fire-and-forget use client.agent.queue(),\n # for live progress events use client.agent.stream().\n response = client.agent.run(\n goal=\"Extract the price table: product name and price per unit\",\n url=\"https://www.tinyfish.ai/pricing\",\n browser_profile=BrowserProfile.LITE, # \"stealth\" for bot-protected sites\n capture_config=CaptureConfig(screenshots=True),\n )\n\n print(f\"Run {response.run_id}: {response.status}\")\n print(response.result)\n\n # Runs are stored and addressable — you can re-fetch any run later.\n run = client.runs.get(response.run_id)\n print(f\"Created {run.created_at}, finished {run.finished_at}\")\n\nexcept AuthenticationError:\n raise SystemExit(\"Invalid API key — check TINYFISH_API_KEY\")\nexcept NotFoundError:\n raise SystemExit(\"Run not found\")\nexcept SDKError as e:\n raise SystemExit(f\"TinyFish API error: {e}\")\n"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-8859d586baada539",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 52,
"ts": "2026-10-17T02:15:22.353Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 1386 bytes to /sandbox/repo/tinyfish_example/agent_run.py",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-8859d586baada539",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtentinyfish_example/wallet_check.pyView changes
/sandbox/repo/tinyfish_example/wallet_check.py
Written content · previous contents were not recorded
"""Check your Wallet balance before spending on Agent/Browser runs.Agent ($0.016/step) and Browser ($0.002/min) draw from a prepaid Wallet.This is the one-line sanity check to run in CI or before a batch of runs.export TINYFISH_API_KEY="..."python wallet_check.py"""from tinyfish import NotFoundError, TinyFish, WalletAutoReloadStateclient = TinyFish()try:wallet = client.wallet.get()print(f"Balance: {wallet.available_balance} {wallet.currency} (as of {wallet.as_of})")if wallet.auto_reload and wallet.auto_reload.state == WalletAutoReloadState.ON:print(f"Auto-reload ON: tops up to {wallet.auto_reload.recharge_to} "f"at threshold {wallet.auto_reload.threshold}")if wallet.pending_top_up:print(f"Pending top-up: {wallet.pending_top_up.amount}")except NotFoundError as e:if e.code == "FEATURE_NOT_AVAILABLE":print("Account is on the legacy subscription/credits plan, not a wallet.")else:raise
Source: write · chatcmpl-tool-a0bfbd086154733f
Recorded tool call
{
"type": "tool_call",
"seq": 48,
"ts": "2026-10-17T02:15:22.353Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/tinyfish_example/wallet_check.py",
"content": "\"\"\"Check your Wallet balance before spending on Agent/Browser runs.\n\nAgent ($0.016/step) and Browser ($0.002/min) draw from a prepaid Wallet.\nThis is the one-line sanity check to run in CI or before a batch of runs.\n\n export TINYFISH_API_KEY=\"...\"\n python wallet_check.py\n\"\"\"\n\nfrom tinyfish import NotFoundError, TinyFish, WalletAutoReloadState\n\nclient = TinyFish()\n\ntry:\n wallet = client.wallet.get()\n print(f\"Balance: {wallet.available_balance} {wallet.currency} (as of {wallet.as_of})\")\n\n if wallet.auto_reload and wallet.auto_reload.state == WalletAutoReloadState.ON:\n print(\n f\"Auto-reload ON: tops up to {wallet.auto_reload.recharge_to} \"\n f\"at threshold {wallet.auto_reload.threshold}\"\n )\n if wallet.pending_top_up:\n print(f\"Pending top-up: {wallet.pending_top_up.amount}\")\n\nexcept NotFoundError as e:\n if e.code == \"FEATURE_NOT_AVAILABLE\":\n print(\"Account is on the legacy subscription/credits plan, not a wallet.\")\n else:\n raise\n"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-a0bfbd086154733f",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 53,
"ts": "2026-10-17T02:15:22.353Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 1019 bytes to /sandbox/repo/tinyfish_example/wallet_check.py",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-a0bfbd086154733f",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtentinyfish_example/requirements.txtView changes
/sandbox/repo/tinyfish_example/requirements.txt
Written content · previous contents were not recorded
tinyfish>=0.9
Source: write · chatcmpl-tool-9b70f9156eff2ece
Recorded tool call
{
"type": "tool_call",
"seq": 49,
"ts": "2026-10-17T02:15:22.353Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/tinyfish_example/requirements.txt",
"content": "tinyfish>=0.9\n"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9b70f9156eff2ece",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 54,
"ts": "2026-10-17T02:15:22.353Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 14 bytes to /sandbox/repo/tinyfish_example/requirements.txt",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9b70f9156eff2ece",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Confirm that the script compiles and that the imported symbols exist (e.g., WalletAutoReloadState, CaptureConfig, BrowserProfile, NotFoundError.code). Do a quick static check without making network calls.
cd /sandbox/repo/tinyfish_example && python3 -m py_compile *.py && python3 - <<'EOF'
import tinyfish
names = ["TinyFish","BrowserProfile","CaptureConfig","NotFoundError","AuthenticationError","RateLimitError","SDKError","WalletAutoReloadState"]
missing = [n for n in names if not hasattr(tinyfish, n)]
print("missing:", missing or "none")
from tinyfish import NotFoundError
print("NotFoundError has .code:", "code" in NotFoundError.__init__.__code__.co_varnames or hasattr(NotFoundError, "code"))
import inspect
from tinyfish import TinyFish
print(inspect.signature(TinyFish.__init__))
print(inspect.signature(TinyFish().search.query) if False else "ok")
EOFmissing: none
missing: none NotFoundError has .code: True (self, *, api_key: str | None = None, base_url: str | None = None, timeout: float = 600.0, max_retries: int = 2) ok
Recorded tool call
{
"type": "tool_call",
"seq": 57,
"ts": "2026-10-17T02:15:24.147Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo/tinyfish_example && python3 -m py_compile *.py && python3 - <<'EOF'\nimport tinyfish\nnames = [\"TinyFish\",\"BrowserProfile\",\"CaptureConfig\",\"NotFoundError\",\"AuthenticationError\",\"RateLimitError\",\"SDKError\",\"WalletAutoReloadState\"]\nmissing = [n for n in names if not hasattr(tinyfish, n)]\nprint(\"missing:\", missing or \"none\")\nfrom tinyfish import NotFoundError\nprint(\"NotFoundError has .code:\", \"code\" in NotFoundError.__init__.__code__.co_varnames or hasattr(NotFoundError, \"code\"))\nimport inspect\nfrom tinyfish import TinyFish\nprint(inspect.signature(TinyFish.__init__))\nprint(inspect.signature(TinyFish().search.query) if False else \"ok\")\nEOF"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-b3da7a15c25be87a",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 58,
"ts": "2026-10-17T02:15:25.503Z",
"turn": 1,
"output": {
"content": [
{
"text": "missing: none\nNotFoundError has .code: True\n(self, *, api_key: str | None = None, base_url: str | None = None, timeout: float = 600.0, max_retries: int = 2)\nok\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-b3da7a15c25be87a",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Also, should I verify the method signatures (e.g., the parameters of search.query, the parameters of fetch.get_contents, the parameters of agent.run) without constructing the client? Constructing the client requires a key, but a fake key "test" can be passed just for signature checking purposes — no network calls will be made.
cd /sandbox/repo/tinyfish_example && python3 - <<'EOF'
import inspect
from tinyfish import TinyFish
c = TinyFish(api_key="sk-test-no-network") # signature check only, no calls
for path, fn in [("search.query", c.search.query), ("fetch.get_contents", c.fetch.get_contents),
("agent.run", c.agent.run), ("runs.get", c.runs.get), ("wallet.get", c.wallet.get)]:
print(path, inspect.signature(fn))
EOFsearch.query (query: 'str', *, purpose: 'str | None' = None, location: 'str | None' = None, language: 'str | None' = None, include_domains: 'str | None' = None,
search.query (query: 'str', *, purpose: 'str | None' = None, location: 'str | None' = None, language: 'str | None' = None, include_domains: 'str | None' = None, exclude_domains: 'str | None' = None, after_date: 'str | None' = None, before_date: 'str | None' = None, recency_minutes: 'int | None' = None, domain_type: 'str | None' = None, pub_year_min: 'int | None' = None, pub_year_max: 'int | None' = None, page: 'int | None' = None) -> 'SearchQueryResponse' fetch.get_contents (urls: 'list[str]', *, purpose: 'str | None' = None, format: 'FetchFormat | None' = None, include_html_head: 'bool | None' = None, links: 'bool | None' = None, image_links: 'bool | None' = None, ttl: 'int | None' = None, per_url_timeout_ms: 'int | None' = None, if_none_match: 'str | None' = None, if_modified_since: 'str | None' = None, include_etag_and_last_modified: 'bool | None' = None, highlights: 'FetchHighlights | dict[str, object] | None' = None) -> 'FetchResponse' agent.run (*, goal: 'str', url: 'str', browser_profile: 'BrowserProfile | None' = None, proxy_config: 'ProxyConfig | None' = None, agent_config: 'AgentConfig | None' = None, capture_config: 'CaptureConfig | None' = None, webhook_url: 'str | None' = None, output_schema: 'dict[str, Any] | None' = None, use_profile: 'bool | None' = None, profile_id: 'str | None' = None, use_vault: 'bool | None' = None, credential_item_ids: 'list[str] | None' = None) -> 'AgentRunResponse' runs.get (run_id: 'str') -> 'RunRetrieveResponse' wallet.get () -> 'WalletResponse'
Recorded tool call
{
"type": "tool_call",
"seq": 61,
"ts": "2026-10-17T02:15:26.719Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo/tinyfish_example && python3 - <<'EOF'\nimport inspect\nfrom tinyfish import TinyFish\nc = TinyFish(api_key=\"sk-test-no-network\") # signature check only, no calls\nfor path, fn in [(\"search.query\", c.search.query), (\"fetch.get_contents\", c.fetch.get_contents),\n (\"agent.run\", c.agent.run), (\"runs.get\", c.runs.get), (\"wallet.get\", c.wallet.get)]:\n print(path, inspect.signature(fn))\nEOF"
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9acad98c9e43cb72",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 62,
"ts": "2026-10-17T02:15:27.431Z",
"turn": 1,
"output": {
"content": [
{
"text": "search.query (query: 'str', *, purpose: 'str | None' = None, location: 'str | None' = None, language: 'str | None' = None, include_domains: 'str | None' = None, exclude_domains: 'str | None' = None, after_date: 'str | None' = None, before_date: 'str | None' = None, recency_minutes: 'int | None' = None, domain_type: 'str | None' = None, pub_year_min: 'int | None' = None, pub_year_max: 'int | None' = None, page: 'int | None' = None) -> 'SearchQueryResponse'\nfetch.get_contents (urls: 'list[str]', *, purpose: 'str | None' = None, format: 'FetchFormat | None' = None, include_html_head: 'bool | None' = None, links: 'bool | None' = None, image_links: 'bool | None' = None, ttl: 'int | None' = None, per_url_timeout_ms: 'int | None' = None, if_none_match: 'str | None' = None, if_modified_since: 'str | None' = None, include_etag_and_last_modified: 'bool | None' = None, highlights: 'FetchHighlights | dict[str, object] | None' = None) -> 'FetchResponse'\nagent.run (*, goal: 'str', url: 'str', browser_profile: 'BrowserProfile | None' = None, proxy_config: 'ProxyConfig | None' = None, agent_config: 'AgentConfig | None' = None, capture_config: 'CaptureConfig | None' = None, webhook_url: 'str | None' = None, output_schema: 'dict[str, Any] | None' = None, use_profile: 'bool | None' = None, profile_id: 'str | None' = None, use_vault: 'bool | None' = None, credential_item_ids: 'list[str] | None' = None) -> 'AgentRunResponse'\nruns.get (run_id: 'str') -> 'RunRetrieveResponse'\nwallet.get () -> 'WalletResponse'\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-9acad98c9e43cb72",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Everything confirmed. Note that runs.get returns RunRetrieveResponse — my agent_run.py uses run.created_at / run.finished_at. Should I check the fields of RunRetrieveResponse right away? The README says it returns Run. Let me check the attributes.
python3 - <<'EOF' import tinyfish, inspect m = inspect.getmodule(type(tinyfish.TinyFish(api_key="x").runs)) from tinyfish.types import RunRetrieveResponse # may vary EOF python3 -c " import tinyfish, inspect src = inspect.getsource(tinyfish.TinyFish(api_key='x').runs.get) print(src[:1200])"
Traceback (most recent call last):
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
ModuleNotFoundError: No module named 'tinyfish.types'
def get(self, run_id: str) -> RunRetrieveResponse:
"""Retrieve a single run by its ID.
Args:
run_id: The run ID returned by `agent.queue()` or any run response.
Returns:
RunRetrieveResponse with the full run details including status and result.
Raises:
ValueError: run_id is empty or whitespace.
NotFoundError: No run exists with that ID.
AuthenticationError: Invalid API key.
"""
if not run_id or not run_id.strip():
raise ValueError("run_id must be a non-empty string")
return self._get(f"/v1/runs/{run_id}", cast_to=RunRetrieveResponse)
Recorded tool call
{
"type": "tool_call",
"seq": 65,
"ts": "2026-10-17T02:15:28.924Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python3 - <<'EOF'\nimport tinyfish, inspect\nm = inspect.getmodule(type(tinyfish.TinyFish(api_key=\"x\").runs))\nfrom tinyfish.types import RunRetrieveResponse # may vary\nEOF\npython3 -c \"\nimport tinyfish, inspect\nsrc = inspect.getsource(tinyfish.TinyFish(api_key='x').runs.get)\nprint(src[:1200])\""
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-a7045f301f1e2752",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 66,
"ts": "2026-10-17T02:15:30.584Z",
"turn": 1,
"output": {
"content": [
{
"text": "Traceback (most recent call last):\n File \"<stdin>\", line 3, in <module>\nModuleNotFoundError: No module named 'tinyfish.types'\n def get(self, run_id: str) -> RunRetrieveResponse:\n \"\"\"Retrieve a single run by its ID.\n\n Args:\n run_id: The run ID returned by `agent.queue()` or any run response.\n\n Returns:\n RunRetrieveResponse with the full run details including status and result.\n\n Raises:\n ValueError: run_id is empty or whitespace.\n NotFoundError: No run exists with that ID.\n AuthenticationError: Invalid API key.\n \"\"\"\n if not run_id or not run_id.strip():\n raise ValueError(\"run_id must be a non-empty string\")\n return self._get(f\"/v1/runs/{run_id}\", cast_to=RunRetrieveResponse)\n\n",
"type": "text"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-a7045f301f1e2752",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The fields of RunRetrieveResponse are unclear. Probably has created_at/finished_at. For safety, adjust agent_run.py to defensively use getattr, or just output the response. Simple fix: use getattr(run, "created_at", None).
File edits
1 file
Modifiedtinyfish_example/agent_run.py+1−1View changes
/sandbox/repo/tinyfish_example/agent_run.py
Replacement excerpt
run = client.runs.get(response.run_id)- print(f"Created {run.created_at}, finished {run.finished_at}")+ print(f"Created {getattr(run, 'created_at', '?')}, finished {getattr(run, 'finished_at', '?')}")
Source: edit · chatcmpl-tool-acfd56a48b19445e
Recorded tool call
{
"type": "tool_call",
"seq": 69,
"ts": "2026-10-17T02:15:31.763Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/tinyfish_example/agent_run.py",
"edits": [
{
"newText": " run = client.runs.get(response.run_id)\n print(f\"Created {getattr(run, 'created_at', '?')}, finished {getattr(run, 'finished_at', '?')}\")",
"oldText": " run = client.runs.get(response.run_id)\n print(f\"Created {run.created_at}, finished {run.finished_at}\")"
}
]
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-acfd56a48b19445e",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 70,
"ts": "2026-10-17T02:15:31.763Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/tinyfish_example/agent_run.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 32 print(response.result)\n 33 \n 34 # Runs are stored and addressable — you can re-fetch any run later.\n 35 run = client.runs.get(response.run_id)\n-36 print(f\"Created {run.created_at}, finished {run.finished_at}\")\n+36 print(f\"Created {getattr(run, 'created_at', '?')}, finished {getattr(run, 'finished_at', '?')}\")\n 37 \n 38 except AuthenticationError:\n 39 raise SystemExit(\"Invalid API key — check TINYFISH_API_KEY\")\n 40 except NotFoundError:\n ...",
"patch": "--- /sandbox/repo/tinyfish_example/agent_run.py\n+++ /sandbox/repo/tinyfish_example/agent_run.py\n@@ -32,9 +32,9 @@\n print(response.result)\n \n # Runs are stored and addressable — you can re-fetch any run later.\n run = client.runs.get(response.run_id)\n- print(f\"Created {run.created_at}, finished {run.finished_at}\")\n+ print(f\"Created {getattr(run, 'created_at', '?')}, finished {getattr(run, 'finished_at', '?')}\")\n \n except AuthenticationError:\n raise SystemExit(\"Invalid API key — check TINYFISH_API_KEY\")\n except NotFoundError:\n",
"firstChangedLine": 36
}
},
"run_id": "cmvbrk3k700ap0itia0s4d1np",
"tool_id": "chatcmpl-tool-acfd56a48b19445e",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Should I attempt a live run here? There's no API key in the environment. I'm blocked. According to the instructions: "Let me know if you get blocked." I should say I'm blocked: I can't make live calls without TINYFISH_API_KEY. That's fine—the deliverables are still valid. Let me summarize.
Done. TinyFish is a fully hosted API (no local stacks, no long-running processes), so I built the example against its official Python SDK (tinyfish 0.9.0 on PyPI, installed and verified here).
What I built — /sandbox/repo/tinyfish_example/
README.md— setup, run instructions, and the pricing tablesearch_and_fetch.py— free-tier example:search.query()→ ranked results, thenfetch.get_contents()→ clean markdown of the top resultagent_run.py— paid-tier example: one blockingagent.run()with agoal/url, lite browser profile, screenshot capture, plusruns.get()to re-fetch the stored runwallet_check.py— balance/auto-reload check before spending on Agent runsrequirements.txt—tinyfish>=0.9
All scripts compile and every SDK symbol/signature was verified against the installed package (no network calls made).
How pricing works
Pay-as-you-go, one API key, no subscription/seats/minimums:
| Product | Price |
|---|---|
| Search | Free ($0.00/req; 30/min, 500/hr) |
| Fetch | Free ($0.00/url; 150/min, 1,000/day) |
| Agent | $0.016 per step (2 concurrent runs default) |
| Browser | $0.002 per minute (5 concurrent sessions) |
| Monitor | $0.005 per check |
Agent/Browser spend from a prepaid Wallet (new accounts get ~$8 free; $10 min top-up; optional auto-reload). Search/Fetch never touch the Wallet and keep working at $0. Agent's per-step price includes LLM inference, residential proxies, anti-bot handling, and run storage — no separate model bill. Higher limits are a sales conversation, not a purchase.
Will this be easy for me to manage?
Yes — it's close to ideal for an agent: pure HTTP/SDK calls, no infra to babysit, idempotent-ish read APIs (runs.list/get), structured errors with retry/backoff built in, and a free tier (Search/Fetch) I can exercise without spending anything. The only friction is the prepaid Wallet for Agent runs, which wallet_check.py makes easy to guard.
Blocked on one thing
I could not execute a live call: there's no TINYFISH_API_KEY in this environment, and the SDK refuses to construct a client without one. Everything else (install, imports, signatures, compilation) is verified. To finish, set TINYFISH_API_KEY (from agent.tinyfish.ai/api-keys) and run python search_and_fetch.py — that path is free, so it costs nothing to validate end-to-end.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents