--- title: How to Use the Automate Endpoint | Tabstack description: 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 Before you can use the `/v1/automate` endpoint, you’ll need: 1. **A valid Tabstack API key:** Sign up at to get your key. 2. **Authentication:** The API uses Bearer token authentication. 3. **SSE-capable client:** Your HTTP client must be able to handle a `text/event-stream` response. 4. **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 This command makes your API key available in your shell session. Terminal window ``` 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:** 1. Open your terminal. 2. Paste the command, replacing the placeholder with your key. 3. Press Enter. This variable will be set for your current terminal session. --- ## Basic Usage ### Endpoint Details - **URL:** `https://api.tabstack.ai/v1/automate` - **Method:** `POST` - **Authentication:** `Bearer ` (Required) - **Content-Type:** `application/json` - **Response Type:** `text/event-stream` (Server-Sent Events) Unlike other Tabstack API endpoints, `/v1/automate` **always** streams its response. You must use a client that can process Server-Sent Events (SSE) to receive real-time updates and the final result. ### 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](#tab-panel-21) - [JavaScript](#tab-panel-22) - [Python](#tab-panel-23) 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" }' ``` ``` 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 function automateTask(); ``` ``` import requests import os import 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 function automate_task() ``` #### 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 1. **Environment:** Ensure you’ve set your `TABSTACK_API_KEY` environment variable (see Prerequisites). 2. **`curl`:** Paste the command directly into your terminal and run it. You will see events stream directly to your console. 3. **JavaScript:** Save the code as `automate.mjs`. Install `node-fetch` if you’re not in a browser environment (`npm install node-fetch`). Run it with Node.js: `node automate.mjs`. (Note: `process.env` requires Node.js). 4. **Python:** Save the code as `automate.py`. Install `requests` (`pip install requests`). Run it from your terminal: `python automate.py`. ### 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:started data: {"task": "Find the top 3 trending repositories", "url": "https://github.com/trending"} event: agent:processing data: {"operation": "Creating task plan", "hasScreenshot": false} event: browser:navigated data: {"title": "Trending - GitHub", "url": "https://github.com/trending"} event: agent:action data: {"action": "extract", "target": "repository list"} event: agent:extracted data: {"extractedData": "[{\"name\": \"awesome-ai\"}, {\"name\": \"web-framework\"}, {\"name\": \"data-viz\"}]"} event: task:completed data: {"success": true, "finalAnswer": "Top 3 repos: awesome-ai, web-framework, data-viz"} event: complete data: {"success": true, "result": {"finalAnswer": "Top 3 repos: awesome-ai, web-framework, data-viz"}} event: done data: {} ``` **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 You can customize the agent’s behavior by passing these parameters in the JSON body of your `POST` request. ### `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. **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) - **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", "email": "alex@example.com", "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) - **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) - **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) - **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 You’ll interact with a stream of events. Here are the most common ones and their purpose. ### Task Events (High-Level Task Status) - **`task:started`**: Fired once. Signals the task has been received and started. - **`task:completed`**: Fired on success. The `data` payload contains the `finalAnswer`. - **`task:aborted`**: Fired on failure. The `data` payload contains a `reason` (e.g., “Exceeded maximum iterations”). - **`task:validated`**: Fired when a validation step succeeds. ### 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. The `data` payload contains `extractedData`. - **`agent:waiting`**: The agent is waiting for a page to load or an element to appear. ### Browser Events (Low-Level Browser State) - **`browser:navigated`**: A new page has loaded. The payload includes the new `title` and `url`. - **`browser:action_started`**: A browser interaction has begun (e.g., `{"action": "typing"}`). - **`browser:action_completed`**: The interaction has finished. ### Stream Control Events - **`complete`**: The final event in a successful stream. The `data` payload contains the full `result`. - **`done`**: Signals the stream is closing (after `complete` or `error`). - **`error`**: Fired if an unrecoverable error occurs. The `data` payload contains an `error` message and `code`. --- ## 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 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. - [JavaScript](#tab-panel-24) - [Python](#tab-panel-25) ``` 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 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() # 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 **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 Follow the same instructions as the “Basic Usage” section. These examples are drop-in replacements. ### 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. - [JavaScript](#tab-panel-26) - [Python](#tab-panel-27) ``` 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 }; } // Usage runAutomationTask( "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 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:] 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 } # Usage result = 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 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 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 A robust implementation must handle two types of errors: 1. **HTTP Errors:** Connection-level failures (e.g., 401 Unauthorized, 503 Service Unavailable). 2. **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 | 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. | - [JavaScript](#tab-panel-28) - [Python](#tab-panel-29) ``` 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; } } // Usage automateWithErrorHandling( "Extract product information", "https://example.com/products" ).catch((err) => console.error("Failed:", err.message)); ``` ``` import requests import os import 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 # Usage try: automate_with_error_handling( 'Extract product information', 'https://example.com/products' ) except Exception as err: print(f'Failed: {err}') ``` #### 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 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 ### 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. - [JavaScript](#tab-panel-30) - [Python](#tab-panel-31) ``` 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 }; } // Usage const contactData = { name: "Alex Johnson", email: "alex@example.com", 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 requests import os import 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} # Usage contact_data = { 'name': 'Alex Johnson', 'email': 'alex@example.com', '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 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 1. Define a `contactData` object or dictionary with the information you want to submit. 2. Call the `submitForm` function, passing the target URL and your data. 3. Replace `https://company.com/contact` with a real form URL to test this. ### 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. - [JavaScript](#tab-panel-32) - [Python](#tab-panel-33) ``` 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; } // Usage collectMultiPageData().then((data) => { console.log(`Collected ${data.length} data points across multiple pages`); console.log("Data:", data); }); ``` ``` import requests import os import 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 # Usage data = collect_multi_page_data() print(f'Collected {len(data)} data points across multiple pages') print('Data:', data) ``` #### 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 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 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. - [JavaScript](#tab-panel-34) - [Python](#tab-panel-35) ``` 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 hour const productUrl = "https://shop.example.com/product/12345"; const targetPrice = 299.99; // Run it once to test monitorPrice(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 hour ``` ``` import requests import os import json import re import 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 hour product_url = 'https://shop.example.com/product/12345' target_price = 299.99 # Run it once to test result = 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 hour ``` #### 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 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 ### 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 task: "Get some products"; // Better - more specific task: "Extract the top 5 products with their names and prices"; // Best - very detailed task: `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 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 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. ``` // Good: Process events immediately while (streaming) { const event = await readEvent(); if (event.extractedData) { processData(event.extractedData); // Process immediately } } // Bad: Collect all then process const allEvents = []; while (streaming) { allEvents.push(await readEvent()); } processAllAtOnce(allEvents); // Misses real-time benefits ``` ### 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 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. --- ## Related Resources - [API Reference: Automate Endpoint](/api/resources/automate/methods/execute/index.md) - [How to Extract JSON Data](https://www.google.com/search?q=/guides/how-to-extract-json) - [How to Generate JSON Data](https://www.google.com/search?q=/guides/how-to-generate-json) - [Quick Start Guide](/getting-started/quick-start/index.md) - [Build Your First Tabstack App](/getting-started/build-your-first-tabs-app/index.md)