Plans
Budgets and rolling forecasts: plans with versions that a person approves, lines by account and period, and the comparison with what was posted. Never posted to the ledger.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/plan-versions/{version_id} |
Get a plan version with its lines | read |
|
POST /v1/plan-versions/{version_id}/approve |
Approve a plan version | approve |
|
GET /v1/plan-versions/{version_id}/comparison |
Compare a plan version with what was posted | read |
|
PUT /v1/plan-versions/{version_id}/lines |
Set the lines of a draft plan version | draft |
|
POST /v1/plan-versions/{version_id}/send-back |
Send a submitted plan version back to draft | draft |
|
POST /v1/plan-versions/{version_id}/submit |
Submit a plan version for approval | draft |
|
GET /v1/plans |
List plans | read |
|
POST /v1/plans |
Create a plan | draft |
required |
GET /v1/plans/{plan_id} |
Get a plan | read |
|
PATCH /v1/plans/{plan_id} |
Rename a plan | draft |
|
POST /v1/plans/{plan_id}/roll |
Roll a forecast forward from actuals | draft |
required |
POST /v1/plans/{plan_id}/versions |
Start a new version of a plan | draft |
required |
GET /v1/plan-versions/{version_id}
Get a plan version with its lines
A version, its status and totals, and every line by account and period. 404 when it does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | get_plan_version_v1_plan_versions__version_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | version_id |
string (uuid) | yes | Id of the plan version (GET /v1/plans/{plan_id}, version_list). |
Example request
curl "$ERP_URL/v1/plan-versions/6c1d2e3f-4051-4b62-9c73-8d94ea5fb607" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | VersionDetail |
| 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)
{
"approved_at": "2026-10-02T14:10:00Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"based_on": "7d2e3f40-5162-4c73-8d84-9ea5fb6c7d18",
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"line_count": 3,
"lines_digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"note": "After the September review",
"number": 2,
"plan_id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"status": "approved",
"submitted_at": "2026-10-02T09:30:00Z",
"submitted_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"totals": {
"expenses": "21700.00",
"net": "20300.00",
"revenue": "42000.00"
}
}
POST /v1/plan-versions/{version_id}/approve
Approve a plan version
Make a submitted version the plan's current one: reports compare with it from now on,
and the version that was current is superseded (it stays, with when). A person's
decision: the policy action approve_plan denies agents (403, naming the rule), and by
default whoever prepared the version (created it, set its lines, submitted it) does not
approve it (403, rule plan_submitter_does_not_approve; a one-person organization allows
approve_own_plan). 409 unless it is submitted, when a newer version is current, or
version_changed when its lines are not the submitted ones.
| Required scope | approve (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | approve_plan_version_v1_plan_versions__version_id__approve_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | version_id |
string (uuid) | yes | Id of the plan version (GET /v1/plans/{plan_id}, version_list). |
| query | lines_digest |
string or null | The lines_digest the approver looked at: when given, the approval is refused (409 version_changed) unless the lines are still exactly those. (pattern ^[0-9a-f]{64}$) |
Example request
curl -X POST "$ERP_URL/v1/plan-versions/6c1d2e3f-4051-4b62-9c73-8d94ea5fb607/approve" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | VersionOut |
| 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)
{
"approved_at": "2026-10-02T14:10:00Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"based_on": "7d2e3f40-5162-4c73-8d84-9ea5fb6c7d18",
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"line_count": 3,
"lines_digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"note": "After the September review",
"number": 2,
"plan_id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"status": "approved",
"submitted_at": "2026-10-02T09:30:00Z",
"submitted_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"totals": {
"expenses": "21700.00",
"net": "20300.00",
"revenue": "42000.00"
}
}
GET /v1/plan-versions/{version_id}/comparison
Compare a plan version with what was posted
Plan against actual for each period of the version (every regular period of a budget's year;
a forecast's periods from its first line to its last): plan, actual, the variance (actual less
plan), the variance in percent of the plan and whether it is good news, per account or report
line, with revenue, expenses and net income added up. The actuals are the profit and loss of
the posted entries by period, closing entries left out and the entries of a year's
adjustment periods counted in its last regular period; an open period's actuals are not
final (periods[].status). Every row ends with the outlook of each fiscal year: the actuals
of its closed periods plus the plan of the others. With against_version, the variance is
against that version instead (forecast against budget). Computed when asked; nothing is
stored. Read-only. 422 when against_version is this version or another entity's.
| Required scope | read (admin implies every scope) |
| Operation id | compare_plan_version_v1_plan_versions__version_id__comparison_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | version_id |
string (uuid) | yes | Id of the plan version (GET /v1/plans/{plan_id}, version_list). |
| query | by |
"account" | "report_line" |
Rows by account or by report_line of the profit and loss. (default "account") |
|
| query | against_version |
string (uuid) or null | Also compare with another version of a plan of the same entity: a budget's current version (current_version_id of GET /v1/plans/{plan_id}) or an earlier forecast. Each cell then carries its amount (against) and the variance is this version's less that one's. |
Example request
curl "$ERP_URL/v1/plan-versions/6c1d2e3f-4051-4b62-9c73-8d94ea5fb607/comparison" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | Comparison |
| 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)
{
"plan_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"version_status": "approved",
"entity": "string",
"currency": "string",
"by": "account",
"against": "actuals",
"against_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"periods": [
{
"period": "string",
"start_date": "2026-09-21",
"end_date": "2026-09-21",
"status": "string"
}
],
"rows": [
{
"key": "string",
"name": "string",
"type": "expense",
"cells": [
null
],
"total": {
"period": null,
"plan": null,
"actual": null,
"against": null,
"variance": null,
"variance_pct": null,
"favourable": null
},
"outlook": [
null
]
}
],
"revenue": {
"key": "string",
"name": "string",
"type": "expense",
"cells": [
{
"period": null,
"plan": null,
"actual": null,
"against": null,
"variance": null,
"variance_pct": null,
"favourable": null
}
],
"total": {
"period": "string",
"plan": "string",
"actual": "string",
"against": null,
"variance": "string",
"variance_pct": null,
"favourable": null
},
"outlook": [
{
"fiscal_year": null,
"actual": null,
"plan_rest": null,
"outlook": null,
"plan": null
}
]
},
"expenses": {
"key": "string",
"name": "string",
"type": "expense",
"cells": [
{
"period": null,
"plan": null,
"actual": null,
"against": null,
"variance": null,
"variance_pct": null,
"favourable": null
}
],
"total": {
"period": "string",
"plan": "string",
"actual": "string",
"against": null,
"variance": "string",
"variance_pct": null,
"favourable": null
},
"outlook": [
{
"fiscal_year": null,
"actual": null,
"plan_rest": null,
"outlook": null,
"plan": null
}
]
},
"net": {
"key": "string",
"name": "string",
"type": "expense",
"cells": [
{
"period": null,
"plan": null,
"actual": null,
"against": null,
"variance": null,
"variance_pct": null,
"favourable": null
}
],
"total": {
"period": "string",
"plan": "string",
"actual": "string",
"against": null,
"variance": "string",
"variance_pct": null,
"favourable": null
},
"outlook": [
{
"fiscal_year": null,
"actual": null,
"plan_rest": null,
"outlook": null,
"plan": null
}
]
}
}
PUT /v1/plan-versions/{version_id}/lines
Set the lines of a draft plan version
Replace every line of a draft: one amount per revenue or expense account and regular
period (YYYY-NN) of the plan's entity, of the budget's year for a budget, in the functional
currency and the account's natural direction. Repeating the request sets the same lines. 409
unless the version is a draft; 422 for an unknown or balance sheet account, an unknown or
adjustment period, a period outside a budget's year, a repeated account and period, or more
decimals than the currency has.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | set_plan_lines_v1_plan_versions__version_id__lines_put |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | version_id |
string (uuid) | yes | Id of the plan version (GET /v1/plans/{plan_id}, version_list). |
Request body
application/json (required): LinesInput
| Field | Type | Required | Description |
|---|---|---|---|
lines |
array of PlanLineInput |
yes | Every line of the draft: they replace the lines it has. One amount per account and period; an empty list clears the draft. (max items 20000) |
Example request
curl -X PUT "$ERP_URL/v1/plan-versions/6c1d2e3f-4051-4b62-9c73-8d94ea5fb607/lines" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"account": "3400",
"amount": "42000.00",
"period": "2027-01"
},
{
"account": "5000",
"amount": "18500.00",
"period": "2027-01"
},
{
"account": "6000",
"amount": "3200.00",
"period": "2027-01"
}
]
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | VersionDetail |
| 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)
{
"approved_at": "2026-10-02T14:10:00Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"based_on": "7d2e3f40-5162-4c73-8d84-9ea5fb6c7d18",
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"line_count": 3,
"lines_digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"note": "After the September review",
"number": 2,
"plan_id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"status": "approved",
"submitted_at": "2026-10-02T09:30:00Z",
"submitted_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"totals": {
"expenses": "21700.00",
"net": "20300.00",
"revenue": "42000.00"
}
}
POST /v1/plan-versions/{version_id}/send-back
Send a submitted plan version back to draft
Return a submitted version to draft, with the reason kept in the audit trail, so its lines can change. 409 unless it is submitted.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | send_back_plan_version_v1_plan_versions__version_id__send_back_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | version_id |
string (uuid) | yes | Id of the plan version (GET /v1/plans/{plan_id}, version_list). |
Request body
application/json (required): SendBackInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why the version goes back to draft. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/plan-versions/6c1d2e3f-4051-4b62-9c73-8d94ea5fb607/send-back" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Marketing figures for Q2 are missing"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | VersionOut |
| 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)
{
"approved_at": "2026-10-02T14:10:00Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"based_on": "7d2e3f40-5162-4c73-8d84-9ea5fb6c7d18",
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"line_count": 3,
"lines_digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"note": "After the September review",
"number": 2,
"plan_id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"status": "approved",
"submitted_at": "2026-10-02T09:30:00Z",
"submitted_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"totals": {
"expenses": "21700.00",
"net": "20300.00",
"revenue": "42000.00"
}
}
POST /v1/plan-versions/{version_id}/submit
Submit a plan version for approval
Fix a draft's lines for approval: they are digested (lines_digest) and change only after
the version is sent back. Open to anyone who may draft, an agent too; approving is a person's
decision. 409 unless it is a draft, 422 when it has no lines.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | submit_plan_version_v1_plan_versions__version_id__submit_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | version_id |
string (uuid) | yes | Id of the plan version (GET /v1/plans/{plan_id}, version_list). |
Example request
curl -X POST "$ERP_URL/v1/plan-versions/6c1d2e3f-4051-4b62-9c73-8d94ea5fb607/submit" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | VersionOut |
| 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)
{
"approved_at": "2026-10-02T14:10:00Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"based_on": "7d2e3f40-5162-4c73-8d84-9ea5fb6c7d18",
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"line_count": 3,
"lines_digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"note": "After the September review",
"number": 2,
"plan_id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"status": "approved",
"submitted_at": "2026-10-02T09:30:00Z",
"submitted_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"totals": {
"expenses": "21700.00",
"net": "20300.00",
"revenue": "42000.00"
}
}
GET /v1/plans
List plans
Budgets and forecasts, the newest first, each with its current (approved) version and how many versions it has. Read-only.
| Required scope | read (admin implies every scope) |
| Operation id | list_plans_v1_plans_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this entity's plans (code or id). | |
| query | kind |
"budget" | "forecast" or null |
Only budget or forecast. |
|
| query | limit |
integer | How many plans, the newest first. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/plans" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of PlanOut |
| 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 |
| 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)
[
{
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_version_id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"entity": "CH01",
"fiscal_year": 2027,
"id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"kind": "budget",
"name": "Budget 2027",
"versions": 2
}
]
POST /v1/plans
Create a plan
Create a budget (one fiscal year, whose periods must exist) or a forecast (a horizon
of 3 to 24 periods, 12 by default) for an entity, with an empty draft as version 1. A plan is
never posted to the ledger: it is read next to it (GET /v1/plan-versions/{version_id}/ comparison). 409 when the entity already has a plan of that name. Needs an
Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_plan_v1_plans_post |
Request body
application/json (required): PlanInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity the plan is for. |
name |
string | yes | What the plan is called, unique within the entity, e.g. Budget 2027. (min length 1; max length 200) |
kind |
"budget" | "forecast" |
yes | budget (the figures of one fiscal year, fixed once approved) or forecast (a number of periods ahead, rolled forward as periods close). |
fiscal_year |
integer or null | The fiscal year of a budget (required for a budget, not allowed for a forecast). Its periods must exist (POST /v1/entities/{entity}/fiscal-years). (min 1900; max 2999) |
|
horizon |
integer or null | How many periods a forecast looks ahead, 3 to 24 (default 12); not allowed for a budget. (min 3; max 24) |
Example request
curl -X POST "$ERP_URL/v1/plans" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"fiscal_year": 2027,
"kind": "budget",
"name": "Budget 2027"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | PlanDetail |
| 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 |
| 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)
{
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_version_id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"entity": "CH01",
"fiscal_year": 2027,
"id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"kind": "budget",
"name": "Budget 2027",
"versions": 2
}
GET /v1/plans/{plan_id}
Get a plan
A plan and all its versions, the newest first (without their lines:
GET /v1/plan-versions/{version_id}). 404 when it does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | get_plan_v1_plans__plan_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | plan_id |
string (uuid) | yes | Id of the plan (GET /v1/plans). |
Example request
curl "$ERP_URL/v1/plans/5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | PlanDetail |
| 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)
{
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_version_id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"entity": "CH01",
"fiscal_year": 2027,
"id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"kind": "budget",
"name": "Budget 2027",
"versions": 2
}
PATCH /v1/plans/{plan_id}
Rename a plan
Change a plan's name (the only thing about a plan that changes; its kind, year and horizon are fixed). 409 when the entity has another plan of that name.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | rename_plan_v1_plans__plan_id__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | plan_id |
string (uuid) | yes | Id of the plan (GET /v1/plans). |
Request body
application/json (required): PlanRename
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The plan's new name. (min length 1; max length 200) |
Example request
curl -X PATCH "$ERP_URL/v1/plans/5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Budget 2027 (board)"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | PlanOut |
| 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)
{
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_version_id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"entity": "CH01",
"fiscal_year": 2027,
"id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"kind": "budget",
"name": "Budget 2027",
"versions": 2
}
POST /v1/plans/{plan_id}/roll
Roll a forecast forward from actuals
A new draft of a forecast, rolled from its current version (else its latest): the closed
periods of the fiscal year it is in become actuals (read from the ledger now and kept as
lines with method actual), the open periods the version planned are carried over, and the
periods new to the horizon (the plan's horizon periods from the first open one) are filled
by run_rate or prior_year, per account by the method of its latest line where that is one
of them. Periods of the horizon that have no fiscal period yet are reported
(horizon_short_by), not invented. The draft is reviewed, submitted and approved like any
version; open to anyone who may draft, an agent too. 409 for a budget; 422 when every period
is closed. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | roll_plan_v1_plans__plan_id__roll_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | plan_id |
string (uuid) | yes | Id of the plan (GET /v1/plans). |
Request body
application/json (required): RollInput
| Field | Type | Required | Description |
|---|---|---|---|
method |
"prior_year" | "run_rate" |
How the periods new to the horizon are filled, for an account whose lines in the version rolled from say neither: run_rate (the average of the last periods closed periods) or prior_year (the same period a year earlier, closed or forecast, times 1 + growth_pct / 100). An account whose latest line is run_rate or prior_year keeps its own method. (default "run_rate") |
|
periods |
integer | How many closed periods run_rate averages. (min 1; max 12; default 3) |
|
growth_pct |
decimal string | Growth in percent over the prior year, for prior_year (e.g. 5 for 5 %). (min -100; max 1000; default "0") |
|
note |
string or null | What this version is about; empty: a note saying what was rolled. (max length 2000) | |
refresh |
boolean | Project again the open periods the version rolled from projected (run_rate, prior_year, schedule), keeping only the lines people entered (manual); false carries every open period over as it was. The rolling forecast every company has rolls with it. (default false) |
Example request
curl -X POST "$ERP_URL/v1/plans/5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6/roll" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"method": "run_rate",
"periods": 3
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | RollResult |
| 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)
{
"version": {
"approved_at": "2026-10-02T14:10:00Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"based_on": "7d2e3f40-5162-4c73-8d84-9ea5fb6c7d18",
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"line_count": 3,
"lines_digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"note": "After the September review",
"number": 2,
"plan_id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"status": "approved",
"submitted_at": "2026-10-02T09:30:00Z",
"submitted_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"totals": {
"expenses": "21700.00",
"net": "20300.00",
"revenue": "42000.00"
}
},
"rolled_from": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"actuals_through": "string",
"horizon_start": "string",
"horizon_end": "string",
"horizon_short_by": 1,
"lines": {}
}
POST /v1/plans/{plan_id}/versions
Start a new version of a plan
A new draft of the plan with the next number: empty, or a copy of the lines of one of
its versions (copy_from), which is how an approved budget is revised. Versions are never
deleted, so every figure that was ever current stays on record. Needs an
Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_plan_version_v1_plans__plan_id__versions_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | plan_id |
string (uuid) | yes | Id of the plan (GET /v1/plans). |
Request body
application/json (required): VersionInput
| Field | Type | Required | Description |
|---|---|---|---|
copy_from |
string (uuid) or null | A version of the same plan whose lines the new draft starts with; empty to start with no lines. | |
note |
string or null | What this version is about (kept with it). (max length 2000) |
Example request
curl -X POST "$ERP_URL/v1/plans/5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6/versions" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"copy_from": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"note": "After the September review"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | VersionOut |
| 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)
{
"approved_at": "2026-10-02T14:10:00Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"based_on": "7d2e3f40-5162-4c73-8d84-9ea5fb6c7d18",
"created_at": "2026-10-01T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"id": "6c1d2e3f-4051-4b62-9c73-8d94ea5fb607",
"line_count": 3,
"lines_digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"note": "After the September review",
"number": 2,
"plan_id": "5b0c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
"status": "approved",
"submitted_at": "2026-10-02T09:30:00Z",
"submitted_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"totals": {
"expenses": "21700.00",
"net": "20300.00",
"revenue": "42000.00"
}
}
Schemas
Comparison
| Field | Type | Required | Description |
|---|---|---|---|
plan_id |
string (uuid) | yes | The plan. |
version_id |
string (uuid) | yes | The version compared. |
version_status |
"approved" | "draft" | "submitted" | "superseded" |
yes | Its status. |
entity |
string | yes | Entity code. |
currency |
string | yes | Functional currency of every amount. |
by |
"account" | "report_line" |
yes | account or report_line. |
against |
"actuals" | "version" |
actuals, or version when compared with another plan version too. (default "actuals") |
|
against_version_id |
string (uuid) or null | The version compared against (against_version). |
|
periods |
array of ComparisonPeriod |
yes | The periods compared, in order. |
rows |
array of ComparisonRow |
yes | Revenue first, then expenses, each by key: every account (or report line) with a plan or an actual in the periods. |
revenue |
ComparisonRow |
yes | The revenue rows added up. |
expenses |
ComparisonRow |
yes | The expense rows added up. |
net |
ComparisonRow |
yes | Revenue less expenses (type is revenue: more is good news). |
ComparisonCell
| Field | Type | Required | Description |
|---|---|---|---|
period |
string | yes | The period as YYYY-NN. |
plan |
string | yes | The planned amount. |
actual |
string | yes | What was posted: the period's entries (and those of the year's adjustment periods, counted in its last regular period), closing entries left out, in the natural direction. |
against |
string or null | With against_version: that version's amount; empty otherwise. |
|
variance |
string | yes | Actual less plan; with against_version, this version's amount less that version's. |
variance_pct |
string or null | yes | Variance as a percent of the plan (with against_version, of that version's amount), one decimal; empty when that is zero. |
favourable |
boolean or null | yes | True when the variance is good news (more revenue, fewer expenses); empty when it is zero. |
ComparisonPeriod
| Field | Type | Required | Description |
|---|---|---|---|
period |
string | yes | The period as YYYY-NN. |
start_date |
string (date) | yes | First day. |
end_date |
string (date) | yes | Last day. |
status |
string | yes | open or closed: an open period's actuals are not final yet. |
ComparisonRow
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | yes | The account code, or the report line. |
name |
string | yes | The account's name, or the report line again. |
type |
"expense" | "revenue" |
yes | revenue or expense. |
cells |
array of ComparisonCell |
yes | One per period, in order. |
total |
ComparisonCell |
yes | The periods added up (period is total). |
outlook |
array of YearOutlook |
yes | Per fiscal year of the periods compared: actuals of its closed periods plus the plan of the others. |
LinesInput
| Field | Type | Required | Description |
|---|---|---|---|
lines |
array of PlanLineInput |
yes | Every line of the draft: they replace the lines it has. One amount per account and period; an empty list clears the draft. (max items 20000) |
PlanDetail
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Plan id. |
entity |
string | yes | Entity code. |
name |
string | yes | The plan's name. |
kind |
"budget" | "forecast" |
yes | budget or forecast. |
fiscal_year |
integer or null | yes | The fiscal year of a budget. |
horizon |
integer or null | yes | How many periods a forecast looks ahead. |
currency |
string | yes | The functional currency its amounts are in. |
current_version_id |
string (uuid) or null | yes | The approved version, the one reports compare with; empty before the first approval. |
versions |
integer | yes | How many versions it has. |
created_by |
object | yes | Who made it (actor object). |
created_at |
string (date-time) | yes | When (UTC). |
version_list |
array of VersionOut |
yes | Every version, the newest first. |
PlanInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity the plan is for. |
name |
string | yes | What the plan is called, unique within the entity, e.g. Budget 2027. (min length 1; max length 200) |
kind |
"budget" | "forecast" |
yes | budget (the figures of one fiscal year, fixed once approved) or forecast (a number of periods ahead, rolled forward as periods close). |
fiscal_year |
integer or null | The fiscal year of a budget (required for a budget, not allowed for a forecast). Its periods must exist (POST /v1/entities/{entity}/fiscal-years). (min 1900; max 2999) |
|
horizon |
integer or null | How many periods a forecast looks ahead, 3 to 24 (default 12); not allowed for a budget. (min 3; max 24) |
PlanLineInput
| Field | Type | Required | Description |
|---|---|---|---|
account |
string | yes | Code of a revenue or expense account. (min length 1; max length 50) |
period |
string | yes | The regular period as YYYY-NN (fiscal year and period number), as the close checklist names it, e.g. 2027-03. (pattern ^\d{4}-\d{2}$) |
amount |
decimal string | yes | The planned amount in the entity's functional currency, as a decimal string, in the account's natural direction: revenue as a positive credit, expenses as a positive debit (as the profit and loss shows them). A negative amount is a reduction. At most the currency's minor units (2 decimals for CHF and EUR); at most 16 digits in all. |
note |
string or null | A note on the amount. (max length 500) |
PlanLineOut
| Field | Type | Required | Description |
|---|---|---|---|
account |
string | yes | Account code. |
account_name |
string | yes | Account name. |
account_type |
"expense" | "revenue" |
yes | revenue or expense. |
report_line |
string | yes | The account's report line, e.g. pl.revenue. |
period |
string | yes | The period as YYYY-NN. |
amount |
string | yes | The planned amount in the functional currency. |
method |
"actual" | "manual" | "prior_year" | "run_rate" | "schedule" |
yes | How the amount was made: manual (entered); a rolled forecast's lines also actual, run_rate, prior_year or schedule. |
note |
string or null | yes | The note on the amount. |
PlanOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Plan id. |
entity |
string | yes | Entity code. |
name |
string | yes | The plan's name. |
kind |
"budget" | "forecast" |
yes | budget or forecast. |
fiscal_year |
integer or null | yes | The fiscal year of a budget. |
horizon |
integer or null | yes | How many periods a forecast looks ahead. |
currency |
string | yes | The functional currency its amounts are in. |
current_version_id |
string (uuid) or null | yes | The approved version, the one reports compare with; empty before the first approval. |
versions |
integer | yes | How many versions it has. |
created_by |
object | yes | Who made it (actor object). |
created_at |
string (date-time) | yes | When (UTC). |
PlanRename
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The plan's new name. (min length 1; max length 200) |
PlanTotals
| Field | Type | Required | Description |
|---|---|---|---|
revenue |
string | yes | Planned revenue (the lines of revenue accounts). |
expenses |
string | yes | Planned expenses (the lines of expense accounts). |
net |
string | yes | Revenue less expenses. |
RollInput
| Field | Type | Required | Description |
|---|---|---|---|
method |
"prior_year" | "run_rate" |
How the periods new to the horizon are filled, for an account whose lines in the version rolled from say neither: run_rate (the average of the last periods closed periods) or prior_year (the same period a year earlier, closed or forecast, times 1 + growth_pct / 100). An account whose latest line is run_rate or prior_year keeps its own method. (default "run_rate") |
|
periods |
integer | How many closed periods run_rate averages. (min 1; max 12; default 3) |
|
growth_pct |
decimal string | Growth in percent over the prior year, for prior_year (e.g. 5 for 5 %). (min -100; max 1000; default "0") |
|
note |
string or null | What this version is about; empty: a note saying what was rolled. (max length 2000) | |
refresh |
boolean | Project again the open periods the version rolled from projected (run_rate, prior_year, schedule), keeping only the lines people entered (manual); false carries every open period over as it was. The rolling forecast every company has rolls with it. (default false) |
RollResult
| Field | Type | Required | Description |
|---|---|---|---|
version |
VersionOut |
yes | The new draft (a person reviews and approves it). |
rolled_from |
string (uuid) or null | yes | The version it was rolled from: the plan's current one, else its latest. |
actuals_through |
string or null | yes | The last closed period, whose actuals the version holds (YYYY-NN); empty when no period of the year is closed yet. |
horizon_start |
string | yes | The first open period: where the forecast begins. |
horizon_end |
string | yes | The last period of the horizon that exists. |
horizon_short_by |
integer | yes | Periods of the horizon that have no fiscal period yet: open the next fiscal year (POST /v1/entities/{entity}/fiscal-years) and roll again to fill them. |
lines |
map of integer | yes | The new version's lines by method (actual, carried over ones keep theirs). |
SendBackInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why the version goes back to draft. (min length 3; max length 1000) |
VersionDetail
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Version id. |
plan_id |
string (uuid) | yes | The plan. |
number |
integer | yes | The version's number within its plan, from 1. |
status |
"approved" | "draft" | "submitted" | "superseded" |
yes | draft (its lines change), submitted (fixed, waiting for a person to approve it or send it back), approved (the plan's current version) or superseded (approved before a later version). |
based_on |
string (uuid) or null | yes | The version it was copied from. |
note |
string or null | yes | What the version is about. |
lines_digest |
string or null | yes | SHA-256 of the lines as submitted: what the approval is of. Empty for a draft. |
line_count |
integer | yes | How many lines it has. |
totals |
PlanTotals |
yes | Its revenue, expenses and their difference. |
created_by |
object | yes | Who made it (actor object). |
created_at |
string (date-time) | yes | When (UTC). |
submitted_by |
object or null | yes | Who submitted it. |
submitted_at |
string (date-time) or null | yes | When it was submitted (UTC). |
approved_by |
object or null | yes | Who approved it. |
approved_at |
string (date-time) or null | yes | When it was approved (UTC). |
superseded_at |
string (date-time) or null | yes | When a later version was approved in its place (UTC). |
lines |
array of PlanLineOut |
yes | The lines, by account and period. |
VersionInput
| Field | Type | Required | Description |
|---|---|---|---|
copy_from |
string (uuid) or null | A version of the same plan whose lines the new draft starts with; empty to start with no lines. | |
note |
string or null | What this version is about (kept with it). (max length 2000) |
VersionOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Version id. |
plan_id |
string (uuid) | yes | The plan. |
number |
integer | yes | The version's number within its plan, from 1. |
status |
"approved" | "draft" | "submitted" | "superseded" |
yes | draft (its lines change), submitted (fixed, waiting for a person to approve it or send it back), approved (the plan's current version) or superseded (approved before a later version). |
based_on |
string (uuid) or null | yes | The version it was copied from. |
note |
string or null | yes | What the version is about. |
lines_digest |
string or null | yes | SHA-256 of the lines as submitted: what the approval is of. Empty for a draft. |
line_count |
integer | yes | How many lines it has. |
totals |
PlanTotals |
yes | Its revenue, expenses and their difference. |
created_by |
object | yes | Who made it (actor object). |
created_at |
string (date-time) | yes | When (UTC). |
submitted_by |
object or null | yes | Who submitted it. |
submitted_at |
string (date-time) or null | yes | When it was submitted (UTC). |
approved_by |
object or null | yes | Who approved it. |
approved_at |
string (date-time) or null | yes | When it was approved (UTC). |
superseded_at |
string (date-time) or null | yes | When a later version was approved in its place (UTC). |
YearOutlook
| Field | Type | Required | Description |
|---|---|---|---|
fiscal_year |
integer | yes | The fiscal year. |
actual |
string | yes | What was posted in its closed periods (compared ones). |
plan_rest |
string | yes | The plan of its periods that are not closed. |
outlook |
string | yes | Actual plus the plan of the rest: where the year lands. |
plan |
string | yes | The plan of all its compared periods, for reference. |