---
title: "Accept Autopost Decision"
method: POST
path: "/companies/{company_id}/autopost-decisions/{decision_id}/accept"
tags: ["accounting-settings"]
---

# Accept Autopost Decision

`POST /companies/{company_id}/autopost-decisions/{decision_id}/accept`

## 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}/autopost-decisions/{decision_id}/accept`
- Auth: none declared

One-click accept of a **held** decision (accountant) — posts the AI's proposal.

A person's accept in the app replaces the soft guardrails (confidence, amount cap,
risk flags). An agent's (MCP, API key, chat) is refused (403) unless the row is
``agent_acceptable``. Closed period, sealed year, unknown account: 409/422. The row
flips ``held → posted`` (a second accept is a 409), with ``accepted_by_kind`` the
kind of actor (``mcp`` over MCP).

The optional ``body``, omitted otherwise: ``expense:receipt:item:`` needs
``employee_id`` + ``category_id``; ``autopost:capitalize:`` needs
``fixed_asset_category_id`` when the hold has none, and ``needs_split_source`` a
``reclassify_from_account_code``.

## Path parameters

- `company_id` string, required
- `decision_id` string, required

## Headers

- `authorization` string, nullable

## Cookies

- `access_token` string, nullable

## Request body

- AutopostAcceptIn: Optional ``POST /autopost-decisions/{id}/accept`` body; omit it for other kinds.
  - `employee_id` string, nullable
  - `category_id` string, nullable
  - `fixed_asset_category_id` string, nullable
  - `useful_life_months` integer, nullable
  - `reclassify_from_account_code` string, nullable
  - `confirm_not_card_statement` boolean
  - `separate_document` boolean

## Response `200`

Successful Response

