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

# Troubleshooting

> What each failure looks like, and what to do about it.

Two different things can fail, and they look nothing alike.

**The call** can be rejected before your API ever runs: a bad key, a function id that is not yours, a malformed body. Those come back as a non-2xx with an error envelope.

**The run** can fail while your API is executing. Those come back as **HTTP 200**. The traceback is in the body.

> [!WARNING]
>
> A failed run returns 200 and `"status": "closed"`, exactly like a successful one. `status` is not a verdict, it only means the run is over. Never branch on the HTTP code alone.

## Telling a good run from a bad one

Look at the type of `result`. On success it is an object matching the API's declared schema. On failure it is a string.

```json Success theme={"system"}
{
  "function_id": "ab857363-...",
  "function_run_id": "4c6472d6-...",
  "session_id": "sess_...",
  "status": "closed",
  "result": {
    "list_type": "top",
    "story_count": 30,
    "stories": [{ "rank": 1, "title": "A Note from LWN", "points": 233 }]
  }
}
```

```json Run raised theme={"system"}
{
  "status": "closed",
  "result": "Script execution failed in unrestricted mode: Traceback (most recent call last):\n  File \"<user_script.py>\", line 82, in run\nValueError: Invalid list_type 'nonsense_zzz'. Choose from: top, new, best.\n"
}
```

```json Wrong variable name theme={"system"}
{
  "status": "closed",
  "result": "Unexpected variable names for run function: ['totally_unknown'] (expected variable names: ['limit', 'list_type'])"
}
```

A one-line check in any language: if `result` is not an object, treat the run as failed and log the string.

## Status codes

| Code | Meaning | What to do |
| - | - | - |
| 200 | Run finished. Check the type of `result` | Use `result`, or read the traceback |
| 307 | Normal. The endpoint redirects to the runner | Follow it. `curl` needs `--location` |
| 400 | `x-notte-api-key` does not match the bearer token | Send the same key in both headers |
| 401 | Missing key, invalid key, or plan limit reached | Check the key at anything.notte.cc/settings/api-keys |
| 404 | Function does not exist, or is not yours | Verify the `function_id` |
| 422 | Request did not validate | Read `loc` in the message, it names the field |
| 429 | Rate limit, or too many active resources | Back off and retry |
| 5xx | Platform fault | Retry with backoff, then contact support |

Every error envelope carries an `X-Error-Class` response header naming the exact class that fired. Quote it when you ask for help, it is far more precise than the status code.

## 307, and why the call needs two headers

The endpoint answers **307** and redirects to the runner, which lives on a different host. Clients drop `Authorization` across hosts but keep custom headers, so the key has to travel twice:

```bash theme={"system"}
curl --location 'https://api.notte.cc/functions/YOUR_FUNCTION_ID/runs/start' \
--header 'x-notte-api-key: YOUR_NOTTE_API_KEY' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_NOTTE_API_KEY' \
--data '{"function_id": "YOUR_FUNCTION_ID", "variables": {}}'
```

Drop `--location` and you get a 307 and no result. Drop `x-notte-api-key` and you get a 422. Send a different value in it than the bearer token and you get a 400. The SDKs handle all of this.

## 401 Unauthorized

Three different situations share this code.

**No `Authorization` header at all** returns the bare shape, with no envelope:

```json theme={"system"}
{ "detail": "Not authenticated" }
```

**A key that is wrong, revoked, or from another environment** returns `X-Error-Class: NotteApiInvalidApiKeyError`. The key is masked in the message, which is a useful way to confirm which key your process actually loaded:

```json theme={"system"}
{
  "status": 401,
  "error": "Token sk-notte-... is invalid",
  "message": "Token sk-notte-... is invalid",
  "session_id": null
}
```

**A plan limit** also returns 401, not 402: `Free plan limit exceeded. Please upgrade your plan to continue using Notte.` If a key that worked an hour ago starts failing, read the message before assuming the key is bad. See [Billing](https://anything.notte.cc/docs/billing.md).

## 404 Function not found

```json theme={"system"}
{ "status": 404, "error": "The function 'nope' does not exist or the user does not have access to it" }
```

`X-Error-Class: InvalidFunctionAccess`. Existence and ownership are deliberately not distinguished, so this is also what you get for someone else's unpublished API. Check the id on the API's page.

## 422 Unprocessable

Validation errors use a different body from the rest, and they name the exact field in `loc`:

```json theme={"system"}
{
  "status_code": 10422,
  "message": "1 validation error: {'type': 'missing', 'loc': ('header', 'x-notte-api-key'), 'msg': 'Field required', 'input': None}",
  "data": null
}
```

Common causes, in order of frequency:

* `('header', 'x-notte-api-key')`: the second auth header is missing.
* `('body', 'variables')`: `variables` is required. Send `{}` when the API takes no inputs.
* `('body', 'function_id')`: the id goes in the body as well as the path.

A 422 can also mean the API reads a secret your workspace has not set. That one is written out in full, names every missing key, and tells you nothing was charged because no session started. See [State and secrets](https://anything.notte.cc/docs/state-and-secrets.md).

## 429 Too many requests

Two limits produce this.

**Per-caller rate limit on APIs you do not own.** Marketplace APIs are rate limited per caller, so one heavy consumer cannot exhaust someone else's listing. Back off and retry.

**Active resource limits.** Your plan caps concurrent browser sessions. The message states the limit and the current count. Wait for runs in flight to finish, or upgrade.

## When the site changed

A traceback that reads like the page moved, rather than like a bad input, is the case [self-healing](https://anything.notte.cc/docs/build.md#self-healing) exists for. The thread that built the API wakes up, re-maps the site, and deploys a fix, usually within minutes. Retry once before you rebuild anything.

Self-healing only fires on a returned traceback. A wrong variable name is your side of the contract and will never heal on its own.

## Before you ask for help

1. Confirm the base URL is `https://api.notte.cc` and the path is `POST /functions/{function_id}/runs/start`.
2. Cheap credential test: `GET https://api.notte.cc/functions?limit=1` with the same key. A 200 there and a failure on your call means the problem is the request, not the key.
3. Capture the `X-Error-Class` header and the `function_run_id`.
4. Open the run in the **Runs** tab of the API's page. Failed runs keep their session recording, so you can watch what the browser actually saw.

Still stuck: [support@notte.cc](mailto:support@notte.cc), with the `function_run_id`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.