Agent-friendly TinyFish CLI built from the public API docs and OpenAPI spec.
Project description
tinyfish-cli
tinyfish-cli is an agent-friendly command-line interface for the TinyFish browser automation API.
It is designed to work well for LLM agents like OpenClaw while still being comfortable for humans:
- compact JSON output by default
--prettyoutput for interactive use- stdin/file-based JSON inputs
- async, SSE, batch, fanout, and suite workflows
- stable input and output shapes for orchestration
The implementation is based on TinyFish's public docs and OpenAPI spec:
- Authentication
- Run browser automation with SSE streaming
- Start automation asynchronously
- Start multiple automations asynchronously
- Runs
- OpenAPI spec
Features
authcommands for saving and inspecting your API keyrun,run-async,run-sse, andrun-batchruns list|get|get-many|wait|cancel|cancel-manybrowser create|usagefanoutfor bounded-concurrency orchestrationsuitefor built-in and custom integration testsagent run ...aliases for agent-oriented callers
Requirements
- Python
3.9+ - a TinyFish API key
This project uses only the Python standard library at runtime.
Installation
Option 1: use the repo-local wrapper
No installation step is required:
./bin/tinyfish --help
You can also invoke it explicitly with Python:
python3 bin/tinyfish --help
Option 2: install into a virtual environment
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -e .
tinyfish --help
Once the package is published to PyPI, installation becomes:
python3 -m pip install tinyfish-cli
Authentication
The CLI resolves the API key in this order:
--api-keyTINYFISH_API_KEY~/.tinyfish/config.json
Default config path:
~/.tinyfish/config.json
Store a key interactively:
./bin/tinyfish auth login
Store a key directly:
./bin/tinyfish auth set tf_your_key_here
Store a key from stdin:
printf '%s' "$TINYFISH_API_KEY" | ./bin/tinyfish auth set
Check auth status:
./bin/tinyfish auth status --pretty
Remove the saved key:
./bin/tinyfish auth logout --pretty
auth status exits with code 0 when authenticated and 1 when no key is available.
Quick Start
Set your key for the current shell:
export TINYFISH_API_KEY="tf_your_real_key"
Verify the CLI sees it:
./bin/tinyfish auth status --pretty
Try a read-only API call:
./bin/tinyfish runs list --limit 1 --pretty
Run a simple automation:
./bin/tinyfish run \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\"}" \
--pretty
For longer jobs, prefer run-async or run-sse instead of synchronous run.
Output Model
By default, commands emit compact JSON on stdout. Add --pretty to indent output for human use.
Errors are emitted as JSON on stderr in this shape:
{
"error": {
"code": "SOME_CODE",
"message": "Human-readable error",
"status": 401,
"details": {
"any": "extra context"
}
}
}
When TinyFish itself returns a structured error object, the CLI preserves that shape where possible.
Command Reference
Top-level commands:
./bin/tinyfish --help
auth
run
run-async
run-sse
run-batch
runs
browser
fanout
suite
run
Runs a synchronous TinyFish automation against POST /v1/automation/run.
Example:
./bin/tinyfish run \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\",\"links\":[]}" \
--browser-profile lite \
--api-integration openclaw \
--pretty
Supported flag-driven request fields:
--url--goal--browser-profile lite|stealth--proxy-enabledor--no-proxy-enabled--proxy-country US|GB|CA|DE|FR|JP|AU--api-integration--enable-agent-memoryor--no-enable-agent-memory--use-vaultor--no-use-vault--credential-item-idrepeated
You can also provide a full JSON object with --input and optionally override fields with flags:
cat <<'JSON' | ./bin/tinyfish run --input - --pretty
{
"url": "https://example.com",
"goal": "Return JSON only with {\"title\":\"...\"}",
"browser_profile": "lite",
"proxy_config": {
"enabled": true,
"country_code": "US"
},
"api_integration": "openclaw",
"feature_flags": {
"enable_agent_memory": true
},
"use_vault": true,
"credential_item_ids": ["cred_123"]
}
JSON
run-async
Starts an async TinyFish automation with POST /v1/automation/run-async.
Example:
./bin/tinyfish run-async \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\"}" \
--pretty
Typical follow-up:
./bin/tinyfish runs wait RUN_ID --pretty
run-sse
Runs a TinyFish automation via SSE streaming.
Example:
./bin/tinyfish run-sse \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\"}" \
--pretty
In --pretty mode, the CLI prints readable progress lines such as:
[started][live][progress][complete]
Show heartbeat events too:
./bin/tinyfish run-sse \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\"}" \
--show-heartbeats \
--pretty
Without --pretty, each SSE event is emitted as compact JSON.
run-batch
Starts multiple async runs at once with POST /v1/automation/run-batch.
Input can be either:
- a JSON array of run request objects
- an object with a top-level
runsarray
Example array input:
cat <<'JSON' | ./bin/tinyfish run-batch --input - --pretty
[
{
"url": "https://example.com/a",
"goal": "Return JSON only with {\"site\":\"a\"}"
},
{
"url": "https://example.com/b",
"goal": "Return JSON only with {\"site\":\"b\"}"
}
]
JSON
runs
Inspect and manage existing runs.
List runs:
./bin/tinyfish runs list --limit 10 --pretty
Filter runs:
./bin/tinyfish runs list \
--status COMPLETED \
--goal "Return JSON only" \
--sort-direction desc \
--limit 5 \
--pretty
Get one run:
./bin/tinyfish runs get RUN_ID --pretty
Get many runs:
./bin/tinyfish runs get-many RUN_1 RUN_2 RUN_3 --pretty
Or from JSON:
cat <<'JSON' | ./bin/tinyfish runs get-many --input - --pretty
{
"run_ids": ["RUN_1", "RUN_2"]
}
JSON
Wait for a run to finish:
./bin/tinyfish runs wait RUN_ID --interval 2 --wait-timeout 300 --pretty
Cancel one run:
./bin/tinyfish runs cancel RUN_ID --pretty
Cancel many runs:
./bin/tinyfish runs cancel-many RUN_1 RUN_2 --pretty
runs wait exits non-zero if the final status is FAILED or CANCELLED.
browser
Remote browser session utilities.
Create a browser session:
./bin/tinyfish browser create --pretty
List browser usage:
./bin/tinyfish browser usage --limit 20 --pretty
Filter browser usage:
./bin/tinyfish browser usage \
--session-id SESSION_ID \
--status running \
--start-after 2026-03-01T00:00:00Z \
--end-before 2026-03-31T23:59:59Z \
--limit 20 \
--page 1 \
--pretty
fanout
fanout is a generic bounded-concurrency executor built for independent TinyFish tasks.
It is not pricing-specific, scraping-specific, or ticketing-specific. It lets an external agent decide what the tasks are while this CLI handles:
- async run creation
- client-side concurrency limits
- polling until terminal status
- aggregation into one JSON envelope
Print machine-readable schemas:
./bin/tinyfish fanout schema input
./bin/tinyfish fanout schema output
./bin/tinyfish fanout schema example
Validate a plan:
./bin/tinyfish fanout validate --input ./examples/fanout-template.json --pretty
Run a plan with up to 5 active tasks:
./bin/tinyfish fanout run \
--input ./examples/fanout-template.json \
--max-concurrency 5 \
--pretty
Run selected tasks only:
./bin/tinyfish fanout run \
--input ./examples/fanout-template.json \
--task site-a \
--task site-b \
--max-concurrency 5 \
--pretty
Detailed fanout docs live in docs/FANOUT.md.
suite
Suites are live TinyFish smoke tests with assertions on the final run payload.
List built-in suites:
./bin/tinyfish suite list --pretty
Show a suite definition:
./bin/tinyfish suite show common-web --pretty
Run the built-in suite:
./bin/tinyfish suite run common-web --pretty
Run a single scenario:
./bin/tinyfish suite run common-web --scenario cart-addition --pretty
Run the built-in suite in fanout mode:
./bin/tinyfish suite run common-web \
--fanout \
--fanout-duplicates 2 \
--fanout-max-concurrency 5 \
--pretty
The built-in common-web suite covers:
- multi-page research on Books to Scrape
- cart interaction on Sauce Demo
- form fill and submit on Selenium Web Form
In fanout mode, each selected scenario is duplicated into task IDs like cart-addition--1 and cart-addition--2, executed concurrently through the generic fanout executor, and then validated with the original suite assertions.
Agent Aliases
For agent-oriented integrations, the CLI also accepts agent run ... aliases.
These map as follows:
tinyfish agent run ...->tinyfish run ...tinyfish agent run async ...->tinyfish run-async ...tinyfish agent run sse ...->tinyfish run-sse ...tinyfish agent run batch ...->tinyfish run-batch ...tinyfish agent run list ...->tinyfish runs list ...tinyfish agent run get ...->tinyfish runs get ...tinyfish agent run get-many ...->tinyfish runs get-many ...tinyfish agent run wait ...->tinyfish runs wait ...tinyfish agent run cancel ...->tinyfish runs cancel ...tinyfish agent run cancel-many ...->tinyfish runs cancel-many ...
Examples:
./bin/tinyfish agent run \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\"}" \
--pretty
./bin/tinyfish agent run async \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\"}" \
--pretty
./bin/tinyfish agent run list --limit 5 --pretty
Agent-Friendly Input Patterns
Single-run input shape
Single-run commands accept a JSON object with TinyFish request fields.
Minimum required fields:
{
"url": "https://example.com",
"goal": "Return JSON only with {\"title\":\"...\"}"
}
Common optional fields:
{
"url": "https://example.com",
"goal": "Return JSON only with {\"title\":\"...\"}",
"browser_profile": "lite",
"proxy_config": {
"enabled": true,
"country_code": "US"
},
"api_integration": "openclaw",
"feature_flags": {
"enable_agent_memory": true
},
"use_vault": true,
"credential_item_ids": ["cred_123"]
}
Batch input shape
Accepted by run-batch:
{
"runs": [
{
"url": "https://example.com/a",
"goal": "Return JSON only with {\"site\":\"a\"}"
},
{
"url": "https://example.com/b",
"goal": "Return JSON only with {\"site\":\"b\"}"
}
]
}
or:
[
{
"url": "https://example.com/a",
"goal": "Return JSON only with {\"site\":\"a\"}"
}
]
Fanout input shape
Accepted by fanout validate and fanout run:
{
"name": "multi-site-checks",
"description": "Generic concurrent TinyFish task plan.",
"request_defaults": {
"browser_profile": "lite",
"api_integration": "openclaw"
},
"tasks": [
{
"id": "site-a",
"meta": {
"site": "site-a",
"kind": "lookup"
},
"request": {
"url": "https://example.com/a",
"goal": "Return JSON only with {\"title\":\"...\",\"price\":\"...\"}"
}
}
]
}
Top-level fields:
name: optional stringdescription: optional stringrequest_defaults: optional object merged into each task requesttasks: required non-empty array
Task fields:
id: required stable identifiermeta: optional object carried through to outputrequest: required TinyFish async request object with at leasturlandgoal
Array shorthand is also accepted:
[
{
"id": "task-1",
"request": {
"url": "https://example.com/1",
"goal": "Return JSON only with {\"title\":\"...\"}"
}
}
]
Fanout output shape
fanout run returns one aggregated JSON object:
{
"job": {
"name": "multi-site-checks",
"description": "Generic concurrent TinyFish task plan.",
"started_at": "2026-03-24T12:00:00+00:00",
"finished_at": "2026-03-24T12:00:21+00:00",
"max_concurrency": 5,
"interval_seconds": 2.0,
"wait_timeout_seconds": 300.0,
"fail_fast": false,
"requested_tasks": 2,
"executed_task_ids": ["site-a", "site-b"]
},
"summary": {
"total": 2,
"completed": 2,
"failed": 0,
"cancelled": 0,
"run_creation_failed": 0,
"wait_timeout": 0,
"polling_error": 0
},
"results": [
{
"id": "site-a",
"meta": {
"site": "site-a",
"kind": "lookup"
},
"request": {
"url": "https://example.com/a",
"goal": "Return JSON only with {\"title\":\"...\",\"price\":\"...\"}",
"browser_profile": "lite",
"api_integration": "openclaw"
},
"run_id": "run_abc123",
"outcome": "COMPLETED",
"run_status": "COMPLETED",
"result": {
"title": "Example Product",
"price": "$349.99"
},
"error": null,
"duration_seconds": 12.418,
"run_response": {
"...": "..."
}
}
]
}
Result semantics:
outcomeis the executor-level outcomerun_statusis the TinyFish terminal status when a run existsresultis the TinyFishresult, with JSON strings parsed into objects or arrays when possibleerroris either a TinyFish error object or a CLI-generated orchestration error
Possible outcome values:
COMPLETEDFAILEDCANCELLEDRUN_CREATION_FAILEDWAIT_TIMEOUTPOLLING_ERROR
Custom suite shape
A custom suite is a JSON object with a scenarios array:
{
"name": "my-suite",
"description": "Custom TinyFish smoke tests",
"scenarios": [
{
"id": "form-check",
"description": "Fill a form and confirm success",
"request": {
"url": "https://example.com/form",
"goal": "Return JSON only with {\"submitted\":true,\"message\":\"...\"}"
},
"assertions": [
{
"type": "equals",
"path": "status",
"value": "COMPLETED"
},
{
"type": "truthy",
"path": "result.submitted"
},
{
"type": "contains",
"path": "result.message",
"value": "Success"
}
]
}
]
}
Custom suite requirements:
- top-level object
- non-empty
scenariosarray - each scenario must have a unique string
id - each scenario must include a
requestobject withurlandgoal assertionsmust be an array if present
Supported assertion types:
equalstruthycontainscontains_allmin_itemstypeall_items_have_keys
Supported path syntax:
- dotted object paths like
result.message - list indexes like
result.items.0.title
Run a custom suite:
./bin/tinyfish suite run --file ./my-suite.json --pretty
Operational Guidance
For LLM agents, these patterns work best:
- ask TinyFish to return JSON only
- define the expected object shape explicitly in the
goal - put orchestration metadata in
meta, not inside the prompt text - use
api_integrationto identify the caller, for exampleopenclaw - use
run-asyncorfanout runfor long or concurrent jobs - omit
--prettywhen another program is parsing stdout
Recommended pattern for a single long-running job:
./bin/tinyfish run-async \
--url https://example.com \
--goal "Return JSON only with {\"title\":\"...\"}" \
--pretty
./bin/tinyfish runs wait RUN_ID --pretty
Recommended pattern for many independent jobs:
./bin/tinyfish fanout run \
--input ./examples/fanout-template.json \
--max-concurrency 5 \
--pretty
If you need application-specific workflow logic such as approvals, deduplication, dependencies, or post-run reconciliation, keep that logic outside this CLI and treat tinyfish-cli as the execution layer.
Failure Semantics
runreturns the final TinyFish response when the HTTP request completes normally.run-asyncreturns the run creation response.run-sseexits non-zero if the stream ends beforeCOMPLETE, or if the final status isFAILEDorCANCELLED.runs waitexits non-zero if the final status isFAILEDorCANCELLED.fanout runexits non-zero if any task ends inFAILED,CANCELLED,RUN_CREATION_FAILED,WAIT_TIMEOUT, orPOLLING_ERROR.suite runexits non-zero if any scenario fails validation or cannot complete successfully.
The synchronous run command can encounter network disconnects on long jobs even when the run succeeded server-side. This CLI wraps that case as a REMOTE_DISCONNECTED error and includes guidance to prefer run-async or run-sse.
Development
Run the test suite:
python3 -m unittest discover -s tests -v
Helpful smoke checks:
python3 bin/tinyfish --help
python3 bin/tinyfish suite run --help
python3 bin/tinyfish fanout schema input
Publishing To PyPI
As of March 24, 2026, the package name tinyfish-cli was available on PyPI.
Build distributions:
python3 -m venv .venv-publish
source .venv-publish/bin/activate
python3 -m pip install --upgrade pip build twine
python3 -m build
python3 -m twine check dist/*
Upload to PyPI:
python3 -m twine upload dist/*
Upload to TestPyPI first if you want a dry run:
python3 -m twine upload --repository testpypi dist/*
You will need a PyPI account and either an API token or Trusted Publishing configured for the repository.
Project Layout
bin/tinyfish repo-local runner
src/tinyfish_cli/cli.py command parsing and handlers
src/tinyfish_cli/client.py HTTP and SSE client
src/tinyfish_cli/fanout.py bounded-concurrency executor
src/tinyfish_cli/suite_runner.py suite loading and execution
src/tinyfish_cli/builtin_suites.py built-in live smoke suites
docs/FANOUT.md detailed fanout documentation
examples/fanout-template.json starter fanout plan
LICENSE package license
MANIFEST.in source distribution include rules
tests/ unit tests
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tinyfish_cli-0.1.0.tar.gz.
File metadata
- Download URL: tinyfish_cli-0.1.0.tar.gz
- Upload date:
- Size: 37.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e95fb4fdf416fe5d59a7794f7ed88adebfa2a272aae048a38f5694d426c61600
|
|
| MD5 |
9f87fb5d5bdfae8a25b0e1ded3177605
|
|
| BLAKE2b-256 |
f83d2b52f36b94d7f29d4220d9211b0d8c6b7c51a3bb814b84559459b552b0cf
|
File details
Details for the file tinyfish_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tinyfish_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 29.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90fa8a8aff98af727c1abd6b5641d27a802b4904ea066bc3e387eb93f2765098
|
|
| MD5 |
1f26ae5e9e2ad2956642040c4fe0acc3
|
|
| BLAKE2b-256 |
24e90c387b24b0e379e33fe83283763e6210725fe6217a3a04718c177bb3748e
|