Skip to main content

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
  • --pretty output 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:

Features

  • auth commands for saving and inspecting your API key
  • run, run-async, run-sse, and run-batch
  • runs list|get|get-many|wait|cancel|cancel-many
  • browser create|usage
  • fanout for bounded-concurrency orchestration
  • suite for built-in and custom integration tests
  • agent 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:

  1. --api-key
  2. TINYFISH_API_KEY
  3. ~/.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-enabled or --no-proxy-enabled
  • --proxy-country US|GB|CA|DE|FR|JP|AU
  • --api-integration
  • --enable-agent-memory or --no-enable-agent-memory
  • --use-vault or --no-use-vault
  • --credential-item-id repeated

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 runs array

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:

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 string
  • description: optional string
  • request_defaults: optional object merged into each task request
  • tasks: required non-empty array

Task fields:

  • id: required stable identifier
  • meta: optional object carried through to output
  • request: required TinyFish async request object with at least url and goal

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:

  • outcome is the executor-level outcome
  • run_status is the TinyFish terminal status when a run exists
  • result is the TinyFish result, with JSON strings parsed into objects or arrays when possible
  • error is either a TinyFish error object or a CLI-generated orchestration error

Possible outcome values:

  • COMPLETED
  • FAILED
  • CANCELLED
  • RUN_CREATION_FAILED
  • WAIT_TIMEOUT
  • POLLING_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 scenarios array
  • each scenario must have a unique string id
  • each scenario must include a request object with url and goal
  • assertions must be an array if present

Supported assertion types:

  • equals
  • truthy
  • contains
  • contains_all
  • min_items
  • type
  • all_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_integration to identify the caller, for example openclaw
  • use run-async or fanout run for long or concurrent jobs
  • omit --pretty when 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

  • run returns the final TinyFish response when the HTTP request completes normally.
  • run-async returns the run creation response.
  • run-sse exits non-zero if the stream ends before COMPLETE, or if the final status is FAILED or CANCELLED.
  • runs wait exits non-zero if the final status is FAILED or CANCELLED.
  • fanout run exits non-zero if any task ends in FAILED, CANCELLED, RUN_CREATION_FAILED, WAIT_TIMEOUT, or POLLING_ERROR.
  • suite run exits 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

Metadata

Release files for tinyfish-cli 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tinyfish-cli 0.1.0
File Size Uploaded Provenance Actions
tinyfish_cli-0.1.0.tar.gz 37.1 kB Without provenance Details for tinyfish_cli-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for tinyfish-cli 0.1.0
File Interpreter ABI Platform Actions
tinyfish_cli-0.1.0-py3-none-any.whl Python 3 none any Details for tinyfish_cli-0.1.0-py3-none-any.whl

Total release size: 67.0 kB

Release files / tinyfish_cli-0.1.0.tar.gz

Download URL tinyfish_cli-0.1.0.tar.gz
Size 37.1 kB
Tags Source
SHA-256 checksum
How to use checksums
e95fb4fdf416fe5d59a7794f7ed88adebfa2a272aae048a38f5694d426c61600
BLAKE2b-256 checksum
How to use checksums
f83d2b52f36b94d7f29d4220d9211b0d8c6b7c51a3bb814b84559459b552b0cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7

Release files / tinyfish_cli-0.1.0-py3-none-any.whl

Download URL tinyfish_cli-0.1.0-py3-none-any.whl
Size 29.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
90fa8a8aff98af727c1abd6b5641d27a802b4904ea066bc3e387eb93f2765098
BLAKE2b-256 checksum
How to use checksums
24e90c387b24b0e379e33fe83283763e6210725fe6217a3a04718c177bb3748e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7

Security for tinyfish-cli 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page