# Clockify Reports API Guide
## Overview
The Reports API provides detailed, summary, and weekly reports for time entries with advanced filtering and aggregation capabilities.
## Authentication
- `X-Api-Key`: Personal API key
- `X-Addon-Token`: For addon authentication
## Base URLs
### Global
```
https://reports.api.clockify.me/v1
```
### Regional
```
EU (Germany): https://euc1.clockify.me/report/v1
USA: https://use2.clockify.me/report/v1
UK: https://euw2.clockify.me/report/v1
AU: https://apse2.clockify.me/report/v1
```
### Subdomain
```
https://yoursubdomainname.clockify.me/report/v1
```
---
## Endpoints
### 1. Detailed Report
**Method:** `POST`
**Path:** `/workspaces/{workspaceId}/reports/detailed`
**Purpose:** Get detailed time entry data with comprehensive filtering
#### Request Body
```json
{
"dateRangeStart": "2024-01-01T00:00:00Z",
"dateRangeEnd": "2024-01-31T23:59:59Z",
"detailedFilter": {
"page": 1,
"pageSize": 50,
"sortColumn": "DATE",
"sortOrder": "DESCENDING",
"options": {
"totals": "CALCULATE",
"description": "CONTAINING",
"includeTimeEntries": true
},
"users": {
"ids": ["5a0ab5acb07987125438b60f"],
"contains": "CONTAINS",
"status": "ACTIVE"
},
"clients": {
"ids": ["64c777ddd3fcab07cfbb210c"],
"contains": "CONTAINS",
"status": "ACTIVE"
},
"projects": {
"ids": ["5b641568b07987035750505e"],
"contains": "CONTAINS",
"status": "ACTIVE"
},
"tasks": {
"ids": ["5b715448b0798751107918ab"],
"contains": "CONTAINS",
"status": "ACTIVE"
},
"tags": {
"ids": ["64c777ddd3fcab07cfbb210c"],
"contains": "CONTAINS",
"status": "ACTIVE"
},
"billable": true,
"invoiced": false,
"description": "meeting",
"customFields": [
{
"customFieldId": "5e4117fe8c625f38930d57b7",
"value": "CF-001",
"status": "ACTIVE"
}
]
},
"amountShown": "EARNED",
"exportType": "JSON"
}
```
#### Request Fields
##### Date Range (Required)
- **dateRangeStart**: ISO-8601 datetime
- **dateRangeEnd**: ISO-8601 datetime
##### Filter Options
| Field | Type | Description |
|-------|------|-------------|
| page | integer | Page number (default: 1) |
| pageSize | integer | Items per page 1-1000 (default: 50) |
| sortColumn | string | Sort by: DATE, USER, PROJECT, CLIENT, DESCRIPTION, AMOUNT, DURATION |
| sortOrder | string | ASCENDING or DESCENDING |
| totals | string | CALCULATE (include totals) or EXCLUDE |
| description | string | CONTAINING (substring match) or EXACT |
| includeTimeEntries | boolean | Include detailed time entries |
##### Entity Filters
Each entity (users, clients, projects, tasks, tags) supports:
- **ids**: Array of IDs to include
- **contains**: CONTAINS (include) or DOES_NOT_CONTAIN (exclude)
- **status**: ACTIVE, ARCHIVED, or ALL
##### Additional Filters
- **billable**: true/false/null (all)
- **invoiced**: true/false/null (all)
- **description**: Text to search in descriptions
- **customFields**: Array of custom field filters
##### Amount Options
- **EARNED**: Billable amount (hourly rate × time)
- **COST**: Cost amount (cost rate × time)
- **HIDE_AMOUNT**: Don't show amounts
#### Response (200 OK)
```json
{
"timeentries": [
{
"_id": "5b715448b0798751107918ab",
"description": "Client meeting",
"tags": [
{
"id": "64c777ddd3fcab07cfbb210c",
"name": "Sprint1"
}
],
"user": {
"id": "5a0ab5acb07987125438b60f",
"name": "John Doe",
"email": "john@example.com"
},
"project": {
"id": "5b641568b07987035750505e",
"name": "Website Redesign",
"color": "#0B83D9",
"clientId": "64c777ddd3fcab07cfbb210c",
"clientName": "Acme Corp"
},
"task": {
"id": "5b715448b0798751107918ab",
"name": "Planning"
},
"timeInterval": {
"start": "2024-01-15T09:00:00Z",
"end": "2024-01-15T11:00:00Z",
"duration": "PT2H"
},
"billable": true,
"isLocked": false,
"hourlyRate": {
"amount": 15000,
"currency": "USD"
},
"costRate": {
"amount": 10000,
"currency": "USD"
},
"amount": 30000,
"customFieldValues": [
{
"customFieldId": "5e4117fe8c625f38930d57b7",
"name": "Project Code",
"value": "PRJ-001"
}
]
}
],
"totals": [
{
"totalTime": 720000,
"totalBillableTime": 660000,
"entriesCount": 45,
"totalAmount": 3300000
}
]
}
```
#### Response Fields
- **timeentries**: Array of time entry objects
- **totals**: Aggregated totals
- **totalTime**: Total seconds
- **totalBillableTime**: Billable seconds
- **entriesCount**: Number of entries
- **totalAmount**: Total amount in cents
---
### 2. Summary Report
**Method:** `POST`
**Path:** `/workspaces/{workspaceId}/reports/summary`
**Purpose:** Get aggregated time data grouped by specified criteria
#### Request Body
```json
{
"dateRangeStart": "2024-01-01T00:00:00Z",
"dateRangeEnd": "2024-01-31T23:59:59Z",
"summaryFilter": {
"groups": ["PROJECT", "USER", "TIMEENTRY"],
"sortColumn": "DURATION",
"sortOrder": "DESCENDING"
},
"exportType": "JSON",
"amountShown": "EARNED"
}
```
#### Group Options
Groups determine how data is aggregated:
- **PROJECT**: Group by project
- **CLIENT**: Group by client
- **USER**: Group by user
- **TASK**: Group by task
- **TAG**: Group by tags
- **TIMEENTRY**: Group by time entry
- **DATE**: Group by date
- **WEEK**: Group by week
- **MONTH**: Group by month
#### Sort Columns
- **GROUP**: Group name
- **DURATION**: Time duration
- **AMOUNT**: Money amount
#### Response (200 OK)
```json
{
"groupOne": [
{
"name": "Website Redesign",
"duration": 7200,
"amount": 108000,
"children": [
{
"name": "John Doe",
"duration": 7200,
"amount": 108000,
"children": [
{
"name": "Planning meeting",
"duration": 3600,
"amount": 54000
},
{
"name": "Design review",
"duration": 3600,
"amount": 54000
}
]
}
]
}
],
"totals": [
{
"totalTime": 7200,
"totalBillableTime": 7200,
"totalAmount": 108000,
"entriesCount": 2
}
]
}
```
#### Use Cases
- Project profitability analysis
- User productivity reports
- Client billing summaries
- Time allocation analysis
---
### 3. Weekly Report
**Method:** `POST`
**Path:** `/workspaces/{workspaceId}/reports/weekly`
**Purpose:** Get week-by-week time breakdown
#### Request Body
```json
{
"dateRangeStart": "2024-01-01T00:00:00Z",
"dateRangeEnd": "2024-01-31T23:59:59Z",
"weeklyFilter": {
"sortColumn": "GROUP",
"sortOrder": "ASCENDING",
"groupByEntities": ["USER", "PROJECT"]
},
"exportType": "JSON",
"amountShown": "EARNED"
}
```
#### Response Structure
```json
{
"weeks": [
{
"weekStart": "2024-01-01",
"weekEnd": "2024-01-07",
"groups": [
{
"name": "John Doe",
"duration": 14400,
"amount": 216000,
"children": [
{
"name": "Website Redesign",
"duration": 14400,
"amount": 216000
}
]
}
]
}
],
"totals": [
{
"totalTime": 86400,
"totalAmount": 1296000
}
]
}
```
---
### 4. Expense Report
**Method:** `POST`
**Path:** `/workspaces/{workspaceId}/reports/expenses`
**Purpose:** Get expense tracking reports (requires EXPENSES feature)
#### Request Body
```json
{
"dateRangeStart": "2024-01-01T00:00:00Z",
"dateRangeEnd": "2024-01-31T23:59:59Z",
"expenseFilter": {
"page": 1,
"pageSize": 50,
"sortColumn": "DATE",
"sortOrder": "DESCENDING",
"users": {
"ids": ["5a0ab5acb07987125438b60f"],
"contains": "CONTAINS"
},
"projects": {
"ids": ["5b641568b07987035750505e"],
"contains": "CONTAINS"
},
"invoiced": false,
"billable": true
},
"exportType": "JSON"
}
```
#### Response
```json
{
"expenses": [
{
"id": "expense123",
"date": "2024-01-15",
"amount": 12500,
"currency": "USD",
"category": "Travel",
"description": "Client site visit",
"user": {
"id": "5a0ab5acb07987125438b60f",
"name": "John Doe"
},
"project": {
"id": "5b641568b07987035750505e",
"name": "Website Redesign"
},
"billable": true,
"invoiced": false
}
],
"totals": {
"totalAmount": 12500,
"expenseCount": 1
}
}
```
---
## Implementation Examples
### Python: Detailed Report
```python
import requests
from datetime import datetime, timedelta
API_KEY = "your_api_key_here"
# Use appropriate regional base URL
BASE_URL = "https://reports.api.clockify.me/v1"
headers = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}
def get_detailed_report(workspace_id, start_date, end_date, user_ids=None, project_ids=None):
"""Get detailed time entry report"""
url = f"{BASE_URL}/workspaces/{workspace_id}/reports/detailed"
payload = {
"dateRangeStart": start_date.isoformat() + "Z",
"dateRangeEnd": end_date.isoformat() + "Z",
"detailedFilter": {
"page": 1,
"pageSize": 1000,
"sortColumn": "DATE",
"sortOrder": "DESCENDING",
"options": {
"totals": "CALCULATE",
"includeTimeEntries": True
}
},
"amountShown": "EARNED",
"exportType": "JSON"
}
# Add filters
if user_ids:
payload["detailedFilter"]["users"] = {
"ids": user_ids,
"contains": "CONTAINS",
"status": "ACTIVE"
}
if project_ids:
payload["detailedFilter"]["projects"] = {
"ids": project_ids,
"contains": "CONTAINS",
"status": "ACTIVE"
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Report failed: {response.text}")
# Get report for last month
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=30)
report = get_detailed_report("workspace_id", start_date, end_date)
# Process results
entries = report['timeentries']
totals = report['totals'][0]
print(f"Total entries: {totals['entriesCount']}")
print(f"Total hours: {totals['totalTime'] / 3600:.2f}")
print(f"Billable hours: {totals['totalBillableTime'] / 3600:.2f}")
print(f"Total amount: ${totals['totalAmount'] / 100:.2f}")
```
### Python: Summary Report by Project
```python
def get_project_summary(workspace_id, start_date, end_date):
"""Get time summary grouped by project"""
url = f"{BASE_URL}/workspaces/{workspace_id}/reports/summary"
payload = {
"dateRangeStart": start_date.isoformat() + "Z",
"dateRangeEnd": end_date.isoformat() + "Z",
"summaryFilter": {
"groups": ["PROJECT", "USER"],
"sortColumn": "DURATION",
"sortOrder": "DESCENDING"
},
"amountShown": "EARNED",
"exportType": "JSON"
}
response = requests.post(url, headers=headers, json=payload)
return response.json()
def parse_summary_report(report):
"""Parse summary report into readable format"""
projects = []
for project_group in report.get('groupOne', []):
project_data = {
"name": project_group['name'],
"hours": project_group['duration'] / 3600,
"amount": project_group['amount'] / 100,
"users": []
}
for user_group in project_group.get('children', []):
project_data["users"].append({
"name": user_group['name'],
"hours": user_group['duration'] / 3600,
"amount": user_group['amount'] / 100
})
projects.append(project_data)
return projects
# Get summary
summary = get_project_summary("workspace_id", start_date, end_date)
projects = parse_summary_report(summary)
for project in projects:
print(f"\n{project['name']}: {project['hours']:.1f}h (${project['amount']:.2f})")
for user in project['users']:
print(f" {user['name']}: {user['hours']:.1f}h (${user['amount']:.2f})")
```
### Python: Weekly Report
```python
def get_weekly_breakdown(workspace_id, start_date, end_date, user_id):
"""Get week-by-week breakdown for a user"""
url = f"{BASE_URL}/workspaces/{workspace_id}/reports/weekly"
payload = {
"dateRangeStart": start_date.isoformat() + "Z",
"dateRangeEnd": end_date.isoformat() + "Z",
"weeklyFilter": {
"sortColumn": "GROUP",
"sortOrder": "ASCENDING",
"groupByEntities": ["USER", "PROJECT"],
"users": {
"ids": [user_id],
"contains": "CONTAINS"
}
},
"amountShown": "EARNED",
"exportType": "JSON"
}
response = requests.post(url, headers=headers, json=payload)
return response.json()
def format_weekly_report(report):
"""Format weekly report for display"""
weeks = []
for week in report.get('weeks', []):
week_data = {
"start": week['weekStart'],
"end": week['weekEnd'],
"total_hours": 0,
"projects": []
}
for user_group in week.get('groups', []):
for project in user_group.get('children', []):
hours = project['duration'] / 3600
week_data["total_hours"] += hours
week_data["projects"].append({
"name": project['name'],
"hours": hours,
"amount": project['amount'] / 100
})
weeks.append(week_data)
return weeks
# Get weekly breakdown
weekly = get_weekly_breakdown("workspace_id", start_date, end_date, "user_id")
weeks = format_weekly_report(weekly)
for week in weeks:
print(f"\nWeek {week['start']} - {week['end']}: {week['total_hours']:.1f}h")
for project in week['projects']:
print(f" {project['name']}: {project['hours']:.1f}h (${project['amount']:.2f})")
```
### Python: Paginated Detailed Report
```python
def get_all_time_entries(workspace_id, start_date, end_date, page_size=1000):
"""Get all time entries across multiple pages"""
all_entries = []
page = 1
while True:
url = f"{BASE_URL}/workspaces/{workspace_id}/reports/detailed"
payload = {
"dateRangeStart": start_date.isoformat() + "Z",
"dateRangeEnd": end_date.isoformat() + "Z",
"detailedFilter": {
"page": page,
"pageSize": page_size,
"options": {
"totals": "EXCLUDE",
"includeTimeEntries": True
}
},
"exportType": "JSON"
}
response = requests.post(url, headers=headers, json=payload)
data = response.json()
entries = data.get('timeentries', [])
if not entries:
break
all_entries.extend(entries)
# Check if we got less than page_size (last page)
if len(entries) < page_size:
break
page += 1
return all_entries
# Get all entries
entries = get_all_time_entries("workspace_id", start_date, end_date)
print(f"Retrieved {len(entries)} time entries")
```
### Python: Custom Field Filtering
```python
def get_entries_by_custom_field(workspace_id, start_date, end_date, field_id, field_value):
"""Get time entries filtered by custom field value"""
url = f"{BASE_URL}/workspaces/{workspace_id}/reports/detailed"
payload = {
"dateRangeStart": start_date.isoformat() + "Z",
"dateRangeEnd": end_date.isoformat() + "Z",
"detailedFilter": {
"page": 1,
"pageSize": 1000,
"customFields": [
{
"customFieldId": field_id,
"value": field_value,
"status": "ACTIVE"
}
],
"options": {
"totals": "CALCULATE",
"includeTimeEntries": True
}
},
"exportType": "JSON"
}
response = requests.post(url, headers=headers, json=payload)
return response.json()
# Get entries for specific project code
report = get_entries_by_custom_field(
"workspace_id",
start_date,
end_date,
"custom_field_id",
"PRJ-001"
)
```
---
## Best Practices
1. **Regional URLs**: Use correct regional base URL for your workspace
2. **Date Ranges**: Keep date ranges reasonable (max 1 year recommended)
3. **Pagination**: Use pagination for large datasets (page_size max 1000)
4. **Filtering**: Filter server-side rather than client-side for performance
5. **Caching**: Cache report results when appropriate
6. **Time Zones**: All dates are in UTC (append 'Z')
7. **Amount Calculations**: Amounts are in smallest currency unit (cents)
8. **Export Types**: Use JSON for programmatic processing
---
## Common Use Cases
### Monthly Invoice Report
```python
def generate_invoice_data(workspace_id, client_id, month_start, month_end):
"""Generate invoice-ready data for a client"""
url = f"{BASE_URL}/workspaces/{workspace_id}/reports/summary"
payload = {
"dateRangeStart": month_start.isoformat() + "Z",
"dateRangeEnd": month_end.isoformat() + "Z",
"summaryFilter": {
"groups": ["PROJECT", "USER"],
"clients": {
"ids": [client_id],
"contains": "CONTAINS"
},
"billable": True,
"invoiced": False
},
"amountShown": "EARNED",
"exportType": "JSON"
}
response = requests.post(url, headers=headers, json=payload)
return response.json()
```
### Team Utilization Report
```python
def team_utilization(workspace_id, start_date, end_date, expected_hours_per_week=40):
"""Calculate team utilization rates"""
report = get_project_summary(workspace_id, start_date, end_date)
# Calculate weeks in range
weeks = (end_date - start_date).days / 7
utilization = []
for project in report.get('groupOne', []):
for user in project.get('children', []):
hours = user['duration'] / 3600
expected = expected_hours_per_week * weeks
util_pct = (hours / expected * 100) if expected > 0 else 0
utilization.append({
"user": user['name'],
"hours": hours,
"expected": expected,
"utilization": util_pct
})
return utilization
```
---
## Rate Limiting
- Addon token: 50 requests/second per workspace
- Large reports may take longer to process
---
## Notes
- Reports API uses different base URL than main API
- Regional/subdomain workspaces require specific URL formats
- Totals are optional (can be excluded for performance)
- Custom fields must be enabled in workspace
- Export types: JSON, CSV, XLSX, PDF (JSON recommended for API use)
- Amounts always in cents (divide by 100 for dollars)
- Durations in seconds (divide by 3600 for hours)