Timeclock
Esta página aún no está disponible en tu idioma.
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_inandclock_out.
The time entry object
Section titled “The time entry object”{ "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"}| Field | Type | Description |
|---|---|---|
id | integer | Unique entry ID |
user | string | Team member’s email |
clock_in_at | string | ISO-8601 shift start |
clock_out_at | string|null | ISO-8601 shift end, null while the shift is still open |
duration_minutes | number|null | Gross shift minutes (set at clock-out / on manual entries) |
break_minutes | number | Accumulated unpaid break minutes |
break_started_at | string|null | Set while a break is running on an open shift |
on_break | boolean | The shift currently has a running break |
net_minutes | number|null | duration_minutes − break_minutes for closed shifts |
note | string|null | Shift note |
edited | boolean | The entry was edited after the fact |
auto_closed | boolean | The shift was auto-capped by the runaway-shift sweep |
needs_review | boolean | Flagged for owner review (e.g. auto-closed) |
created_at | string|null | ISO-8601 creation timestamp |
List time entries
Section titled “List time entries”GET /api/v1/timeclock/entriesReturns 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
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
user_email | string | Only shifts for this team member (case-insensitive email match) |
since | string | Only shifts clocked in at/after this time (ISO-8601 or epoch seconds/milliseconds). Inclusive |
until | string | Only shifts clocked in before this time (ISO-8601 or epoch seconds/milliseconds). Exclusive |
open_only | boolean | Only currently-open shifts, no clock-out yet (default: false) |
limit | integer | Page size, 1–100 (default: 25) |
cursor | string | Opaque cursor from a previous response’s next_cursor |
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"Node.js
Section titled “Node.js”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();Response
Section titled “Response”{ "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.
Retrieve a time entry
Section titled “Retrieve a time entry”GET /api/v1/timeclock/entries/:idReturns a single shift.
Scope required: timeclock.read
curl "https://app.benchkey.com/api/v1/timeclock/entries/812" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”Returns the time entry object. Returns 404 if no entry with that ID exists.
Get a team member’s clock status
Section titled “Get a team member’s clock status”GET /api/v1/timeclock/statusReturns 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
Query parameters
Section titled “Query parameters”| Parameter | Required | Type | Description |
|---|---|---|---|
user | yes | string | Team member’s email (case-insensitive) |
curl "https://app.benchkey.com/api/v1/timeclock/status?user=alex@repairshop.com" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”{ "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, "..." : "..." }}| Field | Type | Description |
|---|---|---|
user | string | The email queried |
clocked_in | boolean | The member has an open shift |
on_break | boolean | The open shift has a running break |
entry | object|null | The open time entry, or null when not clocked in |
Payroll summary
Section titled “Payroll summary”GET /api/v1/timeclock/payrollReturns 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
Query parameters
Section titled “Query parameters”| Parameter | Required | Type | Description |
|---|---|---|---|
start | yes | string | Range start (YYYY-MM-DD, tenant-local) |
end | yes | string | Range end (YYYY-MM-DD, tenant-local, inclusive) |
curl "https://app.benchkey.com/api/v1/timeclock/payroll?start=2026-06-22&end=2026-07-05" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”{ "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 } ]}| Field | Type | Description |
|---|---|---|
user | string | Team member’s email |
total_minutes | number | Net worked minutes (breaks deducted) |
total_break_minutes | number | Unpaid break minutes in the period |
total_hours | number | Net hours |
regular_hours | number | Hours at the regular rate |
overtime_hours | number | Hours past 40/week, bucketed on the tenant workweek (week_start) |
hourly_rate | number | Decimal currency units per hour (0 when no rate is configured) |
overtime_rate | number | Decimal currency units per OT hour (defaults to 1.5× hourly) |
regular_pay | number | Pay at the regular rate |
overtime_pay | number | Pay at the overtime rate |
total_pay | number | regular_pay + overtime_pay |
entry_count | integer | Closed shifts counted in the period |
Record a completed shift
Section titled “Record a completed shift”POST /api/v1/timeclock/entriesRecords 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.
Request body
Section titled “Request body”| Field | Required | Type | Description |
|---|---|---|---|
user_email | yes | string | Team member’s email |
clock_in | yes | string | Shift start, ISO-8601 with explicit timezone |
clock_out | yes | string | Shift end, ISO-8601 with explicit timezone |
break_minutes | no | number | Unpaid break minutes within the shift (≥ 0) |
note | no | string|null | Shift note (max 2000 characters) |
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" }'Node.js
Section titled “Node.js”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 201Response
Section titled “Response”Returns the created time entry object with HTTP 201.
Error codes
Section titled “Error codes”| HTTP status | Code | Meaning |
|---|---|---|
400 | invalid_query | A list filter parameter was supplied as an array or object instead of a single scalar value, or a malformed since/until/start/end |
400 | invalid_filter | open_only was supplied with an unrecognized value. Accepted: true/false/1/0/yes/no |
400 | invalid_cursor | The pagination cursor is malformed |
400 | invalid_body | Request body is not a JSON object |
400 | unknown_field | Body contains a field this endpoint doesn’t accept |
400 | missing_field | A required field is absent (user_email, clock_in, clock_out, or the user query parameter) |
400 | invalid_field | A field value is invalid, e.g. a timestamp without an explicit timezone, a negative break_minutes, or a malformed user_email |
400 | note_too_long | note exceeds 2000 characters |
400 | upstream_invalid_request | The 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 |
402 | payment_required | The tenant’s plan does not include the time clock feature |
404 | not_found | No time entry with that ID exists for this tenant |
422 | create_failed | The time entry could not be created |
403 | insufficient_scope | API key lacks timeclock.read or timeclock.write |
See Errors for the full error envelope format.