# Staff Scheduling and Workforce API

Use the Staff API to maintain staff profiles, plan recurring and one-off shifts, record attendance, and inspect resource coverage. This is useful for service operations such as assigning laundry staff to a store or scheduled service window. Staff and shift IDs are numeric IDs within the authenticated tenant.

All examples use Bearer authentication. Tenant and user identity are resolved from the authenticated request by the gateway; do not send separate identity headers.

```bash
export API="https://api.tradly.app"
export TOKEN="<bearer-token>"
```

## Staff profiles

`GET /v1/staff` lists staff profiles. Optional filters are `account_id`, `department_id`, `status`, and `employment_type`. `GET /v1/staff/{staff_id}` retrieves one profile. These are tenant-admin reads.

`POST /v1/staff` creates a profile and requires `employment_type` (`full_time`, `part_time`, or `contractor`) and `employee_code`; it also accepts fields such as `user_id`, `account_id`, `department_id`, `job_profile_id`, `pay_grade_id`, `manager_id`, `job_title`, `start_date`, `hourly_rate`, `salary`, `currency_id`, and `listing_ids`. Creating or updating staff requires the tenant's HR-manager permission. `listing_ids` associates the staff member with services represented by listings.

```bash
curl -X POST "$API/v1/staff" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "employee_code": "WASH-012",
    "employment_type": "full_time",
    "job_title": "Laundry associate",
    "account_id": 42,
    "listing_ids": [729341]
  }'
```

## Shifts

### List shifts

`GET /v1/staff/shifts`

Optional filters: `account_id`, `staff_id`, `shift_date`, `status`, `from_date`, and `to_date`. Date range bounds are inclusive. Results are ordered by date and start time.

### Create a one-off shift

`POST /v1/staff/shifts`

Requires `shift_manager` permission. Required fields: `staff_id`, `shift_date` (`YYYY-MM-DD`), `start_time`, and `end_time`. Optional fields: `account_id` and `note`. New shifts have `status: "scheduled"`.

```bash
curl -X POST "$API/v1/staff/shifts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "staff_id": 12,
    "account_id": 42,
    "shift_date": "2026-12-10",
    "start_time": "09:00",
    "end_time": "17:00",
    "note": "Front counter and order intake"
  }'
```

### Update or delete a shift

`PUT /v1/staff/shifts/{shift_id}` accepts updates to `account_id`, `shift_date`, `start_time`, `end_time`, `status`, and `note`. `DELETE /v1/staff/shifts/{shift_id}` removes a shift; completed shifts cannot be deleted. Both require `shift_manager` permission.

## Recurring shift templates

`GET /v1/staff/shift-templates` lists templates; optional filters are `account_id` and `active` (`true` or `false`).

`POST /v1/staff/shift-templates` requires `name`, `start_time`, `end_time`, and a non-empty `days_of_week` array. Days use JavaScript weekday numbering: Sunday `0` through Saturday `6`. Optional fields: `account_id`, default `staff_id`, and `note`.

```bash
curl -X POST "$API/v1/staff/shift-templates" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "name": "Weekday morning shift",
    "account_id": 42,
    "staff_id": 12,
    "start_time": "09:00",
    "end_time": "13:00",
    "days_of_week": [1, 2, 3, 4, 5],
    "note": "Laundry intake and sorting"
  }'
```

`PUT /v1/staff/shift-templates/{template_id}` updates template fields. `DELETE` on that path removes it. To generate dated shifts from a template, use `POST /v1/staff/shift-templates/{template_id}/apply` with `from_date` and `to_date`; optional `staff_ids` overrides the template's default staff. If neither the request nor template supplies staff, the API returns an error.

```bash
curl -X POST "$API/v1/staff/shift-templates/5/apply" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{"from_date":"2026-12-01","to_date":"2026-12-31"}'
```

Template creation, updates, deletion, and apply require `shift_manager` permission.

## Attendance

`POST /v1/staff/attendance/clock-in` accepts required `staff_id` and optional `shift_id` and `account_id`. `POST /v1/staff/attendance/clock-out` accepts required `staff_id` and optional `note`. A staff member can clock themselves in or out; acting on another staff profile requires the `workforce.shifts` role permission. A second open clock-in is rejected. Clock-out calculates `hours_worked` and completes a linked shift.

```bash
curl -X POST "$API/v1/staff/attendance/clock-in" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{"staff_id":12,"shift_id":83,"account_id":42}'
```

`GET /v1/staff/attendance` lists records with optional `staff_id`, `account_id`, `from_date`, and `to_date` filters.

## Resource planning

