Skip to content

Timeclock

The Timeclock API exposes your team’s time-clock shifts for payroll export and kiosk integrations. You can list shifts, fetch one, check whether a team member is currently clocked in, pull per-member payroll totals for a date range, and record a completed shift (payroll import / backfill).

Live clock-in/out is not available via the API. The internal clock routes act only on the authenticated app user, they derive the actor from the session, so an API request cannot clock a named team member in or out in real time. The write this resource exposes is the manual-entry path: recording an already-completed shift with both clock_in and clock_out.

{
"object": "time_entry",
"id": 812,
"user": "alex@repairshop.com",
"clock_in_at": "2026-07-06T13:00:00.000Z",
"clock_out_at": "2026-07-06T21:30:00.000Z",
"duration_minutes": 510,
"break_minutes": 30,
"break_started_at": null,
"on_break": false,
"net_minutes": 480,
"note": "Covered front desk",
"edited": false,
"auto_closed": false,
"needs_review": false,
"created_at": "2026-07-06T13:00:00.000Z"
}
FieldTypeDescription
idintegerUnique entry ID
userstringTeam member’s email
clock_in_atstringISO-8601 shift start
clock_out_atstring|nullISO-8601 shift end, null while the shift is still open
duration_minutesnumber|nullGross shift minutes (set at clock-out / on manual entries)
break_minutesnumberAccumulated unpaid break minutes
break_started_atstring|nullSet while a break is running on an open shift
on_breakbooleanThe shift currently has a running break
net_minutesnumber|nullduration_minutes − break_minutes for closed shifts
notestring|nullShift note
editedbooleanThe entry was edited after the fact
auto_closedbooleanThe shift was auto-capped by the runaway-shift sweep
needs_reviewbooleanFlagged for owner review (e.g. auto-closed)
created_atstring|nullISO-8601 creation timestamp

GET /api/v1/timeclock/entries

Returns a cursor-paginated list of time-clock shifts, newest first. Filter by team member (user_email), a [since, until) clock-in window, and open_only=true for currently-running shifts.

Scope required: timeclock.read

ParameterTypeDescription
user_emailstringOnly shifts for this team member (case-insensitive email match)
sincestringOnly shifts clocked in at/after this time (ISO-8601 or epoch seconds/milliseconds). Inclusive
untilstringOnly shifts clocked in before this time (ISO-8601 or epoch seconds/milliseconds). Exclusive
open_onlybooleanOnly currently-open shifts, no clock-out yet (default: false)
limitintegerPage size, 1–100 (default: 25)
cursorstringOpaque cursor from a previous response’s next_cursor
Terminal window
curl "https://app.benchkey.com/api/v1/timeclock/entries?user_email=alex@repairshop.com&since=2026-07-01" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const params = new URLSearchParams({
user_email: "alex@repairshop.com",
since: "2026-07-01",
});
const res = await fetch(
`https://app.benchkey.com/api/v1/timeclock/entries?${params}`,
{ headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } }
);
const { data, has_more, next_cursor } = await res.json();
{
"object": "list",
"data": [ { "object": "time_entry", "id": 812, "user": "alex@repairshop.com" } ],
"has_more": false,
"next_cursor": null
}

Additional resource fields are omitted from this example.

See Pagination for how to page through results.


GET /api/v1/timeclock/entries/:id

Returns a single shift.

Scope required: timeclock.read

Terminal window
curl "https://app.benchkey.com/api/v1/timeclock/entries/812" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"

Returns the time entry object. Returns 404 if no entry with that ID exists.


GET /api/v1/timeclock/status

Returns whether the named team member currently has an open shift, whether they are on break, and the open entry itself. An unknown email is simply not clocked in, no 404.

Scope required: timeclock.read

ParameterRequiredTypeDescription
useryesstringTeam member’s email (case-insensitive)
Terminal window
curl "https://app.benchkey.com/api/v1/timeclock/status?user=alex@repairshop.com" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
{
"object": "timeclock_status",
"user": "alex@repairshop.com",
"clocked_in": true,
"on_break": false,
"entry": {
"object": "time_entry",
"id": 812,
"user": "alex@repairshop.com",
"clock_in_at": "2026-07-06T13:00:00.000Z",
"clock_out_at": null,
"..." : "..."
}
}
FieldTypeDescription
userstringThe email queried
clocked_inbooleanThe member has an open shift
on_breakbooleanThe open shift has a running break
entryobject|nullThe open time entry, or null when not clocked in

GET /api/v1/timeclock/payroll

