---
title: How to Use Interactive Mode | Tabstack
description: Interactive Mode allows the automation agent to pause and request information from the caller when it encounters forms requiring user data like email addresses, credentials, or preferences. Instead of failing or hallucinating, the agent asks for what it needs.
---

Interactive Mode is currently in **Beta**. This initial release is optimized for form-filling scenarios. Future iterations will support more complex interactive workflows.

## The Problem

When an automation agent encounters a form that requires personal data, it has a gap: it doesn’t know your email address, your password, or your preferences. Without that context, the agent either fails the task or fills in garbage data.

Consider this task: *“Sign up for the newsletter.”*

The agent can navigate to the signup page, locate the form, and identify the required fields. But it can’t submit the form without your email address. Interactive Mode solves this by letting the agent pause and ask.

## How Interactive Mode Works

When you enable interactive mode, the automation agent gains the ability to request information mid-task. Here’s the flow:

1. You send an automate request with `interactive: true`
2. The agent executes the task normally, navigating pages and interacting with elements
3. When the agent encounters a form requiring user data, it pauses and emits an `interactive:form_data:request` event containing the form fields it needs filled
4. Your application receives this event, collects the data (from a user prompt, a database, another agent, etc.), and submits it back via the input endpoint
5. The agent fills in the form fields and resumes the task
6. If form validation fails, the agent emits an `interactive:form_data:error` event with the specific errors, and you can re-submit corrected values

The agent fills form fields directly without exposing user-provided values to the LLM, keeping sensitive data like passwords and personal information private.

## Prerequisites

Before using interactive mode, you’ll need:

1. **A valid Tabstack API key:** Sign up at <https://tabstack.ai> to get your key.
2. **An SSE-capable client:** Your HTTP client must handle `text/event-stream` responses.
3. **A way to handle input requests:** Your application needs logic to respond when the agent requests form data.

Terminal window

```
export TABSTACK_API_KEY="your-api-key-here"
```

## Enabling Interactive Mode

Add `interactive: true` to your automate request:

