> ## 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.
</Warning>

## 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.

<CodeGroup>
  ```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'])"
  }
  ```
</CodeGroup>

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 console.notte.cc             |
| 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-7b29... is invalid",
  "message": "Token sk-notte-7b29... 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](/docs/billing).

## 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](/docs/state-and-secrets).

## 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](/docs/build#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`.
