Learn how to launch new process instances via API.
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
Retrieve details about a specific process including its current state.
Get a list of all processes in your organization with filtering options.
Set up OAuth authentication to access the Tallyfy API.
About Tallyfy
- 2025 Tallyfy, Inc.
- Privacy Policy
- Terms of Use
- Report Issue
- Trademarks