Quickstart: Your First Interactive Automation
Build a working interactive automation in under 5 minutes. Start a browser task, receive a mid-task form request from the agent, and supply values so the task can continue to completion.
You’ll build a working interactive automation in under 5 minutes. By the end, you’ll have code that starts a browser task, receives a mid-task form request from the agent, and supplies values so the task can continue to completion.
Prerequisites
Section titled “Prerequisites”- A Tabstack API key (get one at console.tabstack.ai)
- Node.js 20+ or Bun 1.0+ (TypeScript), or Python 3.9+ (Python)
- The Tabstack SDK installed
npm install @tabstack/sdkpip install tabstackSet your API key as an environment variable:
export TABSTACK_API_KEY="your-key-here"The task
Section titled “The task”You’ll automate a newsletter signup. The agent navigates to a signup page, hits the email field, and pauses to ask you for the address. Your code responds with the value, and the agent submits the form.
This is the core pattern for any interactive workflow: the agent does what it can autonomously, asks for what it needs, and continues when you respond.
Full example
Section titled “Full example”Create interactive.ts:
import Tabstack from '@tabstack/sdk'
const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY })
function resolveField(field: { ref: string; label: string }): string { const values: Record<string, string> = { email: process.env.NEWSLETTER_EMAIL ?? '', name: process.env.NEWSLETTER_NAME ?? '' } return values[field.ref] ?? values[field.label.toLowerCase()] ?? ''}
try { const stream = await client.agent.automate({ task: 'Sign up for the newsletter using the email address I provide.', url: 'https://example.com/newsletter', interactive: true })
for await (const event of stream) { if (event.event === 'interactive:form_data:request') { const { requestId, fields } = event.data
const fieldValues = fields.map((f) => ({ ref: f.ref, value: resolveField(f) }))
await client.agent.automateInput(requestId, { fields: fieldValues }) }
if (event.event === 'complete') { console.log('Done:', event.data.finalAnswer) }
if (event.event === 'error') { throw new Error(event.data.error.message) } }} catch (err) { if (err instanceof Tabstack.APIError) { if (err.status === 410) { console.error('Input request expired. Re-run the task and respond faster.') } else { console.error(`API error ${err.status}: ${err.message}`) } } throw err}Create interactive.py:
import osfrom tabstack import Tabstack, APIStatusError
client = Tabstack(api_key=os.environ["TABSTACK_API_KEY"])
def resolve_field(field) -> str: values = { "email": os.environ.get("NEWSLETTER_EMAIL", ""), "name": os.environ.get("NEWSLETTER_NAME", ""), } label = (field.label or "").lower() return values.get(field.ref) or values.get(label) or ""
stream = client.agent.automate( task="Sign up for the newsletter using the email address I provide.", url="https://example.com/newsletter", interactive=True)
for event in stream: if event.event == "interactive:form_data:request": request_id = event.data.request_id
field_values = [ {"ref": f.ref, "value": resolve_field(f)} for f in event.data.fields ]
try: client.agent.automate_input(request_id, fields=field_values) except APIStatusError as e: if e.status_code == 410: # Input request expired before we responded (2-minute window). raise RuntimeError("Input request expired. Re-run the task and respond faster.") raise
elif event.event == "complete": print("Done:", event.data.final_answer)
elif event.event == "error": raise RuntimeError(event.data.error.message)What just happened
Section titled “What just happened”When you set interactive: true, the agent streams events back as it works. When it reaches a form it needs input for, it pauses and emits an interactive:form_data:request event instead of guessing or failing.
That event carries a requestId and a list of fields. Each field has a ref (a stable identifier), a label, a fieldType, and a required flag. You call automateInput with the requestId and your values. The agent receives them and picks up where it left off.
The stream continues until you get a complete event carrying the final result.
The resolveField helper (or resolve_field in Python) is the key integration point: it maps field identifiers to the values your code has available. In production, this is where you look up user data, prompt a UI, or call a secrets store. The example uses environment variables to keep things runnable.
One timing constraint: input requests expire after 2 minutes. If your code doesn’t call automateInput in time, the task fails with a 410 Gone error. In production, handle this by restarting the task.
Next steps
Section titled “Next steps”- Interactive Mode guide: cancellation, expiry handling, security considerations, and the full event reference
- Orchestrator-in-the-loop: wire a parent agent to resolve interactive requests automatically, with no human in the loop
- API reference: all parameters, streaming events, and error codes