Factuarea APIDevelopers
Contract

Time tracking follows the employment period

Breaking change: clocking before an employee's hire date or after their termination date now answers 422 with subcode clocking_outside_employment_period. Daily balances add is_unscheduled and is_outside_employment, the monthly sheet and the team summary add a to_date block for the closed days, the employee schedule accepts a date, hire_date becomes editable, and reassigning or unassigning a schedule that has not started yet cancels it.

The time record now respects each employee's employment period: from hire_date to termination_date, both days included. Until now the API accepted clock entries on any date, the monthly sheet hid the minutes clocked on days without a schedule, a wrong hire_date could not be corrected, and the month balance of the current month mixed closed days with days still to come. This release closes those gaps.

Breaking: clocking outside the employment period answers 422

Every write that creates or moves a clock entry now checks its date against the employee's employment period. When the date falls before hire_date or after termination_date, the request is rejected and nothing is written.

OperationDate that is checked
POST /v1/time-entries/clock-inoccurred_at, or the server time when it is omitted.
POST /v1/time-entries/manualstarted_at and ended_at.
POST /v1/time-correctionsThe proposed time of an add_missing_entry (start and end) or adjust_time correction. No request is created.
POST /v1/time-corrections/{time_correction}/approveChecked again at approval time against the current employment period. The request stays pending.
{
  "error": {
    "type": "invalid_request_error",
    "code": "business_rule_violation",
    "subcode": "clocking_outside_employment_period",
    "message": "No se puede fichar el 04/05/2026: el empleado está de alta desde el 11/05/2026.",
    "param": "occurred_at",
    "doc_url": "https://docs.factuarea.com/guides/errors#business_rule_violation",
    "request_id": "req_01HKQS5NTIMEOUTSIDEMPLOY01"
  }
}

The rule compares the calendar date of the timestamp in its own UTC offset, the same date the time sheet assigns the entry to. Pausing, resuming and clocking out of a span that is already open are never blocked, and neither are remove_entry corrections or rejections.

What to do. If your integration clocks employees before their hire date, for example because it creates them with a provisional date, correct hire_date first with PUT /v1/employees/{employee} (see Editable hire_date) and then send the entries. Branch on error.subcode; the message is Spanish, human-facing text that names the hire or termination date. See clocking_outside_employment_period.

Daily flags: is_unscheduled and is_outside_employment

Each item of days[] in GET /v1/time-balances/monthly-sheet and GET /v1/time-balances/employee/{employee} gains two booleans:

Fieldtrue whenEffect on the day
is_unscheduledThe day is inside the employment period but no work schedule is in effect.worked_minutes are the minutes actually clocked; expected_minutes, balance_minutes and overtime_minutes are 0.
is_outside_employmentThe day is before hire_date or after termination_date.expected_minutes is 0 even if a schedule assignment covers the day; balance and overtime are 0. Entries recorded before this release still count as worked minutes.

The two flags are never true at the same time. Before this release, a period without any schedule reported 0 worked minutes, and minutes clocked on a day without a schedule could count as overtime. Now those minutes always appear as worked and never count as overtime. As a result, total_balance_minutes is the sum of the daily balances and can differ from total_worked_minutes − total_expected_minutes when minutes were clocked on those days.

The frozen daily detail of a monthly close report does not include these flags, and closes that were already frozen keep their figures.

to_date versus the total_* fields

The total_* fields of GET /v1/time-balances/monthly-sheet and of each row of GET /v1/time-balances/team-summary keep their meaning: they cover the whole month. In the current month they are therefore a projection that already subtracts the expected minutes of today and of the remaining days, so total_balance_minutes shows a deficit until the month ends.

Both responses now add a to_date block with the accumulation of the closed days, which means every day before today:

"to_date": {
  "through_date": "2026-05-24",
  "expected_minutes": 7200,
  "worked_minutes": 7290,
  "balance_minutes": 90,
  "overtime_minutes": 60
}
  • through_date is the last closed day included, normally yesterday. Today is left out because its workday is still in progress.
  • In a finished month, to_date equals the total_* fields.
  • When no day of the month has closed yet (its first day, or a future month), through_date is null and the four figures are 0.
  • The block is always present in both responses. GET /v1/time-balances/employee/{employee} does not include it.

