Timesheets
Returns employees' monthly timesheets — planned, actual, and approved minutes, with pagination and filters by period, department, and employee.
Scope
Requires the timesheets.read scope. Without it, the key gets 403 missing_scope. See Scopes and permissions → Scope table for which permission the key's creator needs to grant this scope.
When a timesheet exists
A timesheet is a document a manager builds manually in the Smengo app (the Timesheets page, the "Timesheets" permission); timesheets are not generated automatically or on a schedule. Until the timesheet for a month has been built, the endpoint returns an empty list for that period. An empty response is not an error — before drawing conclusions, check whether the period has been built at all.
- An employee with no planned days in the period gets no row — timesheet rows are built from schedule days. The API alone can't distinguish "the employee didn't work" from "the timesheet hasn't been built" — the only signal is whether other employees have rows for the same period.
- Terminated employees are excluded entirely — historical timesheets included. Last month's timesheet of an employee deleted from the app yesterday is no longer visible through the API. For accounting archives, export timesheets before deleting employees. Employees with revoked access (
access_revoked) do remain in timesheets. - Timesheet rows carry no names — only
employee_id. A report with names needs a second scope,employees.read(a request to Employees): withtimesheets.readalone you get nothing but UUIDs. On the upside, anemployee_idfrom a timesheet always resolves in/employees— both resources exclude terminated employees symmetrically.
Query parameters
| Parameter | Required | Description |
|---|---|---|
limit |
no | Integer 1..200, defaults to 100. |
cursor |
no | The meta.next_cursor value from the previous response — for the next page. |
from, to |
no | YYYY-MM-DD dates in the organization's timezone, boundaries inclusive. Matched against period — the first day of the month a timesheet belongs to. Passed only as a pair; the maximum range is 366 days. When omitted, you get the current month's timesheets. |
department_id |
no | A department UUID. Matched through the employee's department: you get the timesheets of that department's employees. An unknown or another organization's department_id is not an error, it just returns an empty list. |
employee_id |
no | An employee UUID — exact match. An unknown or foreign employee_id also returns an empty list, not an error. |
The resource has no other parameters: any extra parameter returns 400 validation_error — it's explicitly rejected, not silently ignored (see Conventions → Filters). Passing only from without to (or vice versa) is also rejected — 400 with the hint pass both from and to, or neither.
Mind the period matching: the range from=2026-06-15&to=2026-07-15 catches neither the June nor the July timesheet — the first days of both months (2026-06-01, 2026-07-01) fall outside the range. To get June and July timesheets, request from=2026-06-01&to=2026-07-31.
Request
Without parameters — the current month's timesheets:
curl -s https://smengo.com/api/v1/timesheets \
-H "Authorization: Bearer smg_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
For two months:
curl -s "https://smengo.com/api/v1/timesheets?from=2026-06-01&to=2026-07-31" \
-H "Authorization: Bearer smg_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Response
{
"data": [
{
"id": "e2a4c6b8-0d1f-4e3a-9b7c-5f6a8d0e2c31",
"employee_id": "5e6d9f2a-3b7c-4e2f-9a1d-7c8e2f6b4a10",
"period": "2026-06-01",
"planned_minutes": 10080,
"actual_minutes": 10230,
"approved_minutes": 10080,
"days_from_plan": 0,
"shift_count": 21,
"status": "locked"
},
{
"id": "1f3d5b7a-9c0e-4d2f-8b6a-4c8e0a2d6f13",
"employee_id": "0b2f5e7a-9c31-4d6e-8a2b-4f1e6c9d3a52",
"period": "2026-07-01",
"planned_minutes": 9600,
"actual_minutes": 9012,
"approved_minutes": 0,
"days_from_plan": 3,
"shift_count": 20,
"status": "draft"
}
],
"meta": { "next_cursor": null }
}
Response fields
| Field | Type | Description |
|---|---|---|
id |
string (uuid) |
The timesheet's unique identifier. |
employee_id |
string (uuid) |
The employee the timesheet belongs to (see Employees). |
period |
string (YYYY-MM-01) |
The timesheet's month — always the first day of the month, in the organization's timezone; the list is sorted by this field (plus id as a tiebreaker). |
planned_minutes |
number (int ≥ 0) |
Planned minutes for the month — from the schedule. |
actual_minutes |
number (int ≥ 0) |
Actual minutes — from check-ins. |
approved_minutes |
number (int ≥ 0) |
Approved minutes. |
days_from_plan |
number (int) |
The number of planned days with no valid check-in/check-out pair, where the actual was taken equal to the plan. Computed when the timesheet is built — an indicator of how much of the "actual" is really substituted from the plan. |
shift_count |
number (int) |
The number of the employee's planned days in the period. |
status |
"draft" | "approved" | "locked" |
The timesheet's status: draft, approved, or locked. |
All durations are in whole minutes — there are no fractional hours or decimal values in the response.
Monetary fields are not exposed in v1 — rates, payroll amounts, and the cost of worked time are not available through the API. AI explanations (explanations) and manual adjustments (adjustments) are not selected either, and terminated employees are excluded from the response entirely.
Pagination
The list is sorted by period, id. If there are more timesheets than limit, meta.next_cursor holds a cursor for the next page — pass it verbatim as cursor on the next request:
curl -s "https://smengo.com/api/v1/timesheets?limit=1&cursor=eyJrIjoiMjAyNi0wNy0wMSIsImlkIjoiZTJhNGM2YjgtMGQxZi00ZTNhLTliN2MtNWY2YThkMGUyYzMxIn0" \
-H "Authorization: Bearer smg_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
meta.next_cursor is null once there are no more timesheets. Full pagination rules are in Conventions → Pagination.
What's next
- Check-ins — the raw marks
actual_minutesis built from. - Employees — resolving
employee_id. - Scopes and permissions — which permission is needed to grant a key
timesheets.read.