Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encord.com/llms.txt

Use this file to discover all available pages before exploring further.

Editor logs serve as a comprehensive record of actions performed within the Encord platform’s Label Editor.

View Editor Logs

The following method is available on the Project class to retrieve Editor Logs. start_time and end_time are required parameters, while the rest are optional filters. https://docs.encord.com/sdk-documentation/projects-sdk/sdk-activity-logs#object-actions
def get_editor_logs(
        start_time: datetime.datetime,
        end_time: datetime.datetime,
        action: Optional[str] = None,
        actor_user_email: Optional[str] = None,
        workflow_stage_id: Optional[UUID] = None,
        data_unit_id: Optional[UUID] = None) -> Iterator[EditorLog]
Use the following script to view Editor Logs for a specific project and data unit.
import datetime
from encord import EncordUserClient

SSH_PATH = "<your_private_key>"
PROJECT_ID = "<project_id>"
DATA_UNIT_ID = "<data_unit_id>"

# Authenticate
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
    ssh_private_key_path=SSH_PATH,
    domain="https://api.encord.com",
)

# Get the project
project = user_client.get_project(PROJECT_ID)

# Define time range for logs
start_time = datetime.datetime.now() - datetime.timedelta(days=29)
end_time = datetime.datetime.now()

# Logs can be filtered by "action", "actor_user_email", "workflow_stage_id", "data_unit_id"
logs = project.get_editor_logs(
    start_time=start_time,
    end_time=end_time,
)

# Specific logs can be retrieved by passing additional filters
for log in logs:
    print(log.branch_name)

View Task Actions

Task actions can be retrieved using the following method on the Project class. after is a required parameter, while the rest are optional filters.
See the full list of task actions here.
def get_task_actions(
    *,
    after: datetime.datetime,
    before: Optional[datetime.datetime] = None,
    actor_email: Union[str, List[str], None] = None,
    action_type: Union[TaskActionType, List[TaskActionType], None] = None,
    workflow_stage_uuid: Union[UUID, List[UUID], str, List[str], None] = None,
    data_unit_uuid: Union[UUID, List[UUID], str, List[str], None] = None
) -> Iterable[TaskAction]
The following script retrieves task actions for a specific Project and data unit, filtering for actions that occurred in the last 7 days.
import datetime
from encord import EncordUserClient

SSH_PATH = "<your_private_key>"
PROJECT_ID = "<project_id>"
DATA_UNIT_ID = "<data_unit_id>"

# Authenticate
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
    ssh_private_key_path=SSH_PATH,
    domain="https://api.encord.com",
)

# Get the project
project = user_client.get_project(PROJECT_ID)

# Get task action type and the user that made the action
seven_days_ago = datetime.now() - timedelta(days=7)
for action in project.get_task_actions(after=seven_days_ago):
    print(f"{action.action_type} by {action.actor_email}")

Use Cases

Get History of a Label

import datetime
from encord import EncordUserClient

SSH_PATH = "<your_private_key>"
PROJECT_ID = "<project_id>"
DATA_UNIT_ID = "<data_unit_id>"

# Authenticate
user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
    ssh_private_key_path=SSH_PATH,
    domain="https://api.encord.com",
)

# Get the project
project = user_client.get_project(PROJECT_ID)

# Define time range for logs
start_time = datetime.datetime.now() - datetime.timedelta(days=29)
end_time = datetime.datetime.now()

# Logs can be filtered by "action", "actor_user_email", "workflow_stage_id", "data_unit_id"
logs = project.get_editor_logs(
    start_time=start_time,
    end_time=end_time,
)

# Filter log by the name of the label
log_in_branch_a = [
    log for log in logs
    if log.labelName == "Porpoise"
]

Approval and Rejection Rates

The following script prints the approval and rejection rates for users in a Project.
import datetime
from collections import defaultdict
from encord import EncordUserClient
from encord.analytics import TaskActionType

SSH_PATH = "<your_private_key>"
PROJECT_ID = "<project_id>"

user_client: EncordUserClient = EncordUserClient.create_with_ssh_private_key(
    ssh_private_key_path=SSH_PATH,
    domain="https://api.encord.com",
)

# Get the project
project = user_client.get_project(PROJECT_ID)

# Define time range
start_time = datetime.datetime.now() - datetime.timedelta(days=29)

# Fetch only approve and reject actions
actions = list(project.get_task_actions(
    after=start_time,
    action_type=[TaskActionType.APPROVE, TaskActionType.REJECT],
))

# --- Overall rates ---
total = len(actions)
approvals = [a for a in actions if a.action_type == TaskActionType.APPROVE]
rejections = [a for a in actions if a.action_type == TaskActionType.REJECT]

print("=== Overall Review Rates ===")
if not total:
    print("No actions found.")
else:
    print(f"Total review actions : {total}")
    print(f"Approvals            : {len(approvals)} ({len(approvals) / total * 100:.1f}%)")
    print(f"Rejections           : {len(rejections)} ({len(rejections) / total * 100:.1f}%)")