`GET /v1/staff/resource-planning` aggregates shifts by account and date. Optional filters: `from_date`, `to_date`, and `account_id`. Each result includes `account_id`, account details (when assigned), `shift_date`, `shift_count`, and distinct `staff_count`.

```bash
curl "$API/v1/staff/resource-planning?from_date=2026-12-01&to_date=2026-12-31&account_id=42" \
  -H "Authorization: Bearer $TOKEN"
```

## Leave requests and balances

Leave types use active tenant categories with `type: "leave_types"`. `GET /v1/staff/leave-policies` lists policies. `POST /v1/staff/leave-policies` creates one and requires `leave_type_category_id`; optional fields are `name`, `employment_type` (`all`, `full_time`, `part_time`, or `contractor`), `accrual_days_per_year` (or alias `days_per_year`), and carry-over settings.

`PUT /v1/staff/leave-policies/{policy_id}` updates the supplied policy fields using the same names.

`POST /v1/staff/leave-requests` requires `staff_id`, `leave_type_category_id`, `from_date`, `to_date`, and positive `days`; `policy_id` and `note` are optional. A staff member can submit for their own profile; submitting for another staff member requires the `workforce.leave` role permission. New requests start `pending`.

```bash
curl -X POST "$API/v1/staff/leave-requests" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "staff_id": 12,
    "leave_type_category_id": 7,
    "from_date": "2026-12-21",
    "to_date": "2026-12-22",
    "days": 2,
    "note": "Personal leave"
  }'
```

`GET /v1/staff/leave-requests` accepts optional `staff_id` and `status` filters. Users with `leave_manager` permission approve with `PUT /v1/staff/leave-requests/{req_id}/approve` or reject with `PUT /v1/staff/leave-requests/{req_id}/reject`; approval is checked against the configured leave-request workflow and deducts the request from an existing balance.

`GET /v1/staff/leave-balances` accepts `staff_id` and `year`. Tenant admins initialize balances from applicable leave policies with `POST /v1/staff/leave-balances/init` and body `{"year":2026}`; optional `staff_ids` restricts initialization to selected active staff.

## Payroll export

`GET /v1/staff/payroll-runs/{run_id}/export?format=csv` exports a payroll run as a downloadable CSV. Use `format=pdf` for a PDF; it defaults to `csv`. The API returns the appropriate `Content-Type` and attachment filename. This export reads an existing payroll run; it does not create or approve one.

```bash
curl "$API/v1/staff/payroll-runs/17/export?format=csv" \
  -H "Authorization: Bearer $TOKEN" \
  -o payroll.csv

curl "$API/v1/staff/payroll-runs/17/export?format=pdf" \
  -H "Authorization: Bearer $TOKEN" \
  -o payroll.pdf
```

## Endpoint summary

| Method | Endpoint | Purpose |
|---|---|---|
| `GET` | `/v1/staff` | List staff profiles |
| `POST` | `/v1/staff` | Create a staff profile |
| `GET` | `/v1/staff/{staff_id}` | Get a staff profile |
| `GET` / `POST` | `/v1/staff/shifts` | List or create shifts |
| `PUT` / `DELETE` | `/v1/staff/shifts/{shift_id}` | Update or delete a shift |
| `GET` / `POST` | `/v1/staff/shift-templates` | List or create recurring templates |
| `PUT` / `DELETE` | `/v1/staff/shift-templates/{template_id}` | Update or remove a template |
| `POST` | `/v1/staff/shift-templates/{template_id}/apply` | Generate shifts over a date range |
| `GET` | `/v1/staff/resource-planning` | Aggregate scheduled coverage |
| `GET` | `/v1/staff/attendance` | List attendance records |
| `POST` | `/v1/staff/attendance/clock-in` | Clock in |
| `POST` | `/v1/staff/attendance/clock-out` | Clock out |
| `GET` / `POST` | `/v1/staff/leave-policies` | List or create leave policies |
| `PUT` | `/v1/staff/leave-policies/{policy_id}` | Update a leave policy |
| `GET` / `POST` | `/v1/staff/leave-requests` | List requests or submit a leave request |
| `PUT` | `/v1/staff/leave-requests/{req_id}/approve` | Approve a request |
| `PUT` | `/v1/staff/leave-requests/{req_id}/reject` | Reject a request |
| `GET` | `/v1/staff/leave-balances` | List leave balances |
| `POST` | `/v1/staff/leave-balances/init` | Initialize entitlements |
| `GET` | `/v1/staff/payroll-runs/{run_id}/export` | Download CSV or PDF export |

The staff API exposes additional payroll, pay-component, department, and job-profile endpoints not detailed in this scheduling-focused guide.
