curl --request GET \
--url https://api.reasonmachines.com/v3/organizations/{orgId}/events \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.reasonmachines.com/v3/organizations/{orgId}/events"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.reasonmachines.com/v3/organizations/{orgId}/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://api.reasonmachines.com/v3/organizations/{orgId}/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://api.reasonmachines.com/v3/organizations/{orgId}/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://api.reasonmachines.com/v3/organizations/{orgId}/events")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.com/v3/organizations/{orgId}/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_body<string>{
"error": "missing_bearer",
"message": "Authorization required"
}{
"error": {
"type": "rate_limited",
"message": "This API key exceeded its request-rate limit"
}
}Stream workspace events
A resumable Server-Sent Events stream (text/event-stream) of what happens in the workspace’s Sessions, in the order it happened. Requires sessions:read. An Experiments feature (workspace_events_api): elsewhere it returns 403 workspace_events_unavailable.
Event types: message.created, turn.finished.
message.created: a message was added to a Session (a person’s prompt or follow-up, or a message Reason posted).data:message_id,role. Read it with listSessionConversation or listSessionMessages.turn.finished: a turn of a Session ended.data.statusis the Session’s public status for that turn (exit,error,cancelled, …). It is not the verdict: read the Session’soutcomewith getSession.
Each event frame is id: <seq>, event: <type>, data: {"id", "type", "created_at", "session_id", "run_id", "data"}. Events carry ids and states, never conversation content: read the content through the ordinary, access-checked operations.
Resuming. id is the workspace’s event sequence. Reconnect with the Last-Event-ID header (EventSource does this itself) or ?after=<id> and the stream first replays every retained event after it, then continues live. Without either it starts at the current end. Events are retained for 7 days: a cursor older than that gets a reset frame ({"reason": "cursor_expired", "resume_after": "<id>"}), meaning events were lost, so re-read the views you show, then the stream continues from the oldest retained event. Ids increase but may skip numbers.
Other frames: hello first ({"after": "<id>", "latest": "<id>", "retention_seconds": n}); pulse every 45 seconds while nothing arrives, and after events you may not see, carrying the stream’s current id so a reconnect resumes past them; error before the server ends the stream because the key, its workspace, its IP access list or the account no longer admits the caller ({"error": code, "message": text}; reconnect only after fixing the cause); reconnect when the client should come back with its last id: {"reason": "server_draining"} when the server restarts, {"reason": "auth_unavailable", "retry_ms": 5000} when the key could not be re-checked for a few minutes.
Visibility. An event about a Session reaches you only while the key’s owner may read that Session through this API (its privacy, the Reason it is filed under, membership), checked when the event is sent, not when it happened. The credential is re-checked before any frame once 30 seconds have passed since the last check, and at every 45-second pulse.
Scope:sessions:readcurl --request GET \
--url https://api.reasonmachines.com/v3/organizations/{orgId}/events \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.reasonmachines.com/v3/organizations/{orgId}/events"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.reasonmachines.com/v3/organizations/{orgId}/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://api.reasonmachines.com/v3/organizations/{orgId}/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://api.reasonmachines.com/v3/organizations/{orgId}/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://api.reasonmachines.com/v3/organizations/{orgId}/events")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.com/v3/organizations/{orgId}/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_body<string>{
"error": "missing_bearer",
"message": "Authorization required"
}{
"error": {
"type": "rate_limited",
"message": "This API key exceeded its request-rate limit"
}
}Authorizations
Your Reason API key from Settings › API keys. New keys use reason_; legacy ara_ keys remain accepted. Keys are capability-scoped: run, mcp:read, mcp:write, secrets:read, secrets:write, sessions:read, sessions:debug, devices:read, devices:write, devices:use, computers:read, computers:write, ssh:write, knowledge:read, memory:read, memory:write, skills:read, skills:write, pages:read, pages:write, repos:read, repos:write, reviews:read, reviews:write, deployment:read, analytics:read, org:read, org:write, attachments:read, attachments:write, guardrails:read, guardrails:write, network:read, network:write, automations:read, automations:write, agent_auth:read. devices:write, devices:use, computers:read, computers:write, ssh:write, network:read and network:write are opt-in, excluded from default keys. computers:read lists visible cloud and connected computers without waking them or granting execution access. devices:read discovers local Device targets; devices:use separately consents to local execution, so a discover-and-run local workflow needs both. devices:write manages enrollment and device grants; computers:write creates or deletes cloud computers; ssh:write manages retained SSH access. network:read reads the saved workspace network policy; network:write replaces custom allowed hosts and requires a current organization owner/admin. Legacy project PATCH requests containing network_allowed_hosts also require network:write plus their existing run scope and project management permission; saved project hosts do not affect current session egress. Only owners/admins can mint or manage keys with network:write; keys cannot grant themselves more authority. See /docs/network-policies for replacement semantics and runtime applicability. mcp:write manages MCP server configuration only; it does not authorize remote MCP-tool execution. sessions:read reads sessions, including the assistant, reasoning and tool activity in their events. sessions:debug is privileged: only for organization owners/admins, it expands session events to the full diagnostic projection (diagnostic event kinds, status text and raw event metadata).
Path Parameters
Organization id or slug. Resolve it with GET /v3/self.
Query Parameters
Resume after this event id (an earlier frame's id); the Last-Event-ID header does the same. Omit both to start at the current end.
Response
The stream. It stays open until the client leaves, the server restarts (reconnect frame) or access ends (error frame).
Server-Sent Events: hello, then event frames, pulse, reset, and finally error or reconnect.

