Time entries
Reported time (tidrapporter): clocked in and out in the field app, approved in the office, and read here for payroll. Only approved time is billed.
Life of an entry: open (clocked in) → submitted (clocked out) → approved, or rejected with a reason.
List time entries
/time-entriesReported time, newest first, at most 500 entries per call and not paginated — narrow by date instead. For payroll, ask for a pay period's approved time: filter[status]=approved&from=2026-11-01&to=2026-11-30T23:59:59Z, and use duration_minutes (paid minutes, break deducted), type and hourly_rate_cents.
from and to filter on started_at. An entry is approved only once somebody with booking.time.approve signed it off; only approved time reaches an invoice.
curl 'https://api.closepact.com/v1/api/time-entries' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": [
{
"id": "01a15c20-4e5f-7253-a4b5-c6d71e243078",
"organization_id": "01a14eb3-c5f0-7c55-8c2e-6a0e1f7b9d21",
"employee_id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01",
"work_order_id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"started_at": "2026-11-03T07:04:00.000000Z",
"clock_in_received_at": "2026-11-03T07:04:02.000000Z",
"ended_at": "2026-11-03T11:12:00.000000Z",
"clock_out_received_at": "2026-11-03T11:12:03.000000Z",
"break_minutes": 15,
"duration_minutes": 233,
"type": "work",
"start_lat": 59.85861,
"start_lng": 17.63893,
"start_accuracy_m": 12,
"start_source": "gps",
"end_lat": 59.85858,
"end_lng": 17.63901,
"end_accuracy_m": 9,
"end_source": "gps",
"within_geofence": true,
"status": "approved",
"approved_by": "01a14eb3-c634-7238-bc76-18b09b287f98",
"approved_at": "2026-11-04T07:30:00.000000Z",
"rejection_reason": null,
"billable": true,
"hourly_rate_cents": 32000,
"billing_rate_cents": 65000,
"note": "Extra tid på ugnen.",
"metadata": [],
"created_at": "2026-11-03T07:04:00.000000Z",
"updated_at": "2026-11-04T07:30:00.000000Z",
"employee": {
"id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01",
"first_name": "Ali",
"last_name": "Hassan"
},
"work_order": {
"id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"order_number": "1042",
"title": "Flyttstädning – Storgatan 12, Uppsala"
}
}
]
}Query parameters
filter[employee_id]string(uuid)Only this employee's time.
filter[status]stringOnly these statuses. Repeat as
filter[status][]=submitted&filter[status][]=approvedfor several.One of
opensubmittedapprovedrejectedExample
approvedfilter[work_order_id]string(uuid)Only time on this job.
fromstring(date-time)Entries that started at or after this instant. A date means midnight UTC. ISO 8601.
Example
2026-11-01T00:00:00Ztostring(date-time)Entries that started at or before this instant. A bare date means midnight UTC at the start of that day. ISO 8601.
Example
2026-11-30T23:59:59Z
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataarray of objectsrequiredidstring(uuid)requiredThe time entry id.
organization_idstring(uuid)requiredYour organization.
employee_idstring(uuid)requiredWhose time it is.
work_order_idstring(uuid)nullablerequiredThe job it was spent on, or null (workshop, travel between jobs).
started_atstring(date-time)requiredClock-in, by the phone’s clock at the moment of the tap. A clock-in queued without signal keeps the time it was made, not the time it reached the server.
clock_in_received_atstring(date-time)nullablerequiredWhen the server received the clock-in. Far later than
started_atmeans the phone was offline; null for entries typed in by the office.ended_atstring(date-time)nullablerequiredClock-out, or null while
open.clock_out_received_atstring(date-time)nullablerequiredWhen the server received the clock-out, or null.
break_minutesintegerrequiredUnpaid break within the stretch, in minutes.
duration_minutesintegernullablerequiredPaid minutes: from start to end less the break. Written when the entry is closed or edited, never recomputed later. Null while
open.typestringrequiredKind of time.
One of
worktravelovertimestart_latnumbernullablerequiredLatitude at clock-in, or null.
start_lngnumbernullablerequiredLongitude at clock-in, or null.
start_accuracy_mintegernullablerequiredReported accuracy of that position in metres, or null.
start_sourcestringrequiredWhere the start came from: the phone's position, the phone without a position, or typed in afterwards by the office.
One of
gpsmanualofficeend_latnumbernullablerequiredLatitude at clock-out, or null.
end_lngnumbernullablerequiredLongitude at clock-out, or null.
end_accuracy_mintegernullablerequiredReported accuracy in metres, or null.
end_sourcestringrequiredWhere the end came from.
One of
gpsmanualofficewithin_geofencebooleannullablerequiredWhether the clock-in was inside the job address's geofence; null when there was nothing to compare (no position, or no fence on the address).
statusstringrequiredopen: clocked in, still running.submitted: clocked out, waiting for approval.approved: signed off — billable, and ready for payroll.rejected: sent back, with arejection_reason.One of
opensubmittedapprovedrejectedapproved_bystring(uuid)nullablerequiredThe member who approved — or rejected — it, or null.
approved_atstring(date-time)nullablerequiredWhen it was approved, or null (also after a rejection).
rejection_reasonstringnullablerequiredWhy it was rejected, or null.
billablebooleanrequiredWhether the time may be invoiced. Unbillable time is still paid and still shown on the invoice basis, as an unbilled line.
travelreported from the field app starts unbillable until somebody marks it billable; billable travel is invoiced as travel, never with a ROT/RUT deduction.hourly_rate_centsintegernullablerequiredThe employee's
hourly_cost_centscopied at approval, in öre; null before. What the hour cost — for payroll and margin, never invoiced. A later change of the employee's rate does not change it.billing_rate_centsintegernullablerequiredThe employee's
hourly_billing_rate_centscopied at approval, in öre excluding VAT; null before. What the hour is invoiced at when the job was not quoted one service price per hour. A later change of the employee's rate does not change it.notestringnullablerequiredFree text, or null.
metadataobjectrequiredFree-form data.
created_atstring(date-time)requiredISO 8601 date-time, in UTC.
updated_atstring(date-time)requiredISO 8601 date-time, in UTC.
employeeobjectnullablerequiredThe employee, or null if they have been deleted.
work_orderobjectnullablerequiredThe job, or null.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 429The key has made more requests this minute than its limit allows.Errors
Approve time entries
/time-entries/approveSigns off a batch of entries — a week's timesheet at once. Only submitted entries are approved; any other id in the list (open, already approved, rejected, unknown or another organization's) is skipped, and approved tells you how many went through.
Approving stamps approved_by (the member the key acts as) and approved_at, and copies each employee's current hourly_cost_cents onto the entry as hourly_rate_cents and hourly_billing_rate_cents as billing_rate_cents, so a later pay rise or price change does not rewrite what this time cost or is billed at. Approved time is billable and can no longer be edited or deleted without being rejected first.
curl -X POST 'https://api.closepact.com/v1/api/time-entries/approve' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"01a15c20-4e5f-7253-a4b5-c6d71e243078",
"01a15c20-4e60-7364-b5c6-d7e82f354189"
]
}'{
"data": {
"approved": 2
}
}Request body
application/json · required
idsarray of strings(uuid)requiredThe entries to approve.
at least 1 item
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredapprovedintegerrequiredEntries that went from
submittedtoapproved.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Reject a time entry
/time-entries/{timeEntry}/rejectSends an entry back with a reason, whatever its status — including an approved one, which is how approved time is corrected. The entry becomes rejected and editable again; approved_by records who rejected it and approved_at is cleared.
curl -X POST 'https://api.closepact.com/v1/api/time-entries/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/reject' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Fel uppdrag — tiden hör till 1043."
}'{
"data": {
"id": "01a15c20-4e5f-7253-a4b5-c6d71e243078",
"organization_id": "01a14eb3-c5f0-7c55-8c2e-6a0e1f7b9d21",
"employee_id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01",
"work_order_id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"started_at": "2026-11-03T07:04:00.000000Z",
"clock_in_received_at": "2026-11-03T07:04:02.000000Z",
"ended_at": "2026-11-03T11:12:00.000000Z",
"clock_out_received_at": "2026-11-03T11:12:03.000000Z",
"break_minutes": 15,
"duration_minutes": 233,
"type": "work",
"start_lat": 59.85861,
"start_lng": 17.63893,
"start_accuracy_m": 12,
"start_source": "gps",
"end_lat": 59.85858,
"end_lng": 17.63901,
"end_accuracy_m": 9,
"end_source": "gps",
"within_geofence": true,
"status": "rejected",
"approved_by": "01a14eb3-c634-7238-bc76-18b09b287f98",
"approved_at": null,
"rejection_reason": "Fel uppdrag — tiden hör till 1043.",
"billable": true,
"hourly_rate_cents": 32000,
"billing_rate_cents": 65000,
"note": "Extra tid på ugnen.",
"metadata": [],
"created_at": "2026-11-03T07:04:00.000000Z",
"updated_at": "2026-11-04T08:02:00.000000Z"
}
}Path parameters
timeEntrystring(uuid)requiredThe time entry id.
Request body
application/json · required
reasonstringrequiredWhy, for the person who reported it.
at most 500 characters
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredOne stretch of reported time. What the worker reported (
started_at,ended_at,break_minutes), what their phone said (coordinates, accuracy,*_source) and what was concluded (within_geofence,status) are kept apart.idstring(uuid)requiredThe time entry id.
organization_idstring(uuid)requiredYour organization.
employee_idstring(uuid)requiredWhose time it is.
work_order_idstring(uuid)nullablerequiredThe job it was spent on, or null (workshop, travel between jobs).
started_atstring(date-time)requiredClock-in, by the phone’s clock at the moment of the tap. A clock-in queued without signal keeps the time it was made, not the time it reached the server.
clock_in_received_atstring(date-time)nullablerequiredWhen the server received the clock-in. Far later than
started_atmeans the phone was offline; null for entries typed in by the office.ended_atstring(date-time)nullablerequiredClock-out, or null while
open.clock_out_received_atstring(date-time)nullablerequiredWhen the server received the clock-out, or null.
break_minutesintegerrequiredUnpaid break within the stretch, in minutes.
duration_minutesintegernullablerequiredPaid minutes: from start to end less the break. Written when the entry is closed or edited, never recomputed later. Null while
open.typestringrequiredKind of time.
One of
worktravelovertimestart_latnumbernullablerequiredLatitude at clock-in, or null.
start_lngnumbernullablerequiredLongitude at clock-in, or null.
start_accuracy_mintegernullablerequiredReported accuracy of that position in metres, or null.
start_sourcestringrequiredWhere the start came from: the phone's position, the phone without a position, or typed in afterwards by the office.
One of
gpsmanualofficeend_latnumbernullablerequiredLatitude at clock-out, or null.
end_lngnumbernullablerequiredLongitude at clock-out, or null.
end_accuracy_mintegernullablerequiredReported accuracy in metres, or null.
end_sourcestringrequiredWhere the end came from.
One of
gpsmanualofficewithin_geofencebooleannullablerequiredWhether the clock-in was inside the job address's geofence; null when there was nothing to compare (no position, or no fence on the address).
statusstringrequiredopen: clocked in, still running.submitted: clocked out, waiting for approval.approved: signed off — billable, and ready for payroll.rejected: sent back, with arejection_reason.One of
opensubmittedapprovedrejectedapproved_bystring(uuid)nullablerequiredThe member who approved — or rejected — it, or null.
approved_atstring(date-time)nullablerequiredWhen it was approved, or null (also after a rejection).
rejection_reasonstringnullablerequiredWhy it was rejected, or null.
billablebooleanrequiredWhether the time may be invoiced. Unbillable time is still paid and still shown on the invoice basis, as an unbilled line.
travelreported from the field app starts unbillable until somebody marks it billable; billable travel is invoiced as travel, never with a ROT/RUT deduction.hourly_rate_centsintegernullablerequiredThe employee's
hourly_cost_centscopied at approval, in öre; null before. What the hour cost — for payroll and margin, never invoiced. A later change of the employee's rate does not change it.billing_rate_centsintegernullablerequiredThe employee's
hourly_billing_rate_centscopied at approval, in öre excluding VAT; null before. What the hour is invoiced at when the job was not quoted one service price per hour. A later change of the employee's rate does not change it.notestringnullablerequiredFree text, or null.
metadataobjectrequiredFree-form data.
created_atstring(date-time)requiredISO 8601 date-time, in UTC.
updated_atstring(date-time)requiredISO 8601 date-time, in UTC.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Correct a time entry
/time-entries/{timeEntry}Corrects an entry that is not approved (open, submitted or rejected); an approved one answers 409 — reject it first, which leaves a trace. The status does not change.
duration_minutes is recomputed from the result. A changed start or end is marked as typed in by the office (start_source/end_source become office), so a timesheet can tell corrections from clock readings.
curl -X PATCH 'https://api.closepact.com/v1/api/time-entries/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ended_at": "2026-11-03T12:30:00+01:00",
"break_minutes": 30
}'{
"data": {
"id": "01a15c20-4e5f-7253-a4b5-c6d71e243078",
"organization_id": "01a14eb3-c5f0-7c55-8c2e-6a0e1f7b9d21",
"employee_id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01",
"work_order_id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"started_at": "2026-11-03T07:04:00.000000Z",
"clock_in_received_at": "2026-11-03T07:04:02.000000Z",
"ended_at": "2026-11-03T11:30:00.000000Z",
"clock_out_received_at": "2026-11-03T11:30:03.000000Z",
"break_minutes": 30,
"duration_minutes": 236,
"type": "work",
"start_lat": 59.85861,
"start_lng": 17.63893,
"start_accuracy_m": 12,
"start_source": "gps",
"end_lat": 59.85858,
"end_lng": 17.63901,
"end_accuracy_m": 9,
"end_source": "office",
"within_geofence": true,
"status": "submitted",
"approved_by": null,
"approved_at": null,
"rejection_reason": null,
"billable": true,
"hourly_rate_cents": null,
"billing_rate_cents": null,
"note": "Extra tid på ugnen.",
"metadata": [],
"created_at": "2026-11-03T07:04:00.000000Z",
"updated_at": "2026-11-04T07:10:00.000000Z"
}
}Path parameters
timeEntrystring(uuid)requiredThe time entry id.
Request body
application/json · required
started_atstring(date-time)New start. ISO 8601 date-time. An offset is honoured (
2026-11-03T08:00:00+01:00is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.ended_atstring(date-time)nullableNew end; after
started_atwhen both are sent. ISO 8601 date-time. An offset is honoured (2026-11-03T08:00:00+01:00is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.break_minutesintegerUnpaid break in minutes, 0–1440.
0–1,440
work_order_idstring(uuid)nullableMove the time to another of your jobs, or null for none.
billablebooleanWhether the time may be invoiced.
notestringnullableFree text.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredOne stretch of reported time. What the worker reported (
started_at,ended_at,break_minutes), what their phone said (coordinates, accuracy,*_source) and what was concluded (within_geofence,status) are kept apart.idstring(uuid)requiredThe time entry id.
organization_idstring(uuid)requiredYour organization.
employee_idstring(uuid)requiredWhose time it is.
work_order_idstring(uuid)nullablerequiredThe job it was spent on, or null (workshop, travel between jobs).
started_atstring(date-time)requiredClock-in, by the phone’s clock at the moment of the tap. A clock-in queued without signal keeps the time it was made, not the time it reached the server.
clock_in_received_atstring(date-time)nullablerequiredWhen the server received the clock-in. Far later than
started_atmeans the phone was offline; null for entries typed in by the office.ended_atstring(date-time)nullablerequiredClock-out, or null while
open.clock_out_received_atstring(date-time)nullablerequiredWhen the server received the clock-out, or null.
break_minutesintegerrequiredUnpaid break within the stretch, in minutes.
duration_minutesintegernullablerequiredPaid minutes: from start to end less the break. Written when the entry is closed or edited, never recomputed later. Null while
open.typestringrequiredKind of time.
One of
worktravelovertimestart_latnumbernullablerequiredLatitude at clock-in, or null.
start_lngnumbernullablerequiredLongitude at clock-in, or null.
start_accuracy_mintegernullablerequiredReported accuracy of that position in metres, or null.
start_sourcestringrequiredWhere the start came from: the phone's position, the phone without a position, or typed in afterwards by the office.
One of
gpsmanualofficeend_latnumbernullablerequiredLatitude at clock-out, or null.
end_lngnumbernullablerequiredLongitude at clock-out, or null.
end_accuracy_mintegernullablerequiredReported accuracy in metres, or null.
end_sourcestringrequiredWhere the end came from.
One of
gpsmanualofficewithin_geofencebooleannullablerequiredWhether the clock-in was inside the job address's geofence; null when there was nothing to compare (no position, or no fence on the address).
statusstringrequiredopen: clocked in, still running.submitted: clocked out, waiting for approval.approved: signed off — billable, and ready for payroll.rejected: sent back, with arejection_reason.One of
opensubmittedapprovedrejectedapproved_bystring(uuid)nullablerequiredThe member who approved — or rejected — it, or null.
approved_atstring(date-time)nullablerequiredWhen it was approved, or null (also after a rejection).
rejection_reasonstringnullablerequiredWhy it was rejected, or null.
billablebooleanrequiredWhether the time may be invoiced. Unbillable time is still paid and still shown on the invoice basis, as an unbilled line.
travelreported from the field app starts unbillable until somebody marks it billable; billable travel is invoiced as travel, never with a ROT/RUT deduction.hourly_rate_centsintegernullablerequiredThe employee's
hourly_cost_centscopied at approval, in öre; null before. What the hour cost — for payroll and margin, never invoiced. A later change of the employee's rate does not change it.billing_rate_centsintegernullablerequiredThe employee's
hourly_billing_rate_centscopied at approval, in öre excluding VAT; null before. What the hour is invoiced at when the job was not quoted one service price per hour. A later change of the employee's rate does not change it.notestringnullablerequiredFree text, or null.
metadataobjectrequiredFree-form data.
created_atstring(date-time)requiredISO 8601 date-time, in UTC.
updated_atstring(date-time)requiredISO 8601 date-time, in UTC.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors