Get activity feed for a process
The activity feed API provides a comprehensive audit trail of all actions taken on a process, including task completions, comments, status changes, assignments, and system events. This endpoint is essential for compliance, auditing, and building custom activity dashboards.
GET /organizations/{org}/activity-feeds
Authorization: Bearer {your_access_token}
- RequiredAccept: application/json
X-Tallyfy-Client: APIClient
Parameter | Type | Required | Description | Example |
---|---|---|---|---|
entity_type | string | Yes* | Type of entity to filter | run for processes |
entity_id | string | Yes* | ID of the specific process | abc123 |
type | string | No | Filter by activity type | Task , Comment , Process |
verb | string | No | Filter by action performed | completed , commented , updated |
actor_type | string | No | Filter by who performed action | user , guest |
actor_id | integer | No | ID of specific user/guest | 12345 |
source | string | No | Source of activity | Member , Guest , Webhook |
page | integer | No | Page number for pagination | Default: 1 |
per_page | integer | No | Items per page (max 100) | Default: 20 |
sort | string | No | Sort order | -created_at (newest first) |
*Required when fetching activities for a specific process
created
- Entity was createdupdated
- Entity was modifiedcompleted
- Task or step was completedcommented
- Comment was addedassigned
- Task was assignedunassigned
- Assignment was removedarchived
- Entity was archivedactivated
- Entity was activateddeleted
- Entity was deletedreopened
- Task was reopenedstarted
- Process or task was started
// Get all activities for a specific processconst organizationId = 'your_org_id';const processId = 'your_process_id';const accessToken = 'your_access_token';
async function getProcessActivityFeed() { const params = new URLSearchParams({ entity_type: 'run', entity_id: processId, per_page: '50', sort: '-created_at' });
const response = await fetch( `https://api.tallyfy.com/organizations/${organizationId}/activity-feeds?${params}`, { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json', 'X-Tallyfy-Client': 'APIClient' } } );
if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }
const data = await response.json(); return data;}
// Filter for specific activity typesasync function getTaskCompletions(processId) { const params = new URLSearchParams({ entity_type: 'run', entity_id: processId, type: 'Task', verb: 'completed', per_page: '100' });
const response = await fetch( `https://api.tallyfy.com/organizations/${organizationId}/activity-feeds?${params}`, { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json' } } );
return response.json();}
import requestsfrom typing import Optional, Dict, Any
class TallyfyActivityFeed: def __init__(self, org_id: str, access_token: str): self.org_id = org_id self.access_token = access_token self.base_url = "https://api.tallyfy.com" self.headers = { "Authorization": f"Bearer {access_token}", "Accept": "application/json", "X-Tallyfy-Client": "APIClient" }
def get_process_activities( self, process_id: str, verb: Optional[str] = None, activity_type: Optional[str] = None, page: int = 1, per_page: int = 50 ) -> Dict[str, Any]: """Get activity feed for a specific process"""
params = { "entity_type": "run", "entity_id": process_id, "page": page, "per_page": per_page, "sort": "-created_at" }
# Add optional filters if verb: params["verb"] = verb if activity_type: params["type"] = activity_type
response = requests.get( f"{self.base_url}/organizations/{self.org_id}/activity-feeds", headers=self.headers, params=params )
response.raise_for_status() return response.json()
def get_all_activities(self, process_id: str) -> list: """Get all activities with pagination handling""" all_activities = [] page = 1
while True: data = self.get_process_activities( process_id=process_id, page=page, per_page=100 )
activities = data.get("data", []) all_activities.extend(activities)
# Check if there are more pages if not activities or len(activities) < 100: break
page += 1
return all_activities
def export_to_csv(self, process_id: str, filename: str): """Export activity feed to CSV""" import csv from datetime import datetime
activities = self.get_all_activities(process_id)
with open(filename, 'w', newline='', encoding='utf-8') as csvfile: fieldnames = [ 'timestamp', 'actor', 'action', 'type', 'description', 'old_value', 'new_value' ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader()
for activity in activities: writer.writerow({ 'timestamp': activity.get('created_at'), 'actor': activity.get('actor', {}).get('name', 'System'), 'action': activity.get('verb'), 'type': activity.get('type'), 'description': activity.get('description', ''), 'old_value': activity.get('old_value', ''), 'new_value': activity.get('audit', {}).get( activity.get('field', ''), '' ) })
print(f"Exported {len(activities)} activities to {filename}")
# Usage exampleclient = TallyfyActivityFeed('your_org_id', 'your_access_token')
# Get all activitiesactivities = client.get_process_activities('process_id_123')
# Get only task completionscompletions = client.get_process_activities( 'process_id_123', verb='completed', activity_type='Task')
# Export to CSVclient.export_to_csv('process_id_123', 'process_audit_log.csv')
import java.io.IOException;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.HashMap;import java.util.Map;import com.fasterxml.jackson.databind.ObjectMapper;
public class TallyfyActivityFeed { private final String organizationId; private final String accessToken; private final HttpClient httpClient; private final ObjectMapper objectMapper; private static final String BASE_URL = "https://api.tallyfy.com";
public TallyfyActivityFeed(String organizationId, String accessToken) { this.organizationId = organizationId; this.accessToken = accessToken; this.httpClient = HttpClient.newHttpClient(); this.objectMapper = new ObjectMapper(); }
public Map<String, Object> getProcessActivities( String processId, String verb, String type, int page, int perPage ) throws IOException, InterruptedException {
// Build query parameters StringBuilder queryParams = new StringBuilder(); queryParams.append("?entity_type=run"); queryParams.append("&entity_id=").append(processId); queryParams.append("&page=").append(page); queryParams.append("&per_page=").append(perPage); queryParams.append("&sort=-created_at");
if (verb != null && !verb.isEmpty()) { queryParams.append("&verb=").append(verb); } if (type != null && !type.isEmpty()) { queryParams.append("&type=").append(type); }
// Build request HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/organizations/" + organizationId + "/activity-feeds" + queryParams)) .header("Authorization", "Bearer " + accessToken) .header("Accept", "application/json") .header("X-Tallyfy-Client", "APIClient") .GET() .build();
// Send request HttpResponse<String> response = httpClient.send( request, HttpResponse.BodyHandlers.ofString() );
if (response.statusCode() != 200) { throw new RuntimeException( "Failed to get activities: " + response.statusCode() ); }
// Parse response return objectMapper.readValue( response.body(), Map.class ); }
// Get all activities for a process public void getAllProcessActivities(String processId) { try { Map<String, Object> result = getProcessActivities( processId, null, // No verb filter null, // No type filter 1, // Page 1 50 // 50 items per page );
System.out.println("Total activities: " + result.get("total"));
// Process activities var activities = (java.util.List<?>) result.get("data"); for (Object activity : activities) { var activityMap = (Map<String, Object>) activity; System.out.println( activityMap.get("created_at") + " - " + activityMap.get("verb") + " - " + activityMap.get("description") ); }
} catch (Exception e) { e.printStackTrace(); } }
// Get only comments for a process public void getProcessComments(String processId) { try { Map<String, Object> result = getProcessActivities( processId, "commented", // Only comments null, 1, 100 );
var comments = (java.util.List<?>) result.get("data"); System.out.println("Found " + comments.size() + " comments");
} catch (Exception e) { e.printStackTrace(); } }
public static void main(String[] args) { TallyfyActivityFeed client = new TallyfyActivityFeed( "your_org_id", "your_access_token" );
// Get all activities client.getAllProcessActivities("process_id_123");
// Get only comments client.getProcessComments("process_id_123"); }}
package main
import ( "encoding/json" "fmt" "io" "net/http" "net/url" "strconv")
type TallyfyClient struct { OrgID string AccessToken string BaseURL string}
type ActivityFeedResponse struct { Data []Activity `json:"data"` Total int `json:"total"` Page int `json:"page"`}
type Activity struct { ID string `json:"id"` OrgID string `json:"organization_id"` Verb string `json:"verb"` Type string `json:"type"` Description string `json:"description"` CreatedAt string `json:"created_at"` Actor map[string]interface{} `json:"actor"` Audit map[string]interface{} `json:"audit"` Field string `json:"field"` OldValue interface{} `json:"old_value"` Text string `json:"text"`}
func NewTallyfyClient(orgID, accessToken string) *TallyfyClient { return &TallyfyClient{ OrgID: orgID, AccessToken: accessToken, BaseURL: "https://api.tallyfy.com", }}
func (c *TallyfyClient) GetProcessActivities( processID string, filters map[string]string,) (*ActivityFeedResponse, error) {
// Build URL with query parameters u, _ := url.Parse(fmt.Sprintf( "%s/organizations/%s/activity-feeds", c.BaseURL, c.OrgID, ))
q := u.Query() q.Set("entity_type", "run") q.Set("entity_id", processID) q.Set("sort", "-created_at")
// Add optional filters for key, value := range filters { if value != "" { q.Set(key, value) } }
u.RawQuery = q.Encode()
// Create request req, err := http.NewRequest("GET", u.String(), nil) if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+c.AccessToken) req.Header.Set("Accept", "application/json") req.Header.Set("X-Tallyfy-Client", "APIClient")
// Send request client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf( "API error %d: %s", resp.StatusCode, string(body), ) }
// Parse response var result ActivityFeedResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }
return &result, nil}
func (c *TallyfyClient) GetAllActivities(processID string) ([]Activity, error) { var allActivities []Activity page := 1
for { filters := map[string]string{ "page": strconv.Itoa(page), "per_page": "100", }
response, err := c.GetProcessActivities(processID, filters) if err != nil { return nil, err }
allActivities = append(allActivities, response.Data...)
// Check if more pages exist if len(response.Data) < 100 { break }
page++ }
return allActivities, nil}
func (c *TallyfyClient) GetTaskCompletions(processID string) ([]Activity, error) { filters := map[string]string{ "verb": "completed", "type": "Task", "per_page": "100", }
response, err := c.GetProcessActivities(processID, filters) if err != nil { return nil, err }
return response.Data, nil}
func main() { client := NewTallyfyClient("your_org_id", "your_access_token")
// Get all activities activities, err := client.GetAllActivities("process_id_123") if err != nil { panic(err) }
fmt.Printf("Found %d total activities\n", len(activities))
// Get only task completions completions, err := client.GetTaskCompletions("process_id_123") if err != nil { panic(err) }
fmt.Printf("Found %d task completions\n", len(completions))
// Print recent activities for _, activity := range activities[:10] { fmt.Printf("%s: %s - %s\n", activity.CreatedAt, activity.Verb, activity.Description, ) }}
{ "data": [ { "id": "feed_abc123", "organization_id": "org_xyz", "verb": "completed", "source": "Member", "actor": { "id": 12345, "name": "John Smith", "email": "john@example.com", "type": "user" }, "auditable_type": "task", "auditable_id": "task_456", "parentable_type": "run", "parentable_id": "run_789", "type": "Task", "description": "Completed task: Review document", "field": null, "old_value": null, "created_at": "2024-01-18T10:30:00Z", "text": null, "references": [], "audit": { "id": "task_456", "name": "Review document", "status": "completed", "completed_at": "2024-01-18T10:30:00Z", "completed_by": 12345 }, "parent": { "id": "run_789", "name": "Employee Onboarding - Jane Doe", "status": "active" } }, { "id": "feed_def456", "organization_id": "org_xyz", "verb": "commented", "source": "Guest", "actor": { "id": 67890, "name": "External Reviewer", "email": "reviewer@external.com", "type": "guest" }, "auditable_type": "task", "auditable_id": "task_456", "parentable_type": "run", "parentable_id": "run_789", "type": "Comment", "description": "Added comment on task", "field": null, "old_value": null, "created_at": "2024-01-18T09:15:00Z", "text": "Document looks good, approved for next step", "references": [], "audit": null, "parent": { "id": "run_789", "name": "Employee Onboarding - Jane Doe", "status": "active" } }, { "id": "feed_ghi789", "organization_id": "org_xyz", "verb": "updated", "source": "Member", "actor": { "id": 12345, "name": "John Smith", "email": "john@example.com", "type": "user" }, "auditable_type": "task", "auditable_id": "task_123", "parentable_type": "run", "parentable_id": "run_789", "type": "Task", "description": "Updated deadline", "field": "deadline", "old_value": "2024-01-20T00:00:00Z", "created_at": "2024-01-18T08:00:00Z", "text": null, "references": [], "audit": { "id": "task_123", "name": "Prepare workspace", "deadline": "2024-01-25T00:00:00Z" }, "parent": { "id": "run_789", "name": "Employee Onboarding - Jane Doe", "status": "active" } } ], "total": 145, "per_page": 20, "current_page": 1, "last_page": 8, "from": 1, "to": 20}
- data: Array of activity objects
- total: Total number of activities matching the filters
- per_page: Number of items per page
- current_page: Current page number
- last_page: Total number of pages
- from: Starting item number
- to: Ending item number
- id: Unique activity feed ID
- organization_id: Organization ID
- verb: Action performed (completed, updated, commented, etc.)
- source: Who triggered the activity (Member, Guest, Webhook, System)
- actor: Object containing details about who performed the action
- auditable_type: Type of entity that was acted upon (task, step, run, etc.)
- auditable_id: ID of the entity that was acted upon
- parentable_type: Type of parent entity
- parentable_id: ID of parent entity
- type: Category of activity (Task, Comment, Process, etc.)
- description: Human-readable description of the activity
- field: For updates, which field was changed
- old_value: For updates, the previous value
- created_at: Timestamp when the activity occurred
- text: Additional text/content (e.g., comment text)
- references: Array of related references
- audit: Current state of the audited object
- parent: Information about the parent object
Fetch all activities for a process and export to your preferred format:
// Fetch all pages of activitiesasync function exportAuditLog(processId) { let allActivities = []; let page = 1; let hasMore = true;
while (hasMore) { const params = new URLSearchParams({ entity_type: 'run', entity_id: processId, page: page.toString(), per_page: '100', sort: '-created_at' });
const response = await fetch( `https://api.tallyfy.com/organizations/${orgId}/activity-feeds?${params}`, { headers: { 'Authorization': `Bearer ${token}` } } );
const data = await response.json(); allActivities = allActivities.concat(data.data);
hasMore = data.current_page < data.last_page; page++; }
return allActivities;}
Poll for new activities since last check:
let lastCheckTime = new Date().toISOString();
async function getRecentActivities(processId) { const params = new URLSearchParams({ entity_type: 'run', entity_id: processId, per_page: '20', sort: '-created_at' });
const response = await fetch( `https://api.tallyfy.com/organizations/${orgId}/activity-feeds?${params}`, { headers: { 'Authorization': `Bearer ${token}` } } );
const data = await response.json();
// Filter activities newer than last check const newActivities = data.data.filter( activity => new Date(activity.created_at) > new Date(lastCheckTime) );
if (newActivities.length > 0) { lastCheckTime = newActivities[0].created_at; }
return newActivities;}
Get all activities by a specific user:
async function getUserActivities(userId, startDate, endDate) { const params = new URLSearchParams({ actor_type: 'user', actor_id: userId.toString(), per_page: '100', sort: '-created_at' });
const response = await fetch( `https://api.tallyfy.com/organizations/${orgId}/activity-feeds?${params}`, { headers: { 'Authorization': `Bearer ${token}` } } );
return response.json();}
Generate compliance reports showing all changes to sensitive processes:
async function getComplianceData(processId) { // Get all updates and completions const params = new URLSearchParams({ entity_type: 'run', entity_id: processId, per_page: '100' });
const response = await fetch( `https://api.tallyfy.com/organizations/${orgId}/activity-feeds?${params}`, { headers: { 'Authorization': `Bearer ${token}` } } );
const data = await response.json();
// Filter for compliance-relevant activities return data.data.filter(activity => ['completed', 'updated', 'assigned', 'unassigned'].includes(activity.verb) || activity.type === 'Comment' );}
{ "error": "Unauthorized", "message": "Invalid or expired access token"}
{ "error": "Forbidden", "message": "You don't have permission to view this process's activities"}
{ "error": "Not Found", "message": "Process not found or has been deleted"}
{ "error": "Too Many Requests", "message": "Rate limit exceeded. Please wait before making more requests"}
To access activity feeds, the authenticated user must have one of the following:
- Be an administrator in the organization
- Have
PROCESS_READ
permission for the specific process - Be the creator of the process
- Be assigned to at least one task in the process
- Be a support user with appropriate access
- Default rate limit: 1000 requests per hour per organization
- Burst limit: 100 requests per minute
- Use pagination and caching to minimize API calls
The GET endpoint retrieves process instances within organizations supporting extensive filtering options through query parameters and providing paginated results with detailed run information.
The GET endpoint allows retrieval of task lists associated with a specific process run through authorization headers and optional query parameters for filtering sorting and pagination functionality.
A GET endpoint retrieves detailed information about a specific process run using organization and run IDs with optional parameters to include related data such as checklists tasks tags and form field values.
Tracker View > Check process activity
Tallyfy processes maintain detailed activity logs that record all actions including task completions deadline changes comments and archiving which can be viewed by administrators and authorized members through the Settings panel’s Activity tab.
About Tallyfy
- 2025 Tallyfy, Inc.
- Privacy Policy
- Terms of Use
- Report Issue
- Trademarks