> ## Documentation Index
> Fetch the complete documentation index at: https://docs.notte.cc/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>
  Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
</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}
  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}
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}
  from notte_sdk import NotteClient

  client = NotteClient()
  function = client.Function(function_id="d3c31289-f28b-49bd-a340-95e071cfef7e")
  result = function.run(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 |


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