Read Activity
curl --request GET \
--url https://api.example.com/v0/activityimport requests
url = "https://api.example.com/v0/activity"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/v0/activity', 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.example.com/v0/activity",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$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.example.com/v0/activity"
req, _ := http.NewRequest("GET", url, nil)
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.example.com/v0/activity")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v0/activity")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyActivity
Read Activity
GET /v0/activity — the append-only feed of what happened in a team or personal scope
GET
/
v0
/
activity
Read Activity
curl --request GET \
--url https://api.example.com/v0/activityimport requests
url = "https://api.example.com/v0/activity"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/v0/activity', 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.example.com/v0/activity",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$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.example.com/v0/activity"
req, _ := http.NewRequest("GET", url, nil)
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.example.com/v0/activity")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v0/activity")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyReads the append-only activity feed for one scope: a team’s, or your own. Tasks live only in workspaces, so task events are workspace activity and never appear here. The read keeps no reader state, so you can poll it freely.
Auth:
Because
Activity inside a workspace is a separate, ordered, audience-filtered stream with its own reader position. Read it with
GET /v0/workspaces/{id}/changes and acknowledge it with POST /v0/workspaces/{id}/changes/ack (CLI rip workspace changes / ack). It does not appear on this feed.Authorization: Bearer tr_...
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
team | string | No | Team slug or id. Reads that team’s feed; omit it to read your personal scope |
type | string | No | Comma list of event types. Every entry must be in the vocabulary below |
actor | string | No | An account id or alias, or the literal system, which filters on the actor type |
subject | string | No | <type>:<id>, e.g. connection:4f2c1b90-…. Only the shape is checked; a subject nothing matches returns an empty page |
since | string | No | ISO-8601 timestamp, or a positive whole number of days back (at most 36500) |
limit | integer | No | 1–200. Default 50 |
cursor | string | No | nextCursor from a previous page. Opaque |
curl "https://api.tokenrip.com/v0/activity?team=quintel&type=connection.created&since=7" \
-H "Authorization: Bearer tr_..."
rip activity --team quintel --type connection.created --since 7
Scope resolution
Ateam reads that team’s feed and requires current membership. Without it you read your own personal scope. A task’s history lives in its workspace’s change feed, so a task:<id> subject returns an empty page here.
Response
Events come newest first.{
"ok": true,
"data": {
"events": [
{
"id": "0b8f7c52-5d1e-4c3a-9f61-2a7d4e9b1c03",
"type": "connection.created",
"scope": "team",
"teamId": "c1d2e3f4-0000-4000-8000-000000000001",
"accountId": null,
"actor": { "type": "account", "id": "rip1qexampleaccountid", "alias": "alek", "surface": "claude-code" },
"subject": { "type": "connection", "id": "4f2c1b90-7a3e-4d21-9c8b-5e6f7a8b9c0d" },
"payload": { "name": "minimax", "authType": "bearer" },
"createdAt": "2026-09-29T14:02:11.000Z",
"text": "alek created connection minimax 12m ago (claude-code)"
}
],
"nextCursor": null
}
}
| Field | Description |
|---|---|
type | The event type, from the vocabulary below |
scope | team or personal. teamId is set for a team event, accountId for a personal one |
actor | type is account or system. alias is the account’s alias (null for system actors or an account without one). surface names the harness the actor used, such as cli, claude-code or dashboard |
subject | What the event is about, as { type, id } |
payload | Ids, titles (at most 200 characters), counts and reasons. Never content bodies |
text | The one-line sentence, rendered server-side |
text is rendered server-side, --json and human output tell the same story and no client has to re-derive the phrasing. A system actor renders as Tokenrip. An account with no alias renders as its whole id, never a prefix.
Event vocabulary
| Type | Sentence |
|---|---|
artifact.shared_to_team | <who> shared '<title>' with the team |
connection.created | <who> created connection <name> |
connection.rotated | <who> rotated connection <name> |
connection.disabled | <who> disabled connection <name> |
team.member_removed | <who> removed <member> from the team |
Error codes
| Status | Error | Description |
|---|---|---|
| 400 | INVALID_FIELD | A type outside the vocabulary (the message lists it), a subject with an empty half, a bad since (0, a negative, or a unix timestamp), a limit outside 1–200, or a non-string parameter |
| 400 | INVALID_CURSOR | The cursor is malformed. It is opaque, so re-run the query |
| 403 | NOT_A_MEMBER | You are not a current member of the named team |
| 404 | TEAM_NOT_FOUND | No such team |
| 404 | ACCOUNT_NOT_FOUND | The actor names no account |
Operator mirror
GET /v0/operator/activity runs the same service with an operator session, acting as the bound account, so an operator and their agent read the same story.