# --- Per-reviewer breakdown ---
reviewer_stats = defaultdict(lambda: {"approved": 0, "rejected": 0})

for action in actions:
    email = action.actor_email
    if action.action_type == TaskActionType.APPROVE:
        reviewer_stats[email]["approved"] += 1
    elif action.action_type == TaskActionType.REJECT:
        reviewer_stats[email]["rejected"] += 1

print("\n=== Per-Reviewer Breakdown ===")
print(f"{'Reviewer':<35} {'Approved':>10} {'Rejected':>10} {'Approval Rate':>15}")
print("-" * 72)
for email, stats in sorted(reviewer_stats.items()):
    total_by_reviewer = stats["approved"] + stats["rejected"]
    rate = stats["approved"] / total_by_reviewer * 100 if total_by_reviewer else 0
    print(f"{email:<35} {stats['approved']:>10} {stats['rejected']:>10} {rate:>14.1f}%")

Migrating from Activity Logs to New Editor Logs

Overview

Encord introduced a Editor Logs to replace the deprecated Activity Logs. The new logs are more structured, scalable, and aligned with how annotation actions are actually performed in the editor. Migrating to Editor Logs is not just a rename. Using Editor logs is a shift to a more structured, scalable logging system that better reflects real annotation workflows. After migration you will benefit from:
  • Faster queries
  • Cleaner filtering
  • More reliable audit trails
If you’re currently using the deprecated logs, this content will help you transition smoothly. Key Differences
Deprecated Activity LogsNew Editor Logs
Flat log structureStructured, event-based logs
Limited filteringAdvanced filtering and querying
Loosely tied to editor actionsDirectly reflects editor interactions
Less scalableDesigned for high-volume workflows
Deprecated endpointActively maintained API
Conceptual Shift The biggest change is how logs are modeled: Before: Logs were general activity records (who did what, loosely defined) Now: Logs are editor events, tightly scoped to annotation actions Think of the new logs as a timeline of precise annotation events, not just audit entries.

SDK Migration

1. Replace Deprecated Log Calls Deprecated:
project.get_activity_logs()
Editor Logs:
project.get_editor_logs()
2. Update Filtering Logic Deprecated logs often required manual filtering after retrieval. Deprecated:
logs = project.get_activity_logs()

filtered = [
    log for log in logs
    if log["action"] == "LABEL_CREATED"
]
Editor Logs: Benefit: Reduced payload size and faster queries
logs = project.get_editor_logs(
    event_types=["LABEL_CREATED"]
)
3. Adjust to New Schema The structure of each log entry has changed. Deprecated:
{
  "user": "user@email.com",
  "action": "LABEL_CREATED",
  "timestamp": "...",
  "details": {...}
}
Editor Logs:
{
  "event_type": "LABEL_CREATED",
  "actor": {
    "email": "user@email.com"
  },
  "created_at": "...",
  "payload": {...}
}
Key Field Changes
DeprecatedNew
actionevent_type
useractor.email
timestampcreated_at
detailspayload

Common Migration Patterns

Pattern 1: Tracking Label Creation

Before:
logs = project.get_activity_logs()

label_creations = [
    log for log in logs
    if log["action"] == "LABEL_CREATED"
]
After:
label_creations = project.get_editor_logs(
    event_types=["LABEL_CREATED"]
)

Pattern 2: Filtering by User

Before:
[user_log for user_log in logs if user_log["user"] == email]
After:
project.get_editor_logs(
    actor_emails=[email]
)

Pattern 3: Time-Based Queries

Before:
[log for log in logs if log["timestamp"] > start_time]
After:
project.get_editor_logs(
    created_after=start_time
)

Migration Checklist

  • Replace all get_activity_logs() calls
  • Update filtering to use server-side parameters
  • Refactor log parsing to new schema (event_type, actor, payload)
  • Validate downstream systems (analytics, dashboards, exports)
  • Remove deprecated logic and fallbacks

Tips and Tricks

  1. Event Granularity is Higher
You may see more logs than before because actions are more finely tracked. Plan for: Increased volume More precise filtering
  1. Payload Structure Varies by Event
Different event_types have different payload schemas. Recommendation:
if log["event_type"] == "LABEL_CREATED":
    handle_label_created(log["payload"])
  1. Pagination Matters
The new logs are designed for scale. Always handle pagination:
logs = project.get_editor_logs(limit=100, cursor=cursor)
  1. Backward Compatibility
Deprecated logs will be removed. Do not rely on:
  • Old endpoints
  • Old schema fields

Example Migration

Before:
logs = project.get_activity_logs()

for log in logs:
    if log["action"] == "LABEL_CREATED":
        print(log["user"], log["details"])
After:
logs = project.get_editor_logs(event_types=["LABEL_CREATED"])

for log in logs:
    print(log["actor"]["email"], log["payload"])