- AutopostDecisionOut: One AI-accountant autonomy decision (the "autonomy activity" feed row). ``proposal_kind`` / ``receipt_route`` / ``receipt_prefill`` are lifted from the persisted proposal by the router's ``_decision_out`` mapper (the raw proposal JSONB is bulky/internal and is NEVER echoed). Route normalization: ``receipt_route='card'`` only when the proposal carries a single ``txn_id`` (a body-less accept replays the attach); a multi-candidate card hold surfaces as ``'claim'`` — the accept then needs the :class:`AutopostAcceptIn` body. ZET-75 review fields — populated only while there is something to review: ``questions`` / ``risk_flags`` / ``coding_proposal`` ride on **held** coding rows only — both coding kinds, ``code`` (invoice coder) and ``bankcode`` (bank coder), since both persist the same review shape (accepted rows keep their replay payload internally but stop echoing it); ``source_label`` is the source document's filename for ``item``-referenced rows (None when the item is gone — the FE then renders no link).
  - `id` string, required
  - `source_key` string, required
  - `outcome` 'posted' | 'held' | 'shadow', required: Outcome recorded for one autonomy decision (``gl_autopost_decision``). ``posted`` — ``auto`` mode, policy cleared, entry was posted (AI). ``held`` — policy did not clear; nothing posted, falls back to human review. ``shadow`` — ``assist`` mode, policy *would* have posted but did not (shadow).
  - `acceptable` boolean
  - `agent_acceptable` boolean
  - `refusal` 'unrelated' | 'card_statement' | 'credit_note' | 'ar_reminder' | 'no_document_date', nullable
  - `reason` string, required
  - `cause` string, nullable
  - `cause_params` object, nullable
  - `effective_min_confidence` string, required
  - `confidence` string, nullable
  - `match_quality` string, nullable
  - `amount_cents` integer, nullable
  - `source_ref_type` string, nullable
  - `source_ref_id` string, nullable
  - `journal_entry_id` string, nullable
  - `journal_entry_reversed` boolean
  - `receipt_waived_by` string, nullable
  - `receipt_waived_at` string, date-time, nullable
  - `created_at` string, date-time, required
  - `accepted_by` string, nullable
  - `accepted_by_name` string, nullable
  - `accepted_at` string, date-time, nullable
  - `accepted_by_kind` 'human' | 'system' | 'ai' | 'api' | 'mcp' | 'chat': What KIND of actor posted a journal entry — the attribution flag. Two axes in one vocabulary, because the column answers one question — *can a reader take this as a person's own judgement?* **Authorship** — the value says who composed the booking: * ``ai`` = the AI-accountant composed it (an autonomous post, or a one-click accept where the content is still the accountant's proposal); * ``system`` = a mechanical/background ``SystemPrincipal`` post (migrations, close automation, deterministic seeds). **Channel** — the value says which surface a person's own action arrived through, because "a ``UserPrincipal`` acted" is NOT the same claim as "a person decided". All four are derived from the request edge by :func:`~invoiage_db.repos.gl_posting._resolve_origin`: * ``human`` = an interactive browser session (cookie/JWT auth). The only value that asserts a person was looking at the screen; * ``mcp`` = an API-key request carrying the mcp shell's ``X-Zeno-Surface`` marker — a connected agent, which MAY be relaying a person and may not; * ``api`` = any other API-key request (a script, an integration, an agent calling the REST api directly). Not provably a person either; * ``chat`` = the built-in CFO chat relaying a person's approval in-app. Its *authored* bookings stamp ``ai``; ``chat`` is only for the relay. Widening this vocabulary is a **one-way door on rollback**: the PG type is a native enum converted at load (``pg_enums._pg_enum``), so an api whose Python enum lacks a value raises ``LookupError`` reading any row that carries it. See the ``0112`` migration.
  - `proposal_kind` string, nullable
  - `receipt_route` 'card' | 'claim', nullable
  - `receipt_prefill` ReceiptPrefillOut
    - `amount` string, nullable, required
    - `date` string, nullable, required
    - `vendor` string, nullable, required
    - `is_receipt` boolean, nullable
    - `payment_method` string, nullable
    - `payment_card_last4` string, nullable
    - `paid_date` string, nullable
  - `receipt_claim` ReceiptClaimRefOut: The expense claim a receipt hold has already become (Lane B, F2778; C2b). Read-time join ``expense_claim_line.receipt_item_id`` → ``expense_claim`` - the decision itself carries no claim. Present only on a held ``expense:receipt:`` row whose document is on a claim line; the sentence "became expense claim N - approve it in Payroll → Expenses" needs exactly these three facts.
    - `claim_id` string, required
    - `claim_no` integer, required
    - `status` string, required
  - `source_label` string, nullable
  - `questions` string[], nullable
  - `advisories` string[], nullable
  - `risk_flags` RiskFlagOut[], nullable
    - `severity` string, nullable
    - `code` string, nullable
    - `message` string, nullable
  - `coding_proposal` CodingProposalOut: Typed projection of a HELD coding proposal (ZET-75). Lifted by the router only while ``outcome=held`` and the payload is a coding kind — ``code`` (invoice coder) or ``bankcode`` (bank coder) — what a reviewer needs to judge the booking (lines, dates, currency), not the raw replay payload (which stays internal and also survives on accepted rows). ``document_no`` and ``has_recognition`` are invoice-coder facts: a bank coding carries neither, so they read None/False.
    - `currency` string, nullable
    - `posting_date` string, nullable
    - `document_no` string, nullable
    - `has_recognition` boolean
    - `fx` CodingProposalFxOut: Foreign-currency marker on a held coding — the review-surface twin of :class:`~invoiage_core.accounting.reconciliation.schemas.reconciliation.CodingInferenceFx`. Both coders persist the same additive ``fx`` block. **Read ``translation`` first**: * ``converted`` (ZET-384 — the only kind written now, by both coders) — the lines are the document's (or movement's) OWN figures in ``entry_currency``, and ``CodingProposalOut.currency`` is that currency too. Accepting books them converted into ``base_currency`` at the rate published for the booking date — ``rate`` when one was known at hold time — or is refused while none is published. Nothing is ever booked at the foreign face value. * ``rate`` (ZET-203, historical invoice codings) — the lines were restated in the base currency at the rate in ``rate``; the row was held for some other reason. * ``nominal_1to1`` (R2-25, historical) — no rate was captured. On a ``bankcode`` row ``currency`` is the BASE currency (the nominal relabelled 1:1). Since ZET-384 such a row is converted on accept, never booked 1:1. Fields are optional (unlike ``CodingInferenceFx``, whose three are required) because this projects raw JSONB with no schema guarantee — the same defensive stance as :class:`CodingProposalLineOut`. A partial block still yields a caveat instead of a 500.
      - `entry_currency` string, nullable
      - `base_currency` string, nullable
      - `translation` string, nullable
      - `rate` string, nullable
    - `lines` CodingProposalLineOut[]
      - `side` string, nullable
      - `account` string, nullable
      - `vat_code` string, nullable
      - `amount_cents` integer, nullable
      - `amount_basis` string, nullable
      - `text` string, nullable
      - `new_account_name` string, nullable
    - `account` string, nullable
    - `account_reasons` string[], nullable
    - `prefill_account` string, nullable
  - `coding_recommendation` CodingRecommendationOut: The account a HELD document should most likely be coded to (ZET-386). Only a suggestion for the person coding it by hand — never what Accept & post books (that is ``coding_proposal``). ``source``: ``human_history`` = this supplier's latest coding a person decided; ``ai_history`` = its latest coding at all; ``accountant`` = the AI accountant's proposal for a receipt from a supplier with no history.
    - `account_code` string, required
    - `source` 'human_history' | 'ai_history' | 'accountant', required
  - `precedent_accounts` string[], nullable
  - `capitalization_proposal` CapitalizationProposalOut: Typed projection of a HELD fixed-asset capitalization proposal (J2). Lifted by the router only while ``outcome=held`` and ``kind=='capitalize'`` — what a reviewer needs to judge "should this purchase go into the asset register?", not the raw replay payload (which stays internal and also survives on accepted rows). ``needs_category`` is the one field the UI must act on: when true the accept body has to carry a ``fixed_asset_category_id``, because no category claims the extracted ``asset_kind`` (or several do — then ``ambiguous_category`` too, which is a settings problem rather than a document one). ``needs_split_source`` is its mixed-invoice sibling: the capital amount fits none (or several) of the coded lines, so the accept has to carry a ``reclassify_from_account_code`` chosen from ``split_source_options`` — the coded candidate account codes, deliberately not the whole chart (the accept completes a fact, it does not re-code the invoice).
    - `name` string, nullable
    - `amount_cents` integer, nullable
    - `asset_kind` string, nullable
    - `fixed_asset_category_id` string, nullable
    - `booked_account_code` string, nullable
    - `needs_category` boolean
    - `ambiguous_category` boolean
    - `needs_split_source` boolean
    - `split_source_options` string[]
  - `duplicate_of` HeldDuplicateOfOut: What a held "duplicate of already-coded document" decision matched (ZET-410). ``matched_on`` names the detector's arm — ``document_no``: the invoice number ALONE (the arm compares nothing else), described by the booking that holds it (entry, date, the holder's counterparty and gross, so a reviewer can see at a glance whether it is the same invoice); ``payment_reference``: the QR/SCOR reference, with the already-booked document (``item_id``, ``item_label``) that carries it. Every holder field is optional: a named document that no longer exists still says what the arm matched on.
    - `matched_on` 'document_no' | 'payment_reference', required
    - `document_no` string, nullable
    - `journal_entry_id` string, nullable
    - `entry_no` string, nullable
    - `entry_date` string, date, nullable
    - `counterparty_name` string, nullable
    - `gross` string, nullable
    - `currency` string, nullable
    - `item_id` string, nullable
    - `item_label` string, nullable
  - `accountant_metadata` object, 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)
