Overview
Session control commands allow you to manage task execution dynamically. You can pause tasks for inspection, resume them, stop the current task, or terminate the entire session.
All control commands use the same endpoint but with different message payloads:
Method Endpoint Best For REST POST /start/send-messageStateless, serverless, simple integrations WebSocket socket.emit("message", {...})Real-time events, live UIs
newTask
Start a new task in the current session. This is the only control action that returns a taskId for result tracking.
Parameter Type Required Description actionTypestring Yes Must be "newTask" newStatestring Yes Must be "start" taskDetailsstring Yes Task description startingPointstring No URL to start from maxDurationnumber No Max time in ms (default: 300000) maxInputTokensnumber No Max input tokens (default: 100000) maxOutputTokensnumber No Max output tokens (default: 100000) avoidDomainsstring[] No Domains to avoid terminateOnCompletionboolean No Auto-terminate after task (default: false) outputTypestring No Output format: "text", "structured_json", or "structured_csv" outputSchemaobject No JSON Schema for structured output filesstring[] No File IDs to attach (details )
Use REST for simple integrations, WebSocket for real-time event handling:
curl -X POST https://connect.webrun.ai/start/send-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"sessionId": "SESSION_ID",
"message": {
"actionType": "newTask",
"newState": "start",
"taskDetails": "Search for wireless keyboards",
"maxDuration": 60000,
"terminateOnCompletion": true
}
}'
If the task completes within 50 seconds, you get the result inline:
{
"success" : true ,
"sessionId" : "SESSION_ID" ,
"taskId" : "TASK_ID" ,
"result" : {
"type" : "task_completed" ,
"data" : {
"message" : "Found 10 wireless keyboard results" ,
"prompt_tokens" : 8500 ,
"completion_tokens" : 2300 ,
"total_tokens" : 10800
}
}
}
For longer tasks, you receive a pollUrl to check status. Poll every 2-3 seconds until complete:
{
"success" : true ,
"sessionId" : "SESSION_ID" ,
"taskId" : "TASK_ID" ,
"pending" : true ,
"pollUrl" : "https://connect.webrun.ai/task/SESSION_ID/TASK_ID" ,
"message" : "Task still running. Poll GET /task/:sessionId/:taskId for result."
}
Set terminateOnCompletion: true on your final task to auto-close the session and avoid idle charges.
pause
Pause the current task. The agent will stop after completing its current action and wait for a resume command.
Use cases:
Inspect the current state before continuing
Wait for external conditions to be met
Synchronize with other systems
curl -X POST https://connect.webrun.ai/start/send-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"sessionId": "SESSION_ID",
"message": { "actionType": "state", "newState": "pause" }
}'
Response:
{
"success" : true ,
"message" : "Message sent successfully"
}
The agent will complete its current action before pausing. It does not immediately halt mid-action.
resume
Resume a paused task. The agent will continue from where it left off.
curl -X POST https://connect.webrun.ai/start/send-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"sessionId": "SESSION_ID",
"message": { "actionType": "state", "newState": "resume" }
}'
Response:
{
"success" : true ,
"message" : "Message sent successfully"
}
stop
Stop the current task immediately. The session remains active and ready for new tasks.
Difference from terminate:
stop: Ends the current task only, session stays active
terminate: Ends the entire session and all connections
curl -X POST https://connect.webrun.ai/start/send-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"sessionId": "SESSION_ID",
"message": { "actionType": "state", "newState": "stop" }
}'
Response:
{
"success" : true ,
"message" : "Message sent successfully"
}
When to use:
Task is taking too long
Task is heading in wrong direction
Need to intervene and start a different task
After stopping:
You can immediately send a new task or manually interact with the browser before sending the next task.
terminate
End the session completely. This closes the browser instance and all connections (WebSocket, video stream).
curl -X POST https://connect.webrun.ai/start/send-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"sessionId": "SESSION_ID",
"message": { "actionType": "state", "newState": "terminate" }
}'
Response:
{
"success" : true ,
"message" : "Message sent successfully"
}
Always terminate sessions when you’re done to avoid unnecessary charges. Idle sessions expire after 5 minutes of inactivity, but you should terminate explicitly to avoid unnecessary costs.
State Transitions
Understanding how session states work:
┌─────────┐ ┌──────────┐ ┌─────────────┐
│ Pending │────►│ Active │────►│ Completed │
└─────────┘ └──────────┘ └─────────────┘
│
├───────────► Paused ──────► Active (resume)
│
├───────────► Stopped ─────► Active (new task)
│
└───────────► Terminated
State Description Can Transition To pendingSession initializing activeactiveTask running paused, completed, stopped, terminatedpausedTask paused, waiting for resume active, stopped, terminatedstoppedTask stopped, ready for new task active, terminatedcompletedTask finished successfully active (new task), terminatedterminatedSession ended None (final state)
Cost Optimization Tips
1. Terminate Sessions Promptly
Don’t leave sessions idle. Terminate them as soon as your workflow completes:
// Option A: Explicit terminate
await fetch ( ` ${ BASE } /start/send-message` , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"Authorization" : `Bearer ${ API_KEY } `
},
body: JSON . stringify ({
sessionId: session . sessionId ,
message: { actionType: "state" , newState: "terminate" }
})
});
// Option B: Auto-terminate on last task
await fetch ( ` ${ BASE } /start/send-message` , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"Authorization" : `Bearer ${ API_KEY } `
},
body: JSON . stringify ({
sessionId: session . sessionId ,
message: {
actionType: "newTask" ,
newState: "start" ,
taskDetails: "Final task" ,
terminateOnCompletion: true // ← Session closes after this task
}
})
});
2. Use stop to Intervene Early
If a task is taking too long or going off-track, stop it immediately:
// Monitor task progress
const taskStatus = await fetch ( ` ${ BASE } /task/ ${ sessionId } / ${ taskId } ` )
. then ( r => r . json ());
if ( taskStatus . usage . cost > 0.10 ) {
// Stop task if cost exceeds threshold
await fetch ( ` ${ BASE } /start/send-message` , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"Authorization" : `Bearer ${ API_KEY } `
},
body: JSON . stringify ({
sessionId: sessionId ,
message: { actionType: "state" , newState: "stop" }
})
});
}
3. Set Appropriate maxDuration
Limit task duration to prevent runaway costs:
socket . emit ( "message" , {
actionType: "newTask" ,
newState: "start" ,
taskDetails: "Search for products" ,
maxDuration: 30000 // 30 seconds max
});
4. Use pause for Inspection
Pause tasks to inspect state before continuing:
// Pause to check if we're on the right track
await sendMessage ({ actionType: "state" , newState: "pause" });
// Check video stream or poll for current state
const canContinue = await checkIfTaskOnTrack ();
if ( canContinue ) {
await sendMessage ({ actionType: "state" , newState: "resume" });
} else {
await sendMessage ({ actionType: "state" , newState: "stop" });
// Start a different task
}
Control Flow Example
Complete example showing pause/resume/stop/terminate in action:
const API_KEY = "YOUR_API_KEY" ;
const BASE = "https://connect.webrun.ai" ;
async function controlFlowDemo () {
// Create session
const session = await fetch ( ` ${ BASE } /start/start-session` , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"Authorization" : `Bearer ${ API_KEY } `
},
body: JSON . stringify ({
mode: "default" ,
initialTask: {
taskDetails: "Go to amazon.com" ,
startingPoint: "https://amazon.com"
}
})
}). then ( r => r . json ());
const sessionId = session . sessionId ;
// Start a task
const task = await sendMessage ( sessionId , {
actionType: "newTask" ,
newState: "start" ,
taskDetails: "Search for 'mechanical keyboard'"
});
// Wait a bit, then pause to inspect
await new Promise ( r => setTimeout ( r , 5000 ));
await sendMessage ( sessionId , { actionType: "state" , newState: "pause" });
console . log ( "Task paused for inspection" );
// Check something (e.g., video stream, task status)
await new Promise ( r => setTimeout ( r , 3000 ));
// Decision point
const shouldContinue = true ; // Your logic here
if ( shouldContinue ) {
// Resume the task
await sendMessage ( sessionId , { actionType: "state" , newState: "resume" });
console . log ( "Task resumed" );
// Wait for completion
await pollForResult ( sessionId , task . taskId );
} else {
// Stop and try something different
await sendMessage ( sessionId , { actionType: "state" , newState: "stop" });
console . log ( "Task stopped" );
// Start different task
await sendMessage ( sessionId , {
actionType: "newTask" ,
newState: "start" ,
taskDetails: "Navigate to homepage instead"
});
}
// Terminate when done
await sendMessage ( sessionId , { actionType: "state" , newState: "terminate" });
console . log ( "Session terminated" );
}
async function sendMessage ( sessionId , message ) {
return fetch ( ` ${ BASE } /start/send-message` , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"Authorization" : `Bearer ${ API_KEY } `
},
body: JSON . stringify ({ sessionId , message })
}). then ( r => r . json ());
}
async function pollForResult ( sessionId , taskId ) {
const maxAttempts = 60 ;
const interval = 2000 ;
for ( let i = 0 ; i < maxAttempts ; i ++ ) {
const res = await fetch ( ` ${ BASE } /task/ ${ sessionId } / ${ taskId } ` , {
headers: { "Authorization" : `Bearer ${ API_KEY } ` }
});
const data = await res . json ();
if ( data . type === "task_completed" ) return data ;
if ( data . type === "guardrail_trigger" ) throw new Error ( `Guardrail: ${ data . data . value } ` );
if ( data . status === "failed" ) throw new Error ( data . error );
if ( data . pending ) {
await new Promise ( r => setTimeout ( r , interval ));
continue ;
}
return data ;
}
throw new Error ( "Polling timeout" );
}
controlFlowDemo ();