---
title: "Gl Period Close Run"
method: POST
path: "/companies/{company_id}/gl/periods/{period_id}/close-run"
tags: ["gl-actions"]
---

# Gl Period Close Run

`POST /companies/{company_id}/gl/periods/{period_id}/close-run`

## 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}/gl/periods/{period_id}/close-run`
- Auth: none declared

Close a period, validating and snapshotting it — accountant. Consequential, forward-only.

Rebuilds and snapshots the period's balances and records a ``gl_close_run`` carrying
the validation it ran. ``POST …/gl/periods/{period_id}/close`` runs the same close and
returns the period instead of the run.

**The consequence.** With the default ``new_status=closed`` (or ``year_closed``) the
period stops accepting postings — including documents ingested afterwards and dated
inside it, whose AI proposals then fail with "post failed: Period N … is closed". The
status ladder is forward-only: reopen_gl_period is the only way back to ``open``, and
it is an explicit accountant action requiring a written reason, recorded in this same
close-run ledger — never something that happens as a side effect. So close only when
the period's work is actually finished — its documents ingested, its bank entries
reconciled, its held proposals decided — never to tidy up or signal that you are done.
Whatever books the period contains is what gets frozen, so closing over hand-written
entries freezes those instead of the real documents. ``new_status=soft_closed`` signals
the period is finished while it **still accepts postings**; it still snapshots and
records a run.

**Refusals** (422; a refusal rolls the whole transaction back and records no run):

* an illegal transition — the target must rank above the current status
  (``open < soft_closed < closed < year_closed``);
* **draft entries remain** — any close of an ``open`` or ``soft_closed`` period,
  **including a soft close** (a period may not be soft-closed while drafts remain).
  The one exception: a period that is already ``closed`` can move on to
  ``year_closed`` even if it holds drafts, since those are inert until a reopen.
  (Which rule applied is kept in the run's internal validation record; it is not yet
  exposed in this API's response.)
* **the period is busy** (code ``conflict.periodCloseBusy``) — the close could not take
  its fiscal-year or period lock within 15 s, because another close or postings are
  in progress. Nothing was written; retry in a moment.

A period reopened for a correction is re-closed with this operation (or with
``…/close``): the re-close writes a fresh balance-snapshot generation under its own
run, superseding the pre-correction one. A ``year_closed`` period re-closes to plain
``closed`` unless ``new_status=year_closed`` is passed explicitly.

What it REFUSES over is narrow: leftover drafts, plus (409
``conflict.closeRequiredItemsOpen``) any ``required`` item still open on the period's
close to-do list — documents dated in the month still unbooked, accrual reversals
dated in it not yet posted, the bank not tied out. (The cut-off candidates are listed
as advisory and never refuse; a ``soft_closed`` close is not held up by pending
reversals.) Pass ``acknowledge: {"codes": […], "reason": "…",
"accounts": {code: [params.accounts…]}}`` to close anyway (an item naming accounts is
covered only for those listed, ZET-444); both are recorded in the run's validation
snapshot. The full list, with the fix for each item, is
``GET …/closing/gl_period/{period_id}``.

## Path parameters

- `company_id` string, required
- `period_id` string, required

## Headers

- `authorization` string, nullable

## Cookies

- `access_token` string, nullable

## Request body

- GlPeriodCloseRequest
  - `new_status` 'soft_closed' | 'closed' | 'year_closed': The statuses a close may move a period **to** (ZET-161). A strict subset of ``GlPeriodStatus``: the request schema used to advertise the full enum, so ``new_status="open"`` validated and then died with a 422 pointing at a reopen operation that did not exist. Reopening is its own operation (``POST …/gl/periods/{period_id}/reopen``), not a close target.
  - `reason` string, nullable
  - `acknowledge` CloseAcknowledgeIn: "These to-do items are open and we are closing anyway — because …". The soft gate (``closing.gate``), shaped after the VAT filing's ``acknowledge_dirty``: a close refuses while ``required`` work stands, unless the caller names each open code and says why. What was open and what was waved through is written into the audit row the close already creates, so the decision is recorded rather than silent.
    - `codes` string[]
    - `reason` string, required
    - `accounts` object: Per code, the accounts acknowledged (the item's params.accounts).

## Response `200`

Successful Response

- GlCloseRunOut
  - `id` string, required
  - `company_id` string, required
  - `period_id` string, required
  - `status` string, required
  - `reason` string, nullable
  - `started_by` string, nullable
  - `started_at` string, date-time, nullable
  - `completed_at` string, date-time, 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)
