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

# Anything API

> Build and run web automations from a single API call

<Tip>
  STOP. Do not read past this section until you have read and followed [/quickstart.md](/quickstart.md).

  Goal: produce a reliable SDK automation script. The intended authoring flow is generate then edit, not write browser automation from scratch.

  Do not hand-write SDK code with element IDs, selectors, or form fields from memory. Element IDs, selectors, and field mappings must come from a live `observe()` call, CLI `notte page observe` output, or generated workflow code. Guessed browser targets fail on real pages.

  Use the Notte skill and CLI to inspect the live site, validate actions/selectors, handle auth/session state, and export workflow code with `notte sessions workflow-code` before using SDK docs or SDK code.

  SDK reference pages are for understanding, running, or editing generated workflow code. They are not the starting point for manually authoring the initial browser automation. SDK-first code is guesswork on real-world pages with dynamic selectors, auth state, CAPTCHAs, and anti-bot behavior.
</Tip>

The Anything API lets you describe a task in plain English, and Notte will build, deploy, and run a web automation function for you -- all through a single HTTP request. The response streams the agent's progress as an [AI SDK UI message stream](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol), or you can detach and let the run continue in the background.

<CardGroup cols={2}>
  <Card title="Try it now" icon="arrow-up-right-from-square" href="https://anything.notte.cc">
    Open the Anything API web app.
  </Card>

  <Card title="Get your API key" icon="key" href="https://console.notte.cc">
    Create an account on the Console to get started.
  </Card>
</CardGroup>

## Quick start

```bash theme={null}
curl -N -X POST https://anything.notte.cc/api/anything/start \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $NOTTE_API_KEY" \
  -d '{"query": "fetch the top 3 hacker news posts"}'
```

The API streams the agent's work back as an AI SDK UI message stream. The `x-thread-id` response header identifies the thread handling your run, so you can send follow-up turns into the same thread later.

## Request

<ParamField path="query" type="string" required>
  A natural-language description of the task you want automated.
</ParamField>

<ParamField path="thread_id" type="string (UUID)">
  Send a follow-up turn into an existing thread. The sandbox state and conversation carry over. Omit to start a new thread.
</ParamField>

