Tracked times
A tracked time is one time entry: minutes a member of staff worked on one task on one day, or time on leave, business overhead or a time credit. The same rules apply as on the Drum timesheet.
Durations are whole minutes. Fields ending _hours give the same value in hours, rounded to two decimals. Dates are yyyy-mm-dd.
Endpoints
| Method and path | Does |
|---|---|
GET /api/v1/projects/:project_id/tracked_times |
Lists the entries on every task of a project |
GET /api/v1/projects/:project_id/tracked_times/:id |
Reads one entry of a project |
POST /api/v1/projects/:project_id/tracked_times |
Logs an entry against a task of the project |
PATCH /api/v1/projects/:project_id/tracked_times/:id |
Changes an entry |
DELETE /api/v1/projects/:project_id/tracked_times/:id |
Deletes an entry |
GET /api/v1/members/:member_id/tracked_times |
Lists one member’s entries across projects, leave, overhead and time credits |
GET /api/v1/members/:member_id/tracked_times/:id |
Reads one of that member’s entries |
POST, PATCH, DELETE /api/v1/members/:member_id/tracked_times(/:id) |
Log and change that member’s time, including time on no project |
:project_id can be a delivery project or an opportunity.
List entries
Both lists are newest date first and paginated (see Pagination).
| Parameter | Project list | Member list | Meaning |
|---|---|---|---|
start_date, end_date |
Yes | Yes | Worked on or after, or on or before, this date. The member list returns every entry when both are left out |
member_id |
Yes | Entries of one member | |
project_id |
Yes | Entries on one project | |
task_id |
Yes | Yes | Entries on one task |
deliverable_id |
Yes | Yes | Entries on one deliverable |
billing_status |
Yes | Yes | unbilled, partial or billed |
time_type |
Yes | Yes | standard, overtime, vacation, holiday, sick or other |
resource_type_id |
Yes | Yes | Entries under one resource type |
The member list includes a removed member’s time. Its money fields are null on a project the token’s user cannot read.
The tracked time object
{
"id": 4478056,
"date": "2026-09-11",
"duration": 90,
"duration_hours": 1.5,
"note": "Site visit",
"billable": true,
"time_type": "standard",
"billing_status": "unbilled",
"member_id": 1322,
"member_name": "Jordan Reed",
"task_id": 88121,
"task_name": "Site inspection",
"project_name": "Riverside fit-out",
"deliverable_id": 7301,
"resource_type_id": 88,
"resource_type_name": "Senior Engineer",
"leave_type_id": null,
"business_overhead_id": null,
"time_credit_type_id": null,
"timesheet": {"id": 9911, "status": "draft", "locked": false, "start_date": "2026-09-07", "end_date": "2026-09-13"},
"correction": {"allowed": true, "reason": null},
"created_at": "2026-09-11T05:02:11.104Z",
"updated_at": "2026-09-11T05:02:11.104Z"
}
| Field | Meaning |
|---|---|
duration, duration_hours |
Billable time logged, in minutes and hours |
billable, time_type, billing_status |
As recorded |
member_id, member_name |
The member of staff. member_name is the person’s own name |
task_id, task_name, project_name, deliverable_id |
Where the time sits. null on leave, overhead and time credit entries |
resource_type_id, resource_type_name |
The resource type, or null |
leave_type_id, business_overhead_id, time_credit_type_id |
Set on time that is not on a task. The matching leave_type_name, business_overhead_name or time_credit_type_name is added |
timesheet |
The week the entry is in. status is drum_only, draft, finalised, approved or xero_conflict. locked is true when finalised or approved. null until a timesheet exists for that week |
correction |
Whether the token’s user may change or delete the entry. When allowed is false, reason is not_task_time, not_authorized or timesheet_locked |
Money fields:
| Fields | Shown with |
|---|---|
currency, billable_rate_cents, billable_amount_cents, invoice_amount_cents, billed_duration, and when set invoice_duration, invoice_duration_hours, unbillable_duration, unbillable_duration_hours |
view_project_metrics |
expense_rate_cents, expense_amount_cents |
resource_financials. These are staff cost rates |
account_user_id, account_user_name and project_procedure_id are deprecated copies of member_id, member_name and deliverable_id. Use the new names. The filters still accept account_user_id and project_procedure_id for now.
Log time
POST /api/v1/projects/:project_id/tracked_times. Send fields flat or under tracked_time.
| Field | Required | Rules |
|---|---|---|
task_id |
Yes | A task of this project that accepts time. Get it from GET /projects/:id |
date |
Yes | yyyy-mm-dd, from 1900 to 2099 |
duration_minutes |
Yes | Billable minutes, 0 to 1440. 0 only with unbillable_duration_minutes above 0. The response calls this duration |
unbillable_duration_minutes |
No | Unbillable minutes, 0 to 1440. Above 0 needs the account to show unbillable time (422 unbillable_time_disabled) |
note |
No | Up to 5,000 characters |
member_id |
No | Log the time for another member. See Permissions |
resource_type_id |
No | One of the member’s resource types. Default: their default resource type |
Ids may be JSON integers or strings of digits.
POST /api/v1/projects/20488/tracked_times
{"task_id": 88121, "date": "2026-09-11", "duration_minutes": 90, "note": "Site visit"}
Send an Idempotency-Key header to make retries safe (see Idempotency keys).
Returns 201 with the entry, plus day_total, week_total and task_time. The entry’s own fields are shortened here:
{
"id": 4478056,
"duration": 90,
"day_total": {
"date": "2026-09-11", "member_id": 1322,
"duration": 450, "duration_hours": 7.5,
"duration_before_credits": 510, "duration_before_credits_hours": 8.5,
"scheduled_duration": 456, "scheduled_hours": 7.6
},
"week_total": {
"start_date": "2026-09-07", "end_date": "2026-09-13", "member_id": 1322,
"duration": 1890, "duration_hours": 31.5,
"duration_before_credits": 1950, "duration_before_credits_hours": 32.5,
"scheduled_duration": 2280, "scheduled_hours": 38.0
},
"task_time": {
"task_id": 88121, "member_id": 1322,
"basis": "allocation",
"planned_duration": 600, "planned_hours": 10.0,
"logged_duration": 390, "logged_hours": 6.5,
"remaining_duration": 210, "remaining_hours": 3.5,
"over_duration": 0, "over_hours": 0.0
}
}
| Field | Meaning |
|---|---|
day_total, week_total |
The member’s logged time for that day, and for that timesheet week, including leave, overhead and opportunity time. null when the token’s user may not view that member’s timesheet |
duration |
The total as the timesheet shows it, with time credit entries subtracted |
duration_before_credits |
The total before time credit entries are subtracted |
scheduled_duration |
What the member’s work schedule expects, or null with no schedule |
task_time |
The member’s time on the task and the time left, as the timesheet’s “hrs remaining” shows it. null when there is no task or no day_total |
task_time.basis |
allocation (an explicit allocation), assigned_budget (budgeted hours on the member’s own resource lines), task_budget_estimate (an estimate from the task’s remaining budget), or null |
Change an entry
PATCH /api/v1/projects/:project_id/tracked_times/:id. Send at least one of these. Each replaces the value. Nothing is added.
| Field | Rules |
|---|---|
duration_minutes |
0 to 1440 |
unbillable_duration_minutes |
0 to 1440 |
date |
yyyy-mm-dd |
note |
Up to 5,000 characters. Blank or null clears it |
task_id |
Another task of the same project |
member_id |
Another member. Needs the right to log their time |
resource_type_id |
One of the member’s resource types |
Rates and billing fields cannot be set. Amounts are worked out again from the new duration.
Invoiced time can move to another task only if the token’s user is allowed to restructure invoiced time, and then only the task can change. Time that moves off its project comes off that project’s invoice and progress claims.
Returns 200 with the entry, day_total, week_total and task_time. After a date change the totals describe the new day and week.
Delete an entry
DELETE /api/v1/projects/:project_id/tracked_times/:id deletes the entry permanently. No body is needed. Returns 200 with tracked_time_id, deleted: true, and day_total, week_total and task_time for what is left on that day and week:
{"tracked_time_id": 4478056, "deleted": true, "day_total": {...}, "week_total": {...}, "task_time": {...}}
Deleting it again returns 404 tracked_time_not_found.
Leave, overhead and time credit time
The member routes take the same fields and give the same responses as the project routes, with these differences:
- Send one of
task_id,leave_type_id,business_overhead_idortime_credit_type_id. Sending more than one returns422 one_allocation. POSTalways logs the time for the member in the path.member_idin the body is ignored.PATCHandDELETEreturn404 tracked_time_not_foundfor an entry of another member.PATCHcan still move the entry to another member withmember_id.- A
PATCHwith one of the four ids moves the entry to that task, leave type, overhead or time credit type.
| Field | Accepts | Not found |
|---|---|---|
leave_type_id |
A leave type of the account | leave_type_not_found |
business_overhead_id |
A business overhead of the account | business_overhead_not_found |
time_credit_type_id |
A time credit type, while the account shows time credits on the timesheet | time_credit_type_not_found |
The project routes keep time on a task of their project. They refuse the other three ids.
Permissions
| Action | Needs |
|---|---|
| Read a project’s entries | api_read, and read access to the project |
| Read a member’s entries | api_read. You can always read your own. For someone else: admin, or a role with view_staff_timesheet_page together with manage_staff_timesheets, approve_timesheets, or a team or direct report timesheet permission that covers them |
| Log time | api_create, and read access to the project |
| Log time for someone else | Also admin, manage_staff_timesheets, or a project role on this project that can edit all time entries |
| Change or delete | api_update or api_delete. Your own unbilled time, or for billed time and other people’s time: admin, manage_staff_timesheets, or a project role that can edit all time entries |
| A locked week | Admin, manage_staff_timesheets, approve_timesheets, approve_team_timesheets, or approve_direct_report_timesheets with at least one direct report |
Errors
| Status | code |
When |
|---|---|---|
403 |
not_authorized |
The token’s user may not log or change this member’s time |
403 |
timesheet_locked |
The week is finalised or approved, and the token’s user cannot edit through the lock |
403 |
timesheet_not_permitted |
Member list: the token’s user may not view that member’s timesheet |
403 |
invoice_release_not_permitted |
Invoiced time would leave its project, and the token’s user may not take it off the invoice |
404 |
project_not_found |
The project is not in the account, is deleted, or is hidden. The project lists return {"error": "Project not found"} |
404 |
tracked_time_not_found |
The entry does not exist, is outside the route’s project or member, or is hidden |
404 |
task_not_found, member_not_found, resource_type_not_found |
A referenced record cannot be used |
409 |
entry_changed |
The entry changed while Drum was locking it. Read it again before you retry |
409 |
idempotency_key_reused, retry_later, idempotent_record_deleted |
See Idempotency keys |
422 |
missing_input, invalid_input |
A required field is missing, or a value could not be read or is out of range |
422 |
task_closed |
The task does not accept time from this member |
422 |
task_not_in_project |
The new task is not on the route’s project |
422 |
invoiced_task_only |
Invoiced time moving task, with another field changed too |
422 |
unbillable_time_disabled |
Unbillable minutes while the account does not show them |
422 |
one_allocation |
More than one of task, leave type, overhead and time credit type |
422 |
validation_failed |
The entry would not save. errors lists why |
After a timeout on PATCH or DELETE, read the entry before you retry.