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

GET/time-entries

Reported 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"
Response · 200
{
  "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]string

    Only these statuses. Repeat as filter[status][]=submitted&filter[status][]=approved for several.

    One ofopensubmittedapprovedrejected

    Example approved

  • filter[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:00Z

  • tostring(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 objectsrequired
      • idstring(uuid)required

        The time entry id.

      • organization_idstring(uuid)required

        Your organization.

      • employee_idstring(uuid)required

        Whose time it is.

      • work_order_idstring(uuid)nullablerequired

        The job it was spent on, or null (workshop, travel between jobs).

      • started_atstring(date-time)required

        Clock-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)nullablerequired

        When the server received the clock-in. Far later than started_at means the phone was offline; null for entries typed in by the office.

      • ended_atstring(date-time)nullablerequired

        Clock-out, or null while open.

      • clock_out_received_atstring(date-time)nullablerequired

        When the server received the clock-out, or null.

      • break_minutesintegerrequired

        Unpaid break within the stretch, in minutes.

      • duration_minutesintegernullablerequired

        Paid minutes: from start to end less the break. Written when the entry is closed or edited, never recomputed later. Null while open.

      • typestringrequired

        Kind of time.

        One ofworktravelovertime

      • start_latnumbernullablerequired

        Latitude at clock-in, or null.

      • start_lngnumbernullablerequired

        Longitude at clock-in, or null.

      • start_accuracy_mintegernullablerequired

        Reported accuracy of that position in metres, or null.

      • start_sourcestringrequired

        Where the start came from: the phone's position, the phone without a position, or typed in afterwards by the office.

        One ofgpsmanualoffice

      • end_latnumbernullablerequired

        Latitude at clock-out, or null.

      • end_lngnumbernullablerequired

        Longitude at clock-out, or null.

      • end_accuracy_mintegernullablerequired

        Reported accuracy in metres, or null.

      • end_sourcestringrequired

        Where the end came from.

        One ofgpsmanualoffice

      • within_geofencebooleannullablerequired

        Whether 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).

      • statusstringrequired

        open: clocked in, still running. submitted: clocked out, waiting for approval. approved: signed off — billable, and ready for payroll. rejected: sent back, with a rejection_reason.

        One ofopensubmittedapprovedrejected

      • approved_bystring(uuid)nullablerequired

        The member who approved — or rejected — it, or null.

      • approved_atstring(date-time)nullablerequired

        When it was approved, or null (also after a rejection).

      • rejection_reasonstringnullablerequired

        Why it was rejected, or null.

      • billablebooleanrequired

        Whether the time may be invoiced. Unbillable time is still paid and still shown on the invoice basis, as an unbilled line. travel reported 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_centsintegernullablerequired

        The employee's hourly_cost_cents copied 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_centsintegernullablerequired

        The employee's hourly_billing_rate_cents copied 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.

      • notestringnullablerequired

        Free text, or null.

      • metadataobjectrequired

        Free-form data.

      • created_atstring(date-time)required

        ISO 8601 date-time, in UTC.

      • updated_atstring(date-time)required

        ISO 8601 date-time, in UTC.

      • employeeobjectnullablerequired

        The employee, or null if they have been deleted.

      • work_orderobjectnullablerequired

        The 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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 429The key has made more requests this minute than its limit allows.
    Errors

Approve time entries

POST/time-entries/approve
Scope write Module Scheduling & work orders Permission booking.time.approve

Signs 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"
  ]
}'
Response · 200
{
  "data": {
    "approved": 2
  }
}

Request body

application/json · required

  • idsarray of strings(uuid)required

    The 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.
    • dataobjectrequired
      • approvedintegerrequired

        Entries that went from submitted to approved.

  • 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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 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

POST/time-entries/{timeEntry}/reject
Scope write Module Scheduling & work orders Permission booking.time.approve

