-
Notifications
You must be signed in to change notification settings - Fork 0
Public API
The Effort Tracker Public API provides read-only programmatic access to your organization's effort tracking data. Use it to build integrations, generate custom reports, or sync data with external systems.
https://efforttrackerapi.sinaware.com/api/v1/
Explore and test the API interactively using the built-in Swagger UI:
The OpenAPI specification is available at:
https://efforttrackerapi.sinaware.com/api/openapi/v3.json
- Open the Swagger UI link above
- Click Authorize and enter your Azure DevOps PAT in the bearer token field
- For each endpoint, fill in the X-Organization header parameter with your Azure DevOps organization name (e.g.,
myorganization) - Fill in any other required parameters and click Execute
Note: The
X-Organizationheader is required for all endpoints. Swagger UI will show it as a required parameter field on each endpoint.
All requests require an Azure DevOps Personal Access Token (PAT) and organization name.
| Header | Description | Example |
|---|---|---|
Authorization |
Bearer token with your Azure DevOps PAT | Bearer your-pat-token |
X-Organization |
Your Azure DevOps organization name | myorganization |
- Go to
https://dev.azure.com/{your-org}/_usersSettings/tokens - Click New Token
- Select scopes: Work Items (Read) minimum
- Copy the generated token
curl -H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: your-org-name" \
https://efforttrackerapi.sinaware.com/api/v1/stats/summaryThe API enforces rate limits to ensure fair usage:
| Limit | Value |
|---|---|
| Per-token per minute | 30 requests |
| Per-token per hour | 250 requests |
| Per-organization per minute | 100 requests |
| Export endpoint per hour | 10 requests |
Every response includes rate limit information:
| Header | Description |
|---|---|
X-RateLimit-Remaining |
Requests remaining in the current window |
X-RateLimit-Reset |
Unix timestamp when the limit resets |
When rate limited, you'll receive a 429 Too Many Requests response with a Retry-After header indicating how many seconds to wait.
All responses follow a standard envelope:
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 50,
"totalCount": 234,
"totalPages": 5
},
"meta": {
"requestedAt": "2026-05-08T12:00:00Z"
}
}-
data— The response payload -
pagination— Present on paginated endpoints -
meta— Request metadata
Get paginated effort logs for a specific work item.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
workItemId |
integer | Yes | Azure DevOps work item ID |
page |
integer | No | Page number (default: 1) |
pageSize |
integer | No | Results per page (default: 50, max: 500) |
Example:
curl -H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: myorg" \
"https://efforttrackerapi.sinaware.com/api/v1/effortlogs?workItemId=123&page=1&pageSize=25"Response:
{
"data": [
{
"id": "abc-123",
"workItemId": 123,
"date": "2026-05-01",
"hours": 2.5,
"activityType": "Development",
"notes": "Implemented feature X",
"userId": "user-guid",
"userDisplayName": "John Doe",
"projectName": "MyProject",
"areaPath": "MyProject\\Backend",
"createdAt": "2026-05-01T10:30:00Z"
}
],
"pagination": {
"page": 1,
"pageSize": 25,
"totalCount": 42,
"totalPages": 2
},
"meta": {
"requestedAt": "2026-05-08T12:00:00Z"
}
}Query effort logs with advanced filters. Use POST because the filter body can be complex.
Request Body:
{
"dateFrom": "2026-01-01",
"dateTo": "2026-03-31",
"usernames": ["John Doe", "Jane Smith"],
"areas": ["MyProject\\Backend"],
"workItemIds": [100, 101, 102],
"activityTypes": ["Development", "Testing"],
"projects": ["MyProject"],
"notes": "search text",
"page": 1,
"pageSize": 50,
"sortBy": "date",
"sortDesc": true
}All filter fields are optional. Only include the ones you need.
| Field | Type | Description |
|---|---|---|
dateFrom |
string | Start date (YYYY-MM-DD) |
dateTo |
string | End date (YYYY-MM-DD) |
usernames |
string[] | Filter by display names |
areas |
string[] | Filter by area paths |
workItemIds |
integer[] | Filter by work item IDs |
activityTypes |
string[] | Filter by activity types |
projects |
string[] | Filter by project names |
notes |
string | Search within notes (contains) |
page |
integer | Page number (default: 1) |
pageSize |
integer | Results per page (default: 50, max: 500) |
sortBy |
string | Sort field: date, hours, activityType, userDisplayName, projectName, areaPath |
sortDesc |
boolean | Sort descending (default: true) |
Example:
curl -X POST \
-H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: myorg" \
-H "Content-Type: application/json" \
-d '{"dateFrom":"2026-01-01","dateTo":"2026-03-31","pageSize":100}' \
"https://efforttrackerapi.sinaware.com/api/v1/effortlogs/query"Export effort logs as CSV or JSON. Rate limited to 10 requests per hour.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
format |
string | No |
csv or json (default: json) |
dateFrom |
string | No | Start date (YYYY-MM-DD) |
dateTo |
string | No | End date (YYYY-MM-DD) |
usernames |
string | No | Comma-separated usernames |
areas |
string | No | Comma-separated area paths |
activityTypes |
string | No | Comma-separated activity types |
projects |
string | No | Comma-separated project names |
Example (CSV):
curl -H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: myorg" \
"https://efforttrackerapi.sinaware.com/api/v1/effortlogs/export?format=csv&dateFrom=2026-01-01" \
-o export.csvExample (JSON):
curl -H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: myorg" \
"https://efforttrackerapi.sinaware.com/api/v1/effortlogs/export?format=json&dateFrom=2026-01-01"CSV Output Columns:
Id, WorkItemId, Date, Hours, ActivityType, Notes, UserId, UserDisplayName, ProjectName, AreaPath, CreatedAt
Get all active activity types configured for the organization.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
project |
string | No | Filter by project name |
Example:
curl -H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: myorg" \
"https://efforttrackerapi.sinaware.com/api/v1/activitytypes"Response:
{
"data": [
{
"id": "development",
"name": "Development",
"isActive": true,
"displayOrder": 1,
"projectName": "MyProject"
},
{
"id": "testing",
"name": "Testing",
"isActive": true,
"displayOrder": 2,
"projectName": "MyProject"
}
],
"meta": {
"requestedAt": "2026-05-08T12:00:00Z"
}
}Get summary statistics for the entire organization.
Example:
curl -H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: myorg" \
"https://efforttrackerapi.sinaware.com/api/v1/stats/summary"Response:
{
"data": {
"totalRecords": 1250,
"totalHours": 4567.5,
"userCount": 12,
"projectCount": 5
},
"meta": {
"requestedAt": "2026-05-08T12:00:00Z"
}
}Get the number of effort log entries per user.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
project |
string | No | Filter by project name |
Example:
curl -H "Authorization: Bearer YOUR_PAT" \
-H "X-Organization: myorg" \
"https://efforttrackerapi.sinaware.com/api/v1/stats/user-counts"Response:
{
"data": [
{ "user": "John Doe", "count": 150 },
{ "user": "Jane Smith", "count": 98 }
],
"meta": {
"requestedAt": "2026-05-08T12:00:00Z"
}
}All errors follow this format:
{
"error": "Description of what went wrong"
}| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request — Invalid parameters or request body |
| 401 | Unauthorized — Invalid or missing token |
| 429 | Too Many Requests — Rate limit exceeded |
| 500 | Internal Server Error — Something went wrong on our end |
import requests
BASE_URL = "https://efforttrackerapi.sinaware.com/api/v1"
HEADERS = {
"Authorization": "Bearer YOUR_PAT",
"X-Organization": "your-org-name"
}
# Get summary stats
response = requests.get(f"{BASE_URL}/stats/summary", headers=HEADERS)
stats = response.json()["data"]
print(f"Total hours: {stats['totalHours']}")
# Query effort logs
query = {
"dateFrom": "2026-01-01",
"dateTo": "2026-03-31",
"activityTypes": ["Development"],
"pageSize": 100
}
response = requests.post(f"{BASE_URL}/effortlogs/query", json=query, headers=HEADERS)
logs = response.json()["data"]$headers = @{
"Authorization" = "Bearer YOUR_PAT"
"X-Organization" = "your-org-name"
}
# Get summary stats
$stats = Invoke-RestMethod -Uri "https://efforttrackerapi.sinaware.com/api/v1/stats/summary" `
-Headers $headers
Write-Host "Total hours: $($stats.data.totalHours)"
# Query effort logs
$body = @{
dateFrom = "2026-01-01"
dateTo = "2026-03-31"
pageSize = 100
} | ConvertTo-Json
$logs = Invoke-RestMethod -Uri "https://efforttrackerapi.sinaware.com/api/v1/effortlogs/query" `
-Headers $headers -Method Post -Body $body -ContentType "application/json"const BASE_URL = "https://efforttrackerapi.sinaware.com/api/v1";
const headers = {
"Authorization": "Bearer YOUR_PAT",
"X-Organization": "your-org-name",
"Content-Type": "application/json"
};
// Get summary stats
const statsRes = await fetch(`${BASE_URL}/stats/summary`, { headers });
const stats = await statsRes.json();
console.log(`Total hours: ${stats.data.totalHours}`);
// Query effort logs
const queryRes = await fetch(`${BASE_URL}/effortlogs/query`, {
method: "POST",
headers,
body: JSON.stringify({
dateFrom: "2026-01-01",
dateTo: "2026-03-31",
pageSize: 100
})
});
const logs = await queryRes.json();- The public API is read-only. Create/update/delete operations are only available through the Azure DevOps extension UI.
- All dates use YYYY-MM-DD format.
- The API returns data scoped to the organization specified in the
X-Organizationheader. - Your PAT must have access to the specified organization.
- Maximum page size is 500 records per request.