> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tinyfish.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes

> API error codes and how to resolve them

TinyFish surfaces errors in two layers, and you need to handle both:

1. **API-level errors** — returned as non-2xx HTTP responses (auth, validation, rate limits, server errors).
2. **Run-response error codes** — returned with HTTP **200** inside the `error` object of a run response (agent failures, infrastructure issues that map to a finished run, cancellations).

Knowing which layer you're looking at tells you whether the issue is with the request, your account, or the automation itself.

## Error Response Format

API-level errors use one of two schemas depending on the HTTP status code:

```json theme={null}
// 400 Bad Request — may include validation details
{
  "error": {
    "code": "INVALID_INPUT",
    "message": "Field \"url\": Invalid input: expected string, received undefined",
    "details": [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": ["url"],
        "message": "Invalid input: expected string, received undefined"
      }
    ]
  }
}
```

```json theme={null}
// 401 Unauthorized and other errors — simpler schema without details
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid or expired API key"
  }
}
```

<Note>
  Schema-wise `details` is optional on every error response. In practice only `INVALID_INPUT` (400) populates it. The `details` array contains [Zod](https://zod.dev/) validation issues — each issue has at minimum `code`, `path`, and `message`, plus issue-specific fields like `expected` (`invalid_type`) or `values` (`invalid_value`).
</Note>

`/v1/automation/run-batch` uses a per-run error envelope instead of the top-level shape above. See [Batch Errors](#batch-errors) below.

## API-level Error Codes

### MISSING\_API\_KEY

**HTTP Status:** 401

The `X-API-Key` header was not included in the request.

```json theme={null}
{
  "error": {
    "code": "MISSING_API_KEY",
    "message": "X-API-Key header is required"
  }
}
```

**Solution:** Add the `X-API-Key` header to your request:

```bash theme={null}
curl -H "X-API-Key: ***" ...
```

***

### INVALID\_API\_KEY

**HTTP Status:** 401

The provided API key does not exist or has been revoked.

```json theme={null}
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid or expired API key"
  }
}
```

**Solutions:**

1. Verify your API key is correct (no extra whitespace)
2. Check if the key was deleted in the [API Keys dashboard](https://agent.tinyfish.ai/api-keys)
3. Generate a new key if needed

***

### UNAUTHORIZED

**HTTP Status:** 401

Authentication failed for a reason other than missing/invalid key. Typical cases: an upstream service (e.g. Vault) connection expired, or required user context is missing.

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication failed"
  }
}
```

**Solutions:**

1. Check your account status at [agent.tinyfish.ai/api-keys](https://agent.tinyfish.ai/api-keys)
2. For Vault-related calls, reconnect your vault under **Settings → Vault**
3. Try generating a new API key

***

### INVALID\_INPUT

**HTTP Status:** 400

The request body failed validation.

```json theme={null}
{
  "error": {
    "code": "INVALID_INPUT",
    "message": "Field \"browser_profile\": Invalid option: expected one of \"lite\"|\"stealth\"",
    "details": [
      {
        "code": "invalid_value",
        "values": ["lite", "stealth"],
        "path": ["browser_profile"],
        "message": "Invalid option: expected one of \"lite\"|\"stealth\""
      }
    ]
  }
}
```

**Common Causes:**

* `url` is missing or not a valid URL (must include `https://`)
* `goal` is empty or missing
* `browser_profile` is not `"lite"` or `"stealth"`
* `proxy_config.country_code` is not a supported 2-letter code (US, GB, CA, DE, FR, JP, AU)
* `output_schema` is invalid JSON Schema
* Missing required query parameter (e.g. `query` on `/v1/search`)

**Solution:** Check the `details` field for specific validation errors. Each entry includes the failing field `path` and a human-readable `message`.

***

### FORBIDDEN

**HTTP Status:** 403

Authentication succeeded, but the request was rejected — usually because the account lacks an entitlement, capability, or remaining credits.

```json theme={null}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Insufficient credits. You have 0 credits remaining. Requested 1 runs. Add credits at https://..."
  }
}
```

**Common Causes:**

* No remaining credits or expired subscription (returned as `FORBIDDEN` at request time; runs that get accepted and then fail on billing surface [`INSUFFICIENT_CREDITS`](#insufficient-credits) inside the run response)
* `proxy_config.type: "custom"` requested without the custom-proxy entitlement
* `output_schema` provided without the output-schema entitlement
* `capture_config` requests a capability the account isn't enabled for
* Content blocked by policy (see also [`CONTENT_POLICY_VIOLATION`](#content-policy-violation) on run responses)
* Attempting to access a resource you don't own

**Solution:** Check your account balance and subscription at [agent.tinyfish.ai/api-keys](https://agent.tinyfish.ai/api-keys), or contact support to enable a specific capability. The `message` field always specifies which entitlement or condition failed.

***

### NOT\_FOUND

**HTTP Status:** 404

The requested resource does not exist.

```json theme={null}
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Run with id 'a1b2c3d4-...' not found"
  }
}
```

**Common Causes:**

* Invalid `run_id` in `GET /v1/runs/:id`
* Vault connection not found
* Browser context profile not found
* Step HTML/screenshot not found (`GET /v1/runs/:id/steps/:stepId/...`)
* Run was deleted or never existed
* Run ID belongs to a different token scope — CLI/REST run IDs and MCP run IDs live in separate scopes, so a CLI lookup for an MCP-created run returns 404 by design

**Solution:** Verify the resource ID is correct. Run IDs are returned from `/v1/automation/run-async` or can be listed via `GET /v1/runs`.

***

### RETRY\_REQUIRED

**HTTP Status:** 409

A transient conflict prevented the request from completing. Currently emitted by Browser Context Profile setup endpoints (`/v1/profiles/:id/save`, `/v1/profiles/:id/setup-session`, `/v1/profiles/:id/setup-session/cancel`) when the setup session is in an intermediate state.

```json theme={null}
{
  "error": {
    "code": "RETRY_REQUIRED",
    "message": "Setup session is busy. Please retry in a moment."
  }
}
```

**Solution:** Wait briefly and retry. Use a short delay (1–2s) plus exponential backoff if the conflict persists.

***

### RATE\_LIMIT\_EXCEEDED

**HTTP Status:** 429

Too many requests in a short period.

```json theme={null}
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Limit: N requests per minute."
  }
}
```

Rate limits depend on your active plan and which API you're calling:

* `/v1/search` and `/v1/fetch` enforce per-minute request limits tied to your subscription.
* `/v1/automation/run`, `/run-async`, and `/run-batch` enforce a pending-run cap tied to your subscription's concurrency limit.

See your plan for the specific numbers, or contact support if you need higher limits.

**Response headers:**

| Header              | When set                          | Description                            |
| ------------------- | --------------------------------- | -------------------------------------- |
| `Retry-After`       | Search, Fetch (per-minute limits) | Suggested retry delay in seconds       |
| `X-RateLimit-Limit` | Search, Fetch (per-minute limits) | Your current per-minute limit          |
| `X-Request-ID`      | Search, Fetch                     | Request ID for support / observability |

<Note>
  The automation **pending-run cap** (429 from `/v1/automation/run`, `/run-async`, `/run-batch`) does **not** set `Retry-After` or `X-RateLimit-Limit` — wait for existing runs to finish and retry. The body message includes your current count and the maximum.
</Note>

**Solutions:**

1. Respect `Retry-After` when present, otherwise implement exponential backoff
2. Space out requests (recommended: 1-2 seconds between calls)
3. Use batch endpoints for high-volume workloads
4. Contact support for higher rate limits

**Example: Exponential Backoff**

```python theme={null}
import time
import random
from tinyfish import TinyFish, RateLimitError

client = TinyFish()

def call_with_backoff(fn, max_retries=5):
    for attempt in range(max_retries):
        try:
            return fn()
        except RateLimitError as exc:
            if attempt == max_retries - 1:
                raise
            # Prefer server hint when available
            retry_after = getattr(exc, "retry_after", None)
            wait = retry_after if retry_after else (2 ** attempt) + random.uniform(0, 1)
            time.sleep(wait)
```

***

### INTERNAL\_ERROR

**HTTP Status:** 500

An unexpected error occurred on the server.

```json theme={null}
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred"
  }
}
```

**Solutions:**

1. Retry the request after a brief delay
2. If the error persists, check [agent.tinyfish.ai/status](https://agent.tinyfish.ai/status) for outages
3. Contact support with your request details and timestamp (include `X-Request-ID` if you have it)

## Run-response Error Codes

Once a run is accepted, **completion-time failures** are reported inside the run response body — not as HTTP error codes. The HTTP response is **200**, and you inspect `status` + `error`:

```json theme={null}
{
  "run_id": "a1b2c3d4-...",
  "status": "FAILED",
  "result": null,
  "error": {
    "code": "SITE_BLOCKED",
    "message": "Target site blocked the automation. Try using stealth mode.",
    "category": "AGENT_FAILURE",
    "retry_after": null,
    "help_url": "https://docs.tinyfish.ai/key-concepts/browser-profiles#stealth",
    "help_message": "Getting blocked? Try enabling stealth mode."
  }
}
```

The `category` field gives you a quick branching key:

| Category          | Meaning                                                                                            | Recommended action                                                 |
| ----------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `SYSTEM_FAILURE`  | TinyFish-side issue (browser crash, capacity, infra timeout)                                       | Retry the run                                                      |
| `AGENT_FAILURE`   | The agent ran but couldn't achieve the goal (site blocked, content not found, auth required, etc.) | Refine input — change goal, enable stealth, attach a profile, etc. |
| `BILLING_FAILURE` | Out of credits                                                                                     | Add credits                                                        |
| `UNKNOWN`         | Unclassified                                                                                       | Treat as retryable                                                 |

Optional fields:

* `retry_after` — suggested delay in seconds (`null` if not retryable)
* `help_url` — link to troubleshooting docs
* `help_message` — short human-readable guidance

### SERVICE\_BUSY

**Category:** `SYSTEM_FAILURE` · **In-body HTTP:** 200

The platform is temporarily out of capacity (browser pool exhausted, dependent service unavailable). Equivalent to a 503 if returned at the HTTP layer.

**Solution:** Retry with exponential backoff.

### TIMEOUT

**Category:** `SYSTEM_FAILURE` · **In-body HTTP:** 200

An infrastructure or request timeout terminated the run. Equivalent to a 504.

**Solution:** Retry. If the goal is large/multi-step, consider splitting it or simplifying.

### INSUFFICIENT\_CREDITS

**Category:** `BILLING_FAILURE` · **In-body HTTP:** 200

The run was terminated mid-flight because the account ran out of credits. (Pre-flight credit failures surface as [`FORBIDDEN`](#forbidden) at the HTTP layer instead.)

**Solution:** Add credits at [agent.tinyfish.ai/api-keys](https://agent.tinyfish.ai/api-keys), then retry.

### CONTENT\_POLICY\_VIOLATION

**Category:** `AGENT_FAILURE` · **In-body HTTP:** 200

The request — typically the goal text or a target URL — was blocked by content policy.

**Solution:** Adjust the goal or target. Contact support if you believe the block was a false positive.

### MAX\_STEPS\_EXCEEDED

**Category:** `AGENT_FAILURE` · **In-body HTTP:** 200

The automation hit the configured maximum step count without producing a result.

**Solution:** Simplify the goal, split it into multiple runs, or raise the step limit if your plan supports it.

### SITE\_BLOCKED

**Category:** `AGENT_FAILURE` · **In-body HTTP:** 200

The target site blocked the automation (anti-bot, CAPTCHA, IP block). The `status` field is still `FAILED`.

**Solution:** Switch `browser_profile` to `"stealth"`, attach a proxy via `proxy_config`, or use a Browser Context Profile with a warmed session. See the [anti-bot guide](/anti-bot-guide).

### TASK\_FAILED

**Category:** `AGENT_FAILURE` · **In-body HTTP:** 200

The agent ran but couldn't achieve the goal — navigation failed, content not found, authentication required, the result was incorrect, or the task wasn't achievable as described.

**Solution:** Make the goal more concrete (which page, which selector-equivalent description, which field). See the [prompting guide](/prompting-guide). For auth flows, use a vault credential or a saved profile.

### CANCELLED

**Category:** *N/A* · **Status:** `CANCELLED` · **In-body HTTP:** 200

The run was cancelled — either by you (`POST /v1/runs/:id/cancel`), by SDK cancellation, or because the async task lifecycle was terminated. Not an error in the usual sense.

**Solution:** No action needed unless the cancellation was unintended.

## Batch Errors

`/v1/automation/run-batch` validates the whole request first, then dispatches each child run. Batch-level failures (auth, missing capability, validation) follow the standard `{ "error": { "code", "message" } }` shape. Per-run dispatch errors are returned inside the batch response under each run's slot. Treat the batch envelope as "did the batch get accepted?" and inspect each child for "did the individual run get scheduled?"

## Run Status vs Error Codes

<Note>
  HTTP error codes (the table below) indicate **request-level failures** — your request didn't make it to the agent. Run-response error codes indicate **completion-level outcomes** — the run reached the worker and reported back.

  For more on COMPLETED-but-failed runs, see [Understanding Run Status](/faq#what-does-completed-status-mean).
</Note>

## HTTP Status Code Summary

| Status | Meaning                                                  | Error codes returned                                                                                                                                                           |
| ------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 200    | OK (run finished — may still carry a run-response error) | `SERVICE_BUSY`, `TIMEOUT`, `INSUFFICIENT_CREDITS`, `CONTENT_POLICY_VIOLATION`, `MAX_STEPS_EXCEEDED`, `SITE_BLOCKED`, `TASK_FAILED`, `CANCELLED` (all inside the response body) |
| 400    | Bad Request                                              | `INVALID_INPUT`                                                                                                                                                                |
| 401    | Unauthorized                                             | `MISSING_API_KEY`, `INVALID_API_KEY`, `UNAUTHORIZED`                                                                                                                           |
| 403    | Forbidden                                                | `FORBIDDEN`                                                                                                                                                                    |
| 404    | Not Found                                                | `NOT_FOUND`                                                                                                                                                                    |
| 409    | Conflict                                                 | `RETRY_REQUIRED`                                                                                                                                                               |
| 429    | Too Many Requests                                        | `RATE_LIMIT_EXCEEDED`                                                                                                                                                          |
| 500    | Server Error                                             | `INTERNAL_ERROR`                                                                                                                                                               |

## Related

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/authentication">
    API key setup and troubleshooting
  </Card>

  <Card title="FAQ" icon="circle-question" href="/faq">
    Common questions and issues
  </Card>
</CardGroup>