- [curl](#tab-panel-156)
- [Python](#tab-panel-157)
- [TypeScript](#tab-panel-158)

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": "Sign up for the newsletter",
    "url": "https://www.mozilla.org",
    "interactive": true
  }'
```

```
from tabstack import Tabstack


client = Tabstack()


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

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


const client = new Tabstack();


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

When `interactive` is not set or set to `false`, the agent will automatically decline any form data requests and attempt to continue without user input.

## Handling Form Data Requests

When the agent needs user input, it emits an `interactive:form_data:request` event in the SSE stream. The event data contains:

| Field             | Type     | Description                                                                 |
| ----------------- | -------- | --------------------------------------------------------------------------- |
| `requestId`       | `string` | Unique identifier for this request. Use this when submitting your response. |
| `pageUrl`         | `string` | URL of the page containing the form.                                        |
| `pageTitle`       | `string` | Title of the page.                                                          |
| `formDescription` | `string` | Human-readable description of the form’s purpose.                           |
| `fields`          | `array`  | The form fields the agent needs filled.                                     |

### Field Structure

Each field in the `fields` array contains:

| Field          | Type       | Description                                                                                                         |
| -------------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
| `ref`          | `string`   | Element reference ID (e.g., `"E42"`). Include this in your response to map values to fields.                        |
| `label`        | `string`   | The field’s visible label (e.g., `"Email Address"`).                                                                |
| `fieldType`    | `string`   | One of: `text`, `email`, `phone`, `date`, `number`, `select`, `checkbox`, `radio`, `textarea`, `password`, `other`. |
| `required`     | `boolean`  | Whether the field must be filled.                                                                                   |
| `options`      | `string[]` | Available options for `select` and `radio` fields.                                                                  |
| `currentValue` | `string`   | Current value if the field is already partially filled.                                                             |
| `description`  | `string`   | Additional context or hints about the field.                                                                        |

### Example Event

```
event: interactive:form_data:request
data: {
  "requestId": "a1b2c3d4",
  "pageUrl": "https://www.mozilla.org/newsletter/",
  "pageTitle": "Newsletter signup",
  "formDescription": "Newsletter signup form requiring email and format preference",
  "fields": [
    {
      "ref": "E12",
      "label": "Your email address",
      "fieldType": "email",
      "required": true
    },
    {
      "ref": "E15",
      "label": "Format",
      "fieldType": "select",
      "required": false,
      "options": ["HTML", "Text"]
    }
  ]
}
```

## Submitting User Input

Once you have the values for the requested fields, submit them to the input endpoint:

```
POST /v1/automate/{requestId}/input
```

- [curl](#tab-panel-159)
- [Python](#tab-panel-160)
- [TypeScript](#tab-panel-161)

Terminal window

```
curl -X POST "https://api.tabstack.ai/v1/automate/a1b2c3d4/input" \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": [
      { "ref": "E12", "value": "user@example.com" },
      { "ref": "E15", "value": "HTML" }
    ]
  }'
```

```
response = client.agent.automate_input(
    "a1b2c3d4",
    fields=[
        {"ref": "E12", "value": "user@example.com"},
        {"ref": "E15", "value": "HTML"},
    ],
)
```

```
const response = await client.agent.automateInput("a1b2c3d4", {
  fields: [
    { ref: "E12", value: "user@example.com" },
    { ref: "E15", value: "HTML" },
  ],
});
```

The endpoint returns `202 Accepted` on success. If the request has expired or already been answered, it returns `410 Gone`.

Input requests expire after **2 minutes**. If you don’t respond within that window, the request returns `410 Gone` and the task is unrecoverable. You must restart the task from the beginning.

## Handling Validation Errors

If the form submission fails validation (e.g., an invalid email format), the agent emits an `interactive:form_data:error` event. This event includes the same field structure as the original request, plus a `fieldErrors` map showing which fields failed and why.

```
event: interactive:form_data:error
data: {
  "requestId": "e5f6g7h8",
  "pageUrl": "https://www.mozilla.org/newsletter/",
  "pageTitle": "Newsletter signup",
  "formDescription": "Newsletter signup form requiring email and format preference",
  "fields": [
    {
      "ref": "E12",
      "label": "Your email address",
      "fieldType": "email",
      "required": true,
      "description": "Invalid email address"
    }
  ],
  "fieldErrors": {
    "E12": "Invalid email address"
  }
}
```

Handle this the same way as the initial request: collect corrected values from the user and submit them to the input endpoint using the new `requestId`.

## Cancelling a Request

If you want to skip the form or abort the interactive request, submit with `cancelled: true`:

- [curl](#tab-panel-162)
- [Python](#tab-panel-163)
- [TypeScript](#tab-panel-164)

Terminal window

```
curl -X POST "https://api.tabstack.ai/v1/automate/a1b2c3d4/input" \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "cancelled": true }'
```

```
response = client.agent.automate_input(
    "a1b2c3d4",
    cancelled=True,
)
```

```
const response = await client.agent.automateInput("a1b2c3d4", {
  cancelled: true,
});
```

When cancelled, the agent will attempt to continue the task without the form data, which may result in task failure depending on the form’s requirements.

## Complete Example

This example shows the full flow: starting an interactive automation, listening for form data requests, prompting the user, and submitting the response.

- [Python](#tab-panel-165)
- [TypeScript](#tab-panel-166)

```
from tabstack import Tabstack


client = Tabstack()


# Start a task with interactive mode enabled
stream = client.agent.automate(
    task="Sign up for the newsletter",
    interactive=True,
)


# Listen for events in the stream
for event in stream:
    print(f"[{event.event}]")


    # When the agent requests form data
    if event.event == "interactive:form_data:request":
        request_id = event.data.request_id
        fields = event.data.fields


        print(f"\nThe agent needs the following information:")


        field_values = []
        for field in fields:
            # Show available options for select/radio fields
            if field.options:
                print(f"  {field.label} (options: {', '.join(field.options)})")


            prompt = f"  {field.label}{'*' if field.required else ''}: "
            value = input(prompt)
            field_values.append({"ref": field.ref, "value": value})


        # Submit the values back to the agent
        client.agent.automate_input(request_id, fields=field_values)
        print("Input submitted, agent resuming...\n")


    # Handle validation errors
    elif event.event == "interactive:form_data:error":
        request_id = event.data.request_id
        field_errors = event.data.field_errors
        fields = event.data.fields


        print(f"\nForm validation failed:")
        for ref, error in field_errors.items():
            print(f"  {ref}: {error}")


        field_values = []
        for field in fields:
            error = field_errors.get(field.ref, "")
            prompt = f"  {field.label} ({error}): " if error else f"  {field.label}: "
            value = input(prompt)
            field_values.append({"ref": field.ref, "value": value})


        client.agent.automate_input(request_id, fields=field_values)
        print("Corrected input submitted...\n")


    # Print the final result
    elif event.event == "complete":
        if event.data.success:
            print(f"\nTask complete: {event.data.final_answer}")
        else:
            code = event.data.error.code if event.data.error else "UNKNOWN"
            message = event.data.error.message if event.data.error else "(no message)"
            print(f"\nTask failed ({code}): {message}")
```

```
import Tabstack from "@tabstack/sdk";
import * as readline from "readline";


const client = new Tabstack();


// Helper to prompt user for input
function prompt(question: string): Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  return new Promise((resolve) => {
    rl.question(question, (answer) => {
      rl.close();
      resolve(answer);
    });
  });
}


async function run() {
  // Start a task with interactive mode enabled
  const stream = await client.agent.automate({
    task: "Sign up for the newsletter",
    interactive: true,
  });


  for await (const event of stream) {
    console.log(`[${event.event}]`);


    // When the agent requests form data
    if (event.event === "interactive:form_data:request") {
      const { requestId, fields } = event.data;


      console.log("\nThe agent needs the following information:");


      const fieldValues = [];
      for (const field of fields) {
        const label = field.label || "Unknown field";
        const required = field.required ? "*" : "";
        const options = field.options;


        if (options) {
          console.log(`  ${label} (options: ${options.join(", ")})`);
        }


        const value = await prompt(`  ${label}${required}: `);
        fieldValues.push({ ref: field.ref, value });
      }


      // Submit the values back to the agent
      await client.agent.automateInput(requestId, { fields: fieldValues });
      console.log("Input submitted, agent resuming...\n");
    }


    // Handle validation errors
    else if (event.event === "interactive:form_data:error") {
      const { requestId, fields, fieldErrors } = event.data;


      console.log("\nForm validation failed:");
      for (const [ref, error] of Object.entries(fieldErrors)) {
        console.log(`  ${ref}: ${error}`);
      }


      const fieldValues = [];
      for (const field of fields) {
        const error = fieldErrors[field.ref];
        const label = field.label || "Unknown field";


        const suffix = error ? ` (${error})` : "";
        const value = await prompt(`  ${label}${suffix}: `);
        fieldValues.push({ ref: field.ref, value });
      }


      await client.agent.automateInput(requestId, { fields: fieldValues });
      console.log("Corrected input submitted...\n");
    }


    // Print the final result
    else if (event.event === "complete") {
      console.log("\nTask complete:", event.data);
    }
  }
}


run();
```

## Use Cases

Interactive mode is useful whenever the agent needs context that only the caller can provide:

- **Account signups:** The agent navigates the signup flow while you provide email, username, and password.
- **Form submissions:** Contact forms, registration forms, surveys where personal data is required.
- **Checkout flows:** Providing shipping addresses, payment preferences, or coupon codes.
- **Multi-agent pipelines:** A parent orchestrator agent automatically supplies data from its own context when the automation agent requests it, with no human involvement needed.

## Limitations

- **Form-filling only:** This beta release is optimized for form completion. The agent requests data specifically when it encounters form fields requiring user input.
- **2-minute timeout:** Input requests expire after 2 minutes. A `410 Gone` response means the task is unrecoverable. Restart it.
- **Single request per form:** The agent requests all fields for a form in a single event. It does not ask for fields one at a time.
- **Not available via MCP:** Interactive Mode requires a persistent SSE stream to exchange events. It is not supported through the Tabstack MCP server. Use the SDK directly.

## Related

- [API reference: automate](/api/resources/agent/methods/automate/index.md). Start an automation and stream events, including the `interactive` parameter.
- [API reference: automate input](/api/resources/agent/methods/automate_input/index.md). Submit field values or cancel an interactive request.
- [Interactive Mode quickstart](/guides/interactive-mode-quickstart/index.md). Build your first interactive automation in under 5 minutes.
- [Orchestrator-in-the-loop](/guides/orchestrator-interactive-pattern/index.md). Resolve interactive requests from code, with no human in the loop.
- [Automation Events reference](/reference/automate-events/index.md). The full SSE event stream, ordering, and conditional events.
