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

# Agent Endpoints

> Choose the right TinyFish Agent endpoint based on sync, async, or streaming needs

TinyFish Web Agent offers three ways to run automations. Each endpoint serves a different need. Pick the one that matches how you want to handle the request and response.

## The Three Endpoints

| Endpoint     | Pattern          | Best For                             |
| ------------ | ---------------- | ------------------------------------ |
| `/run`       | Synchronous      | Quick tasks, simple integrations     |
| `/run-async` | Start then check | Long tasks, batch processing         |
| `/run-sse`   | Live updates     | Real-time progress, user-facing apps |

***

## Synchronous: `/run`

**Pattern:** Send request → Wait → Get result

The simplest approach. You call the API, it blocks until the automation completes, then returns the result.

```typescript theme={null}
import { TinyFish } from "@tiny-fish/sdk";

const client = new TinyFish();

const run = await client.agent.run({
  url: "https://example.com",
  goal: "Extract the page title",
});

console.log(run.result); // Your data
```

<Warning>
  Runs created via `/run` **cannot be cancelled**. The request blocks until the automation completes, so there is no window to issue a cancellation. If you need the ability to cancel runs, use `/run-async` or `/run-sse` instead.
</Warning>

<Tabs>
  <Tab title="When to use">
    * Tasks that complete in under 30 seconds
    * Simple scripts and one-off automations
    * When you don't need progress updates
  </Tab>

  <Tab title="When to avoid">
    * Long-running tasks (risk of timeout)
    * User-facing apps (no progress feedback)
    * Batch processing (blocks other requests)
    * When you need the ability to cancel a run
  </Tab>
</Tabs>

***

## Asynchronous: `/run-async`

**Pattern:** Send request → Get run ID → Poll for result

The request returns immediately with a `run_id`. You then poll a separate endpoint to check status and get the result when ready.

**1. Start the automation**

```typescript theme={null}
import { TinyFish } from "@tiny-fish/sdk";

const client = new TinyFish();

const queued = await client.agent.queue({
  url: "https://example.com",
  goal: "Extract all product data",
});

if (queued.error) {
  throw new Error(`Failed to queue run: ${queued.error.message}`);
}

const runId = queued.run_id;
```

**2. Poll for the result**

```typescript theme={null}
const run = await client.runs.get(runId);
// run.status: PENDING, RUNNING, COMPLETED, FAILED, or CANCELLED
// run.result: Your data (when COMPLETED)
```

**3. Cancel a run (optional)**

If you need to stop a run before it completes, send a POST to the cancel endpoint:

```typescript theme={null}
await fetch(`https://agent.tinyfish.ai/v1/runs/${runId}/cancel`, {
  method: "POST",
  headers: { "X-API-Key": process.env.TINYFISH_API_KEY },
});
```

<Info>
  Learn more about run statuses and lifecycle in [Runs](/key-concepts/runs).
</Info>

<Tabs>
  <Tab title="When to use">
    * Long-running automations (30+ seconds)
    * Batch processing multiple URLs
    * Fire-and-forget workflows
    * When you need to track runs separately
  </Tab>

  <Tab title="When to avoid">
    * When you need real-time progress updates
    * Simple, quick extractions (adds complexity)
  </Tab>
</Tabs>

***

## Streaming: `/run-sse`

**Pattern:** Send request → Receive event stream → Process events as they arrive

Uses [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) to push updates to you in real-time. You'll receive events for each action the browser takes, plus a streaming URL you can embed in an iframe to watch the automation live.

**1. Start the automation and read events**

```typescript theme={null}
import { TinyFish, EventType } from "@tiny-fish/sdk";

const client = new TinyFish();

const stream = await client.agent.stream({
  url: "https://example.com",
  goal: "Extract all product data",
});

for await (const event of stream) {
  console.log(event.type, event);
}
```

### Event Types

| Event           | Description                                         |
| --------------- | --------------------------------------------------- |
| `STARTED`       | Automation has begun, includes `run_id`             |
| `STREAMING_URL` | URL to watch the browser live (valid 24hrs)         |
| `PROGRESS`      | Browser action taken (click, type, scroll, etc.)    |
| `HEARTBEAT`     | Connection keep-alive (no action needed)            |
| `COMPLETE`      | Automation finished, includes `status` and `result` |

### Cancelling an SSE Run

You can cancel a streaming run using the `run_id` from the `STARTED` event. The `onStarted` callback fires while you consume the stream, so capture `run_id` there and trigger the cancel from a separate context (here, a deadline timer):

```typescript theme={null}
let runId: string | undefined;

