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 Logs | New Editor Logs |
|---|
| Flat log structure | Structured, event-based logs |
| Limited filtering | Advanced filtering and querying |
| Loosely tied to editor actions | Directly reflects editor interactions |
| Less scalable | Designed for high-volume workflows |
| Deprecated endpoint | Actively 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
| Deprecated | New |
|---|
action | event_type |
user | actor.email |
timestamp | created_at |
details | payload |
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
- Event Granularity is Higher
You may see more logs than before because actions are more finely tracked.
Plan for:
Increased volume
More precise filtering
- Payload Structure Varies by Event
Different event_types have different payload schemas.
Recommendation:
if log["event_type"] == "LABEL_CREATED":
handle_label_created(log["payload"])
- Pagination Matters
The new logs are designed for scale.
Always handle pagination:
logs = project.get_editor_logs(limit=100, cursor=cursor)
- 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"])