Skip to main content
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.
Use this when a request is rejected before it starts, or a run finishes without the data you asked for. A problem shows up in one of three places, and each needs a different fix: 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 a status 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
TypeScript
With a 3-step limit, this run couldn’t reach page 5, and it said so:
Keep only 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:
With the outcome schema above, the same page returns the reason directly:
Fix: give the run a way in. Use a Browser Context Profile, which reuses a browser session you already signed in to: Python
TypeScript
Or store the login in Vault and pass 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 default lite browser on a page behind DataDome, the run didn’t fail. The run ended COMPLETED after 5 steps with error: null. Its result, trimmed:
A run can also end 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
TypeScript
The same page and goal with these settings:
If stealth and a proxy aren’t enough, a Browser Context Profile carries real cookies and history, which some sites check for. See the Anti-Bot Guide.

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:
With 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_steps or max_duration_seconds if 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 url on the page with the data, and say exactly which fields you want. See the Prompting Guide.
Out of credits is a different budget. If your wallet can’t cover a run, the request is rejected with HTTP 402 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

When status 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. Python
TypeScript
No API key. If TINYFISH_API_KEY isn’t set and you don’t pass a key, the client fails before sending anything:
401, missing or wrong key. A request with no X-API-Key header:
A key that’s wrong, revoked, or expired comes back from both SDKs as 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:
Remove the keyword or restructure the schema. See Structured output. 402, 403, 429.
  • 402 INSUFFICIENT_CREDITS: your wallet is empty. details.wallet_url links 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, honoring Retry-After when it’s sent.
Every error body has a request_id, which matches the X-Request-ID response header. Include it when you contact support.

Common Mistakes

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.
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.
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".
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.
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.
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).
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.

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