const stream = await client.agent.stream(
  { url: "https://example.com", goal: "Extract all product data" },
  { onStarted: (e) => { runId = e.run_id; } },
);

// Cancel from another context while the stream is being consumed.
const deadline = setTimeout(() => {
  if (runId) {
    fetch(`https://agent.tinyfish.ai/v1/runs/${runId}/cancel`, {
      method: "POST",
      headers: { "X-API-Key": process.env.TINYFISH_API_KEY },
    });
  }
}, 30000);

for await (const event of stream) {
  console.log(event);
}
clearTimeout(deadline);
```

### Handling Events

Use this pattern to process each event type as the automation progresses.

```typescript theme={null}
import { TinyFish, EventType, RunStatus } from "@tiny-fish/sdk";

const client = new TinyFish();

const stream = await client.agent.stream(
  { url: "https://example.com", goal: "Extract all product data" },
  {
    onStarted: (e) => console.log(`Run started: ${e.run_id}`),
    onStreamingUrl: (e) => console.log(`Watch live: ${e.streaming_url}`),
    onProgress: (e) => console.log(`Action: ${e.purpose}`),
    onComplete: (e) => {
      if (e.status === RunStatus.COMPLETED) {
        console.log("Result:", e.result);
      } else {
        console.error(e.error?.message || "Automation failed");
      }
    },
  },
);

for await (const event of stream) {
  // Callbacks fire automatically during iteration
}
```

<Tabs>
  <Tab title="When to use">
    * User-facing apps (show progress)
    * When you want to watch the browser live
    * Debugging and development
    * Long tasks where you want visibility
  </Tab>

  <Tab title="When to avoid">
    * Batch processing (complexity overhead)
    * Backend jobs that don't need progress
  </Tab>
</Tabs>

***

## Common Request Options

All three endpoints accept the same request body. Beyond the required `url` and `goal`, these optional fields apply across `/run`, `/run-async`, and `/run-sse`:

| Field                               | Type   | Description                                                                                                                                                    |
| ----------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_config.max_steps`            | number | Maximum tool-call steps before the agent stops (in **beta** — join the beta to use it). 1–500, defaults to 150. See [Runs](/key-concepts/runs#max-steps).      |
| `agent_config.max_duration_seconds` | number | Maximum wall-clock seconds before the agent stops. Defaults to no limit.                                                                                       |
| `agent_config.mode`                 | string | Agent behavior mode. `strict` (in **beta** — join the beta to use it) enables fail-fast for test automation. See [Runs](/key-concepts/runs#strict-agent-mode). |
| `capture_config`                    | object | Toggle which data to capture during the run (`screenshots`, `html`, `recording`, etc.). `html: true` is required for per-step HTML snapshots.                  |
| `webhook_url`                       | string | HTTPS URL to receive run lifecycle notifications. Must use HTTPS.                                                                                              |

```json theme={null}
{
  "url": "https://example.com",
  "goal": "Extract all product data",
  "agent_config": {
    "max_steps": 50,
    "max_duration_seconds": 300
  },
  "capture_config": { "html": true },
  "webhook_url": "https://your-app.example.com/webhooks/tinyfish"
}
```

For authenticated runs and runtime browser modes, see [Browser Profiles](/key-concepts/browser-profiles) and the [Browser Context Profiles API](/agent-api/browser-context-profiles).

***

## Quick Decision Guide

<Steps>
  <Step title="Need real-time progress updates?">
    **Yes** → Use `/run-sse`
  </Step>

  <Step title="Task takes longer than 30 seconds?">
    **Yes** → Use `/run-async` + polling
  </Step>

  <Step title="Submitting multiple tasks at once?">
    **Yes** → Use `/run-async` (parallel submission)
  </Step>

  <Step title="Simple, quick task?">
    Use `/run` (synchronous)
  </Step>
</Steps>

***

## Comparison Table

| Feature          | `/run`       | `/run-async`     | `/run-sse`   |
| ---------------- | ------------ | ---------------- | ------------ |
| Response type    | Final result | Run ID           | Event stream |
| Progress updates | No           | No (poll status) | Yes          |
| Streaming URL    | In response  | Poll to get      | In events    |
| Cancellable      | No           | Yes              | Yes          |
| Best for         | Quick tasks  | Batch/long tasks | Real-time UI |
| Complexity       | Low          | Medium           | Medium       |

***

## Related

<CardGroup cols={2}>
  <Card title="Runs" icon="play" href="/key-concepts/runs">
    Understand the automation lifecycle
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    Full endpoint specifications
  </Card>
</CardGroup>
