Timesheets
Read crew clock-ins and clock-outs, add missed shifts, correct punches with an audit trail, and approve or void time.
View as MarkdownA time entry is one clock-in and clock-out by one person on one work order (a scheduled visit; see Schedule & crews). Crews punch in the TruTec crew app; the office adds and corrects time. Approved, unvoided time is what payroll exports and job costing use.
The endpoints follow the Timesheets page in TruTec:
- Every add, correction and void is written to the entry's audit trail with the reason.
- Changing an approved entry clears its approval, so it must be approved again.
- After a change, the job's labor cost is recalculated from the new hours.
- Writes act as the person who created the API key. They need the Edit timesheets permission in TruTec to add, correct or void time, and Approve time to approve. Otherwise you get
403naming the permission.
Times you send and the local_clock_in / local_clock_out you read are 24-hour times in the workspace's time zone. clock_in and clock_out are UTC with a Z suffix.
The time entry object#
| Field | Type | Description |
|---|---|---|
id |
integer | |
job_id, work_order_id |
integer | The job and visit. work_order is {id, title}. |
user_id, user |
integer, object | The person, with {id, name}. |
category |
string | work, travel, break (unpaid) or shop. |
paid |
boolean | False for breaks. |
date |
string | The local day of the clock-in. |
clock_in, clock_out |
string | UTC. clock_out is null while the person is on the clock. |
local_clock_in, local_clock_out |
string | Local HH:MM. |
hours |
number or null | Clock time minus any unpaid lunch. |
unpaid_lunch_minutes |
integer | |
wage_rate, wage_rate_kind, is_prevailing_wage |
The hourly cost rate captured at clock-in, and where it came from. | |
approved_at, approved_by |
Approval. | |
voided_at |
string or null | Voided entries keep their row and history but count for nothing. |
manual_entry |
string or null | Set when a foreman or the office entered the time, with why. |
edits |
array | The audit trail (single-entry reads only): field, old_value, new_value, reason, edited_by, created_at. |
Time entries#
List time entries#
GET /v1/time-entries
Permission: Read jobs
Ordered by clock-in, oldest first. Voided entries are left out unless you ask for them.
| Name | Type | Required | Description |
|---|---|---|---|
start_date, end_date |
string | No | Local days of the clock-in. |
user_id |
integer | No | One person. |
job_id, work_order_id |
integer | No | One job or visit. |
approved |
boolean | No | true or false. |
open |
boolean | No | true for people still on the clock. |
category |
string | No | work, travel, break or shop. |
include_voided |
boolean | No | Default false. |
limit, offset |
integer | No | Default 50, maximum 200. |
curl "https://trutec.ai/api/v1/time-entries?start_date=2026-11-02&end_date=2026-11-08&approved=false" \
-H "X-API-Key: sk_your_api_key_here"
{
"data": [
{
"id": 602118,
"job_id": 70412,
"work_order_id": 4120,
"user_id": 344,
"category": "work",
"paid": true,
"clock_in": "2026-11-03T13:00:00Z",
"clock_out": "2026-11-03T22:30:00Z",
"hours": 9.0,
"unpaid_lunch_minutes": 30,
"wage_rate": 28.5,
"wage_rate_kind": "standard",
"is_prevailing_wage": false,
"approved_at": null,
"voided_at": null,
"user": { "id": 344, "name": "Crew Lead" },
"work_order": { "id": 4120, "title": "Sealcoat front lot" },
"date": "2026-11-03",
"local_clock_in": "07:00",
"local_clock_out": "16:30",
"approved_by": null,
"manual_entry": null
}
],
"pagination": { "total": 1, "limit": 50, "offset": 0 }
}
Time entries have no updated_at. To sync time, pull a date range again, or use the bulk export.
Get a time entry#
GET /v1/time-entries/{id}
Permission: Read jobs
Includes edits, the entry's audit trail.
Add a time entry#
POST /v1/time-entries
Permission: Write jobs (and Edit timesheets in TruTec)
Adds a shift the crew app did not record: a paper timecard, a dead phone, a missed punch. The wage rate is captured the same way as a real clock-in. The entry lands unapproved, with no GPS, and with a "created" audit row.
| Name | Type | Required | Description |
|---|---|---|---|
user_id |
integer | Yes | A TruTec login in the workspace. |
work_order_id |
integer | Yes | The visit the time is for. |
date |
string | Yes | Local day of the start. |
clock_in, clock_out |
string | Yes | Local HH:MM. A clock_out before the start rolls to the next day for shifts up to 14 hours. |
category |
string | No | Default work. |
reason |
string | Yes | Kept in the audit trail, for example "paper timecard". |
curl -X POST "https://trutec.ai/api/v1/time-entries" \
-H "X-API-Key: sk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"user_id": 344,
"work_order_id": 4120,
"date": "2026-11-03",
"clock_in": "07:00",
"clock_out": "15:30",
"reason": "Paper timecard"
}'
Returns 201 with the entry, including edits.
Errors: 400 for a missing field, a bad time or an end before the start. 404 when the user or work order is not in your workspace.
Correct a time entry#
PATCH /v1/time-entries/{id}
Permission: Write jobs (and Edit timesheets)
| Name | Type | Required | Description |
|---|---|---|---|
date |
string | No | Move the whole shift to another local day. |
clock_in, clock_out |
string | No | Local HH:MM. Sending clock_out for an open entry closes it. |
category |
string | No | |
reason |
string | No | Kept with each change in the audit trail. |
Each changed field gets an audit row and the entry's approval is cleared. Shifts longer than 24 hours are refused. Voided entries must be restored first (400).
Approve or unapprove#
POST /v1/time-entries/{id}/approve
POST /v1/time-entries/{id}/unapprove
Permission: Write jobs (and Approve time)
Open shifts (no clock-out) and voided entries cannot be approved (400).
Void or restore#
POST /v1/time-entries/{id}/void with {"reason": "Duplicate punch"}
POST /v1/time-entries/{id}/restore
Permission: Write jobs (and Edit timesheets)
Voiding never deletes: the entry stays with its history, stops counting toward hours, payroll and job cost, and loses its approval. A reason is required to void.