Activity Log
Track changes and audit trail for records, emails, and automations
The Activity Log API provides a complete audit trail of all changes in your workspace. Track record modifications, email interactions, comments, and automation triggers.
Endpoints
All activity reads require the workspace:read scope and are account-scoped.
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/activity | Workspace-wide activity feed |
| GET | /v1/modules/{moduleSlug}/activity | Activity feed for a single module |
| GET | /v1/modules/{moduleSlug}/records/{recordId}/activity | Grouped activity feed for a record |
Query parameters: limit (default 20, max 50 for feeds; max 200 for a record),
activityTypes (comma-separated) for the feed endpoints, and groupWithinMinutes
for the record endpoint.
curl -X GET "https://api.getcoherence.io/v1/activity?limit=20" \
-H "Authorization: Bearer sk_live_..."Responses use an { "activities": [...] } envelope. Each entry's fields vary by
activity type. If the activity service is briefly unavailable the endpoint returns
{ "activities": [] } rather than erroring.
Overview
Use the Activity Log API to:
- Build audit trails for compliance
- Display change history in your application
- Track user activity across the workspace
- Implement undo functionality
- Monitor engagement signals (email opens, page views, meetings)
Activity Types
The activityType on each entry is one of:
| Type | Description |
|---|---|
record_created | A new record was created |
record_updated | A record's fields were modified |
comment_posted | A comment was posted on a record |
note_added | A note was added to a record |
email_open | A tracked email was opened |
email_click | A link in a tracked email was clicked |
email_reply | A tracked email received a reply |
form_submit | A form was submitted |
page_view | A tracked page was viewed |
meeting_scheduled | A meeting was scheduled |
meeting_attended | A meeting was attended |
document_viewed | A document was viewed |
proposal_viewed | A proposal was viewed |
call_completed | A call was completed |
task_completed | A task was completed |
custom | Custom activity logged by an integration |
Pass any of these values (comma-separated) to activityTypes on the feed endpoints to filter, e.g. ?activityTypes=record_created,record_updated.
Feed Entry Structure
The workspace feed (/activity) and module feed (/modules/{moduleSlug}/activity) return flat entries:
{
"activities": [
{
"activityLogId": "9d8c7b6a-5f4e-4321-a0b9-c8d7e6f5a4b3",
"activityType": "record_updated",
"moduleSlug": "deals",
"moduleName": "Deals",
"recordId": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
"recordDisplayName": "Enterprise License - Acme",
"actorUserId": "3b4c5d6e-7f80-4912-a0b1-c2d3e4f5a6b7",
"actorDisplayName": "John Smith",
"metadata": {
"changes": {
"value": { "from": 50000, "to": 75000 }
}
},
"occurredAt": "2024-01-15T16:45:00Z"
}
]
}Field Descriptions
| Field | Type | Description |
|---|---|---|
activityLogId | string (UUID) | Unique activity identifier |
activityType | string | Activity type (see Activity Types) |
moduleSlug | string | Slug of the module the record belongs to |
moduleName | string | Display name of the module |
recordId | string (UUID) | Record the activity relates to |
recordDisplayName | string | Display name of the record |
actorUserId | string or null | User who performed the action (null for system/agent actions) |
actorDisplayName | string or null | Display name of the actor |
metadata | object | Type-specific context (e.g. changes for updates) |
occurredAt | string | ISO 8601 timestamp |
Entries are returned most-recent first.
Get Workspace Activity
Retrieve recent activity across all modules in your workspace.
GET /activity
Example Request
curl -X GET "https://api.getcoherence.io/v1/activity?limit=20&activityTypes=record_created,record_updated" \
-H "Authorization: Bearer YOUR_API_KEY"Example Response
{
"activities": [
{
"activityLogId": "9d8c7b6a-5f4e-4321-a0b9-c8d7e6f5a4b3",
"activityType": "record_created",
"moduleSlug": "contacts",
"moduleName": "Contacts",
"recordId": "b2c3d4e5-f6a7-4901-bcde-f23456789012",
"recordDisplayName": "Alice Johnson",
"actorUserId": "3b4c5d6e-7f80-4912-a0b1-c2d3e4f5a6b7",
"actorDisplayName": "Sarah Chen",
"metadata": {
"initialFields": { "status": "lead" }
},
"occurredAt": "2024-01-15T17:00:00Z"
},
{
"activityLogId": "8c7b6a5d-4e3f-4210-b9a8-d7c6e5f4a3b2",
"activityType": "record_updated",
"moduleSlug": "deals",
"moduleName": "Deals",
"recordId": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
"recordDisplayName": "Enterprise License - Acme",
"actorUserId": null,
"actorDisplayName": null,
"metadata": {
"changes": {
"stage": { "from": "proposal", "to": "negotiation" }
}
},
"occurredAt": "2024-01-15T16:44:00Z"
}
]
}Get Module Activity
Retrieve activity for all records in a single module. Same entry shape and query parameters as the workspace feed.
GET /modules/{moduleSlug}/activity
Example Request
curl -X GET "https://api.getcoherence.io/v1/modules/deals/activity?limit=50" \
-H "Authorization: Bearer YOUR_API_KEY"Get Record Activity
Retrieve the activity feed for a specific record. Consecutive record_updated entries by the same actor within a time window are grouped into a single entry with merged field changes.
GET /modules/{moduleSlug}/records/{recordId}/activity
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Maximum entries to return (max 200) |
groupWithinMinutes | integer | 1 | Group consecutive updates by the same actor within this window (1–60) |
Example Request
curl -X GET "https://api.getcoherence.io/v1/modules/contacts/records/a1b2c3d4-e5f6-4890-abcd-ef1234567890/activity?groupWithinMinutes=5" \
-H "Authorization: Bearer YOUR_API_KEY"Example Response
{
"activities": [
{
"groupId": "7b6a5d4c-3e2f-4109-a8b7-c6d5e4f3a2b1",
"activityType": "record_updated",
"actorUserId": "3b4c5d6e-7f80-4912-a0b1-c2d3e4f5a6b7",
"actorDisplayName": "Sarah Chen",
"metadata": {
"changes": {
"deal_stage": { "from": "proposal", "to": "negotiation" },
"deal_value": { "from": 50000, "to": 75000 }
}
},
"activityCount": 2,
"occurredAt": "2024-01-15T14:22:00Z"
},
{
"groupId": "6a5d4c3b-2e1f-4098-b7a6-d5c4e3f2a1b0",
"activityLogId": "5d4c3b2a-1e0f-4987-a6b5-c4d3e2f1a0b9",
"activityType": "comment_posted",
"actorUserId": "4c5d6e7f-8091-4a23-b1c2-d3e4f5a6b7c8",
"actorDisplayName": "Mike Johnson",
"metadata": {
"html": "<p>Spoke with their CTO, proposal looks good.</p>"
},
"activityCount": 1,
"occurredAt": "2024-01-15T10:30:00Z"
},
{
"groupId": "4c3b2a1d-0e9f-4876-b5a4-c3d2e1f0a9b8",
"activityType": "record_created",
"actorUserId": "3b4c5d6e-7f80-4912-a0b1-c2d3e4f5a6b7",
"actorDisplayName": "Sarah Chen",
"metadata": {
"initialFields": { "status": "lead" }
},
"activityCount": 1,
"occurredAt": "2024-01-10T09:00:00Z"
}
]
}Grouped Entry Fields
| Field | Type | Description |
|---|---|---|
groupId | string | Unique ID for the grouped entry |
activityLogId | string | Underlying activity ID (present for ungrouped entries such as comments) |
activityType | string | The primary activity type |
actorUserId | string or null | User who performed the activity |
actorDisplayName | string or null | Actor display name |
metadata | object | Merged metadata: changes ({ fieldSlug: { from, to } }) for updates, initialFields for creation, html for comments |
activityCount | number | How many raw activities were merged into this entry |
occurredAt | string | ISO 8601 timestamp of the earliest activity in the group |
Increase groupWithinMinutes to collapse rapid-fire edits into a single logical change — useful for clean audit displays. Use groupWithinMinutes=1 (the default) when you need finer granularity.
Limits
Activity feeds are simple most-recent-first reads controlled by limit — there is no page or cursor pagination.
| Endpoint | Default limit | Max limit |
|---|---|---|
/activity | 20 | 50 |
/modules/{moduleSlug}/activity | 20 | 50 |
/modules/{moduleSlug}/records/{recordId}/activity | 50 | 200 |
Use Cases
Audit Trail
Build an audit view from a record's grouped activity feed:
async function getAuditTrail(moduleSlug: string, recordId: string) {
const response = await fetch(
`https://api.getcoherence.io/v1/modules/${moduleSlug}/records/${recordId}/activity?limit=200`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
);
const { activities } = await response.json();
return activities.map(entry => ({
occurredAt: entry.occurredAt,
action: entry.activityType,
actor: entry.actorDisplayName ?? 'System',
changes: entry.metadata.changes ?? null
}));
}Change Tracking
Monitor recent record updates across the workspace:
async function trackRecentUpdates() {
const response = await fetch(
'https://api.getcoherence.io/v1/activity?activityTypes=record_updated&limit=50',
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
);
const { activities } = await response.json();
return activities.map(entry => ({
record: entry.recordDisplayName,
module: entry.moduleSlug,
changes: entry.metadata.changes,
changedBy: entry.actorDisplayName ?? 'System',
changedAt: entry.occurredAt
}));
}Undo Support
Use a record's activity feed to revert the most recent field changes with a standard record update (PATCH with a fields object):
async function undoLastChange(moduleSlug: string, recordId: string) {
// Get the most recent grouped entries for the record
const response = await fetch(
`https://api.getcoherence.io/v1/modules/${moduleSlug}/records/${recordId}/activity?limit=10`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
);
const { activities } = await response.json();
const lastUpdate = activities.find(
entry => entry.activityType === 'record_updated' && entry.metadata.changes
);
if (!lastUpdate) {
throw new Error('No changes to undo');
}
// Build a fields payload from the "from" values
const fields: Record<string, unknown> = {};
for (const [fieldSlug, change] of Object.entries(lastUpdate.metadata.changes)) {
fields[fieldSlug] = change.from;
}
// Revert via a normal record update
await fetch(
`https://api.getcoherence.io/v1/modules/${moduleSlug}/records/${recordId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ fields })
}
);
}Error Responses
Errors use the standard envelope. Invalid query parameters (e.g. a limit above the maximum, or a non-UUID recordId) return 400 with an issues array.
Common Errors
| Status | Description |
|---|---|
| 400 | Validation failed — invalid query parameter or record ID |
| 401 | Missing or invalid API key |
| 403 | API key lacks the workspace:read scope |
| 429 | Too many requests |
Example Error Response
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"statusCode": 400,
"issues": [
{
"code": "too_big",
"maximum": 50,
"path": ["limit"],
"message": "Number must be less than or equal to 50"
}
]
}
}Related: API Overview | Records API | Modules