Sends 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."
}'
Response · 200
{
  "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)required

    The time entry id.

Request body

application/json · required

  • reasonstringrequired

    Why, 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.
    • dataobjectrequired

      One 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)required

        The time entry id.

      • organization_idstring(uuid)required

        Your organization.

      • employee_idstring(uuid)required

        Whose time it is.

      • work_order_idstring(uuid)nullablerequired

        The job it was spent on, or null (workshop, travel between jobs).

      • started_atstring(date-time)required

        Clock-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)nullablerequired

        When the server received the clock-in. Far later than started_at means the phone was offline; null for entries typed in by the office.

      • ended_atstring(date-time)nullablerequired

        Clock-out, or null while open.

      • clock_out_received_atstring(date-time)nullablerequired

        When the server received the clock-out, or null.

      • break_minutesintegerrequired

        Unpaid break within the stretch, in minutes.

      • duration_minutesintegernullablerequired

        Paid minutes: from start to end less the break. Written when the entry is closed or edited, never recomputed later. Null while open.

      • typestringrequired

        Kind of time.

        One ofworktravelovertime

      • start_latnumbernullablerequired

        Latitude at clock-in, or null.

      • start_lngnumbernullablerequired

        Longitude at clock-in, or null.

      • start_accuracy_mintegernullablerequired

        Reported accuracy of that position in metres, or null.

      • start_sourcestringrequired

        Where the start came from: the phone's position, the phone without a position, or typed in afterwards by the office.

        One ofgpsmanualoffice

      • end_latnumbernullablerequired

        Latitude at clock-out, or null.

      • end_lngnumbernullablerequired

        Longitude at clock-out, or null.

      • end_accuracy_mintegernullablerequired

        Reported accuracy in metres, or null.

      • end_sourcestringrequired

        Where the end came from.

        One ofgpsmanualoffice

      • within_geofencebooleannullablerequired

        Whether 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).

      • statusstringrequired

        open: clocked in, still running. submitted: clocked out, waiting for approval. approved: signed off — billable, and ready for payroll. rejected: sent back, with a rejection_reason.

        One ofopensubmittedapprovedrejected

      • approved_bystring(uuid)nullablerequired

        The member who approved — or rejected — it, or null.

      • approved_atstring(date-time)nullablerequired

        When it was approved, or null (also after a rejection).

      • rejection_reasonstringnullablerequired

        Why it was rejected, or null.

      • billablebooleanrequired

        Whether the time may be invoiced. Unbillable time is still paid and still shown on the invoice basis, as an unbilled line. travel reported 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_centsintegernullablerequired

        The employee's hourly_cost_cents copied 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_centsintegernullablerequired

        The employee's hourly_billing_rate_cents copied 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.

      • notestringnullablerequired

        Free text, or null.

      • metadataobjectrequired

        Free-form data.

      • created_atstring(date-time)required

        ISO 8601 date-time, in UTC.

      • updated_atstring(date-time)required

        ISO 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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 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

PATCH/time-entries/{timeEntry}
Scope write Module Scheduling & work orders Permission booking.time.approve

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
}'
Response
{
  "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)required

    The 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:00 is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.

  • ended_atstring(date-time)nullable

    New end; after started_at when both are sent. ISO 8601 date-time. An offset is honoured (2026-11-03T08:00:00+01:00 is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.

  • break_minutesinteger

    Unpaid break in minutes, 0–1440.

    0–1,440

  • work_order_idstring(uuid)nullable

    Move the time to another of your jobs, or null for none.

  • billableboolean

    Whether the time may be invoiced.

  • notestringnullable

    Free text.

Responses

  • Headers

    • X-RateLimit-Limit — Requests this key may make per minute.
    • X-RateLimit-Remaining — Requests left in the current minute.
    • dataobjectrequired

      One 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)required

        The time entry id.

      • organization_idstring(uuid)required

        Your organization.

      • employee_idstring(uuid)required

        Whose time it is.

      • work_order_idstring(uuid)nullablerequired

        The job it was spent on, or null (workshop, travel between jobs).

      • started_atstring(date-time)required

        Clock-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)nullablerequired

        When the server received the clock-in. Far later than started_at means the phone was offline; null for entries typed in by the office.

      • ended_atstring(date-time)nullablerequired

        Clock-out, or null while open.

      • clock_out_received_atstring(date-time)nullablerequired

        When the server received the clock-out, or null.

      • break_minutesintegerrequired

        Unpaid break within the stretch, in minutes.

      • duration_minutesintegernullablerequired

        Paid minutes: from start to end less the break. Written when the entry is closed or edited, never recomputed later. Null while open.

      • typestringrequired

        Kind of time.

        One ofworktravelovertime

      • start_latnumbernullablerequired

        Latitude at clock-in, or null.

      • start_lngnumbernullablerequired

        Longitude at clock-in, or null.

      • start_accuracy_mintegernullablerequired

        Reported accuracy of that position in metres, or null.

      • start_sourcestringrequired

        Where the start came from: the phone's position, the phone without a position, or typed in afterwards by the office.

        One ofgpsmanualoffice

      • end_latnumbernullablerequired

        Latitude at clock-out, or null.

      • end_lngnumbernullablerequired

        Longitude at clock-out, or null.

      • end_accuracy_mintegernullablerequired

        Reported accuracy in metres, or null.

      • end_sourcestringrequired

        Where the end came from.

        One ofgpsmanualoffice

      • within_geofencebooleannullablerequired

        Whether 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).

      • statusstringrequired

        open: clocked in, still running. submitted: clocked out, waiting for approval. approved: signed off — billable, and ready for payroll. rejected: sent back, with a rejection_reason.

        One ofopensubmittedapprovedrejected

      • approved_bystring(uuid)nullablerequired

        The member who approved — or rejected — it, or null.

      • approved_atstring(date-time)nullablerequired

        When it was approved, or null (also after a rejection).

      • rejection_reasonstringnullablerequired

        Why it was rejected, or null.

      • billablebooleanrequired

        Whether the time may be invoiced. Unbillable time is still paid and still shown on the invoice basis, as an unbilled line. travel reported 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_centsintegernullablerequired

        The employee's hourly_cost_cents copied 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_centsintegernullablerequired

        The employee's hourly_billing_rate_cents copied 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.

      • notestringnullablerequired

        Free text, or null.

      • metadataobjectrequired

        Free-form data.

      • created_atstring(date-time)required

        ISO 8601 date-time, in UTC.

      • updated_atstring(date-time)required

        ISO 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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 404No such record in this organization.
    Errors
  • Errors
  • 422The request body or query failed validation.
    Errors
  • 429The key has made more requests this minute than its limit allows.
    Errors