Show to_date.balance_minutes as the balance so far, and keep the total_* fields for the projection of the month.

Employee schedule on a date

GET /v1/work-schedules/employee/{employee} accepts an optional date query param (Y-m-d, today by default) and returns the schedule in effect on that day. Use it to read a schedule that starts in the future, such as one assigned from a pending hire date:

curl "https://api.factuarea.com/v1/work-schedules/employee/01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c?date=2026-10-12" \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"

Without a schedule in effect on that day it still answers 404 schedule_assignment_not_found. A date that is not a valid Y-m-d date answers 422.

Editable hire_date

PUT /v1/employees/{employee} accepts hire_date (Y-m-d). When it is omitted, it does not change. It must not be later than the employee's termination_date: otherwise the answer is 422 parameter_invalid_value with subcode invalid_hire_date and param termination_date. A malformed date also answers 422.

curl -X PUT https://api.factuarea.com/v1/employees/01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4c" \
  -d '{ "hire_date": "2026-09-01" }'

Changing it never deletes anything. Existing clock entries and schedule assignments are kept, and entries left outside the new employment period stay in the ledger, flagged with is_outside_employment.

The error catalog now includes invalid_hire_date. Both PUT /v1/employees/{employee} and POST /v1/employees/{employee}/deactivate document it with a 422 example; deactivate already returned it when termination_date was earlier than hire_date. See invalid_hire_date.

Cancelling an assignment that has not started

POST /v1/work-schedules/{schedule}/assign and POST /v1/work-schedules/{schedule}/unassign used to answer 422 when the employee's open assignment started in the future and the new date was on or before its start, for example when bringing forward a schedule assigned from a wrong hire date. That assignment is now cancelled: it is closed as an empty span (effective_to equal to its effective_from), kept in the history and never in effect. An unassign without effective_to, which defaults to today, cancels it too.

Moving back the start of an assignment that is already in effect, meaning one that started today or earlier, still answers 422.

Monthly close

POST /v1/monthly-time-record-closes now includes every employee whose employment period overlaps the month (hired on or before its last day and not terminated before its first day), whatever their current status. An employee terminated during the month appears in that month's report and payroll export; one hired after the month is left out. The response and the seal format do not change.

MCP

The tools share the same rules:

  • clock_in, record_manual_time_entry, request_time_correction and approve_time_correction return the same clocking_outside_employment_period rejection.
  • get_monthly_time_sheet and get_employee_time_balance return the daily flags; get_monthly_time_sheet and get_team_time_balance_summary return to_date.
  • get_employee_work_schedule accepts date, and update_employee accepts hire_date.
  • assign_work_schedule, unassign_work_schedule and close_monthly_time_record follow the new assignment and close rules.

See the MCP tool catalog.

Updated endpoints13

EndpointDescription
POST/v1/time-entries/clock-inClock in an employee
POST/v1/time-entries/manualRecord a manual retroactive entry
POST/v1/time-correctionsRequest a time entry correction
POST/v1/time-corrections/{time_correction}/approveApprove a time entry correction
GET/v1/time-balances/monthly-sheetRetrieve an employee’s monthly time sheet
GET/v1/time-balances/employee/{employee}Retrieve an employee’s time balance for a period
GET/v1/time-balances/team-summaryRetrieve the team time balance summary
GET/v1/work-schedules/employee/{employee}Get an employee’s schedule on a date
PUT/v1/employees/{employee}Update an employee
POST/v1/employees/{employee}/deactivateDeactivate an employee
POST/v1/work-schedules/{schedule}/assignAssign a schedule to an employee
POST/v1/work-schedules/{schedule}/unassignUnassign a schedule from an employee
POST/v1/monthly-time-record-closesClose a monthly time record

On this page

Need a hand?Contact support