Watch an existing browser run
curl --request GET \
--url https://api.pre.dev/browser-agent/{id}/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.pre.dev/browser-agent/{id}/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.pre.dev/browser-agent/{id}/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pre.dev/browser-agent/{id}/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pre.dev/browser-agent/{id}/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pre.dev/browser-agent/{id}/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pre.dev/browser-agent/{id}/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyevent: snapshot
data: {"id":"64b7f3e1c2a9d5e6f8a01234","status":"processing","total":1,"completed":1,"results":[{"status":"PENDING"}]}
event: batch_meta
data: {"name":"Example lookup","taskNames":["Read title"]}
event: task_result
data: {"taskIndex":0,"status":"SUCCESS","data":{"title":"Example Domain"},"creditsUsed":0.1}
event: done
data: {"status":"completed"}
Browser Agents
Stream an existing run
Attach to an existing browser run with an initial snapshot, live SSE updates, and terminal status.
GET
/
browser-agent
/
{id}
/
stream
Watch an existing browser run
curl --request GET \
--url https://api.pre.dev/browser-agent/{id}/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.pre.dev/browser-agent/{id}/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.pre.dev/browser-agent/{id}/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pre.dev/browser-agent/{id}/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pre.dev/browser-agent/{id}/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pre.dev/browser-agent/{id}/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pre.dev/browser-agent/{id}/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyevent: snapshot
data: {"id":"64b7f3e1c2a9d5e6f8a01234","status":"processing","total":1,"completed":1,"results":[{"status":"PENDING"}]}
event: batch_meta
data: {"name":"Example lookup","taskNames":["Read title"]}
event: task_result
data: {"taskIndex":0,"status":"SUCCESS","data":{"title":"Example Domain"},"creditsUsed":0.1}
event: done
data: {"status":"completed"}
Submit with
The existing-run stream’s
The SDKs stream submission responses; use direct HTTP for this existing-run endpoint.
Each status has an example body in the response examples. See errors and retries.
async: true, save the run ID, then attach to this endpoint. This lets your application recover results even if the stream disconnects.
curl --fail-with-body -N "https://api.pre.dev/browser-agent/$BATCH_ID/stream" \
-H "Authorization: Bearer $PREDEV_API_KEY"
Event contract
| Event | Payload | When |
|---|---|---|
snapshot | Full run result with available timelines | On connection |
batch_meta | { name, taskNames } | Display names become available |
task_event | { taskIndex, type, timestamp, iteration?, data } | A task emits an event |
task_result | { taskIndex, ...taskResult } | A task finishes |
done | { status: "completed" } or { status: "failed" } | The batch reaches a terminal state |
error | { error } | The stream cannot continue |
done contains status only. Fetch the run after done for final data and credit usage. This differs from the submission stream, whose done contains the full batch.
Reconnect and recover
An already-terminal run emitssnapshot, then done, then closes. Keepalive lines start with : and are not JSON events. Closing the stream does not cancel the run.
The stream has no event-ID replay contract. After a disconnect, fetch a fresh result or reconnect for a new snapshot; do not assume that every transient event will be replayed. Treat EOF without done as an interrupted stream and check status.
Read the stream in code
Send the key in a header, as on every other endpoint, and read the response body as it arrives. The browser’sEventSource cannot set headers, so use fetch or an HTTP client that streams.
const response = await fetch(`https://api.pre.dev/browser-agent/${batchId}/stream`, {
headers: { Authorization: `Bearer ${process.env.PREDEV_API_KEY}` },
});
if (!response.ok || !response.body) throw new Error(`Stream failed: HTTP ${response.status}`);
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
let finished = false;
while (!finished) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
let boundary;
while (!finished && (boundary = buffer.indexOf('\n\n')) !== -1) {
const frame = buffer.slice(0, boundary);
buffer = buffer.slice(boundary + 2);
const event = frame.match(/^event: (.+)$/m)?.[1];
const data = frame.match(/^data: (.*)$/m)?.[1];
if (!event || data === undefined) continue; // keepalive comment
if (event === 'error') throw new Error(JSON.parse(data).error);
if (event === 'done') finished = true;
console.log(event, JSON.parse(data));
}
}
// After done (or an interrupted stream), fetch GET /browser-agent/{id} for the final result.
import json
import os
import requests
with requests.get(
f"https://api.pre.dev/browser-agent/{batch_id}/stream",
headers={"Authorization": f"Bearer {os.environ['PREDEV_API_KEY']}"},
stream=True,
timeout=(10, 120),
) as response:
response.raise_for_status()
event = None
for line in response.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event = line[len("event: "):]
elif line.startswith("data: ") and event:
data = json.loads(line[len("data: "):])
if event == "error":
raise RuntimeError(data.get("error"))
print(event, data)
if event == "done":
break
# After done (or an interrupted stream), fetch GET /browser-agent/{id} for the final result.
Errors
These statuses arrive before streaming begins. Once headers are sent, handle SSEerror frames as failures even with HTTP 200.
| Status | When |
|---|---|
400 | id is not a valid run ID |
401 | Missing or invalid key |
404 | No run with this ID in your account |
503 | Streaming capacity is full; poll GET /browser-agent/{id} instead |
Authorizations
apiKeyAuthxApiKey
Your pre.dev workspace API key (pdk_...) from Integrations, Built-in tab: https://pre.dev/projects/integrations?tab=built-in
Path Parameters
24-character record ID.
Pattern:
^[a-fA-F0-9]{24}$Response
SSE stream. Errors after headers arrive as error events, not new HTTP status codes.
The response is of type string.
Was this page helpful?

