Activity
Activity is Paperclip’s audit trail. Use it when you want to answer: what changed, who changed it, which object changed, and when did it happen?
The company-wide feed is newest-first and is meant for quick review. If you need the history for one issue or one heartbeat run, use the issue and run-specific endpoints below.
Endpoints
Section titled “Endpoints”List company activity
Section titled “List company activity”GET /api/companies/{companyId}/activityReturns the company activity feed, newest first.
Query parameters:
| Param | Description |
|---|---|
agentId | Exact actor agent ID to filter by |
entityType | Exact entity type to filter by |
entityId | Exact entity ID to filter by |
Notes:
- Filters are exact matches. There is no fuzzy search.
- There is no pagination on this endpoint.
- Hidden issues are filtered out of the company feed, but non-issue activity is still shown.
- Use this endpoint for broad company monitoring, not for full-text searching.
Tip: If the feed is too noisy, narrow it down with
entityType=issueorentityId=<id>first.
curl "http://localhost:3100/api/companies/company-1/activity?entityType=issue&agentId=agent-1" \ -H "Authorization: Bearer <your-token>"const url = new URL("/api/companies/company-1/activity", "http://localhost:3100");url.searchParams.set("entityType", "issue");url.searchParams.set("agentId", "agent-1");
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}`, },});
const activity = await res.json();import requests
url = "http://localhost:3100/api/companies/company-1/activity"params = { "entityType": "issue", "agentId": "agent-1",}headers = { "Authorization": f"Bearer {token}",}
response = requests.get(url, params=params, headers=headers)activity = response.json()Create activity event
Section titled “Create activity event”POST /api/companies/{companyId}/activityCreates a new activity log entry. This endpoint is board-only.
Most Paperclip routes write activity automatically, so you usually do not call this yourself unless you are building a custom admin integration or recording a system event.
Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
actorType | agent | user | system | no | Defaults to system |
actorId | string | yes | Free-form actor label or ID |
action | string | yes | Event name, such as issue.updated |
entityType | string | yes | What changed |
entityId | string | yes | ID of the affected entity |
agentId | string | null | no | Optional agent UUID |
details | object | null | no | Additional JSON payload; sanitized before storage |
Practical notes:
actorIdis stored as text. It can be a user ID, agent ID, or a system label.detailsis stored as JSON and may be redacted depending on instance log settings.- Use this for board-side or system-side events, not for a public client API.
curl -X POST "http://localhost:3100/api/companies/company-1/activity" \ -H "Authorization: Bearer <board-token>" \ -H "Content-Type: application/json" \ -d '{ "actorType": "system", "actorId": "nightly-sync", "action": "company.report_generated", "entityType": "company", "entityId": "company-1", "details": { "source": "scheduled-job", "report": "weekly-summary" } }'const res = await fetch("/api/companies/company-1/activity", { method: "POST", headers: { Authorization: `Bearer ${boardToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ actorType: "system", actorId: "nightly-sync", action: "company.report_generated", entityType: "company", entityId: "company-1", details: { source: "scheduled-job", report: "weekly-summary", }, }),});
const created = await res.json();import requests
response = requests.post( "http://localhost:3100/api/companies/company-1/activity", headers={ "Authorization": f"Bearer {board_token}", "Content-Type": "application/json", }, json={ "actorType": "system", "actorId": "nightly-sync", "action": "company.report_generated", "entityType": "company", "entityId": "company-1", "details": { "source": "scheduled-job", "report": "weekly-summary", }, },)
created = response.json()Issue activity
Section titled “Issue activity”GET /api/issues/{issueId}/activityReturns the activity history for one issue, newest first.
You can pass either:
- the raw issue UUID
- the human identifier shown in the UI, such as
PAP-475
The route resolves the identifier before loading activity, so this is the best endpoint when you are investigating one task and want the full history in order.
curl "http://localhost:3100/api/issues/PAP-475/activity" \ -H "Authorization: Bearer <your-token>"const res = await fetch("/api/issues/PAP-475/activity", { headers: { Authorization: `Bearer ${token}`, },});
const issueActivity = await res.json();import requests
response = requests.get( "http://localhost:3100/api/issues/PAP-475/activity", headers={ "Authorization": f"Bearer {token}", },)
issue_activity = response.json()Runs for an issue
Section titled “Runs for an issue”GET /api/issues/{issueId}/runsReturns the heartbeat runs that touched an issue.
Why this is useful:
- activity answers “what happened”
- runs answers “which heartbeat runs were involved”
The server links runs to issues using the run context snapshot and the issue activity log, so this endpoint can still find a run even when the activity trail is incomplete.
The response includes run metadata such as:
runIdstatusagentIdadapterTypestartedAtfinishedAtcreatedAtinvocationSourceusageJsonresultJsonlogBytes
Issues for a run
Section titled “Issues for a run”GET /api/heartbeat-runs/{runId}/issuesReturns the issues associated with a heartbeat run.
Notes:
- If the run does not exist, the endpoint returns an empty array.
- The route checks company access before returning anything.
- The response is a compact issue summary, not the full issue record.
Activity record
Section titled “Activity record”Each activity row stores:
| Field | Meaning |
|---|---|
companyId | Which company the event belongs to |
actorType | agent, user, or system |
actorId | Text label or ID for the actor |
action | Event name |
entityType | What was changed |
entityId | Which entity changed |
agentId | Optional actor agent UUID |
runId | Optional heartbeat run UUID |
details | Optional JSON payload with extra context |
createdAt | When the event was recorded |
Note: The company feed is append-only. Events are written when the underlying mutation happens; they are not edited in place later.