Skip to content
Get started
SDKs
TypeScript

Automate Features

Execute complex browser automation tasks using natural language with the Tabstack TypeScript SDK.

The Automate operator executes complex browser automation tasks using natural language instructions. Unlike Extract and Generate which work with static content, Automate can interact with pages, fill forms, click buttons, and perform multi-step workflows.

The automate method is accessed through the agent client on your Tabstack instance:

import Tabstack from '@tabstack/sdk';
const client = new Tabstack({
apiKey: process.env.TABSTACK_API_KEY!
});
// Access agent automate method
const stream = await client.agent.automate({
task: 'Your task description',
url: 'https://example.com'
});
for await (const event of stream) {
// Handle streaming events
console.log(event.event, event.data);
}
  • Natural Language Tasks: Describe what you want to accomplish in plain English
  • Real-Time Streaming: Get live updates as the automation progresses
  • Multi-Step Workflows: Execute complex sequences of actions
  • Form Handling: Fill and submit forms automatically
  • Data Extraction: Extract data during automation
  • Guardrails: Set safety constraints to control automation behavior

The automate method returns an async iterable that streams events as the automation runs.

const stream = await client.agent.automate({
task: 'Find the top 3 trending repositories on GitHub and extract their names and star counts',
url: 'https://github.com/trending',
guardrails: 'browse and extract only'
});
for await (const event of stream) {
console.log(`Event: ${event.event}`);
if (event.event === 'task:completed') {
const result = event.data?.finalAnswer;
console.log('Automation completed:', result);
}
}

The automate method returns an async iterable to stream events:

// The for await...of loop handles the streaming automatically
const stream = await client.agent.automate({
task: 'Find products',
url: 'https://example.com'
});
for await (const event of stream) {
// Each event has:
// - event: string (event type)
// - data: object (event-specific data)
console.log(`${event.event}:`, event.data);
}

The automation streams different types of events as it progresses:

Event TypeDescriptionKey Data Fields
startAutomation is starting-
task:setupTask is being initializedtask
task:startedTask execution begantask
task:completedTask finished successfullyfinalAnswer, status
task:abortedTask was abortedreason
task:validatedTask result validatedvalidation
task:validation_errorValidation failederror
Event TypeDescriptionKey Data Fields
agent:processingAgent is processingstatus
agent:statusStatus updatemessage
agent:stepCompleted a stepstep, description
agent:actionPerforming an actionaction, target
agent:reasonedAgent’s reasoningthought, plan
agent:extractedData was extractedextractedData
agent:waitingWaiting for page load/actionreason
Event TypeDescriptionKey Data Fields
browser:navigatedPage navigation occurredurl
browser:action_startedBrowser action startingaction
browser:action_completedBrowser action finishedaction, result
browser:screenshot_capturedScreenshot takenscreenshotId
Event TypeDescription
completeAutomation stream finished
doneFinal event (always sent)
errorAn error occurred
console.log('Starting GitHub trending scraper...\n');
const stream = await client.agent.automate({
task: 'Navigate to GitHub trending, find the top 5 repositories, and for each extract: name, description, primary language, and star count',
url: 'https://github.com/trending',
guardrails: 'browse and extract only',
maxIterations: 50
});
for await (const event of stream) {
switch (event.event) {
case 'agent:status':
console.log(`Status: ${event.data?.message}`);
break;
case 'agent:action':
console.log(`Action: ${event.data?.action}`);
break;
case 'browser:navigated':
console.log(`Navigated to: ${event.data?.url}`);
break;
case 'agent:extracted':
const extracted = event.data?.extractedData;
console.log('Extracted data:', JSON.stringify(extracted, null, 2));
break;
case 'task:completed':
const result = event.data?.finalAnswer;
console.log('\nAutomation completed!');
console.log('Final result:', result);
break;
case 'error':
console.error('Error:', event.data?.error);
break;
}
}
console.log('Filling contact form...\n');
const formData = {
name: 'Alex Johnson',
company: 'Example Corp',
message: 'I am interested in learning more about your product offerings.'
};
const stream = await client.agent.automate({
task: 'Fill out the contact form with the provided data and submit it',
url: 'https://company.example.com/contact',
data: formData,
guardrails: 'do not navigate away from the domain',
maxIterations: 30
});
for await (const event of stream) {
switch (event.event) {
case 'agent:action':
const action = event.data?.action;
const target = event.data?.target;
console.log(`Action: ${action} on ${target || 'page'}`);
break;
case 'agent:status':
console.log(`Status: ${event.data?.message}`);
break;
case 'task:completed':
console.log('\nForm submitted successfully!');
const confirmation = event.data?.finalAnswer;
console.log('Confirmation:', confirmation);
break;
case 'error':
console.error('Error submitting form:', event.data?.error);
break;
}
}
console.log('Starting product research workflow...\n');
const steps: string[] = [];
const extractedData: any[] = [];
const stream = await client.agent.automate({
task: `
1. Search for "wireless headphones" on the e-commerce site
2. Filter results by "customer rating" (4 stars and above)
3. Extract the top 3 products with: name, price, rating, and review count
4. Return the results as structured data
`,
url: 'https://shop.example.com',
guardrails: 'browse and extract only, do not add items to cart',
maxIterations: 100
});
for await (const event of stream) {
switch (event.event) {
case 'agent:step':
const step = event.data?.step;
const description = event.data?.description;
steps.push(description);
console.log(`\nStep ${step}: ${description}`);
break;
case 'agent:action':
console.log(` -> ${event.data?.action}`);
break;
case 'agent:extracted':
const data = event.data?.extractedData;
extractedData.push(data);
console.log(' -> Extracted:', data);
break;
case 'task:completed':
console.log('\nWorkflow completed!');
console.log(`\nCompleted ${steps.length} steps`);
const finalResult = event.data?.finalAnswer;
console.log('\nFinal Results:');
console.log(JSON.stringify(finalResult, null, 2));
break;
case 'error':
console.error('\nWorkflow failed:', event.data?.error);
break;
}
}

