Year-end close
Close a fiscal year into the annual-result account, lock it, reopen it by reversal, book the shareholders' resolution on the result (appropriation) and catch up the years booked before the books were closed.
GET /v1/entities/{entity}/appropriations
List the appropriations of an entity
The posted appropriations, oldest first, with their destinations and whether a later
reversal cancels them. An appropriation that waits for approval is in the approval queue
(GET /v1/approvals) until it posts.
| Required scope | read (admin implies every scope) |
| Operation id | list_appropriations_v1_entities__entity__appropriations_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | fiscal_year |
integer or null | Only the resolutions on the result of this fiscal year. |
Example request
curl "$ERP_URL/v1/entities/CH01/appropriations" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of AppropriationOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year_close_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year": 1,
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"resolution_date": "2026-09-21",
"reference": "string",
"amount": "string",
"reverses_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reversed": true,
"destinations": [
{}
],
"created_by": {},
"created_at": "2026-09-21T09:30:00Z"
}
]
POST /v1/entities/{entity}/appropriations
Appropriate the result of a closed year
Book the shareholders' resolution on the result of a closed year as one entry dated
resolution_date (source appropriation): for a profit Dr annual_result / Cr the
destinations (retained earnings, legal profit reserve, voluntary reserve, dividend payable),
for a loss the mirror image into the loss carried forward. A human (close_fiscal_year); by
default a second person approves the entry. The sum of the destinations may not exceed what
is left of the result (409 appropriation_exceeds_remainder, also refused by a database
trigger); a partial appropriation leaves the rest visible in remainder. For a Swiss
entity the answer carries warnings and withholding_tax: a dividend bears 35 % withholding
tax, which the company must deduct, booked by hand for now. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | appropriate_v1_entities__entity__appropriations_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Request body
application/json (required): AppropriationInput
| Field | Type | Required | Description |
|---|---|---|---|
fiscal_year |
integer | yes | The fiscal year whose closed result is appropriated. |
resolution_date |
string (date) | yes | The date of the resolution, ISO 8601; the entry is dated then, in an open period (normally in the new year). A dividend is a liability only from this date. |
reference |
string | yes | The resolution or minutes it rests on. (min length 1; max length 500) |
destinations |
array of Destination |
yes | Where the result goes. Their sum may not exceed what is left of the result; a partial appropriation leaves the rest visible. (min items 1) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/appropriations" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"destinations": [
{
"account": "2950",
"amount": "3175.00",
"kind": "legal_profit_reserve"
},
{
"account": "2261",
"amount": "30000.00",
"kind": "dividend_payable"
},
{
"amount": "30325.00",
"kind": "retained_earnings"
}
],
"fiscal_year": 2026,
"reference": "Minutes of the general meeting of 20 May 2027, item 3",
"resolution_date": "2027-05-20"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | AppropriationResult |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": "allow",
"reason": "string",
"rule_id": "string",
"table_id": "string"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
},
"amount": "string",
"remainder_after": "string",
"withholding_tax": "string",
"withholding_tax_due": "2026-09-21",
"warnings": [
"string"
]
}
POST /v1/entities/{entity}/appropriations/{appropriation_id}/reverse
Reverse an appropriation
Withdraw an appropriation with an entry of its own (the lines swapped, dated on), through
the same approval as the appropriation; once posted its amount is free again and the year can
be reopened. A human (close_fiscal_year). 404 for an unknown appropriation; 409 for a
reversal, or an appropriation that is already reversed. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | reverse_appropriation_v1_entities__entity__appropriations__appropriation_id__reverse_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| path | appropriation_id |
string (uuid) | yes | Id of the appropriation (GET /v1/entities/{entity}/appropriations). |
Request body
application/json (required): AppropriationReverseInput
| Field | Type | Required | Description |
|---|---|---|---|
on |
string (date) | yes | The date of the reversing entry, in an open period. |
reason |
string | yes | Why; kept in the audit trail. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/appropriations/3fa85f64-5717-4562-b3fc-2c963f66afa6/reverse" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"on": "2027-06-30",
"reason": "Resolution withdrawn"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | AppropriationResult |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": "allow",
"reason": "string",
"rule_id": "string",
"table_id": "string"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
},
"amount": "string",
"remainder_after": "string",
"withholding_tax": "string",
"withholding_tax_due": "2026-09-21",
"warnings": [
"string"
]
}
GET /v1/entities/{entity}/fiscal-years/{year}/close
Get the year-end close of a year
The year close that holds the fiscal year (pending or closed), else the newest one
(reopened or cancelled), with its result, what is appropriated and what is left
(remainder), the closing entry and the approval waiting for a second person, and the
resolutions booked against the result. 404 when the year was never proposed.
| Required scope | read (admin implies every scope) |
| Operation id | get_close_v1_entities__entity__fiscal_years__year__close_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| path | year |
integer | yes | The fiscal year, the calendar year it ends in, e.g. 2026. |
| query | as_of |
string (date) or null | The day appropriation_overdue is judged on, ISO 8601; default: today. |
Example request
curl "$ERP_URL/v1/entities/CH01/fiscal-years/2026/close" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | YearCloseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"fiscal_year": 1,
"kind": "catch_up",
"status": "cancelled",
"result": "string",
"appropriated": "string",
"remainder": "string",
"appropriation_overdue": true,
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"entry_status": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"totals_digest": "string",
"proposed_by": {},
"proposed_at": "2026-09-21T09:30:00Z",
"closed_by": {},
"closed_at": "2026-09-21T09:30:00Z",
"cancelled_by": {},
"cancelled_at": "2026-09-21T09:30:00Z",
"cancel_reason": "string",
"reopened_by": {},
"reopened_at": "2026-09-21T09:30:00Z",
"reopen_reason": "string",
"reversal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"detail": {},
"appropriations": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year_close_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year": 1,
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"resolution_date": "2026-09-21",
"reference": "string",
"amount": "string",
"reverses_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reversed": true,
"destinations": [
{}
],
"created_by": {},
"created_at": "2026-09-21T09:30:00Z"
}
]
}
POST /v1/entities/{entity}/fiscal-years/{year}/close
Propose the year-end close
Close the fiscal year: write the closing entry and submit it through the approval policy.
A human proposes it (the policy action close_fiscal_year denies agents). It is refused with
409 year_close_blocked, listing blockers, while the year holds a draft entry, an entry or a
document waiting for approval, or while its last period is not open, and with 409
role_missing while the entity has no account with the role annual_result. Otherwise the
year is locked at once: from the proposal until the close is cancelled or reopened, no
other entry is posted into the fiscal year, whatever its source (a payment, the FX
revaluation and a bank posting wait). By default a second person approves the entry
(POST /v1/approvals/{approval_id}/approve, close_entries_need_a_second_person); the
approval computes the totals again and refuses with 409 year_changed if they differ from the
proposal's. The response's submit says whether the entry is posted or waits.
Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | propose_close_v1_entities__entity__fiscal_years__year__close_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| path | year |
integer | yes | The fiscal year, the calendar year it ends in, e.g. 2026. |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/fiscal-years/2026/close" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)"
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | CloseResult |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"close": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"fiscal_year": 1,
"kind": "catch_up",
"status": "cancelled",
"result": "string",
"appropriated": "string",
"remainder": "string",
"appropriation_overdue": true,
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"entry_status": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"totals_digest": "string",
"proposed_by": {},
"proposed_at": "2026-09-21T09:30:00Z",
"closed_by": {},
"closed_at": "2026-09-21T09:30:00Z",
"cancelled_by": {},
"cancelled_at": "2026-09-21T09:30:00Z",
"cancel_reason": "string",
"reopened_by": {},
"reopened_at": "2026-09-21T09:30:00Z",
"reopen_reason": "string",
"reversal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"detail": {},
"appropriations": [
{
"id": null,
"fiscal_year_close_id": null,
"fiscal_year": null,
"entry_id": null,
"entry_number": null,
"resolution_date": null,
"reference": null,
"amount": null,
"reverses_id": null,
"reversed": null,
"destinations": null,
"created_by": null,
"created_at": null
}
]
},
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": "allow",
"reason": "string",
"rule_id": "string",
"table_id": "string"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
}
}
GET /v1/entities/{entity}/fiscal-years/{year}/close-preview
Preview the year-end close
What closing the fiscal year would post, computed now, and what stands in the way.
The closing entry brings every profit and loss account of the year to zero against the account
with the role annual_result; result is the profit (positive) or loss, lines the entry,
totals_digest the fingerprint of the figures it comes from. blockers lists what prevents a
proposal (draft entries and entries or documents waiting for approval dated in the year, a
last period that is not open, a missing annual-result account, a year that is already held);
can_propose is true when there is none. Nothing is stored, so an agent may call it. Closing
entries of the year (source close) are left out of the figures, so the preview of a closed
year is what it was before the close.
| Required scope | read (admin implies every scope) |
| Operation id | close_preview_v1_entities__entity__fiscal_years__year__close_preview_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| path | year |
integer | yes | The fiscal year, the calendar year it ends in, e.g. 2026. |
Example request
curl "$ERP_URL/v1/entities/CH01/fiscal-years/2026/close-preview" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ClosePreview |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"entity": "string",
"fiscal_year": 1,
"result": "string",
"result_account": "string",
"result_account_suggestion": "string",
"lines": [
{
"account": "string",
"name": "string",
"debit": "string",
"credit": "string"
}
],
"totals_digest": "string",
"date": "2026-09-21",
"period": "string",
"period_status": "string",
"status": "cancelled",
"blockers": [
"string"
],
"can_propose": true,
"warnings": [
"string"
],
"blocker_reasons": [
{
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
],
"warning_reasons": [
{
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
]
}
POST /v1/entities/{entity}/fiscal-years/{year}/close/cancel
Cancel a proposed year-end close
Withdraw a proposal that is still pending: its approval request and its closing entry are
rejected and the lock on the year is lifted. A human only (close_fiscal_year), with a
reason, audited as year_close.cancel. A rejected approval does the same. 404 when no
proposal is pending; 409 for a closed year, which is reopened instead.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | cancel_close_v1_entities__entity__fiscal_years__year__close_cancel_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| path | year |
integer | yes | The fiscal year, the calendar year it ends in, e.g. 2026. |
Request body
application/json (required): erp__features__close__contracts__year_close__ReasonInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept in the audit trail and on the close. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/fiscal-years/2026/close/cancel" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "The audit found a missing accrual"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | YearCloseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"fiscal_year": 1,
"kind": "catch_up",
"status": "cancelled",
"result": "string",
"appropriated": "string",
"remainder": "string",
"appropriation_overdue": true,
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"entry_status": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"totals_digest": "string",
"proposed_by": {},
"proposed_at": "2026-09-21T09:30:00Z",
"closed_by": {},
"closed_at": "2026-09-21T09:30:00Z",
"cancelled_by": {},
"cancelled_at": "2026-09-21T09:30:00Z",
"cancel_reason": "string",
"reopened_by": {},
"reopened_at": "2026-09-21T09:30:00Z",
"reopen_reason": "string",
"reversal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"detail": {},
"appropriations": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year_close_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year": 1,
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"resolution_date": "2026-09-21",
"reference": "string",
"amount": "string",
"reverses_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reversed": true,
"destinations": [
{}
],
"created_by": {},
"created_at": "2026-09-21T09:30:00Z"
}
]
}
GET /v1/entities/{entity}/fiscal-years/{year}/opening-balances
Get the opening balances of a fiscal year
The balance sheet at the day before the year starts: what the books continue with. Nothing is posted at the start of a year; after a year-end close the profit and loss accounts start at zero and the balance sheet accounts carry their balances, so this equals the closing balance sheet of the year before, account by account. 422 for a year that does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | opening_balances_v1_entities__entity__fiscal_years__year__opening_balances_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| path | year |
integer | yes | The fiscal year, the calendar year it ends in, e.g. 2026. |
Example request
curl "$ERP_URL/v1/entities/CH01/fiscal-years/2026/opening-balances" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | BalanceSheet |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"entity": "string",
"currency": "string",
"as_of": "2026-09-21",
"assets": "string",
"liabilities": "string",
"equity": "string",
"unclosed_earnings": "string",
"balanced": true,
"sections": [
{
"report_line": "string",
"amount": "string",
"accounts": [
null
],
"comparative": null,
"budget": null
}
],
"translation": {
"functional_currency": "string",
"currency": "string",
"source": "string",
"dataset_version": "string",
"closing_rate": null,
"average_rates": [
null
],
"historical_rates": [
null
],
"difference": "string",
"reconciled": true
},
"comparative": {
"as_of": "2026-09-21",
"assets": {
"amount": null,
"change": null,
"change_pct": null
},
"liabilities": {
"amount": null,
"change": null,
"change_pct": null
},
"equity": {
"amount": null,
"change": null,
"change_pct": null
},
"unclosed_earnings": {
"amount": null,
"change": null,
"change_pct": null
}
},
"notes": [
{
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
]
}
POST /v1/entities/{entity}/fiscal-years/{year}/reopen-close
Reopen a closed fiscal year
The decision to reopen a closed year: it reopens the period that holds the closing entry and
reverses the closing entry itself (same date, same period), then sets the close
reopened; periods reopen as before and the year can be closed again. A human with the
approve scope and the policy's reopen_period (agents are denied), a reason, audited as
year_close.reopen. The reversal needs no second approval: this decision covers it. Refused
with 409, listing them, while a resolution on the result is still posted or waits for approval
(reverse it first); 404 when the year has no closed year close. A period of a closed year
cannot be reopened by POST /v1/periods/{period_id}/reopen while its close stands.
| Required scope | approve (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | reopen_close_v1_entities__entity__fiscal_years__year__reopen_close_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| path | year |
integer | yes | The fiscal year, the calendar year it ends in, e.g. 2026. |
Request body
application/json (required): erp__features__close__contracts__year_close__ReasonInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept in the audit trail and on the close. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/fiscal-years/2026/reopen-close" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "The audit found a missing accrual"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | YearCloseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"fiscal_year": 1,
"kind": "catch_up",
"status": "cancelled",
"result": "string",
"appropriated": "string",
"remainder": "string",
"appropriation_overdue": true,
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"entry_status": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"totals_digest": "string",
"proposed_by": {},
"proposed_at": "2026-09-21T09:30:00Z",
"closed_by": {},
"closed_at": "2026-09-21T09:30:00Z",
"cancelled_by": {},
"cancelled_at": "2026-09-21T09:30:00Z",
"cancel_reason": "string",
"reopened_by": {},
"reopened_at": "2026-09-21T09:30:00Z",
"reopen_reason": "string",
"reversal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"detail": {},
"appropriations": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year_close_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fiscal_year": 1,
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"resolution_date": "2026-09-21",
"reference": "string",
"amount": "string",
"reverses_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reversed": true,
"destinations": [
{}
],
"created_by": {},
"created_at": "2026-09-21T09:30:00Z"
}
]
}
POST /v1/entities/{entity}/year-close/catch-up
Close the years booked before the books were closed
For an entity that has been posting for years: one entry (source close), dated the first
day of first_clean_year, brings the cumulative balance of every profit and loss account of
everything before that date to zero against retained_earnings. The most recent year may stay
unappropriated (leave_latest_year_unappropriated): its result goes to annual_result and
is appropriated later like a closed year's. A human (close_fiscal_year); by default a second
person approves; the same lock, digest and cancellation apply as to a year-end close. 409
catch_up_period_not_open names the first period of the year when it is not open (open it
or name a later year). Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | catch_up_v1_entities__entity__year_close_catch_up_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Request body
application/json (required): CatchUpInput
| Field | Type | Required | Description |
|---|---|---|---|
first_clean_year |
integer | yes | The first fiscal year that starts with its profit and loss accounts at zero. The entry is dated its first day, in its first period, which must be open. |
leave_latest_year_unappropriated |
boolean or null | Whether the year before first_clean_year stays in the annual-result account because its general meeting is still ahead (its result is then appropriated later, like a closed year's). Default: true when that year ended less than six months before the first day of first_clean_year. Every earlier year goes to retained earnings. |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/year-close/catch-up" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"first_clean_year": 2027,
"leave_latest_year_unappropriated": true
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | CatchUpResult |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"closes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"fiscal_year": 1,
"kind": "catch_up",
"status": "cancelled",
"result": "string",
"appropriated": "string",
"remainder": "string",
"appropriation_overdue": true,
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_number": "string",
"entry_status": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"totals_digest": "string",
"proposed_by": {},
"proposed_at": "2026-09-21T09:30:00Z",
"closed_by": {},
"closed_at": "2026-09-21T09:30:00Z",
"cancelled_by": {},
"cancelled_at": "2026-09-21T09:30:00Z",
"cancel_reason": "string",
"reopened_by": {},
"reopened_at": "2026-09-21T09:30:00Z",
"reopen_reason": "string",
"reversal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"detail": {},
"appropriations": [
null
]
}
],
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": "allow",
"reason": "string",
"rule_id": "string",
"table_id": "string"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
},
"warnings": [
"string"
]
}
Schemas
AppropriationInput
| Field | Type | Required | Description |
|---|---|---|---|
fiscal_year |
integer | yes | The fiscal year whose closed result is appropriated. |
resolution_date |
string (date) | yes | The date of the resolution, ISO 8601; the entry is dated then, in an open period (normally in the new year). A dividend is a liability only from this date. |
reference |
string | yes | The resolution or minutes it rests on. (min length 1; max length 500) |
destinations |
array of Destination |
yes | Where the result goes. Their sum may not exceed what is left of the result; a partial appropriation leaves the rest visible. (min items 1) |
AppropriationOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the appropriation. |
fiscal_year_close_id |
string (uuid) | yes | The year close it appropriates. |
fiscal_year |
integer | yes | The fiscal year whose result it appropriates. |
entry_id |
string (uuid) | yes | The journal entry (source appropriation). |
entry_number |
string or null | yes | Its number. |
resolution_date |
string (date) | yes | The date of the shareholders' resolution. |
reference |
string | yes | The resolution or minutes it rests on. |
amount |
string | yes | Signed like the result of the year: positive for a profit appropriated, negative for a loss carried forward; the reversal of an appropriation has the opposite sign. |
reverses_id |
string (uuid) or null | yes | For a reversal, the appropriation it reverses; else null. |
reversed |
boolean | yes | True when a later reversal cancels this appropriation. |
destinations |
array of object | yes | Where it went: kind, account code and amount. |
created_by |
object | yes | Who recorded it, as an actor object. |
created_at |
string (date-time) | yes | When it was recorded (UTC). |
AppropriationResult
| Field | Type | Required | Description |
|---|---|---|---|
submit |
SubmitResult |
yes | How the entry went through the approval policy (a second person approves it by default). |
amount |
string | yes | The signed amount appropriated, like the result. |
remainder_after |
string | yes | What would be left of the result once this and every earlier appropriation is posted. |
withholding_tax |
string or null | yes | For a Swiss entity with a dividend destination: the 35 % withholding tax the entry books as payable (dividends under the notification procedure excluded). Null otherwise. |
withholding_tax_due |
string (date) or null | When the withholding tax (or the notification) is due: 30 days after the dividend falls due, taken as the resolution date (Art. 16 para. 1 lit. c VStG). Null without a Swiss dividend. | |
warnings |
array of string | yes | Things to do by hand: the Swiss withholding tax on a dividend and the legal profit reserve. |
AppropriationReverseInput
| Field | Type | Required | Description |
|---|---|---|---|
on |
string (date) | yes | The date of the reversing entry, in an open period. |
reason |
string | yes | Why; kept in the audit trail. (min length 3; max length 1000) |
CatchUpInput
| Field | Type | Required | Description |
|---|---|---|---|
first_clean_year |
integer | yes | The first fiscal year that starts with its profit and loss accounts at zero. The entry is dated its first day, in its first period, which must be open. |
leave_latest_year_unappropriated |
boolean or null | Whether the year before first_clean_year stays in the annual-result account because its general meeting is still ahead (its result is then appropriated later, like a closed year's). Default: true when that year ended less than six months before the first day of first_clean_year. Every earlier year goes to retained earnings. |
CatchUpResult
| Field | Type | Required | Description |
|---|---|---|---|
closes |
array of YearCloseOut |
yes | The year closes the entry belongs to (kind catch_up): the earlier years carried forward, and the latest year when it stays unappropriated. |
submit |
SubmitResult |
yes | How the entry went through the approval policy. |
warnings |
array of string | yes | Things to know, e.g. a year left unappropriated. |
CloseLine
| Field | Type | Required | Description |
|---|---|---|---|
account |
string | yes | Account code. |
name |
string | yes | Account name. |
debit |
string | yes | Debit in the functional currency; zero on a credit line. |
credit |
string | yes | Credit in the functional currency; zero on a debit line. |
ClosePreview
What closing a fiscal year would post, computed now; nothing is stored.
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Entity code. |
fiscal_year |
integer | yes | The fiscal year. |
result |
string | yes | The result of the year in the functional currency: a profit is positive, a loss negative. It is what the closing entry puts on the annual-result account. |
result_account |
string or null | yes | Code of the account with the role annual_result; null when the entity has none (a proposal is refused until it has). |
result_account_suggestion |
string or null | yes | When result_account is null: the code the entity's chart template gives the annual-result account (2979 in the Swiss chart, 3199 in the generic one), to add with POST /v1/entities/{entity}/accounts and the role annual_result (one person); null when the entity has the account or its template is not known. |
lines |
array of CloseLine |
yes | The closing entry: every profit and loss account with a balance brought to zero, and the result on the annual-result account. |
totals_digest |
string | yes | SHA-256 of the account totals the lines come from; the approval checks it again. |
date |
string (date) or null | yes | The date of the entry: the last day of the year; null for a year that has no periods. |
period |
string or null | yes | The period the entry is posted in, YYYY-NN: the year's last adjustment period when it has any, else its last regular period. |
period_status |
string or null | yes | open or closed; the period must be open. |
status |
"cancelled" | "closed" | "pending" | "reopened" or null |
yes | The state of the close that holds the year (pending or closed), or null when nothing holds it. |
blockers |
array of string | yes | What prevents a proposal now: drafts and entries or documents waiting for approval dated in the year, a last period that is not open, a missing annual-result account, and the period-end runs (depreciation, FX revaluation, deferred tax) of the year's last regular period that are due and not done. Empty when the year can be proposed. |
can_propose |
boolean | yes | True when blockers is empty and the year is not held. |
warnings |
array of string | yes | Things to know that do not prevent the proposal. |
blocker_reasons |
array of Reason |
blockers by code, in the same order (erp.contracts.words.YEAR_END): the screens word each as yearEnd.obstacle.<code> from its details. |
|
warning_reasons |
array of Reason |
warnings by code, in the same order. |
CloseResult
| Field | Type | Required | Description |
|---|---|---|---|
close |
YearCloseOut |
yes | The year close as stored. |
submit |
SubmitResult |
yes | How the closing entry went through the approval policy: posted for an organization whose policy lets one person post it, else pending_approval with the approval_id a second person decides. |
Destination
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"dividend_payable" | "group_dividend" | "legal_profit_reserve" | "retained_earnings" | "voluntary_reserve" |
yes | retained_earnings (profit or loss carried forward; the account defaults to the one with the role retained_earnings), legal_profit_reserve or voluntary_reserve (equity accounts you name), dividend_payable (a liability account you name; a profit only) or group_dividend (a dividend to a shareholder that is another entity of the organization: the line goes to the account with the role ic_other_payable, and when the entry posts the shareholder gets a draft entry to accept, ADR-0025; a profit only). |
account |
string or null | Account code in the entity. Required except for retained_earnings and group_dividend (which takes none). It must be active and not a control account. |
|
shareholder |
string or null | For group_dividend only: the code (or id) of the shareholder entity, which needs an active intercompany relationship with this one. |
|
amount |
decimal string | yes | The amount, positive, in the functional currency (a decimal string). For a dividend the gross amount. (greater than 0) |
withholding_tax |
"deduct" | "notification" or null |
A dividend of a Swiss entity: deduct (the default) books 35 % of the gross dividend as Swiss withholding tax payable (role withholding_tax_payable) and the rest as the dividend payable; notification (a group_dividend to a Swiss shareholder that holds at least 10 %, Art. 26a VStV) books the gross dividend and the tax is declared, not paid. Empty for anything else. |
YearCloseOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the year close. |
entity |
string | yes | Entity code. |
fiscal_year |
integer | yes | The fiscal year it closes. |
kind |
"catch_up" | "year_end" |
yes | year_end (the closing entry of the year) or catch_up (the entry that closes the years booked before the books were closed). |
status |
"cancelled" | "closed" | "pending" | "reopened" |
yes | pending (the closing entry waits for a second person; the year is locked), closed (posted; the year is locked), reopened (reversed by a reopening) or cancelled (withdrawn or rejected; the lock is lifted). |
result |
string | yes | The result of the year, profit positive. |
appropriated |
string | yes | What shareholders' resolutions have appropriated so far, signed like the result. |
remainder |
string | yes | result less appropriated: what is left to resolve. |
appropriation_overdue |
boolean | yes | True for a Swiss entity whose general meeting is due (six months after the year end, Art. 699 para. 2 OR) while a remainder is unappropriated. |
journal_entry_id |
string (uuid) | yes | The closing entry. |
entry_number |
string or null | yes | Its number once posted. |
entry_status |
string | yes | pending_approval, posted or rejected. |
approval_id |
string (uuid) or null | yes | The approval request of a closing entry that waits for a second person: POST /v1/approvals/{approval_id}/approve (not by the person who proposed it). |
totals_digest |
string | yes | SHA-256 of the account totals it was computed from. |
proposed_by |
object | yes | Who proposed it, as an actor object. |
proposed_at |
string (date-time) | yes | When it was proposed (UTC). |
closed_by |
object or null | yes | Who posted it; null until closed. |
closed_at |
string (date-time) or null | yes | When it was posted; null until closed. |
cancelled_by |
object or null | yes | Who cancelled it, if cancelled. |
cancelled_at |
string (date-time) or null | yes | When it was cancelled, if cancelled. |
cancel_reason |
string or null | yes | Why it was cancelled, if cancelled. |
reopened_by |
object or null | yes | Who reopened it, if reopened. |
reopened_at |
string (date-time) or null | yes | When it was reopened, if reopened. |
reopen_reason |
string or null | yes | Why it was reopened, if reopened. |
reversal_entry_id |
string (uuid) or null | yes | The reversal of the closing entry that a reopening posted. |
detail |
object | yes | For a catch-up, the result of each earlier year; empty for a year-end close. |
appropriations |
array of AppropriationOut |
yes | The resolutions booked against its result, oldest first. |
erp__features__close__contracts__year_close__ReasonInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept in the audit trail and on the close. (min length 3; max length 1000) |