-
Notifications
You must be signed in to change notification settings - Fork 0
Public API
Effort Tracker provides a REST API for programmatic access to effort data.
https://efforttrackerapi.sinaware.com/api
All API requests require authentication using Azure DevOps access tokens.
| Header | Description |
|---|---|
Authorization |
Bearer token from Azure DevOps |
X-Organization |
Azure DevOps organization name |
X-Project |
Project name (for project-scoped endpoints) |
Create a Personal Access Token (PAT) in Azure DevOps:
- Go to your Azure DevOps organization (e.g.,
https://dev.azure.com/{your-org}) - Click the User Settings icon (top right) → Personal access tokens
- Click + New Token
- Configure the token:
- Name: e.g., "Effort Tracker API"
- Organization: Select your organization (or "All accessible organizations")
- Expiration: Choose an appropriate expiration date
- Scopes: Select Read under Work Items at minimum
- Click Create and copy the token immediately (it won't be shown again)
Use the token in the Authorization header:
curl -H "Authorization: Bearer YOUR_PAT_TOKEN" \
-H "X-Organization: your-org-name" \
https://efforttrackerapi.sinaware.com/api/v1/stats/summaryRetrieve effort logs for a work item.
GET /effortlogs?workItemId={id}&page={page}&pageSize={pageSize}Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| workItemId | integer | Yes | Work item ID |
| page | integer | No | Page number (default: 1) |
| pageSize | integer | No | Items per page (default: 10) |
Response
{
"data": [
{
"id": "log-uuid",
"workItemId": 123,
"date": "2025-01-15",
"hours": 4.5,
"activityType": "Development",
"notes": "Implemented feature",
"userId": "user-guid",
"userDisplayName": "John Doe",
"projectName": "MyProject",
"areaPath": "MyProject\\Web",
"createdAt": "2025-01-15T10:30:00Z"
}
],
"page": 1,
"pageSize": 10,
"totalCount": 25,
"totalPages": 3
}Create a new effort log entry.
POST /effortlogs
Content-Type: application/jsonRequest Body
{
"workItemId": 123,
"date": "2025-01-15",
"hours": 4.5,
"activityType": "Development",
"notes": "Implemented feature"
}Response: 201 Created
{
"id": "log-uuid",
"workItemId": 123,
"date": "2025-01-15",
"hours": 4.5,
"activityType": "Development",
"notes": "Implemented feature",
"userId": "user-guid",
"userDisplayName": "John Doe"
}Update an existing effort log entry.
PUT /effortlogs/{logId}
Content-Type: application/jsonRequest Body
{
"date": "2025-01-16",
"hours": 5.0,
"activityType": "Testing",
"notes": "Updated notes"
}Response: 200 OK
Delete an effort log entry.
DELETE /effortlogs/{logId}Response: 204 No Content
Query effort logs with filters.
POST /effortlogs/query
Content-Type: application/jsonRequest Body
{
"startDate": "2025-01-01",
"endDate": "2025-01-31",
"userIds": ["user-guid-1", "user-guid-2"],
"activityTypes": ["Development", "Testing"],
"workItemIds": [123, 456],
"areaPath": "MyProject\\Web",
"page": 1,
"pageSize": 50
}Response
{
"data": [...],
"page": 1,
"pageSize": 50,
"totalCount": 150,
"totalHours": 342.5
}Export effort logs as CSV.
POST /effortlogs/export
Content-Type: application/jsonRequest Body: Same as query
Response: CSV file download
Get active activity types for a project.
GET /activitytypesResponse
[
{
"id": "type-uuid",
"name": "Development",
"displayOrder": 1,
"isActive": true
}
]Get all activity types including inactive.
GET /activitytypes/allPOST /activitytypes
Content-Type: application/jsonRequest Body
{
"name": "Bug Triage",
"displayOrder": 5,
"isActive": true
}PUT /activitytypes/{id}
Content-Type: application/jsonDELETE /activitytypes/{id}GET /license/statusResponse
{
"userCount": 8,
"freeTierLimit": 5,
"isFreeTier": false,
"hasActiveSubscription": true,
"requiresSubscription": false,
"status": "active",
"subscriptionId": "sub_xxx",
"seatCount": 10,
"message": null,
"managementUrl": "https://paddle.com/..."
}Sync subscription status from Paddle.
POST /license/refreshCreate a secure Paddle checkout session for subscription purchase.
POST /subscription/checkout
Content-Type: application/jsonRequest Body
{
"seats": 10
}Response
{
"checkoutUrl": "https://checkout.paddle.com/...",
"transactionId": "txn_xxx"
}Update seat count on an existing subscription.
POST /subscription/update-seats
Content-Type: application/jsonRequest Body
{
"seats": 15
}Response
{
"success": true,
"newSeatCount": 15
}Get the Paddle customer portal URL for subscription management.
POST /subscription/portalResponse
{
"portalUrl": "https://customer-portal.paddle.com/..."
}Anonymous health check endpoint.
GET /license/healthResponse
{
"status": "healthy",
"timestamp": "2025-01-15T10:30:00Z"
}Get distinct values for filter dropdowns.
GET /effortlogs/distinct/usersGET /effortlogs/distinct/projectsGET /effortlogs/distinct/activitytypesGET /effortlogs/distinct/areasGET /org-admin/statsResponse
{
"totalRecords": 1250,
"totalHours": 4832.5,
"distinctUsers": 12,
"projects": ["ProjectA", "ProjectB"],
"projectCount": 2
}GET /org-admin/usersResponse
[
{
"userId": "user-guid",
"displayName": "John Doe"
}
]POST /org-admin/exportResponse: ZIP file with CSV data
Check the current user's admin permissions.
GET /permissionsResponse
{
"isProjectAdmin": true,
"isOrgAdmin": false
}Permission Logic:
-
isProjectAdmin: User is member of[ProjectName]\Project Administrators -
isOrgAdmin: User is member of[OrgName]\Project Collection Administrators - Org Admins automatically have Project Admin permissions
Caching: Permissions are cached for 5 minutes to reduce API calls.
Initialize tables and seed default activity types for a new organization.
POST /initResponse
{
"success": true,
"message": "Organization initialized successfully"
}Notes:
- Creates organization tables: EffortLogs, ActivityTypes
- Creates global Subscriptions table
- Seeds default activity types (Development, Testing, Documentation, etc.)
- Safe to call multiple times (idempotent)
Invalid request parameters.
{
"error": "Validation failed",
"details": ["Hours must be greater than 0"]
}Missing or invalid authentication.
{
"error": "Unauthorized"
}License limit exceeded.
{
"error": "License limit reached",
"message": "Your organization has 8 users. A subscription is required for more than 5 users."
}Resource not found.
{
"error": "Effort log not found"
}Server error.
{
"error": "An error occurred processing your request"
}Azure Table Storage is temporarily unavailable (cooldown period after table deletion).
{
"error": "Table EffortLogs is temporarily unavailable (may have been recently deleted). Please wait 30 seconds and try again."
}Important: This occurs when tables are deleted and recreated (e.g., during purge operations). Azure Table Storage has a ~30 second soft-delete cooldown period. Simply wait 30 seconds and retry the request.
The internal API (used by the extension) has no rate limiting.
The Public API v1 (/api/v1/*) enforces per-token and per-organization rate limits:
| Tier | Limit | Applies To |
|---|---|---|
| Standard per-minute | 30/min per token | All non-export endpoints |
| Standard per-hour | 250/hour per token | All non-export endpoints |
| Export per-hour | 10/hour per token |
/export endpoints only |
| Org-wide per-minute | 100/min per org | All endpoints |
Rate limit headers are included in all v1 responses:
-
X-RateLimit-Remaining— Remaining requests in current window -
X-RateLimit-Reset— Unix timestamp when the window resets
When limits are exceeded, the API returns 429 Too Many Requests:
{
"error": "Rate limit exceeded. Please retry after the Retry-After period."
}The Retry-After header indicates how many seconds to wait.
Effort Tracker receives subscription events from Paddle:
POST /paddle/webhookThis endpoint is for Paddle's use only and requires signature verification.
The Public API provides read-only access to effort data for external integrations, dashboards, and automation.
Base URL:
https://efforttrackerapi.sinaware.com/api/v1
Swagger UI: https://efforttrackerapi.sinaware.com/api/swagger/ui
OpenAPI Spec: https://efforttrackerapi.sinaware.com/api/swagger.json
All Public API requests require the same authentication as the internal API:
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer {Azure DevOps PAT} |
X-Organization |
Yes | Azure DevOps organization name |
All v1 responses use a standard envelope:
{
"data": ...,
"pagination": {
"page": 1,
"pageSize": 50,
"totalCount": 123,
"totalPages": 3
},
"meta": {
"requestedAt": "2026-05-12T00:00:00+00:00"
}
}-
data— The response payload (array or object depending on endpoint) -
pagination— Present only on paginated endpoints,nullotherwise -
meta— Request metadata (always present)
Retrieve effort logs for a specific work item.
GET /v1/effortlogs?workItemId={id}&page={page}&pageSize={pageSize}Query Parameters
| Parameter | Type | Required | Default | Max |
|---|---|---|---|---|
| workItemId | integer | Yes | — | — |
| page | integer | No | 1 | — |
| pageSize | integer | No | 50 | 500 |
Response — Paginated array of effort logs.
{
"data": [
{
"id": "abc123",
"workItemId": 42,
"date": "2026-05-01",
"hours": 4.0,
"activityType": "Development",
"notes": "Implemented feature X",
"userId": "user-guid",
"userDisplayName": "Alice Johnson",
"projectName": "MyProject",
"areaPath": "MyProject\\Backend",
"createdAt": "2026-05-01T10:00:00+00:00"
}
],
"pagination": { "page": 1, "pageSize": 50, "totalCount": 1, "totalPages": 1 },
"meta": { "requestedAt": "2026-05-12T00:00:00+00:00" }
}Query effort logs with advanced filters.
POST /v1/effortlogs/queryRequest Body
{
"dateFrom": "2026-01-01",
"dateTo": "2026-03-31",
"usernames": ["Alice Johnson"],
"areas": ["MyProject\\Backend"],
"workItemIds": [123, 456],
"activityTypes": ["Development"],
"projects": ["MyProject"],
"notes": "search text",
"page": 1,
"pageSize": 50,
"sortBy": "date",
"sortDesc": true
}All filter fields are optional. If no filters are provided, all logs are returned (paginated).
| 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 | int[] | Filter by work item IDs |
| activityTypes | string[] | Filter by activity type names |
| projects | string[] | Filter by project names |
| notes | string | Search text in notes (contains) |
| page | integer | Page number (default: 1) |
| pageSize | integer | Results per page (default: 50, max: 500) |
| sortBy | string | Sort field: date, hours, user, activityType
|
| sortDesc | boolean | Sort descending (default: false) |
Response — Same format as Get Effort Logs (paginated array).
Export effort logs as CSV or JSON.
GET /v1/effortlogs/export?format={format}&dateFrom={date}&dateTo={date}Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| format | string | No | json |
csv or json
|
| dateFrom | string | No | — | Start date (YYYY-MM-DD) |
| dateTo | string | No | — | End date (YYYY-MM-DD) |
| usernames | string | No | — | Comma-separated user names |
| areas | string | No | — | Comma-separated area paths |
| activityTypes | string | No | — | Comma-separated activity types |
| projects | string | No | — | Comma-separated project names |
JSON Response — Envelope with data array (no pagination).
CSV Response — Raw CSV file with columns: Id, WorkItemId, Date, Hours, ActivityType, Notes, UserId, UserDisplayName, ProjectName, AreaPath, CreatedAt.
Note: Export endpoints have a separate rate limit of 10 requests per hour.
Retrieve active activity types.
GET /v1/activitytypes?project={projectName}Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| project | string | No | Filter by project name |
Response
{
"data": [
{
"id": "type-id",
"name": "Development",
"isActive": true,
"displayOrder": 1
}
],
"meta": { "requestedAt": "2026-05-12T00:00:00+00:00" }
}Get organization-wide statistics.
GET /v1/stats/summaryResponse
{
"data": {
"totalRecords": 150,
"totalHours": 425.5,
"userCount": 8,
"projectCount": 3
},
"meta": { "requestedAt": "2026-05-12T00:00:00+00:00" }
}Get effort log counts per user.
GET /v1/stats/user-counts?project={projectName}Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| project | string | No | Filter by project name |
Response
{
"data": [
{ "user": "Alice Johnson", "count": 45 },
{ "user": "Bob Smith", "count": 32 }
],
"meta": { "requestedAt": "2026-05-12T00:00:00+00:00" }
}