---
title: "Code Gl Item"
method: POST
path: "/companies/{company_id}/gl/items/{item_id}/coding"
tags: ["gl-actions"]
---

# Code Gl Item

`POST /companies/{company_id}/gl/items/{item_id}/coding`

## 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/items/{item_id}/coding`
- Auth: none declared

Code one ingested invoice manually — JE + open item + VAT ± payment (accountant).

The human booking path for an uncoded item (doc/pipeline.md §"Invoice coding
(phase 2)"): the server composes the control leg + VAT lines from NET amounts
and the company's VAT-code rates, posts as the user's entry (``origin`` default,
not ``ai``), creates the ``gl_open_item`` and records the ``manual:code:``
interlock decision so the AI coder never re-codes the item; a held AI proposal
is superseded. The optional ``paid_on``/``bank_gl_account_id`` pair records the
payment in the same transaction.

⚠️ **Send ``posting_date`` when the document's own date is known.** This paragraph is the
description the ``code_gl_item`` MCP tool presents to an agent — ``FastMCP.from_openapi``
reads the ROUTE description and drops ``GlItemCodingIn``'s own — so it is the one place
the warning reaches the caller most likely to omit an optional field. Omitted, the server
derives ``item.invoice_date or item.date.date()`` — and for an item with no extracted
invoice date that is the **upload** timestamp, i.e. the wrong period and, for a late
upload, the wrong fiscal year (ZET-310). A **dateless** document is deliberately still not
refused — manual coding is the human override path and ZCT-259's recovery depends on it
staying permissive — and its ``gl_autopost_decision.reason`` records the substitution.

⚠️ **What IS refused, since ZCT-510: omitting ``posting_date`` when the date derived from
the document falls in no fiscal year the company has books for and is not the ordinary
next-year rollover** (a prior year, or a gap between two existing years). 409, naming the
remedy. Send ``posting_date`` explicitly, or open the year with
``POST /companies/{company_id}/gl/fiscal-years`` first. ``fiscal_year_opened`` on the
response names the year when a coding *did* open one.

Supplier credit note: positive amounts + ``credit_note: true`` (ZCT-812).

A deduction on a supplier bill (the Bezugsprovision on a Quellensteuer bill): a line with a
NEGATIVE ``net_amount_cents`` and no VAT code, booked on the payables side (ZCT-918). Refused
(422) with a VAT code, on a control/bank/VAT account, on a sale, on a credit note, or when the
lines add up to zero or less.

⚠️ **The bank side may already have booked this spend (ZENO-234).** When a bank movement
of the same counterparty was booked without a receipt and looks like this document's
payment (the same amount, or the only such movement within ±35 days), the coding is
refused with 409 ``conflict.documentPaidByBankCoding`` naming the movement: if the
document is its receipt, link it to that movement (``POST /bank-entries/{entry_id}/items``)
instead of coding it; if it is a genuinely different spend, resend with
``confirm_different_spend: true``. A document already linked as a bank booking's receipt
is refused (409 ``conflict.documentIsBankCodingReceipt``) whatever the flag.

⚠️ **"Already paid" (``paid_on``) is refused on a bank account fed by imported statements
(ZENO-232)** — 409 ``conflict.handPaymentOnFedBankAccount``: the statement line books that
payment. Code the document without ``paid_on`` and settle it from the statement line.

**The number is already booked (409 ``conflict.documentNoAlreadyCoded``, ZET-411).**
If the other booking is a DIFFERENT document that prints the same number, resend with
``separate_document: true``: it books without the number, keeping it as a reference.
Refused (409 ``conflict.separateDocumentIsDuplicate``) when that booking has the same
counterparty, amount and date — then it is the same document.

**A credit-card statement is not a bill** (409 ``conflict.cardStatementOverridable``,
ZCT-876). Its lines are imported in Payroll → Expenses → Cards. A person who has checked the
document is NOT a card statement resends from the Zeno app with
``confirm_not_card_statement: true``; that flag is refused (403) on an API key, so an agent
cannot set it.

Errors: 404 missing/cross-tenant item; 409
already booked / payment already on a bank statement / "already paid" on a
statement-fed bank account (ZENO-232) / the bank side booked this spend (ZENO-234) /
closed period / no bookable fiscal
year for the derived date (ZCT-510) / the
document number is already live / the linker recognised this as an out-of-order
duplicate of a booked payable (the last two ZET-173); 422 bad account or VAT
code, missing direction, or unbalanced input.

## Path parameters

- `company_id` string, required
- `item_id` string, required

## Headers

- `authorization` string, nullable

## Cookies

- `access_token` string, nullable

## Request body

- GlItemCodingIn: Body of ``POST /companies/{id}/gl/items/{item_id}/coding``. ``due_date`` defaults from the item's extracted data. ⚠️ **``posting_date`` does NOT** — and this docstring said it did until ZET-310. ``manual_coding.code_item`` derives ``item.invoice_date or item.date.date()``, so it falls back to the extracted invoice date **only when the item has one**; for a document whose extraction produced no invoice date the fallback is ``item.date`` — **the timestamp the file was UPLOADED**, which is the wrong period and, for a late upload, the wrong fiscal year. So: if you know the date the document bears, **send it**. Do not rely on the default for a dateless item. ⚠️ **This text does NOT reach the MCP tool** — an earlier draft of this docstring said it did, and a code review disproved it by building the surface and reading the live tool. ``FastMCP.from_openapi`` flattens the request body into top-level parameters and drops the body model's own description, so ``code_gl_item``'s description comes from the **route** docstring (``routers/accounting/gl_actions.py``) and the warning above is carried there, not here. What this text does reach is ``openapi.json`` and the generated TypeScript clients in both SPAs. Keep the two in step: the route docstring is the one an agent reads. The server stays permissive about a **dateless** booking by design — manual coding is the human override path (ZCT-259's recovery depends on it) — so that warning and the ``gl_autopost_decision.reason`` the booking leaves behind are what stand in for a refusal. The SPA dialog additionally requires the field, but that is a client rule and does not reach this endpoint's other callers. ⚠️ **Since ZCT-510 there is one refusal on this field's absence, and only one**: omitting it when the derived date falls in **no existing fiscal year** and the R2-32 rollover rule would refuse to open one (a prior year or a gap year, never the ordinary next-year rollover) answers **409** naming the remedy, instead of silently opening that year and booking into it. Sending the field explicitly still books anything. The optional "already paid" pair records the payment in the same transaction (F2: an invoice settled from another account or in cash) — both fields required together.
  - `lines` GlItemCodingLineIn[], required
    - `account_code` string, required
    - `net_amount_cents` integer, required: The line's net in cents. Negative for a deduction on a supplier bill (a Bezugsprovision, a VAT-exempt insurer's refund): such a line carries no VAT code, goes to no control, bank or VAT account, and the lines must add up to more than zero. Zero is refused.
    - `vat_code` string, nullable
    - `vat_amount_cents` integer, nullable: Sales documents only: the VAT the document states on this line when it differs from the VAT code's own rate (an overstated invoice owes the difference, Art. 27 / Art. 96 MWSTV). Omit it to book net × the code's rate.
  - `posting_date` string, date, nullable
  - `due_date` string, date, nullable
  - `paid_on` string, date, nullable
  - `bank_gl_account_id` string, nullable
  - `credit_note` boolean
  - `confirm_different_spend` boolean
  - `customs_assessment` GlCustomsAssessmentIn: F2461 / round 11 D14 — the BAZG customs assessment a supplier document records.
    - `assessment_no` string, required: The assessment's identity (the BAZG Veranlagungsverfügung number). Import VAT is established by the assessment, never by the VAT code alone.
    - `assessed_on` string, date, required: The date of the customs assessment. The document's input VAT is deducted in the VAT period this date falls in (Art. 40(4) MWSTG) — on the collected basis too; the payment date does not move it.
    - `kind` 'assessment' | 'additional': 'assessment' — the original assessment; 'additional' — a genuine additional assessment whose document carries only its increment. A replacement of an earlier assessment is refused (422): reverse the replaced document first.
  - `separate_document` boolean
  - `confirm_not_card_statement` boolean

## Response `201`

Successful Response

- GlItemCodingOut: Result of ``POST /companies/{id}/gl/items/{item_id}/coding``. ``fiscal_year_opened`` (ZCT-510) is the label of the fiscal year this coding had to **open**, or ``null`` when it booked into a year that already existed. The year's chart is provisioned through the label door, so a coding can mint a whole fiscal year as a side effect; before this field nothing on the wire said so, and the ``gl_autopost_decision`` row was the only durable trace.
  - `journal_entry_id` string, required
  - `commercial_document_id` string, required
  - `open_item_id` string, required
  - `payment_id` string, nullable
  - `fiscal_year_opened` integer, 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)
