List events
curl --request GET \
--url https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events \
--header 'Authorization: Bearer <token>'import requests
url = "https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events', 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://agp.eu.hcompany.ai/api/v2/sessions/{id}/events",
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://agp.eu.hcompany.ai/api/v2/sessions/{id}/events"
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://agp.eu.hcompany.ai/api/v2/sessions/{id}/events")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events")
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_bodySessions
List events
Paginated list of everything the agent observed and did in a session, for replay and audit.
GET
/
api
/
v2
/
sessions
/
{id}
/
events
List events
curl --request GET \
--url https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events \
--header 'Authorization: Bearer <token>'import requests
url = "https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events', 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://agp.eu.hcompany.ai/api/v2/sessions/{id}/events",
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://agp.eu.hcompany.ai/api/v2/sessions/{id}/events"
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://agp.eu.hcompany.ai/api/v2/sessions/{id}/events")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://agp.eu.hcompany.ai/api/v2/sessions/{id}/events")
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_bodyReturns a session’s events, oldest first, for replay and audit. For live tailing, use
Event types (
Agent activity (
The agent’s step-by-step trace lives inside
Each
/changes. Auth is optional, so public shares work.
Returns a page envelope of events in the event shape.
Path parameters
string
required
The session ID.
Query parameters
integer
default:"1"
Page number (1-based).
integer
default:"50"
Items per page. Maximum:
200.string
default:"timestamp"
Sort order. Options:
timestamp, -timestamp.string
Filter by event type, e.g.
AgentEvent or MetricsUpdateEvent. See Event shape for the full list.Event shape
Every event, on both this endpoint and/changes, is the same envelope:
Event envelope
{ "type": "AgentEvent", "data": { "...": "..." }, "timestamp": "2026-06-01T15:14:05Z" }
| Field | Type | Description |
|---|---|---|
type | string | The event type (see below). |
data | object | Type-specific payload. |
timestamp | string | ISO 8601 time the event was recorded. |
Event types (type)
data is an open JSON object whose shape varies by type, so treat this list as representative rather than exhaustive (other types such as DelayAgentStartEvent, AgentRunStatusChangeEvent, and LiveViewUrlEvent may appear).
type | data | Emitted when |
|---|---|---|
RequestStartEvent | {} | The session request is received. |
RequestStartDispatchedEvent | { "status": "..." } | The run is dispatched for execution. |
AgentStartedEvent | {} | The agent begins executing. |
AgentEvent | the step event (see below) | The agent observes, decides, acts, or answers. |
MetricsUpdateEvent | { "metrics": {...} } | Usage and cost are rolled up. metrics carries steps, total_cost, input_cost, output_cost, reasoning_cost, and cost_per_model[]. Cost fields are in USD and null when a model’s price is unavailable. |
ActiveStateChangeEvent | { "state": "..." } | The agent’s active state changes (running, idle, or awaiting_tool_results, which also carries pending_tool_calls for custom tools). |
AgentCompletionEvent | { "reason": "..." } | The run ends (e.g. "finished"). |
AgentErrorEvent | { "error": "...", "trace": "...", "info": ... } | The run fails. The session moves to failed. |
RunnerSessionEvent | { "runner_session_id": "..." } | The agent connects to a cloud browser. runner_session_id is the browser session id that the session files endpoints address, not the agent session id. |
FileTransferEvent | { "direction": "...", "path": "...", "size_bytes": 1234 } | The agent reads a file out of its browser (direction: "download") or writes one in ("upload"). size_bytes is null for reads. |
AttachmentEvent | { "origin": "agent" | "user", "name": "...", "path": "...", "media_type": "...", "size_bytes": 1234, "url": "..." } | The user or the agent attaches a file to the session. url downloads it. |
Agent activity (AgentEvent.data)
The agent’s step-by-step trace lives inside AgentEvent. Its data is the step event, and data.kind tells you what happened:
data.kind | Payload (under data) | Meaning |
|---|---|---|
policy_event | reasoning_content, content, tool_reqs[], validation_errors[] | The agent’s decision: its reasoning, message, and the action(s) it chose. |
observation_event | type, text, image, metadata (see Observation shapes) | What the agent perceived this step. |
tool_result | tool_req (tool_name, args, id), result, truncated, output_artifact_path | The outcome of an action. result is opaque JSON. Oversized text output is cut to a head/tail preview (16 KiB) with truncated: true. When the agent has a code sandbox, the full text is kept there and output_artifact_path is its path inside the sandbox (not a URL). |
answer_event | answer | The final answer. Same value as /changes answer. |
error_event | error, origin, tool_req? | A recoverable error during the step (e.g. a failed or invalid action). |
message_event | caller_id, content[] | A user or agent message. |
flow_event | flow, origin | A control-flow signal such as pause, resume, or force_answer, including the ones you trigger via the session controls, and reset_history when the agent compacts its context. |
tool_reqs[] entry (and tool_result.tool_req) is { tool_name, args, id }. policy_event.content and reasoning_content may be null.
Parsing a stream means switching on the outer type, then for AgentEvent, on data.kind:
AgentEvent
{
"type": "AgentEvent",
"timestamp": "2026-06-01T15:14:05Z",
"data": {
"kind": "observation_event",
"type": "web",
"text": null,
"image": { "type": "url", "source": "https://.../screenshot-7f3a.png", "media_type": "image/png" },
"metadata": {
"url": "https://example.com",
"title": "Example Domain",
"text": "# Example Domain\nThis domain is for use in illustrative examples...",
"tabs": ["0"],
"current_tab": "0",
"viewport_size": [1200, 1200],
"page_size": [1200, 3000],
"scroll_position": [0, 0]
}
}
}
Observation shapes
Anobservation_event is flat: type names the environment that produced it, image is the screenshot (if any), and metadata is that environment’s snapshot, shaped by type (an open object, so switch on type to read it). The Browser emits one of two types, depending on the environment’s mode.
The page text lives in metadata, not in the top-level text: read metadata.text for web and metadata.page_markdown (or page_html) for textual_web. The top-level text carries only occasional step notices (e.g. [page unchanged since previous observation]) and is null for most observations.
web is emitted in visual mode: a screenshot (top-level image) plus page metadata.
metadata field | Type | Description |
|---|---|---|
url | string | Current page URL. |
title | string | Page title. |
text | string | Visible page rendered as markdown. Empty unless the mode sets markdown: true. |
tabs | string[] | Open tab IDs. |
current_tab | string | Active tab ID. |
viewport_size | integer[] | Viewport [width, height] in pixels. |
page_size | integer[] | Full page [width, height] in pixels. |
scroll_position | integer[] | Scroll offset [x, y] in pixels. |
cursor_position | integer[] | null | Cursor [x, y] in pixels. |
textual_web is emitted in text mode: paginated page text under metadata, no screenshot.
metadata field | Type | Description |
|---|---|---|
url | string | Current page URL. |
title | string | Page title. |
tabs | string[] | Open tab IDs. |
current_tab | string | Active tab ID. |
mode | string | "markdown" or "html". |
page_markdown | string | Full page rendered as markdown. |
page_html | string | Full page HTML. |
text_offset | integer | Start character offset of the current chunk. |
chunk_size | integer | Characters per chunk. |
chunk_number | integer | Current chunk, 1-based. |
total_chunks | integer | Total chunks for the page. |
Images
Theimage on an observation_event, and any image inside message_event.content[], is { source, type, media_type }. type is url for platform-served screenshots (fetch source with your API key) or base64 for inline images. Images embedded in a tool_result.result or an answer_event.answer stay inline as base64 within that opaque payload, never as URLs.
Examples
curl "https://agp.eu.hcompany.ai/api/v2/sessions/$SESSION_ID/events?size=10" \
-H "Authorization: Bearer $HAI_API_KEY"
from hai_agents import Client
client = Client()
page = client.sessions.list_session_events(session_id, size=10)
for event in page.items:
print(event.type, event.timestamp)
import { HaiAgentsClient } from "hai-agents";
const client = new HaiAgentsClient();
const page = await client.sessions.listSessionEvents({ id: sessionId, size: 10 });
for (const event of page.items) {
console.log(event.type, event.timestamp);
}
Was this page helpful?