curl --request GET \
--url https://api.reasonmachines.com/v3/organizations/{orgId}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.reasonmachines.com/v3/organizations/{orgId}/changes"
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}/changes', 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}/changes",
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}/changes"
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}/changes")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.com/v3/organizations/{orgId}/changes")
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 changes
A Server-Sent Events stream (text/event-stream) that says when a workspace view changes, so a client re-reads that view instead of polling it. Requires sessions:read. An Experiments feature (workspace_events_api): elsewhere it returns 403 workspace_events_unavailable.
Frames, each a JSON data line:
hellofirst:{"revisions": {topic: revision, ...}, "push": bool}with every topic this stream carries.revwhen something changed:{"revisions": {...}}with only the topics whose revision moved. Re-read those views.pulseevery 45 seconds while nothing changed:{"push": bool}.pushfalse means the server is detecting changes by polling for a while; frames still arrive, a second or so later.errorbefore the server ends the stream because the key, its workspace, its IP access list, the account or a watched Session no longer admits the caller:{"error": code, "message": text}(invalid_token,missing_scope,org_not_found,ip_not_allowed,session_access_changed, …). Reconnect only after fixing the cause;session_access_changedreconnects at once and drops that Session.reconnectwhen the client should come back:{"reason": "server_draining", "retry_ms": 0}when the server restarts, or{"reason": "auth_unavailable", "retry_ms": 5000}when the key could not be re-checked for a few minutes. Reconnect afterretry_ms; the nexthellocarries the current revisions, so compare them with what you last read.
Topics. A revision is an opaque string: compare it for equality, never order it.
sessions: the Session list (a Session started, renamed, archived, moved or changed status or activity). Re-read listSessions.reasons: the Reasons (a Reason added, archived, renamed or re-instructed). Re-read listReasons.devices: connected Devices and their roots, online state and sharing. Only with devices:read. Re-read listDevices.incident: Reason’s own incident banner.session:{id}:workspace,:sidechats,:machine_target,:shared_links,:hands_target,:pull_requests,:machine_runtime: views of a Session named insessions.workspacemoves with its record (status, title, privacy);pull_requestswith its pull requests’ state;machine_runtimewith its computer parking or waking.hands_target,machine_targetandmachine_runtimeshow Devices and computers, so they come only with devices:read.
A Session id in sessions the key’s owner may not read through this API is left out, not an error. The stream re-checks the key, its workspace, its IP access list, the account and each watched Session before any frame once 30 seconds have passed since the last check, and at every 45-second pulse, and ends with an error frame when one no longer admits the caller.
sessions:readcurl --request GET \
--url https://api.reasonmachines.com/v3/organizations/{orgId}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.reasonmachines.com/v3/organizations/{orgId}/changes"
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}/changes', 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}/changes",
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}/changes"
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}/changes")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.com/v3/organizations/{orgId}/changes")
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
Up to 8 comma-separated Session ids whose views this stream also follows (session:{id}:… topics). Ids the key's owner may not read are left out.
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 rev, pulse, and finally error or reconnect.

