---
title: "List Payroll Runs"
method: GET
path: "/companies/{company_id}/payroll/runs"
tags: ["payroll"]
---

# List Payroll Runs

`GET /companies/{company_id}/payroll/runs`

## Request

- Base URL: `https://app.zeno-works.ch` (the document declares no server; this is the origin it was published from)
- URL: `https://app.zeno-works.ch/companies/{company_id}/payroll/runs`
- Auth: none declared

The company's payroll runs — one per wage period — newest period first
(CompanyMember read; the money is aggregate, no per-employee PII).

A run walks ``draft`` → ``calculated`` → ``approved`` → ``posted`` → ``paid`` →
``locked`` (plus ``reversed``); ``run_kind`` separates a ``regular`` run from a
correction/reversal, which are always **separate runs**, never in-place edits, and
are linked by ``original_run_id`` / ``reversed_by_run_id``. ``journal_entry_id`` is
the wage entry it posted (``null`` before ``posted``), and the lifecycle stamps
(``calculated_at``, ``approved_at``, ``posted_at``, ``paid_at``, ``locked_at``) say
when each step happened.

Each row also carries ``employee_count``, ``gross_total``/``net_total`` and the
validation ``error_count``/``warning_count`` — a run with errors must not be
approved. ``staleness`` is computed at read time: non-null means master data
changed after the run was calculated (a retroactive correction), so the frozen
figures no longer match; it is advisory, and its ``action`` says what to do.

Filters: ``status_filter`` narrows to one lifecycle state, ``year`` to runs whose
period starts in that calendar year. No paging. Use
``GET …/payroll/runs/due`` for wage months that have **no** run yet.

## Path parameters

- `company_id` string, required

## Query parameters

- `status_filter` 'draft' | 'calculated' | 'approved' | 'posted' | 'paid' | 'locked' | 'reversed': Run lifecycle (payroll SP0; enforced from SP6 via ``invoiage_core.payroll.states``).
- `year` integer, nullable

## Headers

- `authorization` string, nullable

## Cookies

- `access_token` string, nullable

## Response `200`

Successful Response

- PayrollRunsResponse
  - `runs` PayrollRunOut[], required
    - `id` string, required
    - `period_start` string, date, required
    - `period_end` string, date, required
    - `payment_date` string, date, nullable
    - `status` 'draft' | 'calculated' | 'approved' | 'posted' | 'paid' | 'locked' | 'reversed', required: Run lifecycle (payroll SP0; enforced from SP6 via ``invoiage_core.payroll.states``).
    - `run_kind` 'regular' | 'correction' | 'reversal', required: Run kind: corrections/reversals are separate runs, never in-place edits.
    - `journal_entry_id` string, nullable
    - `created_at` string, date-time, required
    - `calculated_at` string, date-time, nullable
    - `approved_at` string, date-time, nullable
    - `posted_at` string, date-time, nullable
    - `paid_at` string, date-time, nullable
    - `locked_at` string, date-time, nullable
    - `payment_batch_ref` string, nullable
    - `original_run_id` string, nullable
    - `reversed_by_run_id` string, nullable
    - `validated_at` string, date-time, nullable
    - `error_count` integer
    - `warning_count` integer
    - `employee_count` integer
    - `gross_total` string, nullable
    - `net_total` string, nullable
    - `staleness` PayrollStalenessOut: Why this artefact no longer matches the live master data. Absent (``null``) means "not stale". Never blocks anything: a correction over a locked or filed period is reported here, never refused (refusing would restore the dead end ZET-123 fixed).
      - `reasons` string[]
      - `action` string, nullable
      - `changed_fields` string[]
      - `audit_id` string, nullable
      - `changed_at` string, date-time, nullable
      - `reason` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/zeno-works/apis/zeno-api.md) · [All operations](https://skmtc.dev/zeno-works/apis/zeno-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zeno-works/apis/zeno-api/revisions/1eb93dde146c?raw)
