FairCheck HR in your Pocket

Timesheets

Upload the hours people actually worked, as dated shifts. Shifts live on the
employee, so they attach to every payslip whose pay period covers them —
including a fortnightly or monthly payslip, which is priced against each
calendar week's hours separately. Approved rows are evidence and can produce a
shortfall in dollars; unapproved rows are estimates and only ever flag.

POST /timesheets — upload a file

Multipart form upload. Same file the web import accepts (.csv or .xlsx, up
to 5 MB, one row per shift).

curl -X POST https://faircheck.com.au/api/v1/timesheets \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@timesheet.csv"

Add -F dry_run=1 to match rows without writing anything.

Columns

Header row required; column order does not matter; names are case-insensitive.

Column Required Format
employee_ref one of ref/name The payroll id printed on the payslip. Matched first.
employee_name one of ref/name Used when there is no ref. Two people of one name are never guessed between.
work_date yes YYYY-MM-DD or DD/MM/YYYY
start yes HH:MM, 24-hour
finish yes HH:MM; a finish at or before the start reads as overnight
break_minutes no Unpaid break minutes. Blank means none.
approved no yes/no. yes marks the hours as actually worked (evidence).
public_holiday no yes/no

The template is available at GET /payslip-compliance/timesheets/template.csv
when signed in.

Matching

People are matched exactly as payslips are: by payroll ref, then by name. A
person not on file is reported as unmatched and their rows are not written —
create them through the web import (which asks which employer to file them
under) or the employee screen, then upload again. An ambiguous match (two
people share the name and the file carries no ref) is reported and never
resolved by guessing.

Re-uploading rows for dates already on file replaces those dates' shifts, so
the upload is safe to repeat.

Response

201 Created (or 200 OK for a dry run):

{
  "data": {
    "dry_run": false,
    "rows": 12,
    "shifts_written": 10,
    "shifts_replaced": 0,
    "people": [
      {
        "employee_ref": "EMP-042",
        "employee_name": "Jane Citizen",
        "status": "matched",
        "employee_id": 17,
        "rows": 10,
        "hours": 76.0,
        "errors": []
      },
      {
        "employee_ref": null,
        "employee_name": "N Brown",
        "status": "ambiguous",
        "employee_id": null,
        "rows": 2,
        "hours": 15.0,
        "errors": []
      }
    ]
  },
  "errors": null,
  "meta": {"matched": 1, "unmatched": 0, "ambiguous": 1, "invalid": 0}
}

status is one of matched, unmatched, ambiguous, invalid. Row-level
problems (a bad time, an overlapping shift) are listed under errors with the
file line number; a person with any invalid row has none of their rows written.

Status Meaning
201 Written (or 200 for a dry run)
413 File larger than 5 MB
422 Not a readable timesheet (wrong type, or a required column missing), or no file

How the hours are used

Each payslip's check reads the shifts covering its pay period from the employee
record (Rosters::PeriodShifts), splits them into ordinary, overtime, weekend
and public-holiday hours per calendar week, and prices them against the award.
Hours supplied directly on a payslip (hours on POST /payslips) still take
precedence for that payslip. See Payslips.