---
title: How to Use the Automate Endpoint | Tabstack
description: Run multi-step browser tasks from natural-language instructions with the Tabstack automate endpoint, streaming typed events from task start to final answer.
---

This approach moves beyond simple, static extraction to handle dynamic, interactive tasks. Use it for:

- Complex web extraction requiring clicks, navigation, and stateful interaction.
- Automated form submission and multi-step workflows.
- Multi-page data collection, including pagination and “load more” buttons.
- Dynamic content extraction from SPAs (Single Page Applications).
- Price monitoring and competitive research that requires site interaction.
- Testing and validating web application workflows.

**Key Capabilities:**

- **Natural Language Control:** Execute tasks using plain-English instructions.
- **AI-Powered Interaction:** The agent can click, type, scroll, and navigate.
- **Real-Time Streaming:** Get live feedback as the agent works.
- **Data & Context:** Pass in JSON data for form-filling.
- **Interactive Mode:** Let the agent pause and request user input for forms requiring personal data.
- **Safety Guardrails:** Define “do-not-do” rules for safe execution.

The Tabstack SDKs are the recommended way to call automate. `client.agent.automate(...)` returns a stream of typed events that you iterate with `for await ... of` (TypeScript) or `for ... in` (Python), with no SSE framing to parse yourself. This guide is SDK-first. If you are not using an SDK, the curl and raw HTTP path is preserved in [Using the raw HTTP API directly](#using-the-raw-http-api-directly) at the end.

## Prerequisites

Before you can use automate, you’ll need:

1. **A Tabstack API key.** Create one in the [console](https://console.tabstack.ai) and set it as the `TABSTACK_API_KEY` environment variable. The [Quickstart](/getting-started/quick-start/index.md) walks through the full setup.
2. **An installed SDK (recommended):** `@tabstack/sdk` for TypeScript or `tabstack` for Python. See the [TypeScript](/sdks/typescript/quickstart/index.md) and [Python](/sdks/python/quickstart/index.md) quickstarts. For non-SDK use, any client that can read a `text/event-stream` response works (see the [raw HTTP appendix](#using-the-raw-http-api-directly)).
3. **A clear task:** a specific, natural language description of your goal.

Both SDKs read `TABSTACK_API_KEY` from the environment automatically, so `new Tabstack()` (TypeScript) and `Tabstack()` (Python) take no arguments once the variable is set.

---

## Quickstart

The minimal automate call needs a `task` and, usually, a starting `url`. The SDK opens the stream and you iterate it; the final answer arrives on the `task:completed` event. Here is the same call in each SDK, with the raw curl equivalent alongside.

- [TypeScript](#tab-panel-110)
- [Python](#tab-panel-111)
- [curl](#tab-panel-112)

```
import Tabstack from "@tabstack/sdk";


const client = new Tabstack();


const stream = await client.agent.automate({
  task: "Find the top 3 trending repositories and extract their names",
  url: "https://github.com/trending",
});


for await (const event of stream) {
  if (event.event === "task:completed") {
    console.log(event.data.finalAnswer);
  }
}
```

```
from tabstack import Tabstack


client = Tabstack()


stream = client.agent.automate(
    task="Find the top 3 trending repositories and extract their names",
    url="https://github.com/trending",
)


for event in stream:
    if event.event == "task:completed":
        print(event.data.final_answer)
```

Terminal window

```
curl -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "task": "Find the top 3 trending repositories and extract their names",
    "url": "https://github.com/trending"
  }'
```

The client constructor takes no arguments because the SDK reads `TABSTACK_API_KEY` from the environment. The `for await` / `for ... in` loop runs until the stream ends; there is no separate close call. With curl, the `-N` flag disables buffering so the Server-Sent Events print to your terminal as they arrive, and you parse the `event:` / `data:` lines yourself (see the [raw HTTP appendix](#using-the-raw-http-api-directly)).

## How the stream works

Every automate run streams a sequence of typed events. Each event has a string `event` name and a `data` payload; switch on `event.event` and the SDK narrows `data` to the right shape. The lifecycle runs from `task:started`, through execution events, to `task:completed`, then `complete` and `done`. A minimal progress reporter (setup as in [Quickstart](#quickstart)):

- [TypeScript](#tab-panel-113)
- [Python](#tab-panel-114)
- [curl](#tab-panel-115)

```
for await (const event of stream) {
  switch (event.event) {
    case "task:started":
      console.log("Task started");
      break;
    case "agent:action":
      console.log("Action:", event.data.action);
      break;
    case "agent:extracted":
      console.log("Extracted:", event.data.extractedData);
      break;
    case "task:completed":
      console.log("Done:", event.data.finalAnswer);
      break;
    case "error":
      console.error("Error:", event.data.error);
      break;
  }
}
```

```
for event in stream:
    match event.event:
        case "task:started":
            print("Task started")
        case "agent:action":
            print("Action:", event.data.action)
        case "agent:extracted":
            print("Extracted:", event.data.extracted_data)
        case "task:completed":
            print("Done:", event.data.final_answer)
        case "error":
            print("Error:", event.data.error)
```

Terminal window

```
# curl gives you the raw SSE stream. Each message is an `event:` line
# followed by a `data:` line of JSON. You match on the event name and
# parse the data yourself; see the raw HTTP appendix for a full parser.
```

For the complete event catalogue see [Response structure and event types](#response-structure-and-event-types) below, and for an end-to-end walkthrough of every event with runnable code, see [Understanding Automate Event Flow](/guides/automate-event-flow/index.md).

---

## Request parameters

You configure the agent through the options you pass to `automate(...)` (or the JSON body, for curl). The TypeScript names are camelCase; the Python names are snake\_case. The full option list is in the [TypeScript](/sdks/typescript/automate/index.md) and [Python](/sdks/python/automate/index.md) SDK references.

### `task` (required)

- **Type:** `string`
- **Description:** The natural language description of your objective. Be as specific as possible.

**Examples:**

```
// Good for data extraction
{
  "task": "Find the top 5 products in the 'Electronics' category and extract their names, prices, and ratings."
}


// Good for form filling
{
  "task": "Fill out the contact form with the provided information and submit it. Then, verify that the 'Thank You' message appears."
}


// Good for multi-step workflows
{
  "task": "Go to the search bar, search for 'running shoes', filter by 'size 10' and 'brand: Nike', and extract the names of the first 3 results."
}
```

**Tips for writing good tasks:**

- **Be specific:** “Extract top 3” is better than “extract products.”
- **Mention quantities:** “first 5,” “all results on the page,” etc.
- **Describe interactions:** “Click the ‘Next’ button,” “filter by size,” “submit the form.”
- **State the output:** “extract their names and prices,” “return the final confirmation text.”

### `url` (optional)

- **Type:** `string` (URI format)

- **Description:** The starting URL for the task.

- **When to include:** Use this when you know the exact starting page for the task.

- **When to omit:** Omit this if your task is a general web search (e.g., “Find the weather in Boston”). The agent determines the starting point.

### `data` (optional)

- **Type:** `unknown` (TypeScript, object-shaped in practice) / `dict` (Python)
- **Description:** A JSON object providing context or data for the task, typically for form-filling. The agent maps the keys in this object (like `firstName`) to the fields on the page.

```
{
  "task": "Submit the registration form with my information",
  "url": "https://example.com/register",
  "data": {
    "firstName": "Alex",
    "lastName": "Johnson",
    "email": "alex@example.com"
  }
}
```

### `geo_target` (optional)

- **Type:** `{ country: string }` (TypeScript) / `dict` (Python). Both SDKs use the snake\_case name `geo_target`. This is the exception: the other automate params are camelCase (`maxIterations`, `maxValidationAttempts`). A camelCase `geoTarget` is silently ignored.
- **Description:** Geotargeting parameters for region-specific browsing, e.g. `{ country: "US" }`. See [Geotargeting](/guides/geotargeting/index.md).

### `guardrails` (optional)

- **Type:** `string`
- **Description:** Safety constraints describing what the agent should **NOT** do. This is critical for preventing unintended actions.

**Examples:**

```
// Read-only extraction
{
  "task": "Extract product information",
  "guardrails": "Browse and extract only. Do not click buttons, submit forms, or add anything to a cart."
}


// Domain restrictions
{
  "task": "Research company information",
  "guardrails": "Stay on the company's official website (example.com). Do not navigate to any external links, social media, or partner sites."
}
```

### `maxIterations` (optional)

- **Type:** `number`
- **Default:** `50`
- **Range:** `1-100`
- **Python name:** `max_iterations`
- **Description:** The maximum number of steps (page loads, clicks, extractions) the agent can take before terminating. This prevents infinite loops.

**When to adjust:**

- **Lower (10-20):** For simple, single-page tasks.
- **Default (50):** Sufficient for most tasks with moderate complexity.
- **Higher (75-100):** For complex, multi-page workflows or deep pagination.

### `maxValidationAttempts` (optional)

- **Type:** `number`
- **Default:** `3`
- **Range:** `1-10`
- **Python name:** `max_validation_attempts`
- **Description:** The maximum number of times the agent will try to validate that its final step was successful (e.g., re-checking for a “Thank You” message after form submission).

### `interactive` (optional)

- **Type:** `boolean`
- **Default:** `false`
- **Description:** Enable interactive mode to allow the agent to pause and request user input when it encounters forms requiring personal data (email, passwords, preferences, etc.). When enabled, the agent emits `interactive:form_data:request` events and you respond with `client.agent.automateInput(...)`. See [Interactive mode](#interactive-mode) below and the [Interactive Mode guide](/guides/interactive-mode/index.md).

---

## Response structure and event types

You consume a stream of typed events. The tables below list the common events and their key `data` fields, grouped by category. Field names are camelCase in TypeScript and snake\_case in Python (for example `finalAnswer` / `final_answer`, `extractedData` / `extracted_data`).

### Task events (high-level task status)

| Event Type              | Description                | Key Data Fields                                  |
| ----------------------- | -------------------------- | ------------------------------------------------ |
| `start`                 | Automation is starting     | -                                                |
| `task:setup`            | Task is being initialized  | `task`                                           |
| `task:started`          | Task execution began       | `task`, `plan`, `successCriteria`, `actionItems` |
| `task:completed`        | Task finished successfully | `finalAnswer`, `success`                         |
| `task:aborted`          | Task was aborted           | `reason`                                         |
| `task:validated`        | Task result validated      | `completionQuality`, `observation`               |
| `task:validation_error` | Validation failed          | `error`                                          |

### Agent events (the AI’s reasoning and actions)

| Event Type         | Description                  | Key Data Fields                       |
| ------------------ | ---------------------------- | ------------------------------------- |
| `agent:processing` | Agent is processing          | `operation`, `hasScreenshot`          |
| `agent:status`     | Status update                | `message`                             |
| `agent:step`       | Iteration begins             | `currentIteration`                    |
| `agent:action`     | Performing an action         | `action`, `ref`, `value`              |
| `agent:reasoned`   | Agent’s reasoning            | `reasoning`                           |
| `agent:extracted`  | Data was extracted           | `extractedData`                       |
| `agent:waiting`    | Waiting for page load/action | `iterationId`, `seconds`, `timestamp` |

### Browser events (low-level browser state)

| Event Type                    | Description              | Key Data Fields    |
| ----------------------------- | ------------------------ | ------------------ |
| `browser:navigated`           | Page navigation occurred | `url`, `title`     |
| `browser:action_started`      | Browser action starting  | `action`           |
| `browser:action_completed`    | Browser action finished  | `action`, `result` |
| `browser:screenshot_captured` | Screenshot taken         | `screenshotId`     |

### Interactive events (when `interactive: true`)

| Event Type                      | Description                             | Key Data Fields                          |
| ------------------------------- | --------------------------------------- | ---------------------------------------- |
| `interactive:form_data:request` | Agent needs user input for a form       | `requestId`, `fields`, `formDescription` |
| `interactive:form_data:error`   | Form validation failed after user input | `requestId`, `fields`, `fieldErrors`     |

### Stream control events

| Event Type | Description                                                                     |
| ---------- | ------------------------------------------------------------------------------- |
| `complete` | Final result event. `data` carries `finalAnswer`, `stats`, `success`, `error?`. |
| `done`     | Signals the stream is closing (always sent last).                               |
| `error`    | An unrecoverable error occurred. `data` carries the error detail.               |

For the payload of each event with runnable handlers, see [Understanding Automate Event Flow](/guides/automate-event-flow/index.md) and the [Automate Events reference](/reference/automate-events/index.md).

---

## Working with streaming responses

The Quickstart logs the final answer. In a real application you process events as they arrive to drive a UI, and capture the final result at the end.

### Processing events in real time

Switch on `event.event` and react to each phase. The SDK delivers typed data, so there is no buffering or JSON parsing to manage.

- [TypeScript](#tab-panel-116)
- [Python](#tab-panel-117)
- [curl](#tab-panel-118)

```
const stream = await client.agent.automate({
  task: "Extract the top 5 products with their prices",
  url: "https://shop.example.com/products",
});


for await (const event of stream) {
  switch (event.event) {
    case "agent:status":
      console.log("Status:", event.data.message);
      break;
    case "browser:navigated":
      console.log("Navigated to:", event.data.url);
      break;
    case "agent:extracted":
      console.log("Extracted:", event.data.extractedData);
      break;
    case "task:completed":
      console.log("Done:", event.data.finalAnswer);
      break;
  }
}
```

```
stream = client.agent.automate(
    task="Extract the top 5 products with their prices",
    url="https://shop.example.com/products",
)


for event in stream:
    match event.event:
        case "agent:status":
            print("Status:", event.data.message)
        case "browser:navigated":
            print("Navigated to:", event.data.url)
        case "agent:extracted":
            print("Extracted:", event.data.extracted_data)
        case "task:completed":
            print("Done:", event.data.final_answer)
```

Terminal window

```
curl -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "task": "Extract the top 5 products with their prices",
    "url": "https://shop.example.com/products"
  }'
# Parse the streamed event:/data: lines yourself; see the raw HTTP appendix.
```

### Collecting the final result

Often you want to process events as they arrive and also capture the final outcome. The `complete` event carries the canonical final state in `event.data` (`finalAnswer`, `stats`, `success`, and an optional `error`).

- [TypeScript](#tab-panel-119)
- [Python](#tab-panel-120)
- [curl](#tab-panel-121)

```
async function runAutomationTask(task: string, url: string) {
  const stream = await client.agent.automate({ task, url });


  let finalAnswer: string | null = null;
  let stats: unknown = null;


  for await (const event of stream) {
    if (event.event === "agent:extracted") {
      console.log("Progress:", event.data.extractedData);
    }
    if (event.event === "complete") {
      finalAnswer = event.data.finalAnswer;
      stats = event.data.stats;
    }
  }


  return { finalAnswer, stats };
}


const result = await runAutomationTask(
  "Find the top 3 articles and their publication dates",
  "https://news.example.com",
);
console.log(result.finalAnswer);
```

```
def run_automation_task(task: str, url: str):
    stream = client.agent.automate(task=task, url=url)


    final_answer = None
    stats = None


    for event in stream:
        if event.event == "agent:extracted":
            print("Progress:", event.data.extracted_data)
        if event.event == "complete":
            final_answer = event.data.final_answer
            stats = event.data.stats


    return {"final_answer": final_answer, "stats": stats}




result = run_automation_task(
    "Find the top 3 articles and their publication dates",
    "https://news.example.com",
)
print(result["final_answer"])
```

Terminal window

```
curl -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "task": "Find the top 3 articles and their publication dates",
    "url": "https://news.example.com"
  }'
# The final result arrives on the `complete` event near the end of the stream.
```

---

## Interactive mode

When you pass `interactive: true` and the agent hits a form needing user data, the stream pauses with an `interactive:form_data:request` event carrying a `requestId` and a `fields` array. Collect the values, then resume the task by submitting them with `client.agent.automateInput(...)`. If validation fails, you receive an `interactive:form_data:error` event with `fieldErrors`, and you resubmit corrected values.

- [TypeScript](#tab-panel-122)
- [Python](#tab-panel-123)
- [curl](#tab-panel-124)

```
const stream = await client.agent.automate({
  task: "Sign up for the newsletter",
  url: "https://example.com",
  interactive: true,
});


for await (const event of stream) {
  if (event.event === "interactive:form_data:request") {
    const { requestId, fields } = event.data;


    // Collect a value for each requested field (from a user, DB, or secrets store).
    const fieldValues = fields.map((field) => ({
      ref: field.ref,
      value: lookupValue(field), // your logic
    }));


    await client.agent.automateInput(requestId, { fields: fieldValues });
  }
}
```

```
stream = client.agent.automate(
    task="Sign up for the newsletter",
    url="https://example.com",
    interactive=True,
)


for event in stream:
    if event.event == "interactive:form_data:request":
        request_id = event.data.request_id


        # Collect a value for each requested field.
        field_values = [
            {"ref": field.ref, "value": lookup_value(field)}  # your logic
            for field in event.data.fields
        ]


        client.agent.automate_input(request_id, fields=field_values)
```

Terminal window

```
# When the stream emits interactive:form_data:request, POST the values back
# to resume the task, keyed by the requestId from the event:
curl -X POST https://api.tabstack.ai/v1/automate/$REQUEST_ID/input \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fields": [{ "ref": "email", "value": "you@example.com" }] }'
```

To cancel an interactive request instead of providing data, submit `{ cancelled: true }` (TypeScript) / `cancelled=True` (Python). For a complete walkthrough, see the [Interactive Mode guide](/guides/interactive-mode/index.md).

---

## Error handling

A robust implementation handles two kinds of failure:

1. **HTTP-level errors** raised before the stream opens (a malformed body, a missing `task`, an invalid token, a rate limit). With the SDK these surface as typed exception classes; wrap the `automate(...)` call in try/catch.
2. **In-stream `error` events** that arrive mid-task when the task runner itself fails (for example an unreachable URL). Handle these with a `case "error":` arm inside the loop.

> **See also:** [Error Reference](/production/error-reference/index.md) for the canonical list of every status code, error message, and SDK exception across all endpoints, including the automate stream’s agent-level abort codes.

### Common error status codes

| Status Code | Error                                     | Description                            |
| ----------- | ----------------------------------------- | -------------------------------------- |
| 400         | `task is required`                        | Missing required `task` parameter.     |
| 400         | `invalid URL format`                      | Malformed URL if provided.             |
| 400         | `maxIterations must be between 1 and 100` | Invalid iteration limit.               |
| 401         | `Unauthorized - Invalid token`            | Missing or invalid Bearer token.       |
| 500         | `failed to call automate server`          | Internal server error.                 |
| 503         | `automate service not available`          | Service not configured or unavailable. |

- [TypeScript](#tab-panel-125)
- [Python](#tab-panel-126)
- [curl](#tab-panel-127)

```
import Tabstack, {
  BadRequestError,
  AuthenticationError,
  RateLimitError,
} from "@tabstack/sdk";


const client = new Tabstack();


try {
  const stream = await client.agent.automate({
    task: "Extract product information",
    url: "https://example.com/products",
  });


  for await (const event of stream) {
    if (event.event === "error") {
      console.error("In-stream error:", event.data.error);
      break;
    }
    // ... handle the rest of the events
  }
} catch (err) {
  if (err instanceof AuthenticationError) {
    console.error("Auth failed, check your API key");
  } else if (err instanceof RateLimitError) {
    console.error("Rate limited, back off and retry");
  } else if (err instanceof BadRequestError) {
    console.error("Bad request:", err.message);
  } else {
    throw err;
  }
}
```

```
from tabstack import (
    Tabstack,
    BadRequestError,
    AuthenticationError,
    RateLimitError,
)


client = Tabstack()


try:
    stream = client.agent.automate(
        task="Extract product information",
        url="https://example.com/products",
    )
    for event in stream:
        if event.event == "error":
            print("In-stream error:", event.data.error)
            break
        # ... handle the rest of the events
except AuthenticationError:
    print("Auth failed, check your API key")
except RateLimitError:
    print("Rate limited, back off and retry")
except BadRequestError as exc:
    print("Bad request:", exc)
```

Terminal window

```
curl -sS -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{ "task": "Extract product information", "url": "https://example.com/products" }'
# HTTP errors (401, 503, ...) arrive as the response status before the stream.
# Task-level failures arrive as an `event: error` message inside the stream.
```

If a task is terminated early, you may also see a `task:aborted` event before the stream closes. Agent-level aborts also surface inside the `complete` event with `success: false` and a structured `error`; see the [Automate Events reference](/reference/automate-events/index.md) for the distinction.

---

## Advanced usage patterns

### Form submission with data

Pass a `data` object and the agent maps its keys to the fields on the page. Use `guardrails` to keep the agent from straying.

- [TypeScript](#tab-panel-128)
- [Python](#tab-panel-129)
- [curl](#tab-panel-130)

```
const contactData = {
  name: "Alex Johnson",
  email: "alex@example.com",
  company: "Acme Inc",
  message: "Interested in learning more about your products",
};


const stream = await client.agent.automate({
  task: "Fill out and submit the contact form with the provided information",
  url: "https://company.com/contact",
  data: contactData,
  guardrails:
    "Only fill and submit the form, do not navigate away or click other links",
});


for await (const event of stream) {
  if (event.event === "task:completed") {
    console.log("Result:", event.data.finalAnswer);
  }
}
```

```
contact_data = {
    "name": "Alex Johnson",
    "email": "alex@example.com",
    "company": "Acme Inc",
    "message": "Interested in learning more about your products",
}


stream = client.agent.automate(
    task="Fill out and submit the contact form with the provided information",
    url="https://company.com/contact",
    data=contact_data,
    guardrails="Only fill and submit the form, do not navigate away or click other links",
)


for event in stream:
    if event.event == "task:completed":
        print("Result:", event.data.final_answer)
```

Terminal window

```
curl -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "task": "Fill out and submit the contact form with the provided information",
    "url": "https://company.com/contact",
    "data": {
      "name": "Alex Johnson",
      "email": "alex@example.com",
      "company": "Acme Inc",
      "message": "Interested in learning more about your products"
    },
    "guardrails": "Only fill and submit the form, do not navigate away or click other links"
  }'
```

### Multi-page data collection

Raise `maxIterations` for tasks that page through results, and collect `agent:extracted` events as they arrive.

- [TypeScript](#tab-panel-131)
- [Python](#tab-panel-132)
- [curl](#tab-panel-133)

```
const extracted: unknown[] = [];


const stream = await client.agent.automate({
  task: 'Search for "wireless headphones", go through the first 3 pages of results, and extract product names and prices from each page',
  url: "https://shop.example.com",
  maxIterations: 75,
  guardrails: "Browse and extract only, do not add items to cart or checkout",
});


for await (const event of stream) {
  if (event.event === "agent:extracted") {
    extracted.push(event.data.extractedData);
    console.log("Pages collected:", extracted.length);
  }
}


console.log(`Collected ${extracted.length} data points`);
```

```
extracted = []


stream = client.agent.automate(
    task='Search for "wireless headphones", go through the first 3 pages of results, and extract product names and prices from each page',
    url="https://shop.example.com",
    max_iterations=75,
    guardrails="Browse and extract only, do not add items to cart or checkout",
)


for event in stream:
    if event.event == "agent:extracted":
        extracted.append(event.data.extracted_data)
        print("Pages collected:", len(extracted))


print(f"Collected {len(extracted)} data points")
```

Terminal window

```
curl -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "task": "Search for \"wireless headphones\", go through the first 3 pages of results, and extract product names and prices from each page",
    "url": "https://shop.example.com",
    "maxIterations": 75,
    "guardrails": "Browse and extract only, do not add items to cart or checkout"
  }'
```

Your task description should explicitly mention pagination (“go through the first 3 pages”) to guide the agent.

### Price monitoring

A focused single-page task: extract a price and stock status, then compare against a target. Keep `maxIterations` low for simple tasks.

- [TypeScript](#tab-panel-134)
- [Python](#tab-panel-135)
- [curl](#tab-panel-136)

```
async function monitorPrice(productUrl: string, targetPrice: number) {
  const stream = await client.agent.automate({
    task: "Navigate to the product page and extract the current price. Also check if the product is in stock.",
    url: productUrl,
    maxIterations: 20,
    guardrails: "Browse and extract only, do not make purchases",
  });


  let answer: string | null = null;
  for await (const event of stream) {
    if (event.event === "task:completed") {
      answer = event.data.finalAnswer;
    }
  }
  console.log("Result:", answer, "(target:", targetPrice, ")");
  return answer;
}


await monitorPrice("https://shop.example.com/product/12345", 299.99);
```

```
def monitor_price(product_url: str, target_price: float):
    stream = client.agent.automate(
        task="Navigate to the product page and extract the current price. Also check if the product is in stock.",
        url=product_url,
        max_iterations=20,
        guardrails="Browse and extract only, do not make purchases",
    )


    answer = None
    for event in stream:
        if event.event == "task:completed":
            answer = event.data.final_answer


    print("Result:", answer, "(target:", target_price, ")")
    return answer




monitor_price("https://shop.example.com/product/12345", 299.99)
```

Terminal window

```
curl -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "task": "Navigate to the product page and extract the current price. Also check if the product is in stock.",
    "url": "https://shop.example.com/product/12345",
    "maxIterations": 20,
    "guardrails": "Browse and extract only, do not make purchases"
  }'
```

For a complete, scheduled price monitor built as an example app, see [Price Monitor](/examples/price-monitor/index.md).

---

## Best practices

### 1. Write clear, specific task descriptions

The quality of your task description is the single most important factor for success.

```
// Vague, may not get what you want
Get some products


// Better, more specific
Extract the top 5 products with their names and prices


// Best, very detailed
Go to the laptops category, filter by price (under $1000), sort by rating
(highest first), and extract the top 5 results including product name,
price, rating, and number of reviews
```

### 2. Use guardrails for safety

Always specify what the agent should **NOT** do, especially on e-commerce or sensitive sites.

```
{
  "task": "Research competitor pricing",
  "guardrails": "Browse and view pages only. Do not submit any forms, create accounts, or make purchases. Stay on the main website, do not follow external links."
}
```

### 3. Set appropriate iteration limits

Match `maxIterations` to your task’s complexity to prevent runaways.

- **Simple (10-20):** Single page extraction, basic form fill.
- **Medium (30-50):** Multi-page search, simple workflows.
- **Complex (60-100):** Deep pagination, multi-step form-filling with validation.

### 4. Provide context with the `data` parameter

When filling forms, structure your `data` object to match the conceptual groups on the page.

```
{
  "task": "Complete the registration form",
  "data": {
    "personalInfo": { "firstName": "Alex", "lastName": "Johnson" },
    "contact": { "email": "alex@example.com", "phone": "555-1234" },
    "preferences": { "newsletter": true }
  }
}
```

### 5. Handle streaming events progressively

Don’t wait for the `complete` event. Process events as they arrive to build a real-time experience and collect data incrementally. Act on `agent:extracted` as it fires rather than buffering everything to the end.

### 6. Watch for errors and aborts

Add a `case "error":` arm for in-stream task-runner failures, and watch for `task:aborted`. Both are your signal that a task has failed and you should stop processing.

### 7. Test with simple tasks first

Start with a simple task (like “Navigate to the products page”) to verify your connection. Then build up to more complex workflows, adding filters and interactions one at a time.

---

## Using the raw HTTP API directly

If you are not using an SDK, you can call `/v1/automate` over plain HTTP. The SDK examples above are the recommended path; everything in this appendix is what the SDK does for you.

### Endpoint details

- **URL:** `https://api.tabstack.ai/v1/automate`
- **Method:** `POST`
- **Authentication:** `Bearer <your-api-key>` (required)
- **Content-Type:** `application/json`
- **Response Type:** `text/event-stream` (Server-Sent Events)

Unlike other Tabstack API endpoints, `/v1/automate` **always** streams its response. Without an SDK you need a client that can process Server-Sent Events (SSE) to receive real-time updates and the final result.

### The SSE stream

You won’t get a single JSON response; you’ll get a series of events. Each message is an `event:` line and a `data:` line carrying a JSON payload. A successful run looks like:

```
event: task:trace_context
data: {"traceId": "4bf92f3577b34da6a3ce929d0e0e4736"}


event: cdp:endpoint_connected
data: {"endpointIndex": 1, "total": 1, "iterationId": "iter_01", "timestamp": 1720000000000}


event: agent:processing
data: {"operation": "Creating task plan", "hasScreenshot": false}


event: browser:navigated
data: {"url": "https://github.com/trending", "title": "Trending repositories"}


event: task:started
data: {"task": "Find the top 3 trending repositories", "url": "https://github.com/trending", "plan": "..."}


event: agent:extracted
data: {"extractedData": "[{\"name\": \"awesome-ai\"}]"}


event: task:completed
data: {"success": true, "finalAnswer": "Top 3 repos: ..."}


event: complete
data: {"success": true, "finalAnswer": "Top 3 repos: ...", "stats": {}}


event: done
data: {}
```

The stream opens with `task:trace_context` (skipped if tracing is disabled), then `cdp:endpoint_connected`, then `agent:processing` / `agent:status` while the agent builds a task plan, then `browser:navigated`, then `task:started`. It emits progress events as the agent works, and ends with `task:completed`, then `complete` (the canonical final result), then `done`. For the full ordering and the conditional events in between, see the [Automate Events reference](/reference/automate-events/index.md).

### Minimal request (curl)

Terminal window

```
curl -X POST https://api.tabstack.ai/v1/automate \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "task": "Find the top 3 trending repositories and extract their names",
    "url": "https://github.com/trending"
  }'
```

The `-N` flag disables curl’s buffering so events print as they arrive.

### Parsing the stream (JavaScript with `fetch`)

SSE messages can arrive split across chunks, so buffer the bytes and only process complete lines (ending in `\n`).

```
async function processAutomationEvents() {
  const response = await fetch("https://api.tabstack.ai/v1/automate", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.TABSTACK_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      task: "Extract the top 5 products with their prices",
      url: "https://shop.example.com/products",
    }),
  });


  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }


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


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


    buffer += decoder.decode(value, { stream: true });


    let eolIndex;
    while ((eolIndex = buffer.indexOf("\n")) >= 0) {
      const line = buffer.substring(0, eolIndex).trim();
      buffer = buffer.substring(eolIndex + 1);


      if (line.startsWith("data: ")) {
        const data = line.slice(6);
        try {
          const event = JSON.parse(data);
          console.log("Event:", event);
        } catch (e) {
          // Ignore incomplete JSON
        }
      }
    }
  }
}


processAutomationEvents();
```

### Parsing the stream (Python with `requests`)

Python’s `requests` handles buffering with `iter_lines()`, so the code is simpler. `raise_for_status()` catches HTTP errors early.

```
import requests
import os
import json


def process_automation_events():
    response = requests.post(
        'https://api.tabstack.ai/v1/automate',
        headers={
            'Authorization': f'Bearer {os.environ["TABSTACK_API_KEY"]}',
            'Content-Type': 'application/json'
        },
        json={
            'task': 'Extract the top 5 products with their prices',
            'url': 'https://shop.example.com/products'
        },
        stream=True
    )


    response.raise_for_status()


    for line in response.iter_lines():
        if not line:
            continue


        line = line.decode('utf-8')
        if line.startswith('data: '):
            data = line[6:]
            try:
                event = json.loads(data)
                print('Event:', event)
            except json.JSONDecodeError:
                pass


process_automation_events()
```

### Collecting the final result (raw)

Watch for the `complete` event and keep its payload. Storing all events as you go also helps with debugging.

```
import requests
import os
import json


def run_automation_task(task, url):
    response = requests.post(
        'https://api.tabstack.ai/v1/automate',
        headers={
            'Authorization': f'Bearer {os.environ["TABSTACK_API_KEY"]}',
            'Content-Type': 'application/json'
        },
        json={'task': task, 'url': url},
        stream=True
    )
    response.raise_for_status()


    events = []
    final_result = None


    for line in response.iter_lines():
        if not line:
            continue
        line = line.decode('utf-8')
        if line.startswith('data: '):
            data = line[6:]
            try:
                event = json.loads(data)
                events.append(event)
                if 'success' in event and 'finalAnswer' in event:
                    final_result = event['finalAnswer']
            except json.JSONDecodeError:
                pass


    return {
        'success': final_result is not None,
        'result': final_result,
        'event_count': len(events),
    }
```

### Error handling (raw)

Handle errors at two levels: HTTP errors (caught before streaming via `response.ok` / `raise_for_status()`) and stream-level `event: error` messages emitted while the agent works.

```
import requests


try:
    response = requests.post(
        'https://api.tabstack.ai/v1/automate',
        headers={'Authorization': f'Bearer {TABSTACK_API_KEY}', 'Content-Type': 'application/json'},
        json={'task': 'Extract product information', 'url': 'https://example.com/products'},
        stream=True,
    )
    response.raise_for_status()  # HTTP-level errors


    for line in response.iter_lines():
        line = line.decode('utf-8') if line else ''
        if 'event: error' in line or (line.startswith('data: ') and '"error"' in line):
            print('Task error in stream:', line)
except requests.exceptions.HTTPError as http_err:
    code = http_err.response.status_code
    if code == 401:
        print('Authentication failed. Check your API key.')
    elif code == 503:
        print('Automate service is unavailable. Try again later.')
    raise
```

---

## Related resources

- [API Reference: Automate Endpoint](/api/resources/agent/methods/automate/index.md)
- [Understanding Automate Event Flow](/guides/automate-event-flow/index.md)
- [Interactive Mode](/guides/interactive-mode/index.md)
- [How to Extract JSON Data](/guides/how-to-extract-json/index.md)
- [How to Generate JSON Data](/guides/how-to-generate-json/index.md)
- [Quick Start Guide](/getting-started/quick-start/index.md)
- [Build Your First Tabstack App](/getting-started/build-your-first-tabs-app/index.md)
