Capability: Watching, cancelling, and splitting runs works with any API key.
max_duration_seconds is generally available. max_steps is in beta: enroll at agent.tinyfish.ai/beta first, or a request that sets it returns 403 FORBIDDEN.RUNNING longer than you expected, or when a big goal keeps running out of steps or time. A run usually takes this long because the goal is too big for one browser session, not because something broke. The fix is to watch it, stop it if you need to, and send the work again as smaller runs.
Check whether it’s stuck or just slow
Start the run withqueue so you get a run_id straight away, then poll it with runs.get. While the run is going, status is RUNNING, num_of_steps is null, and streaming_url is a live view of the browser you can open.
Python
GET /v1/runs/{id}:
num_of_steps is on the TypeScript SDK’s Run type (run.num_of_steps), but it isn’t exposed on the Python SDK’s Run model yet — read it from the raw REST response (as above) if you need it in Python.streaming_url or use stream instead of queue. Every PROGRESS event has a purpose that says what the agent is doing, so a run that’s paging through results looks different from one that keeps retrying the same click.
Python
After a run ends,
streaming_url no longer shows the browser. To see what happened, read the run’s step timeline. Each entry in steps has a timestamp, a status, and an action describing what the agent did, and video_url is a recording of the whole run. Neither SDK returns steps or video_url yet, so read them from the REST endpoint. video_url is a signed link that expires after 15 minutes, so fetch it when you need it rather than storing it.
Python
Set limits that fit the task
Two fields inagent_config bound a run. Set both on anything that pages through results.
Neither limit makes the run fail. The run ends
COMPLETED with whatever the agent wrote into result, and error is null. To tell a finished run from one that was cut short, add a status field to your output schema, as shown in Troubleshoot Runs.
To pick values, run the goal once on a small piece (one page, one item), look at num_of_steps, and multiply by the number of pieces. Leave headroom: one shop page took 2 steps, but a 20-page goal had used 14 steps by page 5. Set max_duration_seconds to the longest you’re willing to wait, and allow a few extra seconds for the wrap-up.
Cancel the run
Cancel a run withPOST /v1/runs/{id}/cancel. The Python and TypeScript SDKs don’t have a cancel method yet, so call the endpoint directly with your API key. This works on runs started with queue, stream, or /v1/automation/run-batch. A run started with run (/v1/automation/run) can’t be cancelled, so use queue for anything that might run long.
Python
COMPLETED or FAILED with message: "Run already finished". A 404 means the run ID doesn’t exist, and a 403 means your key can see the run but isn’t allowed to cancel it.
To stop several runs at once, send up to 100 IDs to POST /v1/runs/batch/cancel:
results entry for each run it found and a not_found list for IDs it didn’t.
Recover what the run found
Cancelling throws away the run’s work. After the cancel above, the run had been going for almost a minute, and this is whatGET /v1/runs/{id} returned:
result is null. If you want partial data back, let a limit stop the run instead of cancelling it. When max_steps or max_duration_seconds runs out, the agent writes up what it has before it ends. Ask for a field that says where it got to, so you know where to pick up:
max_duration_seconds: 60. It reports how far it got (last_page: 5), but products came back empty even though the reason says it extracted 80. A long run’s wrap-up is a summary, and it can drop the data itself. Use it to find out where to resume, not as the data. The reliable way to keep what you’ve collected is to make each run small enough to finish, which is the next section.
Split the task into smaller runs
Instead of one run that pages through everything, start one run per page (or per item) and run them at the same time. Each run is short, finishes on its own, and returns its own result, so a problem on one page doesn’t cost you the rest. Pythonmax_steps.
To send many runs in one request, use POST /v1/automation/run-batch with 1 to 100 runs. The SDKs don’t have a batch method, so call it with cURL or the CLI (tinyfish agent batch run --input runs.csv). It returns all the run IDs at once, and you can poll them together with POST /v1/runs/batch:
output_schema goes at the top level of the request and applies to every run. See Multiple URLs for more on running work in parallel.
Retry the parts that didn’t finish
Once the work is split, retry only the pieces that need it, and decide based on how the run ended:
Check
error.category before treating CANCELLED as “stop retrying” — a billing-rejected run also comes back CANCELLED, not FAILED, and that one still needs credits added. A run you (or someone else) cancelled on purpose is also CANCELLED, with error.category: "UNKNOWN", so a retry loop that reads only status will restart runs you meant to stop.
Python
run-batch aren’t idempotent: if a batch request times out on your side and you send it again, you can end up with two copies of every run. Call runs.list or POST /v1/runs/batch first to see whether the runs exist.
Common Mistakes
Cancelling a run to "save" what it found so far
Cancelling a run to "save" what it found so far
A cancelled run returns
result: null. If you want partial data, let max_steps or max_duration_seconds stop it, or split the work so each finished piece is already saved.Starting long runs with run
Starting long runs with run
run waits for the result and can’t be cancelled. Use queue or stream for anything that might take more than a minute or two.Reading the end of a capped run as the data
Reading the end of a capped run as the data
When a long run hits a limit, the wrap-up says how far it got, but the data fields can be empty. Use it to find where to resume, then get the rest with smaller runs.
Treating max_duration_seconds as an exact cutoff
Treating max_duration_seconds as an exact cutoff
The agent finishes its current step and writes up its result after the limit, so the run ends a bit later. Set the limit a little below the longest you’re willing to wait.
Setting max_steps to exactly what a small test used
Setting max_steps to exactly what a small test used
A run that hits the cap uses one extra step to wrap up, and the same goal can take more steps on a slower page. Leave headroom.
Retrying every run with an UNKNOWN error
Retrying every run with an UNKNOWN error
Cancelled runs report
category: "UNKNOWN", except a run the billing check rejected, which reports BILLING_FAILURE. Check error.category before deciding whether to retry.Re-sending a batch request after a timeout
Re-sending a batch request after a timeout
run-batch isn’t idempotent. Check whether the runs were created before you send it again.Related
Troubleshoot Runs
Fix each finish reason
Runs
Run lifecycle, statuses, and finish reasons
Runs Reference
List, get, cancel, batch, and per-step screenshots and HTML
Agent Mode, Max Steps, and Max Duration
Default vs Strict mode, step and time limits
Run TinyAgent Across Multiple URLs
Fire the same goal at many URLs at once
Error Codes
Every code and how to handle it