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.

MethodEndpointDescription
GET/v1/activityWorkspace-wide activity feed
GET/v1/modules/{moduleSlug}/activityActivity feed for a single module
GET/v1/modules/{moduleSlug}/records/{recordId}/activityGrouped 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:

TypeDescription
record_createdA new record was created
record_updatedA record's fields were modified
comment_postedA comment was posted on a record
note_addedA note was added to a record
email_openA tracked email was opened
email_clickA link in a tracked email was clicked
email_replyA tracked email received a reply
form_submitA form was submitted
page_viewA tracked page was viewed
meeting_scheduledA meeting was scheduled
meeting_attendedA meeting was attended
document_viewedA document was viewed
proposal_viewedA proposal was viewed
call_completedA call was completed
task_completedA task was completed
customCustom 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

FieldTypeDescription
activityLogIdstring (UUID)Unique activity identifier
activityTypestringActivity type (see Activity Types)
moduleSlugstringSlug of the module the record belongs to
moduleNamestringDisplay name of the module
recordIdstring (UUID)Record the activity relates to
recordDisplayNamestringDisplay name of the record
actorUserIdstring or nullUser who performed the action (null for system/agent actions)
actorDisplayNamestring or nullDisplay name of the actor
metadataobjectType-specific context (e.g. changes for updates)
occurredAtstringISO 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

ParameterTypeDefaultDescription
limitinteger50Maximum entries to return (max 200)
groupWithinMinutesinteger1Group 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

FieldTypeDescription
groupIdstringUnique ID for the grouped entry
activityLogIdstringUnderlying activity ID (present for ungrouped entries such as comments)
activityTypestringThe primary activity type
actorUserIdstring or nullUser who performed the activity
actorDisplayNamestring or nullActor display name
metadataobjectMerged metadata: changes ({ fieldSlug: { from, to } }) for updates, initialFields for creation, html for comments
activityCountnumberHow many raw activities were merged into this entry
occurredAtstringISO 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.

EndpointDefault limitMax limit
/activity2050
/modules/{moduleSlug}/activity2050
/modules/{moduleSlug}/records/{recordId}/activity50200

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

StatusDescription
400Validation failed — invalid query parameter or record ID
401Missing or invalid API key
403API key lacks the workspace:read scope
429Too 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