# Custom Tools Source: https://docs.smooth.sh/api-reference/custom-tools Register custom tools that the AI agent can call during task execution Custom tools allow you to extend the agent's capabilities by registering functions that it can call during execution. When the agent needs to perform an action that your tool provides, it will pause and wait for you to execute the tool and return the result. ## How Custom Tools Work 1. **Register tools** when creating a task via the `custom_tools` parameter 2. **Poll for tool calls** - the agent sends `tool_call` events when it needs to use a tool 3. **Execute the tool** locally and return the result via the Event endpoint 4. **Agent continues** with the tool's output ```mermaid theme={null} sequenceDiagram participant Client participant API participant Agent Client->>API: POST /task (with custom_tools) API-->>Client: {id, status: "running"} Agent->>API: Tool call needed Client->>API: GET /task/{id}?event_t=0 API-->>Client: {events: [{name: "tool_call", payload: {name: "my_tool", input: {...}}}]} Note over Client: Execute tool locally Client->>API: POST /task/{id}/event (tool response) API-->>Client: {id: "evt_response"} Agent->>API: Continues with tool result ``` ## Step 1: Define Your Tools When creating a task, define your custom tools using the `custom_tools` parameter: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "task": "Look up the weather in New York and send me a summary via Slack", "custom_tools": [ { "name": "get_weather", "description": "Get the current weather for a city. Use this when you need weather information.", "inputs": { "type": "object", "properties": { "city": { "type": "string", "description": "The city name" } }, "required": ["city"] }, "output": "Weather information as a JSON object with temperature, conditions, and humidity" }, { "name": "send_slack_message", "description": "Send a message to a Slack channel. Use this to notify the user.", "inputs": { "type": "object", "properties": { "channel": { "type": "string", "description": "The Slack channel name" }, "message": { "type": "string", "description": "The message to send" } }, "required": ["channel", "message"] }, "output": "Boolean indicating success" } ] }' ``` ### Tool Definition Schema | Field | Type | Required | Description | | ------------- | ------ | -------- | ---------------------------------------------- | | `name` | string | Yes | Unique identifier for the tool | | `description` | string | Yes | Explains what the tool does and when to use it | | `inputs` | object | Yes | JSON Schema describing the input parameters | | `output` | string | Yes | Description of what the tool returns | ## Step 2: Poll for Tool Calls When the agent needs to call one of your tools, it emits a `tool_call` event. Poll the task endpoint to receive these events: ```bash theme={null} curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123?event_t=0" \ -H "apikey: YOUR_API_KEY" ``` **Response with tool call:** ```json theme={null} { "r": { "id": "task_abc123", "status": "running", "events": [ { "id": "tc_weather_001", "name": "tool_call", "payload": { "name": "get_weather", "input": { "city": "New York" } }, "timestamp": 1699999999999 } ] } } ``` **Important:** The `id` field in the event is crucial - you'll use it to send the response. ## Step 3: Execute and Respond Execute the tool locally, then send the result back via the Event endpoint: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "tool_call", "payload": { "code": 200, "output": { "temperature": 72, "conditions": "Partly cloudy", "humidity": 45 } }, "id": "tc_weather_001" }' ``` ### Response Format | Field | Type | Description | | ---------------- | ------ | ---------------------------------------------- | | `name` | string | Must be `"tool_call"` | | `id` | string | **Must match** the event ID from the tool call | | `payload.code` | number | `200` for success, `400` or `500` for errors | | `payload.output` | any | The tool's result (or error message) | ### Handling Errors If your tool fails, return an error: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "tool_call", "payload": { "code": 400, "output": "City not found: New Yrok" }, "id": "tc_weather_001" }' ``` The agent will receive the error and may try again with corrected input or take an alternative approach. ## Complete Example: Weather Bot Here's a complete implementation that handles custom tool calls: ```javascript theme={null} const API_KEY = 'YOUR_API_KEY'; const BASE_URL = 'https://api.smooth.sh/api/v1'; // Define your tool implementations const toolImplementations = { async get_weather({ city }) { // In reality, you'd call a weather API const weatherData = { 'New York': { temperature: 72, conditions: 'Partly cloudy', humidity: 45 }, 'London': { temperature: 58, conditions: 'Rainy', humidity: 80 }, 'Tokyo': { temperature: 68, conditions: 'Clear', humidity: 55 } }; const weather = weatherData[city]; if (!weather) { throw new Error(`Weather data not available for: ${city}`); } return weather; }, async send_slack_message({ channel, message }) { // In reality, you'd use the Slack API console.log(`[Slack #${channel}] ${message}`); return true; } }; async function request(method, path, body = null) { const response = await fetch(`${BASE_URL}${path}`, { method, headers: { 'apikey': API_KEY, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : null }); return response.json(); } async function handleToolCall(taskId, event) { const { name, input } = event.payload; console.log(`Tool called: ${name}`, input); let response; try { const implementation = toolImplementations[name]; if (!implementation) { throw new Error(`Unknown tool: ${name}`); } const output = await implementation(input); response = { code: 200, output }; } catch (error) { response = { code: 400, output: error.message }; } // Send the response await request('POST', `/task/${taskId}/event`, { name: 'tool_call', payload: response, id: event.id }); console.log(`Tool response sent for ${name}:`, response); } async function runTaskWithTools() { // Create task with custom tools const { r: task } = await request('POST', '/task', { task: 'Look up the weather in New York and send a summary to the #general Slack channel', custom_tools: [ { name: 'get_weather', description: 'Get current weather for a city', inputs: { type: 'object', properties: { city: { type: 'string', description: 'The city name' } }, required: ['city'] }, output: 'Weather data with temperature, conditions, humidity' }, { name: 'send_slack_message', description: 'Send a message to a Slack channel', inputs: { type: 'object', properties: { channel: { type: 'string', description: 'Channel name' }, message: { type: 'string', description: 'Message to send' } }, required: ['channel', 'message'] }, output: 'Boolean indicating success' } ] }); console.log(`Task created: ${task.id}`); console.log(`Live URL: ${task.live_url}`); // Poll and handle tool calls let lastEventT = 0; const processedEvents = new Set(); while (true) { const { r: taskStatus } = await request('GET', `/task/${task.id}?event_t=${lastEventT}`); // Check if task is done if (!['running', 'waiting'].includes(taskStatus.status)) { console.log(`Task finished with status: ${taskStatus.status}`); console.log('Output:', taskStatus.output); break; } // Process tool calls if (taskStatus.events) { for (const event of taskStatus.events) { if (event.name === 'tool_call' && !processedEvents.has(event.id)) { processedEvents.add(event.id); await handleToolCall(task.id, event); } } lastEventT = taskStatus.events[taskStatus.events.length - 1].timestamp; } await new Promise(r => setTimeout(r, 1000)); } } runTaskWithTools().catch(console.error); ``` ## Using with Sessions Custom tools work the same way with session workflows. Define them when creating the session: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "task": null, "url": "https://example.com", "custom_tools": [ { "name": "save_to_database", "description": "Save extracted data to the database", "inputs": { "type": "object", "properties": { "data": { "type": "object" }, "table": { "type": "string" } }, "required": ["data", "table"] }, "output": "The saved record ID" } ] }' ``` Then when running tasks within the session, the agent can use your custom tools: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "session_action", "payload": { "name": "run_task", "input": { "task": "Extract all products from this page and save them to the products database table" } }, "id": "evt_task_001" }' ``` ## Python SDK Example The SDK provides a decorator-based approach for custom tools: ```python theme={null} from smooth import SmoothClient, tool client = SmoothClient(api_key="YOUR_API_KEY") @tool def get_weather(city: str) -> dict: """Get current weather for a city. Args: city: The city name to get weather for Returns: Weather data with temperature, conditions, humidity """ # Your weather API call here return { "temperature": 72, "conditions": "Partly cloudy", "humidity": 45 } @tool def send_slack_message(channel: str, message: str) -> bool: """Send a message to a Slack channel. Args: channel: The Slack channel name message: The message to send Returns: True if message was sent successfully """ # Your Slack API call here print(f"[Slack #{channel}] {message}") return True # The SDK handles polling and tool execution automatically task = client.run( task="Look up the weather in New York and send a summary to #general", tools=[get_weather, send_slack_message] ) result = task.result() print(f"Task output: {result.output}") ``` ## Best Practices 1. **Write clear descriptions** - The agent uses descriptions to decide when to call your tool 2. **Define complete schemas** - Include all required fields and descriptions for each input 3. **Handle errors gracefully** - Return meaningful error messages so the agent can recover 4. **Set appropriate timeouts** - Long-running tools should be async with proper timeout handling 5. **Validate inputs** - Check that required fields are present before executing # Send Event Source: https://docs.smooth.sh/api-reference/event/send-event /api-reference/openapi.json post /task/{task_id}/event Send an event to a running task. This is used for: - **Session actions**: `run_task`, `goto`, `extract`, `evaluate_js`, `close` - **Custom tool responses**: Responding to tool calls from the agent The SDK handles this automatically when using `session.run_task()`, `session.goto()`, etc. # Delete Extension Source: https://docs.smooth.sh/api-reference/extension/delete-extension /api-reference/openapi.json delete /browser/extension/{extension_id} Delete an uploaded browser extension by its ID. # List Extensions Source: https://docs.smooth.sh/api-reference/extension/list-extensions /api-reference/openapi.json get /extension List all uploaded browser extensions. # Upload Extension Source: https://docs.smooth.sh/api-reference/extension/upload-extension /api-reference/openapi.json post /extension Upload a browser extension (.zip file) to be used in tasks. # Delete File Source: https://docs.smooth.sh/api-reference/file/delete-file /api-reference/openapi.json delete /file/{file_id} Delete an uploaded file by its ID. # Upload File Source: https://docs.smooth.sh/api-reference/file/upload-file /api-reference/openapi.json post /file Upload a file to be used by tasks. Files can be passed to tasks via the `files` parameter. # API Overview Source: https://docs.smooth.sh/api-reference/introduction Complete guide to the Smooth REST API **We recommend using one of our SDKs instead of calling the API directly.** The SDKs handle authentication, polling, and error handling automatically, making integration much simpler. If we don't support your language yet, reach out at **[support@circlemind.co](mailto:support@circlemind.co)** and we'll be happy to support your implementation. The Smooth API enables programmatic browser automation through REST endpoints. You can run AI-powered tasks, create interactive browser sessions, and extend agent capabilities with custom tools. ## Quick Start Get your API key and start running tasks in minutes. Unlock your free welcome credits. No credit card required. ## Authentication All API endpoints require authentication via the `apikey` header: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task": "Go to hacker news and get the top story"}' ``` ## Two Ways to Use the API ### 1. Simple Tasks Run one-shot tasks that execute and return results: ```bash theme={null} # Submit a task curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task": "Go to hacker news and get the top 5 stories"}' # Response: {"r": {"id": "task_abc123", "status": "running", ...}} # Poll for results curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123" \ -H "apikey: YOUR_API_KEY" # Response: {"r": {"id": "task_abc123", "status": "done", "output": "..."}} ``` ### 2. Session Workflows For multi-step workflows, create a session and send actions: ```bash theme={null} # Create a session (task=null) curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task": null, "url": "https://example.com"}' # Response: {"r": {"id": "task_abc123", "status": "running", "live_url": "..."}} # Send actions via the Event endpoint curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "session_action", "payload": {"name": "run_task", "input": {"task": "Click the login button"}}, "id": "evt_001" }' # Poll for the result curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123?event_t=0" \ -H "apikey: YOUR_API_KEY" ``` Learn how to create sessions, send actions, and poll for results Understand how to efficiently poll for task results and events ## Core Endpoints | Endpoint | Method | Description | | ------------------ | ------ | ----------------------------------------------- | | `/task` | POST | Submit a task or create a session (`task=null`) | | `/task` | GET | List all tasks | | `/task/{id}` | GET | Get task status and results | | `/task/{id}` | DELETE | Cancel a running task | | `/task/{id}/event` | POST | Send an event to a running task/session | ## Session Actions When using sessions, send actions via `POST /task/{id}/event`: | Action | Event Type | Description | | ---------- | ---------------- | ----------------------------------------- | | Navigate | `browser_action` | Go to a URL | | Extract | `browser_action` | Extract structured data from the page | | JavaScript | `browser_action` | Execute JavaScript in the browser | | Run Task | `session_action` | Run an AI-powered task within the session | | Close | `session_action` | Close the session | See complete examples of all session actions ## Custom Tools Extend agent capabilities by registering custom tools: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "task": "Look up the weather and send it to Slack", "custom_tools": [ { "name": "get_weather", "description": "Get weather for a city", "inputs": {"type": "object", "properties": {"city": {"type": "string"}}}, "output": "Weather data" }, { "name": "send_slack", "description": "Send a Slack message", "inputs": {"type": "object", "properties": {"message": {"type": "string"}}}, "output": "Success boolean" } ] }' ``` When the agent calls a tool, you receive a `tool_call` event. Execute the tool locally and respond via the Event endpoint. Complete guide to implementing custom tools with the API ## Additional Features ### Browser Profiles Persist cookies and authentication across tasks: ```bash theme={null} # Create a profile curl -X POST "https://api.smooth.sh/api/v1/profile" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"id": "my-profile"}' # Use the profile in a task curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task": "Check my account balance", "profile_id": "my-profile"}' ``` ### Structured Output Get structured data matching a JSON schema: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "task": "Get the top 3 stories from Hacker News", "response_model": { "type": "array", "items": { "type": "object", "properties": { "title": {"type": "string"}, "url": {"type": "string"}, "points": {"type": "number"} } } } }' ``` ### File Uploads Pass files to tasks: ```bash theme={null} # Upload a file curl -X POST "https://api.smooth.sh/api/v1/file" \ -H "apikey: YOUR_API_KEY" \ -F "file=@invoice.pdf" # Response: {"r": {"id": "file_xyz789"}} # Use in a task curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task": "Extract the total from this invoice", "files": ["file_xyz789"]}' ``` ## Response Format All responses are wrapped in an `r` field: ```json theme={null} { "r": { "id": "task_abc123", "status": "done", "output": "...", "credits_used": 15 } } ``` ### Task Status Values | Status | Description | | ----------- | --------------------------- | | `waiting` | Task is queued | | `running` | Task is executing | | `done` | Task completed successfully | | `failed` | Task failed | | `cancelled` | Task was cancelled | ## Error Handling | HTTP Code | Meaning | | --------- | ------------------------------------------ | | 400 | Invalid request parameters | | 403 | Premium feature not available on your plan | | 404 | Resource not found | | 422 | Validation error | | 429 | Rate limit exceeded or credits exhausted | Error responses include a detail message: ```json theme={null} { "detail": "API credits exhausted. Please upgrade your plan." } ``` ## Next Steps Create interactive sessions for multi-step workflows Extend agent capabilities with your own tools Best practices for polling task results Use the SDK for automatic polling and easier integration # Polling Mechanism Source: https://docs.smooth.sh/api-reference/polling How to poll for task results and events The Smooth API uses polling to deliver task results and events. This page explains how the polling mechanism works and best practices for implementing it. ## Why Polling? The API uses polling rather than webhooks or WebSockets for several reasons: * **Simplicity** - No need to set up webhook endpoints or maintain WebSocket connections * **Reliability** - No lost events due to connection issues * **Firewall-friendly** - Works behind firewalls that block incoming connections * **Stateless** - Each request is independent; easy to implement in any language ## Basic Polling For simple tasks, poll the task endpoint until the status changes to `done`, `failed`, or `cancelled`: ```bash theme={null} # Initial request curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123" \ -H "apikey: YOUR_API_KEY" ``` **Response (running):** ```json theme={null} { "r": { "id": "task_abc123", "status": "running", "output": null, "live_url": "https://live.smooth.sh/v/..." } } ``` **Response (completed):** ```json theme={null} { "r": { "id": "task_abc123", "status": "done", "output": "The top 5 stories from Hacker News are...", "credits_used": 15, "recording_url": "https://..." } } ``` ### HTTP Status Codes | Status Code | Meaning | | ----------- | ----------------------------------------------------- | | `200` | Task has completed (`done`, `failed`, or `cancelled`) | | `202` | Task is still running (`waiting` or `running`) | | `404` | Task not found | ## Event-Based Polling For sessions and custom tools, use the `event_t` parameter to receive events incrementally. This is more efficient than fetching the full response each time. ### The event\_t Parameter The `event_t` (event timestamp) parameter filters events to only return those that occurred **after** the specified timestamp: ```bash theme={null} # First poll - get all events curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123?event_t=0" \ -H "apikey: YOUR_API_KEY" ``` ```json theme={null} { "r": { "id": "task_abc123", "status": "running", "events": [ { "id": "evt_001", "name": "browser_action", "payload": {"code": 200, "output": null}, "timestamp": 1699999990000 }, { "id": "evt_002", "name": "tool_call", "payload": {"name": "my_tool", "input": {"x": 1}}, "timestamp": 1699999999000 } ] } } ``` ```bash theme={null} # Subsequent polls - only new events curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123?event_t=1699999999000" \ -H "apikey: YOUR_API_KEY" ``` ```json theme={null} { "r": { "id": "task_abc123", "status": "running", "events": [ { "id": "evt_003", "name": "browser_action", "payload": {"code": 200, "output": {"title": "Example"}}, "timestamp": 1700000005000 } ] } } ``` ### Event Structure Each event contains: | Field | Type | Description | | ----------- | ------- | -------------------------------------------------------------- | | `id` | string | Unique event identifier (use for matching responses) | | `name` | string | Event type: `browser_action`, `session_action`, or `tool_call` | | `payload` | object | Event-specific data | | `timestamp` | integer | Unix timestamp in milliseconds | ## Polling Loop Implementation Here's a robust polling implementation: ```javascript theme={null} async function pollTask(taskId, options = {}) { const { pollInterval = 1000, timeout = 300000, // 5 minutes onEvent = null } = options; const startTime = Date.now(); let lastEventT = 0; while (true) { // Check timeout if (Date.now() - startTime > timeout) { throw new Error('Polling timeout exceeded'); } // Poll for updates const response = await fetch( `https://api.smooth.sh/api/v1/task/${taskId}?event_t=${lastEventT}`, { headers: { 'apikey': API_KEY } } ); const { r: task } = await response.json(); // Process events if (task.events && task.events.length > 0) { for (const event of task.events) { if (onEvent) { await onEvent(event); } } // Update timestamp for next poll lastEventT = task.events[task.events.length - 1].timestamp; } // Check if task is complete if (!['running', 'waiting'].includes(task.status)) { return task; } // Wait before next poll await new Promise(r => setTimeout(r, pollInterval)); } } // Usage const result = await pollTask('task_abc123', { pollInterval: 1000, timeout: 300000, onEvent: async (event) => { console.log('Event:', event.name, event.id); // Handle tool calls if (event.name === 'tool_call') { const output = await executeMyTool(event.payload); await sendToolResponse(taskId, event.id, output); } } }); ``` ## Best Practices ### 1. Use Appropriate Poll Intervals | Scenario | Recommended Interval | | ----------------------------- | -------------------- | | Simple tasks | 1-2 seconds | | Sessions with actions | 500ms - 1 second | | Custom tools (time-sensitive) | 500ms | | Background monitoring | 5-10 seconds | ### 2. Implement Exponential Backoff For long-running tasks, increase the interval over time: ```javascript theme={null} async function pollWithBackoff(taskId) { let interval = 1000; const maxInterval = 10000; while (true) { const task = await getTask(taskId); if (task.status !== 'running' && task.status !== 'waiting') { return task; } await new Promise(r => setTimeout(r, interval)); // Increase interval up to max interval = Math.min(interval * 1.2, maxInterval); } } ``` ### 3. Handle Network Errors ```javascript theme={null} async function resilientPoll(taskId) { let retries = 0; const maxRetries = 5; while (true) { try { const task = await getTask(taskId); retries = 0; // Reset on success if (task.status !== 'running' && task.status !== 'waiting') { return task; } } catch (error) { retries++; if (retries >= maxRetries) { throw new Error(`Polling failed after ${maxRetries} retries: ${error.message}`); } // Exponential backoff on errors await new Promise(r => setTimeout(r, Math.pow(2, retries) * 1000)); continue; } await new Promise(r => setTimeout(r, 1000)); } } ``` ### 4. Track Processed Events Avoid processing the same event twice: ```javascript theme={null} const processedEvents = new Set(); function handleEvents(events) { for (const event of events) { if (processedEvents.has(event.id)) { continue; // Already processed } processedEvents.add(event.id); // Process event... } } ``` ### 5. Clean Up on Errors If your polling loop fails, consider cancelling the task to avoid resource leaks: ```javascript theme={null} async function runWithCleanup(taskId) { try { return await pollTask(taskId); } catch (error) { // Cancel task on error try { await fetch(`https://api.smooth.sh/api/v1/task/${taskId}`, { method: 'DELETE', headers: { 'apikey': API_KEY } }); } catch (cancelError) { // Ignore cancel errors } throw error; } } ``` ## Python SDK The Python SDK handles all polling automatically: ```python theme={null} from smooth import SmoothClient client = SmoothClient(api_key="YOUR_API_KEY") # Automatic polling with result() task = client.run(task="Go to google.com") result = task.result() # Blocks until complete, handles polling internally # With timeout result = task.result(timeout=60) # Raises TimeoutError after 60 seconds # Session polling is also automatic with client.session() as session: # Each action polls internally for the response session.goto("https://example.com") data = session.extract(schema={"type": "object"}, prompt="Extract data") print(data.output) ``` ## Debugging ### Checking Event Flow Add logging to understand the event flow: ```javascript theme={null} const { r: task } = await getTask(taskId, eventT); console.log(`Status: ${task.status}`); console.log(`Events since ${eventT}:`); for (const event of task.events || []) { console.log(` [${event.timestamp}] ${event.name} (${event.id})`); console.log(` Payload:`, JSON.stringify(event.payload)); } ``` ### Common Issues | Issue | Cause | Solution | | -------------------- | ----------------------------- | --------------------------------------------- | | Missing events | Using wrong `event_t` | Always use the last event's timestamp | | Duplicate processing | Not tracking processed events | Keep a Set of processed event IDs | | Timeout errors | Poll interval too long | Reduce interval for time-sensitive operations | | Rate limiting | Polling too fast | Increase poll interval or use backoff | # Create Profile Source: https://docs.smooth.sh/api-reference/profile/create-profile /api-reference/openapi.json post /profile Creates a new browser profile. Profiles persist cookies and authentication across tasks. # Delete Profile Source: https://docs.smooth.sh/api-reference/profile/delete-profile /api-reference/openapi.json delete /profile/{profile_id} Delete a browser profile by its ID. # List Profiles Source: https://docs.smooth.sh/api-reference/profile/list-profiles /api-reference/openapi.json get /profile List all browser profiles for the user. # Open Browser Session (Deprecated) Source: https://docs.smooth.sh/api-reference/profile/open-browser-session-deprecated /api-reference/openapi.json post /browser/session **Deprecated**: Use `POST /task` with `task=null` instead, and send actions via `POST /task/{task_id}/event`. Opens an interactive browser instance for 5 minutes. # Session Workflow Source: https://docs.smooth.sh/api-reference/sessions Create interactive browser sessions for multi-step workflows Sessions allow you to maintain a persistent browser instance and perform multiple actions sequentially. This is useful for complex workflows like logging in, navigating, and extracting data across multiple pages. ## How Sessions Work 1. **Create a session** by calling `POST /task` with `task=null` 2. **Send actions** via `POST /task/{task_id}/event` 3. **Poll for results** via `GET /task/{task_id}?event_t={timestamp}` 4. **Close the session** by sending a `close` event ```mermaid theme={null} sequenceDiagram participant Client participant API participant Browser Client->>API: POST /task (task=null) API-->>Client: {id, status: "running", live_url} Client->>API: POST /task/{id}/event (goto) API-->>Client: {id: "evt_123"} loop Poll for result Client->>API: GET /task/{id}?event_t=0 API-->>Client: {events: [{id: "evt_123", payload: {code: 200}}]} end Client->>API: POST /task/{id}/event (extract) API-->>Client: {id: "evt_456"} loop Poll for result Client->>API: GET /task/{id}?event_t=1699999999 API-->>Client: {events: [{id: "evt_456", payload: {code: 200, output: {...}}}]} end Client->>API: POST /task/{id}/event (close) API-->>Client: {id: "evt_789"} ``` ## Step 1: Create a Session Create a session by submitting a task with `task=null`. The browser will open and wait for actions. ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "task": null, "url": "https://example.com", "device": "desktop" }' ``` **Response:** ```json theme={null} { "r": { "id": "task_abc123", "status": "running", "live_url": "https://live.smooth.sh/v/...", "output": null } } ``` Save the `id` - you'll use it for all subsequent actions. ## Step 2: Send Actions Use the Event endpoint to send actions to your session. Each action requires: * `name`: The event type (`browser_action` or `session_action`) * `payload`: Contains the action name and input parameters * `id`: A unique ID to match the response (use any unique string like UUID) ### Navigate to a URL ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "browser_action", "payload": { "name": "goto", "input": { "url": "https://example.com/login" } }, "id": "evt_goto_001" }' ``` ### Run a Task (Agent-powered) Execute an AI-powered task within the session: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "session_action", "payload": { "name": "run_task", "input": { "task": "Log in with username test@example.com and password secret123", "max_steps": 32 } }, "id": "evt_login_001" }' ``` ### Extract Data Extract structured data from the current page: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "browser_action", "payload": { "name": "extract", "input": { "schema": { "type": "object", "properties": { "username": {"type": "string"}, "email": {"type": "string"}, "plan": {"type": "string"} } }, "prompt": "Extract the user profile information" } }, "id": "evt_extract_001" }' ``` ### Execute JavaScript Run custom JavaScript in the browser: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "browser_action", "payload": { "name": "evaluate_js", "input": { "js": "return document.title" } }, "id": "evt_js_001" }' ``` ## Step 3: Poll for Results After sending an action, poll the task endpoint to receive the result. Use the `event_t` parameter to only receive new events. ```bash theme={null} curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123?event_t=0" \ -H "apikey: YOUR_API_KEY" ``` **Response with action result:** ```json theme={null} { "r": { "id": "task_abc123", "status": "running", "events": [ { "id": "evt_extract_001", "name": "browser_action", "payload": { "code": 200, "output": { "username": "john_doe", "email": "john@example.com", "plan": "Pro" } }, "timestamp": 1699999999999 } ] } } ``` **Understanding the response:** * `code: 200` - Action succeeded, `output` contains the result * `code: 400` - Bad request, `output` contains error message * `code: 500` - Internal error, `output` contains error message For the next poll, use the last event's `timestamp`: ```bash theme={null} curl -X GET "https://api.smooth.sh/api/v1/task/task_abc123?event_t=1699999999999" \ -H "apikey: YOUR_API_KEY" ``` ## Step 4: Close the Session When finished, close the session to release resources: ```bash theme={null} curl -X POST "https://api.smooth.sh/api/v1/task/task_abc123/event" \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "name": "session_action", "payload": { "name": "close" }, "id": "evt_close_001" }' ``` ## Complete Example: Login and Extract Data Here's a complete workflow that logs into a website and extracts user data: ```javascript theme={null} const API_KEY = 'YOUR_API_KEY'; const BASE_URL = 'https://api.smooth.sh/api/v1'; async function request(method, path, body = null) { const response = await fetch(`${BASE_URL}${path}`, { method, headers: { 'apikey': API_KEY, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : null }); return response.json(); } async function sendEvent(taskId, event) { return request('POST', `/task/${taskId}/event`, event); } async function pollForResult(taskId, eventId, lastEventT = 0) { while (true) { const { r: task } = await request('GET', `/task/${taskId}?event_t=${lastEventT}`); if (task.status !== 'running' && task.status !== 'waiting') { throw new Error(`Task ended with status: ${task.status}`); } if (task.events) { for (const event of task.events) { if (event.id === eventId) { if (event.payload.code === 200) { return { result: event.payload.output, lastEventT: event.timestamp }; } else { throw new Error(event.payload.output); } } } // Update timestamp for next poll lastEventT = task.events[task.events.length - 1].timestamp; } await new Promise(r => setTimeout(r, 1000)); } } async function main() { // 1. Create session console.log('Creating session...'); const { r: session } = await request('POST', '/task', { task: null, url: 'https://example.com', device: 'desktop' }); const taskId = session.id; console.log(`Session created: ${taskId}`); console.log(`Live URL: ${session.live_url}`); let lastEventT = 0; // 2. Navigate to login page console.log('Navigating to login...'); const gotoId = 'evt_goto_' + Date.now(); await sendEvent(taskId, { name: 'browser_action', payload: { name: 'goto', input: { url: 'https://example.com/login' } }, id: gotoId }); const gotoResult = await pollForResult(taskId, gotoId, lastEventT); lastEventT = gotoResult.lastEventT; console.log('Navigation complete'); // 3. Run login task console.log('Logging in...'); const loginId = 'evt_login_' + Date.now(); await sendEvent(taskId, { name: 'session_action', payload: { name: 'run_task', input: { task: 'Fill in the login form with email "test@example.com" and password "secret123", then click the login button', max_steps: 16 } }, id: loginId }); const loginResult = await pollForResult(taskId, loginId, lastEventT); lastEventT = loginResult.lastEventT; console.log('Login complete:', loginResult.result); // 4. Extract user data console.log('Extracting user data...'); const extractId = 'evt_extract_' + Date.now(); await sendEvent(taskId, { name: 'browser_action', payload: { name: 'extract', input: { schema: { type: 'object', properties: { username: { type: 'string' }, email: { type: 'string' }, accountType: { type: 'string' } } }, prompt: 'Extract the logged-in user profile information' } }, id: extractId }); const extractResult = await pollForResult(taskId, extractId, lastEventT); console.log('Extracted data:', extractResult.result); // 5. Close session console.log('Closing session...'); const closeId = 'evt_close_' + Date.now(); await sendEvent(taskId, { name: 'session_action', payload: { name: 'close' }, id: closeId }); console.log('Session closed'); } main().catch(console.error); ``` ## Action Reference | Action | Event Type | Payload | Description | | ------------- | ---------------- | ---------------------------------------------------- | ----------------------- | | `goto` | `browser_action` | `{name: "goto", input: {url}}` | Navigate to a URL | | `extract` | `browser_action` | `{name: "extract", input: {schema, prompt?}}` | Extract structured data | | `evaluate_js` | `browser_action` | `{name: "evaluate_js", input: {js, args?}}` | Execute JavaScript | | `run_task` | `session_action` | `{name: "run_task", input: {task, max_steps?, ...}}` | Run an AI-powered task | | `close` | `session_action` | `{name: "close"}` | Close the session | ## Using the Python SDK The SDK handles all the polling complexity for you: ```python theme={null} from smooth import SmoothClient client = SmoothClient(api_key="YOUR_API_KEY") with client.session(url="https://example.com") as session: # Navigate session.goto("https://example.com/login") # Run a task result = session.run_task( task="Log in with test@example.com and password secret123", max_steps=16 ) print(f"Login result: {result.output}") # Extract data data = session.extract( schema={ "type": "object", "properties": { "username": {"type": "string"}, "email": {"type": "string"} } }, prompt="Extract user profile information" ) print(f"Extracted: {data.output}") # Execute JavaScript title = session.evaluate_js("return document.title") print(f"Page title: {title.output}") ``` # Cancel Task Source: https://docs.smooth.sh/api-reference/task/cancel-task /api-reference/openapi.json delete /task/{task_id} Cancel a running task by its ID. # Get Task Source: https://docs.smooth.sh/api-reference/task/get-task /api-reference/openapi.json get /task/{task_id} Returns the status and result of a task by its ID. Use `event_t` parameter for polling to receive only new events since the given timestamp. # List Tasks Source: https://docs.smooth.sh/api-reference/task/list-tasks /api-reference/openapi.json get /task List all tasks for the authenticated user. # Submit Task Source: https://docs.smooth.sh/api-reference/task/submit-task /api-reference/openapi.json post /task Submits a task to be executed by the Smooth agent. If `task` is `null`, opens a browser session that waits for actions via the Event endpoint. # Claude Code Source: https://docs.smooth.sh/cli/claude-code Setup Smooth CLI for Claude Code ## Installation ```bash theme={null} pip install smooth-py ``` Get your API key from [app.smooth.sh](https://app.smooth.sh), then run: ```bash theme={null} smooth config --api-key YOUR_API_KEY ``` ```bash theme={null} /plugin marketplace add circlemind-ai/smooth-sdk /plugin install smooth-browser ``` Alternatively, you can copy the [SKILL.md file](https://raw.githubusercontent.com/circlemind-ai/smooth-sdk/refs/heads/master/skills/smooth-browser/SKILL.md) directly to your agent's skill folder at `~/.claude/skills/`. Ask Claude to do something on the web. It will use Smooth automatically. ``` Find a one-way flight from London to NY leaving tomorrow ``` **Pro Tip:** You can also give Claude complex goals. Our skill will teach Claude how to break them into subtasks and distribute them across multiple concurrent browser sessions automatically. # Stay Logged In Source: https://docs.smooth.sh/cli/features/authentication Let your agent access your accounts without re-authenticating Browser profiles save your login state so your agent can access authenticated content. Log in once manually, then your agent can work with that account indefinitely. ## What Your Agent Can Do * **Check your email** - Read and summarize messages from Gmail, Outlook, etc. * **Manage social media** - Post updates, check notifications, respond to messages * **Access work tools** - Use Jira, Notion, Salesforce, or any SaaS you're logged into * **Monitor dashboards** - Check analytics, reports, and admin panels ## How It Works 1. Create a profile and log in manually through the live view 2. Your authentication (cookies, sessions) is saved to that profile 3. Future sessions with the same profile are already logged in Your credentials stay on your machine and in the secure profile storage. The agent never sees your password. ## Example: Check LinkedIn Messages First session - log in manually: > "Start a session with my linkedin profile and go to linkedin.com. I'll log in." You log in through the live view URL. Cookies are saved. Next time - already authenticated: > "Using my linkedin profile, check my messages and summarize any from recruiters this week" Your agent is already logged in and can access your messages immediately. ## Example: Post to Social Media > "Using my twitter profile, draft a tweet about our new product launch and show me a preview before posting" Your agent uses your saved Twitter session to compose the tweet. ## Example: Check Work Dashboard > "Using my salesforce profile, go to my deals dashboard and tell me which deals are closing this week" Your agent accesses Salesforce with your saved authentication. You control when to log in and what accounts to save. The agent only uses profiles you've explicitly set up. # Extract Data Source: https://docs.smooth.sh/cli/features/data-extraction Pull structured information from any webpage Your agent can extract structured data from any webpage and return it in a format you can work with. No parsing HTML, no writing selectors - just describe what you want. ## What Your Agent Can Do * **Scrape product listings** - Get prices, names, and details from e-commerce sites * **Extract contact info** - Pull emails, phone numbers, and addresses from directories * **Gather research data** - Collect information from multiple sources into structured tables * **Parse search results** - Turn search engine results into actionable data ## Example: Extract Search Results Ask your agent: > "Search Google for 'best restaurants in Austin' and extract the top 10 results with name, rating, and address" Your agent returns structured data: ```json theme={null} [ {"name": "Franklin Barbecue", "rating": 4.8, "address": "900 E 11th St"}, {"name": "Uchi", "rating": 4.7, "address": "801 S Lamar Blvd"}, ... ] ``` ## Example: Scrape a Product Page > "Go to this Amazon product page and extract the title, price, rating, and number of reviews" ```json theme={null} { "title": "Sony WH-1000XM5 Wireless Headphones", "price": 328.00, "rating": 4.6, "reviews": 12847 } ``` ## Example: Gather Competitive Intelligence > "Go to our three main competitors' pricing pages and extract all their plan names and prices" Your agent visits multiple sites and returns consolidated data you can compare. ## How Extraction Works 1. Your agent navigates to the page (or you provide a URL) 2. You describe what data you want in plain English 3. The agent analyzes the page and extracts matching information 4. Data is returned in a structured format Be specific about what fields you want. "Extract the products" is okay, but "Extract product name, price, and availability" gets better results. # Upload & Download Files Source: https://docs.smooth.sh/cli/features/files Share files with your agent and receive files back Your agent can work with files in both directions - you can give it files to upload to websites, and it can download files from the web for you. ## What Your Agent Can Do * **Fill forms with attachments** - Upload resumes, documents, or images to web forms * **Download reports** - Grab PDFs, spreadsheets, or exports from dashboards * **Process documents** - Give the agent a file and have it upload it somewhere * **Collect assets** - Download images, files, or exports from multiple sources ## Uploading Files Give your agent a local file to use during its session: > "Here's my resume (resume.pdf). Go to the company careers page and apply for the Software Engineer position" Your agent uploads your resume to the job application form. > "Upload this invoice (invoice.pdf) to the expense reporting system and categorize it as 'Travel'" Your agent fills out the expense form with your document attached. ## Downloading Files Your agent can download files and make them available to you: > "Go to our analytics dashboard, generate a report for last month, and download it" > "Find the latest SEC 10-K filing for Apple and download it" > "Go to the Google Slides presentation at this URL and download it as a PDF" After the session, you can retrieve downloaded files from the session. ## Example: Bulk Document Processing > "For each of these 5 contracts in my folder, upload them to the DocuSign portal and send for signature to [legal@company.com](mailto:legal@company.com)" Your agent processes each file, handling the uploads and form filling. # Watch Your Agent Work Source: https://docs.smooth.sh/cli/features/live-view See exactly what your agent sees in real-time Every session comes with a live view URL where you can watch your agent browse in real-time. You can also take over and interact with the browser yourself. ## What You Can Do * **Watch progress** - See your agent navigate, click, and type in real-time * **Debug issues** - See exactly where things go wrong when a task fails * **Take over** - Manually handle a step the agent can't (like a CAPTCHA) * **Verify actions** - Confirm the agent is doing what you expect before it continues ## How It Works When a session starts, you get a live URL: ``` Session started successfully! Session ID: abc123 Live URL: https://live.smooth.sh/v/... ``` Open that URL in your browser to watch and interact. ## Example: Supervise a Sensitive Task > "Using my bank profile, go to my account and check my balance. I'll watch via live view." You monitor the live view as the agent navigates your banking site, ready to intervene if needed. ## Example: Handle a CAPTCHA The agent encounters a CAPTCHA it can't solve: 1. You see it in the live view 2. You solve the CAPTCHA manually 3. The agent continues where it left off ## Example: Verify Before Submitting > "Fill out the insurance claim form with these details. Stop before submitting so I can review." You check the live view to verify the form is correct, then tell the agent to submit. The live view is interactive. You can click, type, and scroll - the agent will see your changes and can continue from there. Live views are session-specific and expire when the session closes. Share them only with people you trust. # Access Localhost Source: https://docs.smooth.sh/cli/features/localhost Let your agent browse your local development environment Your agent can access localhost and your local network through a secure tunnel. This is perfect for testing local apps, accessing internal tools, or working with development servers. ## What Your Agent Can Do * **Test your local app** - Have the agent click through your dev server and report bugs * **Access internal dashboards** - Browse admin panels and internal tools on your network * **Fill local forms** - Submit data to your local APIs through the browser * **Screenshot local pages** - Get visual feedback from your running application ## How It Works When you start a session, the browser traffic is routed through your machine by default. Your agent sees exactly what you'd see if you opened the browser yourself. ``` Your Machine Cloud Browser | | | <-- secure tunnel --> | | | localhost:3000 agent browses localhost:3000 ``` The tunnel is automatic. Just give your agent a localhost URL and it works. ## Example: Test a Local App Ask your agent: > "Go to localhost:3000, click through the signup flow, and tell me if there are any broken links or UI issues" Your agent will browse your local dev server, interact with it like a real user, and report back. ## Example: Access Internal Tools > "Go to 192.168.1.100:8080/admin, check the system status dashboard, and summarize any warnings" Your agent can access anything on your local network that your machine can reach. ## Disabling the Tunnel If you don't need local access and want the browser to use its own IP, tell your agent to use the `--no-proxy` flag when starting the session. # OpenClaw Source: https://docs.smooth.sh/cli/openclaw Setup Smooth CLI for OpenClaw ## Installation ```bash theme={null} pip install smooth-py ``` Get your API key from [app.smooth.sh](https://app.smooth.sh), then run: ```bash theme={null} smooth config --api-key YOUR_API_KEY ``` ```bash theme={null} npx clawhub@latest install smooth-browser ``` Alternatively, you can copy the [SKILL.md file](https://raw.githubusercontent.com/circlemind-ai/smooth-sdk/refs/heads/master/skills/smooth-browser/SKILL.md) directly to your agent's skill folder. Ask your agent to do something on the web. It will use Smooth automatically. ``` Find a one-way flight from London to NY leaving tomorrow ``` **Pro Tip:** You can also give your agent complex goals. Our skill will teach it how to break them into subtasks and distribute them across multiple concurrent browser sessions automatically. # Other Agents Source: https://docs.smooth.sh/cli/other-agents Setup Smooth CLI for any agent Smooth CLI works with any agent that can run CLI commands. ## Supported Agents * Codex * Cursor * Antigravity * Cline * Factory AI * Github Copilot * Kiro * OpenCode * Windsurf * Any other agent that can run CLI commands ## Installation ```bash theme={null} pip install smooth-py ``` Get your API key from [app.smooth.sh](https://app.smooth.sh), then run: ```bash theme={null} smooth config --api-key YOUR_API_KEY ``` ```bash theme={null} npx skills add https://github.com/circlemind-ai/smooth-sdk ``` Alternatively, you can copy the [SKILL.md file](https://raw.githubusercontent.com/circlemind-ai/smooth-sdk/refs/heads/master/skills/smooth-browser/SKILL.md) directly to your agent's skill folder. Ask your agent to do something on the web. It will use Smooth automatically. ``` Find a one-way flight from London to NY leaving tomorrow ``` **Pro Tip:** You can also give your agent complex goals. Our skill will teach it how to break them into subtasks and distribute them across multiple concurrent browser sessions automatically. # Overview Source: https://docs.smooth.sh/cli/overview Give your AI agent a browser that actually works