<ParamField path="detach" type="boolean" default="false">
  Start the run and return immediately instead of streaming. See [Detached mode](#detached-mode).
</ParamField>

<ParamField path="model" type="string">
  Optional model override for the agent.
</ParamField>

<ParamField path="reasoningEffort" type="string">
  Optional reasoning effort override for the agent.
</ParamField>

<ParamField path="version" type="string" default="v2">
  API contract version. `"v2"` is the only supported value and the default, so you can omit this field. Explicit `"v1"` returns `400` -- see [Migrating from v1](#migrating-from-v1).
</ParamField>

```json POST /api/anything/start theme={null}
{
  "query": "fetch the top 3 hacker news posts"
}
```

**Headers**

| Header          | Required | Description              |
| --------------- | -------- | ------------------------ |
| `Authorization` | Yes      | `Bearer <NOTTE_API_KEY>` |
| `Content-Type`  | Yes      | `application/json`       |

## Response

### Streamed (default)

By default, the response is an [AI SDK UI message stream](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) -- the same format the Vercel AI SDK's `useChat` hook consumes, so you can back a chat UI with it or read the stream yourself. Call it from your server, not from the browser: see [Calling from a browser](#calling-from-a-browser).

**Response headers**

| Header                          | Description                                                                      |
| ------------------------------- | -------------------------------------------------------------------------------- |
| `x-thread-id`                   | ID of the thread handling this run. Use it as `thread_id` in follow-up requests. |
| `x-vercel-ai-ui-message-stream` | `v1` -- the AI SDK's stream-format version. Unrelated to the retired v1 backend. |

### Detached mode

Pass `detach: true` to start the run and return immediately with `202 Accepted`:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://anything.notte.cc/api/anything/start \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $NOTTE_API_KEY" \
    -d '{"query": "fetch the top 3 hacker news posts", "detach": true}'
  ```

  ```python start_detached.py theme={null} theme={null}
  import os

  import requests

  NOTTE_API_KEY = os.environ["NOTTE_API_KEY"]

  response = requests.post(
      "https://anything.notte.cc/api/anything/start",
      headers={
          "Authorization": f"Bearer {NOTTE_API_KEY}",
          "Content-Type": "application/json",
      },
      json={"query": "fetch the top 3 hacker news posts", "detach": True},
      timeout=30,
  )
  response.raise_for_status()  # 202 Accepted

  run = response.json()
  print("Thread ID:", run["thread_id"])
  print("Status:", run["status"])  # "started"
  print("Follow along at:", run["url"])
  ```
</CodeGroup>

The response body contains:

| Field       | Description                                                                |
| ----------- | -------------------------------------------------------------------------- |
| `thread_id` | ID of the thread handling the run (also sent as the `x-thread-id` header). |
| `url`       | Link to the thread page in the app, where you can follow the run.          |
| `status`    | `"started"`                                                                |

## Follow-up turns

Pass the `thread_id` from a previous response to continue the conversation in the same thread. The sandbox state and conversation history carry over, so the agent can build on its earlier work:

```bash theme={null}
curl -N -X POST https://anything.notte.cc/api/anything/start \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $NOTTE_API_KEY" \
  -d '{
    "query": "now also include each post'\''s comment count",
    "thread_id": "820a7cff-613b-4529-9ba4-52c7a6777713"
  }'
```

Follow-up turns work in both streamed and detached mode.

## Consuming the stream

### Python

```python consume_stream.py theme={null} theme={null}
import json
import os

import requests

NOTTE_API_KEY = os.environ["NOTTE_API_KEY"]

response = requests.post(
    "https://anything.notte.cc/api/anything/start",
    headers={
        "Authorization": f"Bearer {NOTTE_API_KEY}",
        "Content-Type": "application/json",
    },
    json={"query": "fetch the top 3 hacker news posts"},
    stream=True,
    timeout=(10, 600),
)
response.raise_for_status()

# The thread ID lets you send follow-up turns later
thread_id = response.headers["x-thread-id"]
print("Thread ID:", thread_id)

for line in response.iter_lines():
    if not line:
        continue
    decoded = line.decode("utf-8")
    if not decoded.startswith("data: "):
        continue
    payload = decoded[len("data: ") :]
    if payload == "[DONE]":
        break
    chunk = json.loads(payload)
    print(chunk.get("type"), chunk)
```

### TypeScript

```typescript theme={null}
const response = await fetch("https://anything.notte.cc/api/anything/start", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${NOTTE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query: "fetch the top 3 hacker news posts" }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}
if (!response.body) {
  throw new Error("ReadableStream not available");
}

// The thread ID lets you send follow-up turns later
const threadId = response.headers.get("x-thread-id");
console.log("Thread ID:", threadId);

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let finished = false;

while (!finished) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split("\n");
  // The last element is a partial line: keep it for the next chunk
  buffer = lines.pop() ?? "";

  for (const rawLine of lines) {
    // Lines may be terminated with CRLF, so drop any trailing carriage return
    const line = rawLine.replace(/\r$/, "");
    if (!line.startsWith("data: ")) continue;
    const payload = line.slice("data: ".length);
    if (payload === "[DONE]") {
      finished = true;
      break;
    }
    const chunk = JSON.parse(payload);
    console.log(chunk.type, chunk);
  }
}
```

If you are building a chat interface, you can skip the manual parsing: the stream is directly compatible with the [AI SDK's `useChat` hook](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot). Point it at [your own proxy route](#calling-from-a-browser), not at `anything.notte.cc`.

## Using the Node SDK

The Node SDK wraps this endpoint as [`client.anything.start()`](/typescript-sdk-reference/notteanything/start). It posts to `api.notte.cc/anything/start`, which proxies to the same v2 backend and pipes the stream back, so you get the parsed events without writing the reader yourself:

```typescript theme={null}
import { NotteClient } from 'notte-sdk';

