Skip to main content

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:
MethodEndpointBest For
RESTPOST /start/send-messageStateless, serverless, simple integrations
WebSocketsocket.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. Parameters:
  • actionType: "newTask" (required)
  • newState: "start" (required)
  • taskDetails: Task description (required)
  • maxDuration: Max time for task in ms (optional)
  • maxInputTokens: Max input tokens (optional)
  • maxOutputTokens: Max output tokens (optional)
  • startingUrl: Starting URL (optional)
  • avoidDomains: Array of domains to avoid (optional)
  • terminateOnCompletion: Auto-terminate session after task (optional, default: false)
curl -X POST https://connect.enigma.click/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
    }
  }'
Response (task completed within 50 seconds):
{
  "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
    }
  }
}
Response (task still running after 50 seconds):
{
  "success": true,
  "sessionId": "SESSION_ID",
  "taskId": "TASK_ID",
  "pending": true,
  "pollUrl": "https://connect.enigma.click/task/SESSION_ID/TASK_ID",
  "message": "Task still running. Poll GET /task/:sessionId/:taskId for result."
}
Cost Optimization: Set terminateOnCompletion: true when you’re done with the session to avoid idle session charges. The session will automatically close after this task completes.

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.enigma.click/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.enigma.click/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.enigma.click/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.enigma.click/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 continue to incur costs until they time out (max 5 minutes) or are explicitly terminated.

State Transitions

Understanding how session states work:
┌─────────┐     ┌──────────┐     ┌─────────────┐
│ Pending │────►│  Active  │────►│  Completed  │
└─────────┘     └──────────┘     └─────────────┘

                     ├───────────► Paused ──────► Active (resume)

                     ├───────────► Stopped ─────► Active (new task)

                     └───────────► Terminated
StateDescriptionCan Transition To
pendingSession initializingactive
activeTask runningpaused, completed, stopped, terminated
pausedTask paused, waiting for resumeactive, stopped, terminated
stoppedTask stopped, ready for new taskactive, terminated
completedTask finished successfullyactive (new task), terminated
terminatedSession endedNone (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.enigma.click";

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({
      taskDetails: "Go to amazon.com",
      startingUrl: "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();

Next Steps