Capability: Works with any API key. Fixing login walls needs a Browser Context Profile or Vault credentials, and the stealth fix uses
browser_profile and proxy_config. max_steps is in beta: enroll at agent.tinyfish.ai/beta first, or a request that sets it returns 403 FORBIDDEN.
The third case is the most common.
COMPLETED means the run finished, not that it did what you asked. When the agent hits a login page, a bot check, or its step limit, it usually stops, writes what it found and why it stopped into result, and ends with status: COMPLETED and error: null.
Make every run report how it ended
Add astatus and a reason to your output schema, and tell the agent in the goal what each status means. Your code can then branch on a field instead of reading prose.
Python
status and reason in required. If a data field is required and the agent can’t reach the data, it fills the field with a placeholder rather than leaving it out. See Common mistakes.
Auth required
The page asks the agent to sign in, and the run has no way to log in. On a page behind a login with no schema, the run completes with an empty value:use_vault: true, with credential_item_ids to limit the run to specific items. Never put a password in the goal. See Browser Context Profiles and Vault setup.
If the run ends FAILED with TASK_FAILED and a message about authentication, the fix is the same. A failed run may also include profile_hint with reason: "auth_wall" and a setup_url that opens profile setup for that domain.
Blocked
A CAPTCHA, a “verify you are human” page, or a bot-protection service stops the agent. With the defaultlite browser on a page behind DataDome, the run didn’t fail.
The run ended COMPLETED after 5 steps with error: null. Its result, trimmed:
FAILED with SITE_BLOCKED, or include profile_hint with reason: "bot_challenge".
Fix: switch to the stealth browser and route through a proxy. Retrying with the same settings usually hits the same wall.
Python
Budget exhausted
The run hit its step limit (agent_config.max_steps) or time limit (agent_config.max_duration_seconds) before finishing. The agent wraps up and returns what it collected so far. Here’s a five-page goal with max_steps: 2.
The run ended COMPLETED after 3 steps with error: null. Its result listed the page 1 products, then:
max_duration_seconds: 10 on the same goal, the run returned pages 1 and 2 with "Note: Pages 3, 4, and 5 were not completed due to budget exhaustion."
A run that stops at the step limit without producing any result ends FAILED with MAX_STEPS_EXCEEDED.
Fix: pick one.
- Raise
max_stepsormax_duration_secondsif the task needs more room. - Split the goal into smaller runs, for example one run per page, and run them in parallel. See Recover a Stuck or Long-Running Run.
- Remove steps the agent doesn’t need: start
urlon the page with the data, and say exactly which fields you want. See the Prompting Guide.
INSUFFICIENT_CREDITS, or the run ends CANCELLED with BILLING_REJECTED and no steps. Add money in your wallet and run it again.
Runs that fail
Whenstatus is FAILED or CANCELLED, result is null and error says why. Start with error.category:
error.help_url and error.help_message, when present, point to the page that covers the fix. The full list of codes is in Error Codes.
A run you started with queue or stream that is still RUNNING long after you expected can be stopped with POST /v1/runs/{id}/cancel. It ends CANCELLED. See Runs.
Setup failures
These errors come back before a run starts, so nothing is charged. PythonTINYFISH_API_KEY isn’t set and you don’t pass a key, the client fails before sending anything:
X-API-Key header:
AuthenticationError, status 401, code INVALID_API_KEY, message Invalid or expired API key. Copy a fresh key from your API keys page and check for spaces or line breaks. The key is checked before the request body, so fix a 401 first: a request with a bad key and a bad body only shows the 401.
A 401 with VAULT_RECONNECT_REQUIRED means a password manager connected to Vault needs to be reconnected. details.providers lists which ones.
400, invalid input. INVALID_INPUT means the request body didn’t pass validation. details lists each problem with its path. Common causes: a url without https://, an empty goal, a browser_profile other than lite or stealth, or a lowercase country_code.
Unsupported output schema. Output schemas support a subset of JSON Schema. A keyword outside it is rejected before the run starts, with the keyword and where it is:
402 INSUFFICIENT_CREDITS: your wallet is empty.details.wallet_urllinks to it.403 FORBIDDEN: your key or plan can’t use that feature. Check the message for which one.429 RATE_LIMIT_EXCEEDED: too many requests or too many pending runs. Wait and retry, honoringRetry-Afterwhen it’s sent.
request_id, which matches the X-Request-ID response header. Include it when you contact support.
Common Mistakes
Treating COMPLETED as success
Treating COMPLETED as success
Your code checks
status == "COMPLETED" and stores whatever is in result, including a note that the page needed a login. Check the data too. Add a status and reason to your output schema, as in Make every run report how it ended, and only store results where status is success.Required fields come back with made-up values
Required fields come back with made-up values
A schema that requires
name sent to a page behind a login returned "name": "GitHub Profile Name", a placeholder, because the field had to be filled. Require only status and reason. Leave data fields optional so the agent can leave them out when it can’t find them.A typo in agent_config is ignored in Python
A typo in agent_config is ignored in Python
In Python,
AgentConfig(max_step=5) is accepted and the misspelled field is dropped, so the run uses the default limit. Passing a plain dict to agent_config raises AttributeError: 'dict' object has no attribute 'model_dump'. Use AgentConfig(max_steps=...) with the exact field names. The TypeScript SDK rejects unknown keys before sending, with Unrecognized key: "max_step".Setting max_steps right at the number you expect
Setting max_steps right at the number you expect
A run that stops at its step limit takes one more step to write its result, so
max_steps: 2 shows num_of_steps: 3. A time limit isn’t a hard cut either: a run with max_duration_seconds: 10 ended about 20 seconds after it started, once the agent wrapped up. Leave headroom in both limits, and budget for one extra step on runs that hit the cap.Retrying a blocked run with the same settings
Retrying a blocked run with the same settings
A run blocked by a bot check is retried with the default browser and fails the same way. Retry with
browser_profile: "stealth" and proxy_config: { enabled: true }. If it’s still blocked, use a Browser Context Profile.Putting a password in the goal
Putting a password in the goal
The goal says “log in with user@example.com and password …”. The password ends up in run logs, and the run is harder to reuse. Use a Browser Context Profile (
use_profile) or Vault (use_vault).Looking up an MCP run from the CLI or API
Looking up an MCP run from the CLI or API
A run started from an MCP client returns
404 NOT_FOUND when you fetch it from the CLI or REST API. Look up runs from the same place you started them. MCP runs and API or CLI runs are listed separately.Related
Error Codes
Full list of API error codes
Runs
Run lifecycle, statuses, and finish reasons
Agent Mode, Max Steps, and Max Duration
What each limit is for
Browser Context Profiles
Reuse a signed-in browser session
Anti-Bot Guide
Get past bot detection
Get a TinyAgent Run as Structured JSON
Pin down an exact, predictable result shape