const client = new NotteClient();
const run = await client.anything.start({ task: 'fetch the top 3 hacker news posts' });

console.log('Thread ID:', run.threadId);

for await (const chunk of run.chunks) {
  console.log(chunk.type, chunk);
}
```

Two differences from calling the endpoint directly:

* The SDK field is `task`, not `query` -- the proxy maps it before forwarding.
* The proxy forwards only that field, so `thread_id`, `detach`, `model`, and `reasoningEffort` are not available through it. `run.threadId` still tells you which thread ran, but sending a follow-up turn into it means calling `anything.notte.cc` directly, as above.

The Python SDK does not wrap the Anything API yet -- use the HTTP example above.

## Calling from a browser

<Warning>
  Your `NOTTE_API_KEY` is a server-side secret. A browser client that sends `Authorization: Bearer <NOTTE_API_KEY>` ships the key to every visitor, who can then spend your credits. There are no browser-scoped keys for this endpoint.
</Warning>

Put a route on your own server between the browser and the Anything API. The route holds the key, forwards the request, and pipes the stream back unchanged -- so `useChat` still sees the AI SDK UI message stream it expects:

```typescript app/api/anything/route.ts theme={null}
export async function POST(request: Request) {
  const upstream = await fetch("https://anything.notte.cc/api/anything/start", {
    method: "POST",
    headers: {
      // Read server-side; never sent to the browser
      Authorization: `Bearer ${process.env.NOTTE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(await request.json()),
  });

  // Forward the stream-format header, or useChat will not recognise the stream,
  // and the thread ID, so the client can send follow-up turns
  const headers = new Headers();
  for (const name of [
    "content-type",
    "x-vercel-ai-ui-message-stream",
    "x-thread-id",
  ]) {
    const value = upstream.headers.get(name);
    if (value) headers.set(name, value);
  }

  return new Response(upstream.body, { status: upstream.status, headers });
}
```

Then point the browser at your route -- `useChat({ api: "/api/anything" })`. Authenticate and authorize your own users in that route: it is now the thing standing between them and your Notte credits.

## Re-running a created function

When a run creates a reusable **Notte Function**, you can run it again via the [Notte CLI](https://github.com/nottelabs/notte-cli) or [SDK](/quickstart):

<CodeGroup>
  ```bash CLI theme={null}
  notte functions run \
    --function-id d3c31289-f28b-49bd-a340-95e071cfef7e \
    --vars '{"count": "3"}' \
    -o json
  ```

  ```python run_function.py theme={null} theme={null}
  from notte_sdk import NotteClient

  client = NotteClient()
  result = client.functions.run(
      function_id="d3c31289-f28b-49bd-a340-95e071cfef7e",
      variables={"count": "3"},
  )
  print(result)
  ```
</CodeGroup>

## Migrating from v1

<Warning>
  The v1 backend is retired. Requests with `version: "v1"` return `400 {"error": "unsupported_version"}` with a retirement message.
</Warning>

If you were calling the v1 API:

* **Omit `version`** (or send `"v2"`). It now defaults to `"v2"`.
* **Responses are an AI SDK UI message stream** instead of the v1 SSE event feed. The v1 event types (`status`, `thinking_delta`, `done`, ...) no longer exist.
* **Follow-ups use `thread_id`.** The v1 concepts `resume_strategy`, snapshot semantics, and `claude_code_session_id` are gone: pass the `thread_id` from the `x-thread-id` response header to continue a thread.
* The `x-vercel-ai-ui-message-stream: v1` response header refers to the AI SDK's own stream-format version, not the retired v1 backend.

## Error handling

| Status | Meaning                                                                         |
| ------ | ------------------------------------------------------------------------------- |
| `400`  | Missing `query` field, invalid JSON, or `version: "v1"` (`unsupported_version`) |
| `401`  | Missing or invalid API key                                                      |