Returns per-team-member payroll totals (regular/overtime hours and pay, weekly overtime bucketed on the tenant’s configured workweek) for closed shifts whose clock-in falls in the inclusive [start, end] local-date range, computed in the tenant’s report timezone. Per-shift detail is available from List time entries.

Scope required: timeclock.read

ParameterRequiredTypeDescription
startyesstringRange start (YYYY-MM-DD, tenant-local)
endyesstringRange end (YYYY-MM-DD, tenant-local, inclusive)
Terminal window
curl "https://app.benchkey.com/api/v1/timeclock/payroll?start=2026-06-22&end=2026-07-05" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
{
"object": "payroll_summary",
"period": { "start": "2026-06-22", "end": "2026-07-05" },
"week_start": "monday",
"data": [
{
"object": "payroll_line",
"user": "alex@repairshop.com",
"total_minutes": 4800,
"total_break_minutes": 300,
"total_hours": 80,
"regular_hours": 80,
"overtime_hours": 0,
"hourly_rate": 22,
"overtime_rate": 33,
"regular_pay": 1760,
"overtime_pay": 0,
"total_pay": 1760,
"entry_count": 10
}
]
}
FieldTypeDescription
userstringTeam member’s email
total_minutesnumberNet worked minutes (breaks deducted)
total_break_minutesnumberUnpaid break minutes in the period
total_hoursnumberNet hours
regular_hoursnumberHours at the regular rate
overtime_hoursnumberHours past 40/week, bucketed on the tenant workweek (week_start)
hourly_ratenumberDecimal currency units per hour (0 when no rate is configured)
overtime_ratenumberDecimal currency units per OT hour (defaults to 1.5× hourly)
regular_paynumberPay at the regular rate
overtime_paynumberPay at the overtime rate
total_paynumberregular_pay + overtime_pay
entry_countintegerClosed shifts counted in the period

POST /api/v1/timeclock/entries

Records a completed shift for a named team member (payroll import / kiosk backfill). clock_in and clock_out must be ISO-8601 instants with an explicit timezone (Z or ±HH:MM); shifts that overlap an existing entry for the same person are rejected.

Scope required: timeclock.write

Send an Idempotency-Key header to make retries safe. See Idempotency.

FieldRequiredTypeDescription
user_emailyesstringTeam member’s email
clock_inyesstringShift start, ISO-8601 with explicit timezone
clock_outyesstringShift end, ISO-8601 with explicit timezone
break_minutesnonumberUnpaid break minutes within the shift (≥ 0)
notenostring|nullShift note (max 2000 characters)
Terminal window
curl -X POST https://app.benchkey.com/api/v1/timeclock/entries \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: shift-alex-20260706-$(uuidgen)" \
-d '{
"user_email": "alex@repairshop.com",
"clock_in": "2026-07-06T09:00:00-04:00",
"clock_out": "2026-07-06T17:30:00-04:00",
"break_minutes": 30,
"note": "Covered front desk"
}'
const res = await fetch("https://app.benchkey.com/api/v1/timeclock/entries", {
method: "POST",
headers: {
Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
user_email: "alex@repairshop.com",
clock_in: "2026-07-06T09:00:00-04:00",
clock_out: "2026-07-06T17:30:00-04:00",
break_minutes: 30,
}),
});
const entry = await res.json(); // HTTP 201

Returns the created time entry object with HTTP 201.


HTTP statusCodeMeaning
400invalid_queryA list filter parameter was supplied as an array or object instead of a single scalar value, or a malformed since/until/start/end
400invalid_filteropen_only was supplied with an unrecognized value. Accepted: true/false/1/0/yes/no
400invalid_cursorThe pagination cursor is malformed
400invalid_bodyRequest body is not a JSON object
400unknown_fieldBody contains a field this endpoint doesn’t accept
400missing_fieldA required field is absent (user_email, clock_in, clock_out, or the user query parameter)
400invalid_fieldA field value is invalid, e.g. a timestamp without an explicit timezone, a negative break_minutes, or a malformed user_email
400note_too_longnote exceeds 2000 characters
400upstream_invalid_requestThe internal route rejected the shift, e.g. clock_out not after clock_in, the shift overlaps an existing entry for the same person, or the break is longer than the shift
402payment_requiredThe tenant’s plan does not include the time clock feature
404not_foundNo time entry with that ID exists for this tenant
422create_failedThe time entry could not be created
403insufficient_scopeAPI key lacks timeclock.read or timeclock.write

See Errors for the full error envelope format.

System status