Periods and locking
Payroll period kinds, how export commit and period lock freeze hours, the preconditions for locking, and how to handle corrections after a period is closed.
Last updated: · Markdown version
Payroll period kinds
kind |
Range | Notes |
|---|---|---|
week |
ISO week, Monday–Sunday | The default |
four_weeks |
28 days from an anchor Monday | The admin sets the anchor in "Instellingen"; default anchor 2024-01-01 |
semi_month |
1st–15th and 16th–end of month | — |
month |
Calendar month | — |
Admins set the org's period kind in "Instellingen" → "Organisatie en verloning". The API accepts any startsOn/endsOn range for exports and locks, so you can export a month in one call. The dashboard's overview, export and lock screens still work per week (known gap).
Two freezing steps
| Step | Endpoint | Who | Effect | Event |
|---|---|---|---|---|
| Export commit | POST /exports {"mode":"commit"} |
write key / office | Approved days in range → exported. Approved expenses → exported. Approved leave days are recorded as exported (each leave day is exported once). An immutable export run is stored |
export.completed |
| Period lock | POST /periods/lock {startsOn, endsOn, kind} |
admin key or admin user | Exported days in range → locked. The period is recorded as locked |
period.locked |
Both are irreversible. Typically you commit when the file goes to payroll and lock after payroll has processed it.
Commit rules
- Only
approved, conflict-free days are included. The preview'spendingCounttells you how many days in the range are stilldraftorsubmitted. - A commit fails with 422 when nothing is approved (
"Nothing approved to export in this period") or when the mapping reports validation issues ("Export has N validation issue(s)"). - Committing the same range twice within one minute returns 409 (
"This period was just exported; refresh before exporting again"). - Days exported in an earlier run are not exported again. A later commit over the same range only picks up days approved since.
Lock preconditions
POST /periods/lock refuses with 409 while:
- any day in the range with minutes is still
draft,submittedorapproved(captured:{"code":"conflict","message":"1 day(s) in this period are not exported yet"}), or - any expense in the range is
submittedorapproved("N expense(s) in this period are not exported or rejected yet").
Success returns { "data": { "days": 7 } }, the number of calendar days in the range.
Write keys get 403 {"code":"forbidden","message":"Admin role required"} on this endpoint. Use an API key with the admin scope, an admin user access token, or the dashboard ("Export" → "Periode vergrendelen"). tt lock and the MCP tool lock_period work when they run with an admin key.
Corrections after export or lock
Exported and locked days are immutable. A claim on such a day is refused with 423 {"code":"locked","message":"This day is exported or locked; book a correction in an open period"} and nothing is stored.
To correct:
- Record the difference as new hours on a date in an open period. For example, a
manual_officeclaim on a day in the current week withnote: "Correction for 2026-12-01: +30 min". A separate correction placement also works if your payroll needs it. - Approve and export it in the next run. The payroll system processes the difference.
- For negative corrections, agree a procedure with your payroll provider. Pay lines are never negative.
Reading what was exported
GET /exports/runslists committed runs: id, range, row count, content hash, created at. It is paginated.GET /exports/runs/{id}returns the mapping snapshot,pay_linesandwarnings.GET /exports/runs/{id}/filereturns the exact file as committed (text/csv).GET /days?from=…&to=…&status=exported,lockedshows which days are frozen, withexport_run_id.