Build a simple progress tracker:

interface ProgressState {
status: string;
currentStep: number;
totalSteps: number;
lastAction: string;
isComplete: boolean;
error?: string;
}
const progress: ProgressState = {
status: 'Starting...',
currentStep: 0,
totalSteps: 0,
lastAction: '',
isComplete: false
};
function displayProgress(progress: ProgressState) {
console.clear();
console.log('=== Automation Progress ===\n');
console.log(`Status: ${progress.status}`);
console.log(`Step: ${progress.currentStep}/${progress.totalSteps || '?'}`);
console.log(`Last Action: ${progress.lastAction}`);
if (progress.isComplete) {
console.log('\nComplete!');
} else if (progress.error) {
console.log(`\nError: ${progress.error}`);
}
}
try {
const stream = await client.agent.automate({
task: 'Find and extract the top 5 blog posts',
url: 'https://blog.example.com'
});
for await (const event of stream) {
switch (event.event) {
case 'agent:status':
progress.status = event.data?.message || 'Processing...';
break;
case 'agent:step':
progress.currentStep = event.data?.step || 0;
break;
case 'agent:action':
progress.lastAction = event.data?.action || '';
break;
case 'task:completed':
progress.isComplete = true;
progress.status = 'Completed';
break;
case 'error':
progress.error = event.data?.error;
break;
}
displayProgress(progress);
}
} catch (error) {
progress.error = error.message;
displayProgress(progress);
}

Each event includes an event property and a data object:

const stream = await client.agent.automate({
task: 'Find products',
url: 'https://example.com'
});
for await (const event of stream) {
// Access event type
console.log('Event type:', event.event);
// Access data fields directly using optional chaining
const message = event.data?.message;
const step = event.data?.step ?? 0;
// Access nested data
if (event.data?.extractedData) {
const data = event.data.extractedData;
// Process extracted data
}
// Log all event data
console.log('Event data:', event.data);
}
OptionTypeDefaultDescription
taskstring-The task description in natural language
urlstring-Starting URL for the automation
dataRecord<string, unknown>-Context data (e.g., form fields to fill)
geoTarget{ country: string }-Geotargeting parameters for region-specific browsing (e.g., { country: 'US' })
guardrailsstring-Safety constraints for automation behavior
maxIterationsnumber50Maximum iterations (range: 1-100)
maxValidationAttemptsnumber3Maximum validation retry attempts (range: 1-10)

Guardrails are natural language constraints that control automation behavior:

// Examples of guardrails:
// Browse only, no modifications
guardrails: 'browse and extract only'
// Stay on specific domain
guardrails: 'do not navigate away from the domain'
// No purchases
guardrails: 'do not add items to cart or make purchases'
// Read-only operations
guardrails: 'read-only operations, do not submit forms or click buttons that modify data'
// Specific constraints
guardrails: 'only search and extract data, do not click on external links'
// Vague
'Get some products'
// Specific
'Find the top 5 best-selling products in the Electronics category and extract their names, prices, and average ratings'
// Simple task - lower limit
maxIterations: 30
// Complex multi-step workflow - higher limit
maxIterations: 100

Protect against unintended actions:

// Good: Clear guardrails
{
guardrails: 'browse and extract only, do not submit forms or make purchases'
}
// Risky: No guardrails
{
// Could potentially trigger unintended actions
}

Don’t just wait for completion, handle progress and errors:

const stream = await client.agent.automate({
task: 'Find products',
url: 'https://example.com'
});
for await (const event of stream) {
switch (event.event) {
case 'agent:status':
// Show progress
break;
case 'task:completed':
// Handle success
break;
case 'error':
// Handle errors
break;
case 'task:aborted':
// Handle aborted tasks
break;
}
}

Add your own timeout logic for long-running automations:

const timeout = 300000; // 5 minutes
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const stream = await client.agent.automate({
task: 'Complex task',
url: 'https://example.com'
});
for await (const event of stream) {
if (controller.signal.aborted) {
console.log('Automation timed out');
break;
}
// Handle events
}
} finally {
clearTimeout(timeoutId);
}

Keep track of the automation flow:

const log: string[] = [];
const stream = await client.agent.automate({
task: 'Find products',
url: 'https://example.com'
});
for await (const event of stream) {
const logEntry = `[${new Date().toISOString()}] ${event.event}: ${JSON.stringify(event.data)}`;
log.push(logEntry);
if (event.event === 'task:completed' || event.event === 'error') {
// Save log to file or database
await saveLog(log);
}
}

Always wrap automation in try-catch blocks:

try {
const stream = await client.agent.automate({
task: 'Find products',
url: 'https://example.com'
});
for await (const event of stream) {
if (event.event === 'error') {
const error = event.data?.error;
console.error('Automation error:', error);
// Handle gracefully
break;
}
if (event.event === 'task:completed') {
// Success
}
}
} catch (error) {
console.error('Fatal error:', error.message);
// Cleanup and notify
}