How to Use the Automate Endpoint
Automating complex, multi-step browser workflows has traditionally been brittle and time-consuming. The Tabstack API automate endpoint solves this by letting you describe your goal in natural language. An AI agent interprets your task, navigates websites, interacts with elements, and extracts data, all while streaming real-time progress updates.
This approach moves beyond simple, static scraping to handle dynamic, interactive tasks. It’s a powerful tool for:
- Complex web scraping 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 via Server-Sent Events (SSE).
- Data & Context: Pass in JSON data for form-filling.
- Safety Guardrails: Define “do-not-do” rules for safe execution.
Prerequisites
Section titled “Prerequisites”Before you can use the /v1/automate endpoint, you’ll need:
- A valid Tabstack API key: Sign up at https://tabstack.ai to get your key.
- Authentication: The API uses Bearer token authentication.
- SSE-capable client: Your HTTP client must be able to handle a
text/event-streamresponse. - A clear task: A specific, natural language description of your goal.
We recommend storing your API key in an environment variable for security.
Set Your API Key
Section titled “Set Your API Key”This command makes your API key available in your shell session.
export TABSTACK_API_KEY="your-api-key-here"The export command is a bash command that sets an environment variable. We’re setting the variable name TABSTACK_API_KEY, which is what our code examples will look for throughout this guide. You’ll need to replace "your-api-key-here" with the actual API key you received from Tabstack.
How to Run:
- Open your terminal.
- Paste the command, replacing the placeholder with your key.
- Press Enter. This variable will be set for your current terminal session.
Basic Usage
Section titled “Basic Usage”Endpoint Details
Section titled “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)
Minimal Request Example
Section titled “Minimal Request Example”Let’s start with a simple task: extracting the top 3 trending repositories from GitHub. We’ll send a POST request with our task and url in the JSON body.
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" }'async function automateTask() { 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: "Find the top 3 trending repositories and extract their names", url: "https://github.com/trending", }), });
const reader = response.body.getReader(); const decoder = new TextDecoder();
while (true) { const { value, done } = await reader.read(); if (done) break;
const chunk = decoder.decode(value); const lines = chunk.split("\n");
for (const line of lines) { if (line.startsWith("data: ")) { const data = line.slice(6); if (data === "[DONE]") { console.log("Stream completed"); return; }
try { const event = JSON.parse(data); console.log("Event:", event); } catch (e) { // Skip parse errors for incomplete JSON chunks } } } }}
// Call the functionautomateTask();import requestsimport osimport json
def automate_task(): response = requests.post( 'https://api.tabstack.ai/v1/automate', headers={ 'Authorization': f'Bearer {os.environ["TABSTACK_API_KEY"]}', 'Content-Type': 'application/json' }, json={ 'task': 'Find the top 3 trending repositories and extract their names', 'url': 'https://github.com/trending' }, stream=True # Enable streaming )
# Check for HTTP errors response.raise_for_status()
for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: '): data = line[6:] # Remove 'data: ' prefix
if data == '[DONE]': print('Stream completed') break
try: event = json.loads(data) print('Event:', event) except json.JSONDecodeError: # Skip parse errors for incomplete chunks pass
# Call the functionautomate_task()Code Explanations
Section titled “Code Explanations”Here’s a breakdown of what each example is doing:
curl Breakdown:
This sends a POST request with authentication and a JSON payload containing your task and URL. The key flag here is -N, which disables buffering—without it, you won’t see the streaming events in real-time. The response streams back as Server-Sent Events showing the agent’s progress.
JavaScript Breakdown:
The code makes an authenticated POST request, then processes the streaming response using getReader(). The while loop reads chunks as they arrive, and since chunks may contain multiple events or partial events, the code splits by newlines and processes complete lines. The try/catch handles incomplete JSON that might arrive mid-chunk. This streaming approach lets you show real-time updates as the agent works.
Python Breakdown:
The Python version is more straightforward thanks to the requests library. Setting stream=True enables streaming, and iter_lines() handles the line-by-line processing automatically. The code decodes each line, checks for the SSE data: prefix, and parses the JSON. Python’s requests library makes streaming simpler than the JavaScript version.
How to Run These Examples
Section titled “How to Run These Examples”- Environment: Ensure you’ve set your
TABSTACK_API_KEYenvironment variable (see Prerequisites). curl: Paste the command directly into your terminal and run it. You will see events stream directly to your console.- JavaScript: Save the code as
automate.mjs. Installnode-fetchif you’re not in a browser environment (npm install node-fetch). Run it with Node.js:node automate.mjs. (Note:process.envrequires Node.js). - Python: Save the code as
automate.py. Installrequests(pip install requests). Run it from your terminal:python automate.py.
Understanding Server-Sent Events (SSE)
Section titled “Understanding Server-Sent Events (SSE)”Because automation tasks can take time, the endpoint streams its progress. You won’t get a single JSON response; you’ll get a series of events. Here’s an example of the event flow you’ll receive.
event: task:starteddata: {"task": "Find the top 3 trending repositories", "url": "https://github.com/trending"}
event: agent:processingdata: {"operation": "Creating task plan", "hasScreenshot": false}
event: browser:navigateddata: {"title": "Trending - GitHub", "url": "https://github.com/trending"}
event: agent:actiondata: {"action": "extract", "target": "repository list"}
event: agent:extracteddata: {"extractedData": "[{\"name\": \"awesome-ai\"}, {\"name\": \"web-framework\"}, {\"name\": \"data-viz\"}]"}
event: task:completeddata: {"success": true, "finalAnswer": "Top 3 repos: awesome-ai, web-framework, data-viz"}
event: completedata: {"success": true, "result": {"finalAnswer": "Top 3 repos: awesome-ai, web-framework, data-viz"}}
event: donedata: {}Stream Explanation:
Each message in the stream consists of an event: type (like agent:processing or task:completed) and a data: payload containing a JSON object with details. The stream begins with event: task:started, which confirms the task has begun. As the AI works, you’ll see event: agent:processing when the AI is thinking or planning its next steps. When the agent navigates to a new page, event: browser:navigated is emitted. The event: agent:extracted indicates the agent has successfully extracted data from the page. Near the end, both event: task:completed and event: complete signal the task has finished successfully and contain the final answer. Finally, event: done signals that the stream is closing.
This real-time flow allows you to build responsive UIs that show “Navigating…”, “Extracting data…”, “Task complete!”
Request Parameters
Section titled “Request Parameters”You can customize the agent’s behavior by passing these parameters in the JSON body of your POST request.
task (required)
Section titled “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)
Section titled “url (optional)”- Type:
string(URI format) - Description: The starting URL for the task.
Example:
{ "task": "Extract the latest blog post titles", "url": "https://blog.example.com"}- 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 will determine the best starting point (e.g., Google).
data (optional)
Section titled “data (optional)”- Type:
object - Description: A JSON object providing context or data for the task, typically for form-filling.
Example:
{ "task": "Submit the registration form with my information", "url": "https://example.com/register", "data": { "firstName": "Alex", "lastName": "Johnson", "credentials": { "username": "alexj2025", "password": "super-secret-password!" } }}The agent will intelligently map the keys in this object (like firstName) to the fields on the web page.
guardrails (optional)
Section titled “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."}
// Limited interaction{ "task": "Search for items and view details", "guardrails": "Only use search, filter, and pagination. Do not perform any checkout actions or create an account."}
// 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)
Section titled “maxIterations (optional)”- Type:
number - Default:
50 - Range:
1-100 - 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)
Section titled “maxValidationAttempts (optional)”- Type:
number - Default:
3 - Range:
1-10 - 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).
Response Structure and Event Types
Section titled “Response Structure and Event Types”You’ll interact with a stream of events. Here are the most common ones and their purpose.
Task Events (High-Level Task Status)
Section titled “Task Events (High-Level Task Status)”task:started: Fired once. Signals the task has been received and started.task:completed: Fired on success. Thedatapayload contains thefinalAnswer.task:aborted: Fired on failure. Thedatapayload contains areason(e.g., “Exceeded maximum iterations”).task:validated: Fired when a validation step succeeds.
Agent Events (The AI’s “Thoughts” and Actions)
Section titled “Agent Events (The AI’s “Thoughts” and Actions)”agent:processing: The agent is planning or thinking.agent:status: A human-readable message about the current plan (e.g.,"plan": "1. Navigate to page...").agent:action: The agent is performing an action (e.g.,{"action": "click", "target": "Search button"}).agent:reasoned: A human-readable thought process (e.g.,"reasoning": "Search results loaded. Now extracting data.").agent:extracted: Fired when a chunk of data is extracted. Thedatapayload containsextractedData.agent:waiting: The agent is waiting for a page to load or an element to appear.
Browser Events (Low-Level Browser State)
Section titled “Browser Events (Low-Level Browser State)”browser:navigated: A new page has loaded. The payload includes the newtitleandurl.browser:action_started: A browser interaction has begun (e.g.,{"action": "typing"}).browser:action_completed: The interaction has finished.
Stream Control Events
Section titled “Stream Control Events”complete: The final event in a successful stream. Thedatapayload contains the fullresult.done: Signals the stream is closing (aftercompleteorerror).error: Fired if an unrecoverable error occurs. Thedatapayload contains anerrormessage andcode.
Working with Streaming Responses
Section titled “Working with Streaming Responses”The “Basic Usage” example is good for logging, but in a real application, you’ll want more robust logic.
Processing Events in Real-Time
Section titled “Processing Events in Real-Time”To build a responsive UI, you should process events as they arrive. These examples show a more robust way to handle the stream, including buffering incomplete lines.
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;
// Add the new chunk to the buffer buffer += decoder.decode(value, { stream: true });
// Process all complete lines in the buffer let eolIndex; while ((eolIndex = buffer.indexOf("\n")) >= 0) { const line = buffer.substring(0, eolIndex).trim(); buffer = buffer.substring(eolIndex + 1); // Remove processed line from buffer
if (line.startsWith("data: ")) { const data = line.slice(6); if (data === "[DONE]") break;
try { const event = JSON.parse(data);
// Handle different event types if (event.finalAnswer) { console.log("✓ Task completed:", event.finalAnswer); } else if (event.message) { console.log("Status:", event.message); } else if (event.url) { console.log("→ Navigated to:", event.url); } else if (event.extractedData) { console.log("📦 Extracted:", event.extractedData); } } catch (e) { // Ignore parse errors (likely incomplete JSON) } } } } // After the loop, `buffer` may contain a final partial chunk. // Add `decoder.decode(undefined, { stream: false })` to flush if needed.}
processAutomationEvents();import requestsimport osimport 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()
# The requests.Response.iter_lines() method is robust # and handles chunking and line endings automatically. for line in response.iter_lines(): if not line: continue
line = line.decode('utf-8')
if line.startswith('data: '): data = line[6:] if data == '[DONE]': break
try: event = json.loads(data)
# Handle different event types if 'finalAnswer' in event: print(f'✓ Task completed: {event["finalAnswer"]}') elif 'message' in event: print(f'Status: {event["message"]}') elif 'url' in event: print(f'→ Navigated to: {event["url"]}') elif 'extractedData' in event: print(f'📦 Extracted: {event["extractedData"]}')
except json.JSONDecodeError: pass # Ignore parse errors
process_automation_events()Code Explanations
Section titled “Code Explanations”JavaScript Breakdown:
This version adds error checking and proper buffering. The key improvement is the buffer management—since SSE messages can arrive split across chunks, the code maintains a buffer and only processes complete lines (ending in \n). Any incomplete data stays in the buffer until the next chunk arrives. This prevents parsing errors from partial JSON.
Python Breakdown:
Python’s requests library handles buffering automatically with iter_lines(), making the code simpler. The raise_for_status() call catches HTTP errors early. You don’t need manual buffer management—the library takes care of splitting the stream into complete lines.
How to Run
Section titled “How to Run”Follow the same instructions as the “Basic Usage” section. These examples are drop-in replacements.
Collecting Final Results
Section titled “Collecting Final Results”Often, you’ll want to process real-time events and capture the final complete event. This pattern wraps the logic in a function that returns the final payload.
async function runAutomationTask(task, url) { 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, url }), });
if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }
const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let finalResult = null; const events = [];
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); if (data === "[DONE]") break;
try { const event = JSON.parse(data); events.push(event);
// Capture the final 'complete' event's result if (event.success !== undefined && event.result) { finalResult = event.result; } } catch (e) { // Ignore } } } }
return { success: finalResult !== null, result: finalResult, eventCount: events.length, events: events, // Full log of all events };}
// UsagerunAutomationTask( "Find the top 3 articles and their publication dates", "https://news.example.com").then((result) => { console.log("Task completed:", result.success); console.log("Result:", result.result); console.log("Total events:", result.eventCount);});import requestsimport osimport 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:] if data == '[DONE]': break
try: event = json.loads(data) events.append(event)
# Capture the final 'complete' event's result if 'success' in event and 'result' in event: final_result = event['result'] except json.JSONDecodeError: pass
return { 'success': final_result is not None, 'result': final_result, 'event_count': len(events), 'events': events # Full log of all events }
# Usageresult = run_automation_task( 'Find the top 3 articles and their publication dates', 'https://news.example.com')
print('Task completed:', result['success'])print('Result:', result['result'])print('Total events:', result['event_count'])Code Explanations
Section titled “Code Explanations”These functions build on the previous examples.
JavaScript & Python:
These functions collect all events while watching for the final complete event. Storing all events helps with debugging—you can see exactly what the agent did if something goes wrong. The code detects the complete event by checking for both success and result properties, then returns both the final result and the full event log.
How to Run
Section titled “How to Run”These examples are self-contained. Save the code and run the file (e.g., python run_task.py or node run_task.mjs). The “Usage” block at the end will execute the task and print the final result.
Error Handling
Section titled “Error Handling”A robust implementation must handle two types of errors:
- HTTP Errors: Connection-level failures (e.g., 401 Unauthorized, 503 Service Unavailable).
- Stream Errors: Task-level failures that occur after the connection is established (e.g., the agent can’t find an element).
These examples show how to catch both.
Common Error Status Codes
Section titled “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. |
async function automateWithErrorHandling(task, url) { try { 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, url }), });
// 1. Handle HTTP errors if (!response.ok) { const errorData = await response.json().catch(() => ({})); // Handle non-JSON errors throw new Error( `HTTP ${response.status}: ${errorData.error || response.statusText}` ); }
const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let hasError = false;
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);
// 2. Handle stream-level errors if ( line.startsWith("event: error") || (line.startsWith("data: ") && line.includes('"error"')) ) { const data = line.slice(6); try { const event = JSON.parse(data); if (event.error) { console.error("Task error:", event.error); hasError = true; } } catch (e) { // Continue } } } }
if (hasError) { throw new Error("Task failed during execution"); }
return { success: true }; } catch (error) { console.error("Automation error:", error.message);
// Handle specific error types if (error.message.includes("401")) { console.error("Authentication failed. Check your API key."); } else if (error.message.includes("503")) { console.error("Automate service is unavailable. Try again later."); } throw error; }}
// UsageautomateWithErrorHandling( "Extract product information", "https://example.com/products").catch((err) => console.error("Failed:", err.message));import requestsimport osimport json
def automate_with_error_handling(task, url): try: 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 )
# 1. Handle HTTP errors response.raise_for_status()
has_error = False
for line in response.iter_lines(): if not line: continue
line = line.decode('utf-8')
# 2. Handle stream-level errors if 'event: error' in line or (line.startswith('data: ') and '"error"' in line): data = line[6:] if line.startswith('data: ') else line try: event = json.loads(data) if 'error' in event: print(f'Task error: {event["error"]}') has_error = True except json.JSONDecodeError: pass
if has_error: raise Exception('Task failed during execution')
return {'success': True}
except requests.exceptions.HTTPError as http_err: print(f'HTTP error: {http_err}') if http_err.response.status_code == 401: print('Authentication failed. Check your API key.') elif http_err.response.status_code == 503: print('Automate service is unavailable. Try again later.') raise except Exception as error: print(f'Automation error: {error}') raise
# Usagetry: automate_with_error_handling( 'Extract product information', 'https://example.com/products' )except Exception as err: print(f'Failed: {err}')Code Explanations
Section titled “Code Explanations”JavaScript Breakdown:
Handle errors at two levels: HTTP errors (wrong API key, bad request) are caught before streaming starts via response.ok. Stream errors (agent can’t complete the task) are detected by watching for event: error messages in the stream itself. This two-level approach ensures you catch failures whether they happen during connection or during task execution.
Python Breakdown:
Python follows the same two-level pattern. The raise_for_status() method handles HTTP errors by automatically raising exceptions. Stream-level errors are caught by checking for error events in the stream. Separating these error types makes debugging easier—you know whether the problem is with your request or with task execution.
How to Run
Section titled “How to Run”Save and run these functions. To test the error handling, try running them with an invalid API key (to trigger a 401) or a task that is designed to fail.
Advanced Usage Patterns
Section titled “Advanced Usage Patterns”Form Submission with Data
Section titled “Form Submission with Data”One of the most powerful features is the ability to fill and submit forms. By passing a data object, you can provide the information the AI agent needs.
async function submitForm(formUrl, formData) { 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: "Fill out and submit the contact form with the provided information", url: formUrl, data: formData, guardrails: "Only fill and submit the form, do not navigate away or click other links", }), });
// (Add full streaming and error handling logic here) const reader = response.body.getReader(); const decoder = new TextDecoder(); let submissionSuccess = false;
while (true) { const { value, done } = await reader.read(); if (done) break;
const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split("\n");
for (const line of lines) { // Look for confirmation in the agent's reasoning or final answer if ( line.includes("form submitted") || line.includes("submission successful") ) { submissionSuccess = true; }
if (line.startsWith("data: ")) { try { const event = JSON.parse(line.slice(6)); if (event.finalAnswer) { console.log("Result:", event.finalAnswer); submissionSuccess = true; } } catch (e) { // Continue } } } } return { success: submissionSuccess };}
// Usageconst contactData = { name: "Alex Johnson", company: "Acme Inc", message: "Interested in learning more about your products",};
submitForm("https://company.com/contact", contactData).then((result) => console.log("Form submitted:", result.success));import requestsimport osimport json
def submit_form(form_url, form_data): response = requests.post( 'https://api.tabstack.ai/v1/automate', headers={ 'Authorization': f'Bearer {os.environ["TABSTACK_API_KEY"]}', 'Content-Type': 'application/json' }, json={ 'task': 'Fill out and submit the contact form with the provided information', 'url': form_url, 'data': form_data, 'guardrails': 'Only fill and submit the form, do not navigate away or click other links' }, stream=True )
response.raise_for_status() submission_success = False
for line in response.iter_lines(): if not line: continue
line = line.decode('utf-8')
# Look for confirmation in the agent's reasoning or final answer if 'form submitted' in line.lower() or 'submission successful' in line.lower(): submission_success = True
if line.startswith('data: '): try: event = json.loads(line[6:]) if 'finalAnswer' in event: print('Result:', event['finalAnswer']) submission_success = True except json.JSONDecodeError: pass
return {'success': submission_success}
# Usagecontact_data = { 'name': 'Alex Johnson', 'company': 'Acme Inc', 'message': 'Interested in learning more about your products'}
result = submit_form('https://company.com/contact', contact_data)print('Form submitted:', result['success'])Code Explanations
Section titled “Code Explanations”The key here is the data parameter—the agent intelligently maps your object keys to form fields on the page. The guardrails prevent unintended actions like clicking ads. The code watches the stream for success indicators (either in the agent’s reasoning or the final answer), letting you confirm the form was actually submitted.
How to Run
Section titled “How to Run”- Define a
contactDataobject or dictionary with the information you want to submit. - Call the
submitFormfunction, passing the target URL and your data. - Replace
https://company.com/contactwith a real form URL to test this.
Multi-Page Data Collection
Section titled “Multi-Page Data Collection”The agent isn’t limited to a single page. You can instruct it to perform complex, multi-page workflows, like pagination or following search results.
async function collectMultiPageData() { 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: '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, // Increase iterations for multi-page tasks guardrails: "Browse and extract only, do not add items to cart or checkout", }), });
// (Add full streaming and error handling logic here) const reader = response.body.getReader(); const decoder = new TextDecoder(); const extractedData = []; 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: ")) { try { const event = JSON.parse(line.slice(6));
// Collect data as it's extracted if (event.extractedData) { extractedData.push(event.extractedData); console.log("Extracted data from page:", extractedData.length); } if (event.finalAnswer) { console.log("Final result:", event.finalAnswer); } if (event.url) { console.log("Currently on:", event.url); } } catch (e) { // Continue } } } } return extractedData;}
// UsagecollectMultiPageData().then((data) => { console.log(`Collected ${data.length} data points across multiple pages`); console.log("Data:", data);});import requestsimport osimport json
def collect_multi_page_data(): response = requests.post( 'https://api.tabstack.ai/v1/automate', headers={ 'Authorization': f'Bearer {os.environ["TABSTACK_API_KEY"]}', 'Content-Type': 'application/json' }, json={ '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, # Increase iterations for multi-page tasks 'guardrails': 'Browse and extract only, do not add items to cart or checkout' }, stream=True )
response.raise_for_status() extracted_data = []
for line in response.iter_lines(): if not line: continue
line = line.decode('utf-8')
if line.startswith('data: '): try: event = json.loads(line[6:])
# Collect data as it's extracted if 'extractedData' in event: extracted_data.append(event['extractedData']) print(f'Extracted data from page: {len(extracted_data)}')
if 'finalAnswer' in event: print('Final result:', event['finalAnswer'])
if 'url' in event: print('Currently on:', event['url']) except json.JSONDecodeError: pass
return extracted_data
# Usagedata = collect_multi_page_data()print(f'Collected {len(data)} data points across multiple pages')print('Data:', data)Code Explanations
Section titled “Code Explanations”Multi-page tasks need more iterations—here we set maxIterations: 75 to give the agent enough steps for pagination. The code collects extractedData events as they arrive, building up results from each page. Your task description should explicitly mention pagination (“go through the first 3 pages”) to guide the agent.
How to Run
Section titled “How to Run”Run the file directly. The function will execute, and you’ll see “Extracted data from page: 1”, “Currently on: …page=2”, “Extracted data from page: 2”, etc., logged to the console.
Price Monitoring and Alerts
Section titled “Price Monitoring and Alerts”This final example combines concepts to build a practical price monitor. It runs a task, parses the stream for specific data, and checks it against a target.
async function monitorPrice(productUrl, targetPrice) { 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: `Maps to the product page and extract the current price. Also check if the product is in stock.`, url: productUrl, maxIterations: 20, // Simple task guardrails: "Browse and extract only, do not make purchases", }), });
// (Add full streaming and error handling logic here) const reader = response.body.getReader(); const decoder = new TextDecoder(); let currentPrice = null; let inStock = false;
while (true) { const { value, done } = await reader.read(); if (done) break;
const chunk = decoder.decode(value, { stream: true });
// Parse the stream for clues, even before the final answer if (chunk.toLowerCase().includes("in stock")) { inStock = true; } const priceMatch = chunk.match(/\$?([\d,]+\.?\d{0,2})/); if (priceMatch) { currentPrice = parseFloat(priceMatch[1].replace(",", "")); } }
const result = { currentPrice, inStock, belowTarget: currentPrice && currentPrice <= targetPrice, };
if (result.belowTarget && result.inStock) { console.log( `🎉 ALERT: Price dropped to $${currentPrice}! (Target: $${targetPrice})` ); }
return result;}
// Usage: Monitor price every hourconst productUrl = "https://shop.example.com/product/12345";const targetPrice = 299.99;
// Run it once to testmonitorPrice(productUrl, targetPrice).then((result) => { console.log(`Price: $${result.currentPrice}, In Stock: ${result.inStock}`);});
// Use this to run it on a schedule// setInterval(() => {// monitorPrice(productUrl, targetPrice)// .then(result => {// console.log(`Price: $${result.currentPrice}, In Stock: ${result.inStock}`);// });// }, 3600000); // Check every hourimport requestsimport osimport jsonimport reimport time
def monitor_price(product_url, target_price): response = requests.post( 'https://api.tabstack.ai/v1/automate', headers={ 'Authorization': f'Bearer {os.environ["TABSTACK_API_KEY"]}', 'Content-Type': 'application/json' }, json={ 'task': 'Navigate to the product page and extract the current price. Also check if the product is in stock.', 'url': product_url, 'maxIterations': 20, # Simple task 'guardrails': 'Browse and extract only, do not make purchases' }, stream=True )
response.raise_for_status() current_price = None in_stock = False
for line in response.iter_lines(): if not line: continue
chunk = line.decode('utf-8')
# Parse the stream for clues, even before the final answer if 'in stock' in chunk.lower(): in_stock = True
price_match = re.search(r'\$?([\d,]+\.?\d{0,2})', chunk) if price_match: current_price = float(price_match.group(1).replace(',', ''))
result = { 'current_price': current_price, 'in_stock': in_stock, 'below_target': current_price and current_price <= target_price }
if result['below_target'] and result['in_stock']: print(f"🎉 ALERT: Price dropped to ${current_price}! (Target: ${target_price})")
return result
# Usage: Monitor price every hourproduct_url = 'https://shop.example.com/product/12345'target_price = 299.99
# Run it once to testresult = monitor_price(product_url, target_price)print(f"Price: ${result['current_price']}, In Stock: {result['in_stock']}")
# Use this to run it on a schedule# while True:# result = monitor_price(product_url, target_price)# print(f"Price: ${result['current_price']}, In Stock: {result['in_stock']}")# time.sleep(3600) # Check every hourCode Explanations
Section titled “Code Explanations”This price monitor demonstrates parsing the stream for specific patterns. Instead of waiting for the final result, the code extracts prices as they appear in the stream using regex. This can be faster for simple data. The function compares the found price against your target and alerts if it’s dropped. Wrap it in a scheduler to check prices periodically.
How to Run
Section titled “How to Run”Set a productUrl and targetPrice. Run the script. It will perform one check. You can uncomment the setInterval (JS) or while True (Python) loop to run it continuously.
Best Practices
Section titled “Best Practices”1. Write Clear, Specific Task Descriptions
Section titled “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 wanttask: "Get some products";
// Better - more specifictask: "Extract the top 5 products with their names and prices";
// Best - very detailedtask: `Maps 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
Section titled “2. Use Guardrails for Safety”Always specify what the agent should NOT do, especially when dealing with 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
Section titled “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
Section titled “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": { "phone": "555-1234" }, "preferences": { "newsletter": true } }}5. Handle Streaming Events Progressively
Section titled “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.
// Good: Process events immediatelywhile (streaming) { const event = await readEvent(); if (event.extractedData) { processData(event.extractedData); // Process immediately }}
// Bad: Collect all then processconst allEvents = [];while (streaming) { allEvents.push(await readEvent());}processAllAtOnce(allEvents); // Misses real-time benefits6. Monitor Event Streams for Errors
Section titled “6. Monitor Event Streams for Errors”Watch for event: error or task:aborted events. These are your signal that a task has failed, and you should stop processing.
if (event.error || event.task === 'aborted') { console.error('Task failed:', event); // Handle error appropriately break;}7. Test with Simple Tasks First
Section titled “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 (“Navigate and extract the first product”), adding filters and interactions one by one.