Labour costs
Rostered and actual hours and cost per calendar day and location, against the location's labour target. Rostered figures need rostering access and actual figures need timesheet access; costs need pay access. Days with no shifts or timesheets are left out. Sales are not included, so the labour percentage itself is yours to work out.
Needs one of the roster:read or timesheets:read permissions, depending on the operation. The business sees it as "Rosters and shifts".
Fields
| Field | Type | Access | Meaning |
|---|---|---|---|
| date | date | Read-only | The calendar day in the location's timezone. |
| location_id | string | Read-only | The location. |
| position_id | string, may be null | Read-only | The position, when grouped by position. |
| target_labour_percentage | number, may be null | Read-only | The location's labour cost target, as a percentage of sales. Returned only with settings:read. |
| rostered | object | Read-only | Shifts starting that day, published and draft. Returned only with roster:read. |
| actual | object | Read-only | Timesheets starting that day, approved and pending. Rejected and still-open timesheets are left out. Returned only with timesheets:read. |
Operations
List rostered and actual labour by day and location
GET/v1/labour-costs
Permission: roster:read, timesheets:read Needs any one of: roster:read, timesheets:read.
| Parameter | Type | Meaning | |
|---|---|---|---|
| from | date | Required | The first day of the range, in the location's timezone. With to, at most 31 days. |
| to | date | Required | The last day of the range, inclusive. |
| location_id | string | Optional | Only this location. |
| group_by | "position" | Optional |
{
"data": [
{
"date": "2026-10-06",
"location_id": "66f1c2a4b3d9e8f7a6b5c4d3",
"position_id": "66f1c2a4b3d9e8f7a6b5c4d3",
"target_labour_percentage": 1.5,
"rostered": {
"published_shift_count": null,
"published_hours": null,
"published_cost": null,
"draft_shift_count": null,
"draft_hours": null,
"draft_cost": null,
"open_shift_count": null
},
"actual": {
"approved_hours": null,
"approved_count": null,
"approved_cost": null,
"pending_hours": null,
"pending_count": null,
"pending_cost": null
}
}
],
"has_more": true,
"next_cursor": "text"
}| Status | When |
|---|---|
| 400 | The error's code is invalid_request or range_too_long. The Errors page describes each code. |
| 401 | The error's code is invalid_token or token_expired. The Errors page describes each code. |
| 403 | The error's code is origin_not_allowed, permission_denied, not_available, connection_disconnected, connection_suspended or app_suspended. The Errors page describes each code. |
| 404 | There is nothing at this address, or no such record in this business. |
| 429 | Too many requests. Retry-After says when to try again. |
| 500 | Something went wrong at Shiftly. Quote the request id. |
| 503 | This environment is not set up for partner apps yet. |
