Revenue contracts
IFRS 15 revenue contracts: the customer, the transaction price and the performance obligations with their standalone selling prices; a second person approves the terms, which allocates the price and writes the recognition schedule, and a person records when a point-in-time obligation is satisfied. Nothing here posts yet.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
POST /v1/periods/{period_id}/revenue-recognition |
Run the revenue recognition of a period | draft |
|
GET /v1/periods/{period_id}/revenue-recognition/preview |
Preview the revenue recognition of a period | read |
|
GET /v1/revenue-contracts |
List revenue contracts | read |
|
POST /v1/revenue-contracts |
Create a revenue contract | draft |
required |
GET /v1/revenue-contracts/{contract_id} |
Get a revenue contract | read |
|
POST /v1/revenue-contracts/{contract_id}/activate |
Activate a revenue contract | draft |
|
POST /v1/revenue-contracts/{contract_id}/cancel |
Cancel a revenue contract | draft |
|
GET /v1/revenue-contracts/{contract_id}/changes |
List a revenue contract's changes | read |
|
POST /v1/revenue-contracts/{contract_id}/changes |
Ask for an active revenue contract to end early or be modified | draft |
|
POST /v1/revenue-contracts/{contract_id}/changes/preview |
Preview ending a revenue contract early or modifying it | read |
|
GET /v1/revenue-contracts/{contract_id}/schedule |
Get a revenue contract's recognition schedule | read |
|
POST /v1/revenue-obligations/{obligation_id}/satisfy |
Record that a point-in-time obligation is satisfied | draft |
POST /v1/periods/{period_id}/revenue-recognition
Run the revenue recognition of a period
Recognise the revenue due by the period end and present the contracts' positions.
Posts at the period end through the approval policy (source rev): the recognition entry
(Dr contract liability, naming the contract / Cr the obligation's revenue account) for every
schedule line due by the period end, a line from an earlier period marked as a catch-up; then,
once that entry is posted, the presentation entry for each contract whose debit position
(recognised, not yet billed) changed: Dr contract asset, or the unbilled receivable for a
contract whose right to consideration is unconditional / Cr contract liability, reversed on the
first day of the next period. The run is incremental: run it again while the period is open
(after a late satisfaction or invoice) and it posts only what changed; with nothing to post,
run_id is null. A rejected recognition entry releases its lines for the next run. Refused:
404 for an unknown period; 409 when the period is closed or has not started yet
(period_not_started), or an earlier recognition entry waits for approval; 422 for an
adjustment period, a missing contract account (roles contract_liability, contract_asset,
unbilled_receivable), or when the next period is not opened.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | run_revenue_recognition_v1_periods__period_id__revenue_recognition_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | period_id |
string (uuid) | yes | Fiscal period id (GET /v1/entities/{entity}/periods). |
Example request
curl -X POST "$ERP_URL/v1/periods/3fa85f64-5717-4562-b3fc-2c963f66afa6/revenue-recognition" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecognitionRunOut |
| 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)
{
"run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"period": "string",
"recognised": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"revenue_account": "string",
"currency": "string",
"amount": "string",
"functional_amount": "string",
"billed_part": "string",
"lines": 1,
"catch_up": 1
}
],
"total": "string",
"exchange_differences": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"amount": "string"
}
],
"remeasured": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"amount": "string",
"carrying": "string",
"closing_rate": "string",
"remeasured": "string",
"change": "string"
}
],
"presentation": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"position": "string",
"caption": "contract_asset",
"presented": "string",
"change": "string"
}
],
"recognition": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": null,
"reason": null,
"rule_id": null,
"table_id": null
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
},
"presentation_entry": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": null,
"reason": null,
"rule_id": null,
"table_id": null
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
},
"waiting": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"rate_date": "2026-09-21",
"lines": 1,
"remeasurement": true,
"note": "string",
"note_reason": {
"words": null,
"code": null,
"details": null,
"text": null
}
}
],
"notes": [
"string"
],
"note_reasons": [
{
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
],
"completed": [
"string"
],
"recognised_through": "2026-09-21",
"left_for_later": 0
}
GET /v1/periods/{period_id}/revenue-recognition/preview
Preview the revenue recognition of a period
What POST /v1/periods/{period_id}/revenue-recognition would post now; nothing is written.
recognised lists, per obligation, the schedule lines due by the period end and not yet
recognised; presentation the contracts whose debit position the period's entries do not
present yet. notes says when an earlier recognition entry waits for approval, an earlier
open period is to run first or a contract waits for its rate, in English and by code
(note_reasons). Refused: 404 for an unknown period; 422 for an adjustment period.
| Required scope | read (admin implies every scope) |
| Operation id | preview_revenue_recognition_v1_periods__period_id__revenue_recognition_preview_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | period_id |
string (uuid) | yes | Fiscal period id (GET /v1/entities/{entity}/periods). |
Example request
curl "$ERP_URL/v1/periods/3fa85f64-5717-4562-b3fc-2c963f66afa6/revenue-recognition/preview" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecognitionRunOut |
| 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)
{
"run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"period": "string",
"recognised": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"revenue_account": "string",
"currency": "string",
"amount": "string",
"functional_amount": "string",
"billed_part": "string",
"lines": 1,
"catch_up": 1
}
],
"total": "string",
"exchange_differences": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"amount": "string"
}
],
"remeasured": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"amount": "string",
"carrying": "string",
"closing_rate": "string",
"remeasured": "string",
"change": "string"
}
],
"presentation": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"position": "string",
"caption": "contract_asset",
"presented": "string",
"change": "string"
}
],
"recognition": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": null,
"reason": null,
"rule_id": null,
"table_id": null
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
},
"presentation_entry": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": null,
"reason": null,
"rule_id": null,
"table_id": null
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
},
"waiting": [
{
"contract": "string",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"rate_date": "2026-09-21",
"lines": 1,
"remeasurement": true,
"note": "string",
"note_reason": {
"words": null,
"code": null,
"details": null,
"text": null
}
}
],
"notes": [
"string"
],
"note_reasons": [
{
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
],
"completed": [
"string"
],
"recognised_through": "2026-09-21",
"left_for_later": 0
}
GET /v1/revenue-contracts
List revenue contracts
Revenue contracts, the newest first, each with its obligations. Read-only.
| Required scope | read (admin implies every scope) |
| Operation id | list_revenue_contracts_v1_revenue_contracts_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this entity's contracts (code or id). | |
| query | status |
"active" | "cancelled" | "completed" | "draft" | "pending_approval" or null |
Only contracts with this status. | |
| query | party_id |
string (uuid) or null | Only this customer's contracts. | |
| query | limit |
integer | How many contracts, the newest first. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/revenue-contracts" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of RevenueContractOut |
| 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-04T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"end_date": "2027-12-31",
"entity": "CH01",
"id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
"number": "RC-2027-000001",
"obligations": [],
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"plan_version": 1,
"right_to_consideration": "conditional",
"start_date": "2027-01-01",
"status": "draft",
"title": "Platform 2027",
"transaction_price": "12000.00"
}
]
POST /v1/revenue-contracts
Create a revenue contract
Create a revenue contract as a draft: the customer, the period, the transaction price
(fixed, net of VAT, in the contract's currency, by default the entity's functional currency;
revenue is booked in the functional currency, ADR-0050) and its performance obligations, each
with its standalone selling price and how it was established (IFRS 15.77-79), or a product
whose SSP in force on the start day is taken. Nothing is allocated or scheduled until a second
person approves the terms (POST .../{contract_id}/activate). A draft's terms are not edited:
cancel it and create a new one. 404 for an unknown entity, customer, product or account; 422
when an obligation's service period is outside the contract's, a product has no SSP in the
currency, or an account is not a revenue account. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_revenue_contract_v1_revenue_contracts_post |
Request body
application/json (required): RevenueContractInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity. |
party |
string | yes | Code (or id) of the customer. |
title |
string | yes | What the contract is. (min length 1; max length 200) |
start_date |
string (date) | yes | The first day of the contract. |
end_date |
string (date) | yes | The last day of the contract. |
currency |
string or null | ISO 4217 code of the currency the contract is priced and billed in; default the entity's functional currency. Revenue is recognised in the functional currency: what was billed in advance at the rate it was billed at, what is recognised before it is billed at the rate of the period end (ADR-0050). (pattern ^[A-Z]{3}$) |
|
transaction_price |
decimal string | yes | Fixed consideration, net of VAT, in the contract's currency. (greater than 0) |
right_to_consideration |
"conditional" | "unconditional" |
Whether a debit position (revenue before billing) is a contract asset (conditional, the default: the right depends on more than time) or an unbilled receivable (unconditional, with right_basis); IFRS 15.107-108. (default "conditional") |
|
right_basis |
string or null | Why the right is unconditional. (min length 3; max length 2000) | |
obligations |
array of ObligationInput |
yes | What the contract promises. (min items 1; max items 50) |
Example request
curl -X POST "$ERP_URL/v1/revenue-contracts" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"end_date": "2027-12-31",
"entity": "CH01",
"obligations": [
{
"description": "Platform subscription",
"method": "monthly",
"pattern": "over_time",
"ssp": "11000.00",
"ssp_basis": "List price, sold without discount"
},
{
"description": "Onboarding",
"pattern": "point_in_time",
"ssp": "2000.00",
"ssp_basis": "Day rate times days, sold separately"
}
],
"party": "C-CH",
"start_date": "2027-01-01",
"title": "Platform 2027",
"transaction_price": "12000.00"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | RevenueContractOut |
| 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-04T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"end_date": "2027-12-31",
"entity": "CH01",
"id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
"number": "RC-2027-000001",
"obligations": [],
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"plan_version": 1,
"right_to_consideration": "conditional",
"start_date": "2027-01-01",
"status": "draft",
"title": "Platform 2027",
"transaction_price": "12000.00"
}
GET /v1/revenue-contracts/{contract_id}
Get a revenue contract
One revenue contract with its obligations, their allocated prices once it is active, and who approved its terms. 404 when it does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | get_revenue_contract_v1_revenue_contracts__contract_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | contract_id |
string (uuid) | yes | Id of the revenue contract (GET /v1/revenue-contracts). |
Example request
curl "$ERP_URL/v1/revenue-contracts/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RevenueContractOut |
| 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-04T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"end_date": "2027-12-31",
"entity": "CH01",
"id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
"number": "RC-2027-000001",
"obligations": [],
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"plan_version": 1,
"right_to_consideration": "conditional",
"start_date": "2027-01-01",
"status": "draft",
"title": "Platform 2027",
"transaction_price": "12000.00"
}
POST /v1/revenue-contracts/{contract_id}/activate
Activate a revenue contract
Ask for the terms of a draft contract to be approved. A human decision: the policy
action activate_revenue_contract denies agents (403, naming the rule), and by default a
second person approves (POST /v1/approvals/{approval_id}/approve; never the person who
created the contract or asked). The approval is bound to the digest of the terms. On approval
the transaction price is allocated to the obligations by relative SSP and the schedule is
written: a line per month of service for an over-time obligation, one event line for a
point-in-time obligation. An organization with one person allows the action in its approval
policy, and then a person with the approve scope activates at once. 409 when the contract is
not a draft or the customer is not active.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | activate_revenue_contract_v1_revenue_contracts__contract_id__activate_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | contract_id |
string (uuid) | yes | Id of the revenue contract (GET /v1/revenue-contracts). |
Example request
curl -X POST "$ERP_URL/v1/revenue-contracts/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/activate" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ActivationOut |
| 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)
{
"contract": {
"created_at": "2026-10-04T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"end_date": "2027-12-31",
"entity": "CH01",
"id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
"number": "RC-2027-000001",
"obligations": [],
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"plan_version": 1,
"right_to_consideration": "conditional",
"start_date": "2027-01-01",
"status": "draft",
"title": "Platform 2027",
"transaction_price": "12000.00"
},
"decision": "allow",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
POST /v1/revenue-contracts/{contract_id}/cancel
Cancel a revenue contract
Cancel a draft contract, or one waiting for approval (its request is withdrawn), with a
reason kept in the audit trail. An active contract is not cancelled (409
contract_active_not_cancelled): what it recognised stays, and it is ended early or modified
(POST .../{contract_id}/changes). 409 otherwise.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | cancel_revenue_contract_v1_revenue_contracts__contract_id__cancel_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | contract_id |
string (uuid) | yes | Id of the revenue contract (GET /v1/revenue-contracts). |
Request body
application/json (required): ContractCancelInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why it is cancelled. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/revenue-contracts/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/cancel" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "The customer signed a different offer"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RevenueContractOut |
| 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-04T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"end_date": "2027-12-31",
"entity": "CH01",
"id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
"number": "RC-2027-000001",
"obligations": [],
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"plan_version": 1,
"right_to_consideration": "conditional",
"start_date": "2027-01-01",
"status": "draft",
"title": "Platform 2027",
"transaction_price": "12000.00"
}
GET /v1/revenue-contracts/{contract_id}/changes
List a revenue contract's changes
The changes asked for the contract, the oldest first, with the amounts each named, whether it was applied, and the credit notes it made. 404 when the contract does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | list_revenue_contract_changes_v1_revenue_contracts__contract_id__changes_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | contract_id |
string (uuid) | yes | Id of the revenue contract (GET /v1/revenue-contracts). |
Example request
curl "$ERP_URL/v1/revenue-contracts/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/changes" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ContractChangeOut |
| 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",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "end",
"end_date": "2026-09-21",
"new_price": "string",
"reason": "string",
"amounts": {
"kind": "end",
"old_price": "string",
"new_price": "string",
"old_end_date": "2026-09-21",
"end_date": "2026-09-21",
"effective_date": "2026-09-21",
"earned": "string",
"recognised": "string",
"billed": "string",
"credit_total": "string",
"to_bill": "string",
"credits": [
null
],
"recurring_invoices": [
null
]
},
"status": "applied",
"modification_plan_version": 1,
"credit_notes": [
"string"
],
"approved_by": {},
"approved_at": "2026-09-21T09:30:00Z",
"created_by": {},
"created_at": "2026-09-21T09:30:00Z"
}
]
POST /v1/revenue-contracts/{contract_id}/changes
Ask for an active revenue contract to end early or be modified
Ask a second person to approve a change of an active contract no subscription bills (a subscription's contract changes by the subscription's amendments), as a prospective contract modification (IFRS 15.21(a)): revenue recognised stays, the schedule from the first day not recognised is rewritten.
end ends the contract on end_date, before its last day, by default at what the schedule
earned through that day (a part of a month by its days); modify gives it a new price
and/or end_date (also a later one) from the first day not recognised. When the new price
is below what was billed, the difference is credited on the invoices, newest first (a credit
note per invoice, posted with the change under its approval); above it, the rest is billed
later. A further promise at its standalone selling price is a new contract (IFRS 15.20).
A human decision: agents are refused (policy action activate_revenue_contract, 403),
and a second person approves (POST /v1/approvals/{approval_id}/approve), never the person
who asked; approving refuses (409 terms_changed) when revenue was recognised or an invoice
posted since. 409 when the contract is not active or a subscription's, an invoice or credit
note billing it is not posted, revenue is recognised after end_date, a promise is not
delivered or a change waits; 422 for a day outside the term (end), a change that changes
nothing or a price of zero.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | request_revenue_contract_change_v1_revenue_contracts__contract_id__changes_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | contract_id |
string (uuid) | yes | Id of the revenue contract (GET /v1/revenue-contracts). |
Request body
application/json (required): ContractChangeInput
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"end" | "modify" |
yes | end: end the contract early, on end_date, before its last day. modify: a new transaction price and/or last day (earlier or later) from effective_date (IFRS 15.21(a)); what the old terms earned before that day stays. |
end_date |
string (date) or null | end: the last day of service (required). modify: the new last day; default the current one. |
|
price |
decimal string or null | The transaction price after the change, net of VAT, in the contract's currency. end: default what the schedule earned through end_date (a part of a month by its days); a higher price (a fee for ending early) is recognised on the last day. modify: default the current price. (greater than 0) |
|
effective_date |
string (date) or null | modify only: the day the change takes effect, the day the parties agreed it; default today where the company is (or the first day of the term, when it starts later). Never before the first day whose revenue is not recognised, and on or before the last day. What the schedule earned before it stays, recognised or not (a part of a month by its days); the rest of the new price is spread from it. |
|
reason |
string | yes | Why the contract changes. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/revenue-contracts/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/changes" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"end_date": "2026-04-30",
"kind": "end",
"reason": "The customer gave up the maintenance after four months"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | ContractChangeAskedOut |
| 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)
{
"change": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "end",
"end_date": "2026-09-21",
"new_price": "string",
"reason": "string",
"amounts": {
"kind": "end",
"old_price": "string",
"new_price": "string",
"old_end_date": "2026-09-21",
"end_date": "2026-09-21",
"effective_date": "2026-09-21",
"earned": "string",
"recognised": "string",
"billed": "string",
"credit_total": "string",
"to_bill": "string",
"credits": [
null
],
"recurring_invoices": [
null
]
},
"status": "applied",
"modification_plan_version": 1,
"credit_notes": [
"string"
],
"approved_by": {},
"approved_at": "2026-09-21T09:30:00Z",
"created_by": {},
"created_at": "2026-09-21T09:30:00Z"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
POST /v1/revenue-contracts/{contract_id}/changes/preview
Preview ending a revenue contract early or modifying it
What the change would do, worked out from what is posted now; nothing is written. The same refusals as asking for it.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | preview_revenue_contract_change_v1_revenue_contracts__contract_id__changes_preview_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | contract_id |
string (uuid) | yes | Id of the revenue contract (GET /v1/revenue-contracts). |
Request body
application/json (required): ContractChangeInput
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"end" | "modify" |
yes | end: end the contract early, on end_date, before its last day. modify: a new transaction price and/or last day (earlier or later) from effective_date (IFRS 15.21(a)); what the old terms earned before that day stays. |
end_date |
string (date) or null | end: the last day of service (required). modify: the new last day; default the current one. |
|
price |
decimal string or null | The transaction price after the change, net of VAT, in the contract's currency. end: default what the schedule earned through end_date (a part of a month by its days); a higher price (a fee for ending early) is recognised on the last day. modify: default the current price. (greater than 0) |
|
effective_date |
string (date) or null | modify only: the day the change takes effect, the day the parties agreed it; default today where the company is (or the first day of the term, when it starts later). Never before the first day whose revenue is not recognised, and on or before the last day. What the schedule earned before it stays, recognised or not (a part of a month by its days); the rest of the new price is spread from it. |
|
reason |
string | yes | Why the contract changes. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/revenue-contracts/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/changes/preview" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"end_date": "2026-04-30",
"kind": "end",
"reason": "The customer gave up the maintenance after four months"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ContractChangeAmounts |
| 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)
{
"kind": "end",
"old_price": "string",
"new_price": "string",
"old_end_date": "2026-09-21",
"end_date": "2026-09-21",
"effective_date": "2026-09-21",
"earned": "string",
"recognised": "string",
"billed": "string",
"credit_total": "string",
"to_bill": "string",
"credits": [
{
"invoice_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"invoice_number": "string",
"amount": "string"
}
],
"recurring_invoices": [
{
"template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string",
"end_date": "2026-09-21",
"ends": true
}
]
}
GET /v1/revenue-contracts/{contract_id}/schedule
Get a revenue contract's recognition schedule
The recognition schedule, by obligation and day: what is revenue when, and whether it was posted. Empty until the contract is active. 404 when the contract does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | get_revenue_contract_schedule_v1_revenue_contracts__contract_id__schedule_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | contract_id |
string (uuid) | yes | Id of the revenue contract (GET /v1/revenue-contracts). |
Example request
curl "$ERP_URL/v1/revenue-contracts/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/schedule" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ScheduleLineOut |
| 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",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"plan_version": 1,
"trigger": "event",
"period_start": "2026-09-21",
"period_end": "2026-09-21",
"recognise_on": "2026-09-21",
"amount": "string",
"superseded": true,
"recognised": true,
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
]
POST /v1/revenue-obligations/{obligation_id}/satisfy
Record that a point-in-time obligation is satisfied
Record the day control of a point-in-time obligation transferred to the customer, with the
evidence: its schedule line is revenue from that day. A human decision: the policy action
satisfy_obligation denies agents (403). 409 unless the contract is active and the obligation
is open and at a point in time; 422 for a day in the future or before the contract starts.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | satisfy_revenue_obligation_v1_revenue_obligations__obligation_id__satisfy_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | obligation_id |
string (uuid) | yes | Id of the performance obligation (obligations[].id of the contract). |
Request body
application/json (required): SatisfyInput
| Field | Type | Required | Description |
|---|---|---|---|
on |
string (date) | yes | The day control transferred. |
evidence |
string | yes | What shows it: a delivery note, an acceptance, a go-live record. (min length 3; max length 2000) |
Example request
curl -X POST "$ERP_URL/v1/revenue-obligations/9f2a3b4c-5d6e-4f7a-8b9c-0d1e2f3a4b5c/satisfy" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"evidence": "Acceptance protocol AP-2027-004",
"on": "2027-01-20"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ObligationOut |
| 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",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"pattern": "over_time",
"method": "monthly",
"service_start": "2026-09-21",
"service_end": "2026-09-21",
"ssp": "string",
"ssp_basis": "string",
"allocated_price": "string",
"revenue_account": "string",
"status": "cancelled",
"satisfied_on": "2026-09-21",
"satisfied_by": {},
"satisfaction_evidence": "string"
}
Schemas
ActivationOut
| Field | Type | Required | Description |
|---|---|---|---|
contract |
RevenueContractOut |
yes | The contract after the request. |
decision |
"allow" | "require_approval" |
yes | allow: active now; require_approval: a second person decides (POST /v1/approvals/{approval_id}/approve). |
approval_id |
string (uuid) or null | yes | The approval request, when one is open. |
ContractCancelInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why it is cancelled. (min length 3; max length 1000) |
ContractChangeAmounts
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"end" | "modify" |
yes | end or modify. |
old_price |
string | yes | The transaction price before. |
new_price |
string | yes | The transaction price after. |
old_end_date |
string (date) | yes | The last day before. |
end_date |
string (date) | yes | The last day after. |
effective_date |
string (date) | yes | The first day the new terms apply to: the day after the last day for an early end, the day asked (default today) for a modification. Revenue the old terms earned before it stays. |
earned |
string | yes | end: what the schedule earned through the last day (a part of a month by its days), spread over the months not recognised yet; what the price adds beyond it is recognised on the last day. modify: the new price, of which what the old terms earned before effective_date stays and the rest is spread from it. |
recognised |
string | yes | Revenue recognised so far; it stays. |
billed |
string | yes | What the contract billed, net, before the change. |
credit_total |
string | yes | What the credit notes credit, net of VAT: what was billed beyond the new price. Zero when the price is not below what was billed. |
to_bill |
string | yes | What is left to invoice after the change (new price less what stays billed); revenue recognised before it is billed is a contract asset. |
credits |
array of ContractChangeCredit |
yes | A credit note per invoice, dated the day the change is applied. |
recurring_invoices |
array of ContractChangeRecurring |
yes | The recurring invoices that bill the contract past its new last day: they stop on it when the change is applied. |
ContractChangeAskedOut
| Field | Type | Required | Description |
|---|---|---|---|
change |
ContractChangeOut |
yes | The change asked, waiting for approval. |
approval_id |
string (uuid) | yes | The approval request a second person decides (POST /v1/approvals/{approval_id}/approve); never the person who asked. |
ContractChangeCredit
| Field | Type | Required | Description |
|---|---|---|---|
invoice_id |
string (uuid) | yes | The invoice the credit note credits. |
invoice_number |
string or null | yes | Its number. |
amount |
string | yes | What it credits, net of VAT, in the contract's currency. |
ContractChangeInput
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"end" | "modify" |
yes | end: end the contract early, on end_date, before its last day. modify: a new transaction price and/or last day (earlier or later) from effective_date (IFRS 15.21(a)); what the old terms earned before that day stays. |
end_date |
string (date) or null | end: the last day of service (required). modify: the new last day; default the current one. |
|
price |
decimal string or null | The transaction price after the change, net of VAT, in the contract's currency. end: default what the schedule earned through end_date (a part of a month by its days); a higher price (a fee for ending early) is recognised on the last day. modify: default the current price. (greater than 0) |
|
effective_date |
string (date) or null | modify only: the day the change takes effect, the day the parties agreed it; default today where the company is (or the first day of the term, when it starts later). Never before the first day whose revenue is not recognised, and on or before the last day. What the schedule earned before it stays, recognised or not (a part of a month by its days); the rest of the new price is spread from it. |
|
reason |
string | yes | Why the contract changes. (min length 3; max length 1000) |
ContractChangeOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the change. |
contract_id |
string (uuid) | yes | The revenue contract. |
kind |
"end" | "modify" |
yes | end: ended early; modify: a new price and/or last day. |
end_date |
string (date) | yes | The contract's last day after the change. |
new_price |
string | yes | The transaction price after the change. |
reason |
string | yes | Why it was asked. |
amounts |
ContractChangeAmounts |
yes | What the request named. |
status |
"applied" | "pending_approval" | "rejected" |
yes | pending_approval, applied (in force) or rejected. |
modification_plan_version |
integer or null | yes | The contract's plan version the change made, once applied. |
credit_notes |
array of string | yes | The numbers of the credit notes it made. |
approved_by |
object or null | yes | Who approved it. |
approved_at |
string (date-time) or null | yes | When it was approved, else empty. |
created_by |
object | yes | Who asked. |
created_at |
string (date-time) | yes | When it was asked. |
ContractChangeRecurring
| Field | Type | Required | Description |
|---|---|---|---|
template_id |
string (uuid) | yes | The recurring invoice that bills the contract. |
name |
string | yes | Its name. |
end_date |
string (date) | yes | Its last day after the change: the contract's. |
ends |
boolean | yes | True when nothing is left for it to invoice by then: it ends. False: it keeps running to its new last day. |
ContractModificationOut
| Field | Type | Required | Description |
|---|---|---|---|
plan_version |
integer | yes | The plan version it started. |
effective_date |
string (date) | yes | The first day the new terms apply to. |
old_price |
string | yes | The transaction price before. |
new_price |
string | yes | The transaction price after. |
old_end_date |
string (date) | yes | The last day before. |
new_end_date |
string (date) | yes | The last day after. |
reason |
string | yes | Why the terms changed, in English. |
reason_words |
Reason or null |
The same by code (revenueContracts.change), for the screens to word in the person's language: a subscription's early end names the subscription, its last day and the reason the person gave; null for a change worded only in reason. |
|
approved_by |
object | yes | Who approved the change. |
created_at |
string (date-time) | yes | When (UTC). |
ExchangeDifferenceItem
| Field | Type | Required | Description |
|---|---|---|---|
contract |
string | yes | Number of the revenue contract. |
contract_id |
string (uuid) | yes | Id of the revenue contract. |
currency |
string | yes | The contract's currency. |
amount |
string | yes | The realised exchange difference posted, in the functional currency: positive a gain, negative a loss. A contract in a foreign currency carries its position at historical rates; billing what was recognised before it was billed (or crediting what was billed in advance) at another rate leaves this difference on the contract liability, which the run moves to the exchange gain or loss account. |
ObligationInput
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | yes | What is promised. (min length 1; max length 500) |
product |
string or null | Code (or id) of a product of the entity (GET /v1/products). It gives the revenue account and, when ssp is omitted, the SSP in force on the contract's start day, with its basis. |
|
pattern |
"over_time" | "point_in_time" |
yes | over_time (revenue as the service is given, over service_start to service_end) or point_in_time (revenue when a person records that control transferred: POST /v1/revenue-obligations/{obligation_id}/satisfy). |
method |
"monthly" | "straight_line" or null |
Over time: straight_line (by days) or monthly (each calendar month the same, a part of a month by its days); default straight_line. None at a point in time. |
|
service_start |
string (date) or null | Over time: the first day of service; default the contract's. | |
service_end |
string (date) or null | Over time: the last day of service; default the contract's. | |
quantity |
decimal string | With a product and no ssp: how many units (seats, days) the obligation is; the product's SSP is per unit, and for a recurring product per month or year, so the obligation's SSP is the unit SSP times the quantity times the months (or years) of its service period. (greater than 0; default "1") |
|
ssp |
decimal string or null | Standalone selling price of the whole obligation, in the contract's currency; required without a product SSP. (greater than 0) | |
ssp_basis |
string or null | How the SSP was established (IFRS 15.77-79); required with ssp. (min length 3; max length 2000) |
|
revenue_account |
string or null | Revenue account (code or id); default the product's, else the entity's default revenue account. |
ObligationOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Obligation id. |
line_no |
integer | yes | Position, from 1. |
description |
string | yes | What is promised. |
product_id |
string (uuid) or null | yes | The product. |
pattern |
"over_time" | "point_in_time" |
yes | over_time or point_in_time. |
method |
"monthly" | "straight_line" or null |
yes | Over time: the method. |
service_start |
string (date) or null | yes | Over time: the first day of service. |
service_end |
string (date) or null | yes | Over time: the last day of service. |
ssp |
string | yes | Standalone selling price. |
ssp_basis |
string | yes | How it was established. |
allocated_price |
string or null | yes | Its part of the transaction price; null until the contract is activated. |
revenue_account |
string | yes | Code of the revenue account. |
status |
"cancelled" | "open" | "satisfied" |
yes | The status. |
satisfied_on |
string (date) or null | yes | Point in time: when control transferred. |
satisfied_by |
object or null | yes | Who recorded it. |
satisfaction_evidence |
string or null | yes | What shows it. |
PresentationItem
| Field | Type | Required | Description |
|---|---|---|---|
contract |
string | yes | Number of the revenue contract. |
contract_id |
string (uuid) | yes | Id of the revenue contract. |
position |
string | yes | Revenue recognised less billed at the period end, in the functional currency: positive is a debit position (presented as an asset), negative a contract liability (presented where it is). |
caption |
"contract_asset" | "unbilled_receivable" |
yes | Where a debit position is presented: unbilled_receivable when only time must pass before payment is due (the contract's right_to_consideration unconditional), else contract_asset. |
presented |
string | yes | What the period's entries present after this run. |
change |
string | yes | What this run posts (or would post) for the contract. |
RecognitionItem
| Field | Type | Required | Description |
|---|---|---|---|
contract |
string | yes | Number of the revenue contract, e.g. RC-2026-000001. |
contract_id |
string (uuid) | yes | Id of the revenue contract. |
obligation_id |
string (uuid) | yes | Id of the performance obligation. |
line_no |
integer | yes | Position of the obligation in its contract, from 1. |
description |
string | yes | What the obligation promises. |
revenue_account |
string | yes | Code of the revenue account credited. |
currency |
string | yes | The contract's currency, which amount is in. |
amount |
string | yes | Revenue recognised, in the contract's currency. |
functional_amount |
string | yes | The same revenue in the entity's functional currency, as booked: for a contract in another currency, what billing in advance covered at the rate it was billed at, the rest at the rate of the period end (ADR-0050). |
billed_part |
string | yes | Of amount, the part billing in advance covered (booked at the rate the contract liability is carried at); the rest was recognised before it was billed. Equals amount for a contract in the functional currency. |
lines |
integer | yes | Schedule lines recognised. |
catch_up |
integer | yes | Of those, lines due before the period began (recognised in this period). |
RecognitionRunOut
| Field | Type | Required | Description |
|---|---|---|---|
run_id |
string (uuid) or null | yes | Id of the recorded run; null for a preview or when nothing was due. |
period |
string | yes | The period as YYYY-NN, e.g. 2026-09. |
recognised |
array of RecognitionItem |
yes | Revenue recognised per obligation: the schedule lines due by the period end (time lines of the months through it, satisfied point-in-time obligations). |
total |
string | yes | Revenue recognised, in the functional currency. |
exchange_differences |
array of ExchangeDifferenceItem |
Realised exchange differences of contracts in a foreign currency, posted with the recognition entry (ADR-0050). | |
remeasured |
array of RemeasurementItem |
Unbilled receivables in a foreign currency remeasured at the closing rate (monetary items, IAS 21.23), posted with the presentation entry (ADR-0050). | |
presentation |
array of PresentationItem |
yes | The contracts whose presentation changes: each one's debit position at the period end and the change posted (reversed on the first day of the next period). |
recognition |
SubmitResult or null |
yes | The recognition entry (Dr contract liability / Cr revenue, and any exchange difference) with the approval policy decision; null when nothing was due. |
presentation_entry |
SubmitResult or null |
yes | The presentation entry (Dr contract asset or unbilled receivable / Cr contract liability, auto-reversing); null when no presentation changes, or when the recognition entry waits for approval (run again once it is decided). |
waiting |
array of WaitingItem |
Contracts in a foreign currency left for a later run because the ECB rate of the period end is not published yet (a run before the period's last day): their lines due stay due and their unbilled receivable is not remeasured; everything else is recognised as usual (ADR-0050). | |
notes |
array of string | What the run left for later or to see to first, in English words. | |
note_reasons |
array of Reason |
notes by code, in the same order (erp.contracts.words.RECOGNITION_NOTES): the screens word each as periodClose.recognitionNote.<code> from its details. |
|
completed |
array of string | The contracts the run completed: their last day is before today and on or before the period end, and every schedule line in force is recognised by a posted entry. | |
recognised_through |
string (date) | yes | The last day whose revenue the run recognises: the period end once it has passed, else yesterday (revenue is recognised only once it is earned). |
left_for_later |
integer | Schedule lines of the period whose day has not passed yet: left for a run after it (a run of the current period before its last day). (default 0) |
RemeasurementItem
| Field | Type | Required | Description |
|---|---|---|---|
contract |
string | yes | Number of the revenue contract. |
contract_id |
string (uuid) | yes | Id of the revenue contract. |
currency |
string | yes | The contract's currency. |
amount |
string | yes | The unbilled receivable in the contract's currency. |
carrying |
string | yes | What it is carried at in the functional currency (the rates it was recognised at). |
closing_rate |
string | yes | The ECB rate of the period end. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$) |
remeasured |
string | yes | amount at the closing rate. |
change |
string | yes | What this run posts (or would post): Dr or Cr the unbilled receivable against unrealised exchange differences, reversed on the first day of the next period. |
RevenueContractInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity. |
party |
string | yes | Code (or id) of the customer. |
title |
string | yes | What the contract is. (min length 1; max length 200) |
start_date |
string (date) | yes | The first day of the contract. |
end_date |
string (date) | yes | The last day of the contract. |
currency |
string or null | ISO 4217 code of the currency the contract is priced and billed in; default the entity's functional currency. Revenue is recognised in the functional currency: what was billed in advance at the rate it was billed at, what is recognised before it is billed at the rate of the period end (ADR-0050). (pattern ^[A-Z]{3}$) |
|
transaction_price |
decimal string | yes | Fixed consideration, net of VAT, in the contract's currency. (greater than 0) |
right_to_consideration |
"conditional" | "unconditional" |
Whether a debit position (revenue before billing) is a contract asset (conditional, the default: the right depends on more than time) or an unbilled receivable (unconditional, with right_basis); IFRS 15.107-108. (default "conditional") |
|
right_basis |
string or null | Why the right is unconditional. (min length 3; max length 2000) | |
obligations |
array of ObligationInput |
yes | What the contract promises. (min items 1; max items 50) |
RevenueContractOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Contract id. |
entity |
string | yes | Entity code. |
number |
string | yes | RC-2027-000001, per entity and year of the start date. |
party_id |
string (uuid) | yes | The customer's id. |
party_code |
string | yes | The customer's code. |
party_name |
string | yes | The customer's name. |
title |
string | yes | What the contract is. |
currency |
string | yes | The currency it is priced and billed in; revenue is booked in the entity's functional currency (ADR-0050). |
start_date |
string (date) | yes | The first day. |
end_date |
string (date) | yes | The last day. |
transaction_price |
string | yes | Fixed consideration, net of VAT. |
right_to_consideration |
string | yes | conditional or unconditional. |
right_basis |
string or null | yes | Why the right is unconditional. |
status |
"active" | "cancelled" | "completed" | "draft" | "pending_approval" |
yes | The status. |
plan_version |
integer | yes | The schedule's current plan version. |
terms_digest |
string or null | yes | SHA-256 of the approved terms. |
approved_by |
object or null | yes | Who approved the terms. |
approved_at |
string (date-time) or null | yes | When (UTC). |
cancel_reason |
string or null | yes | Why it was cancelled. |
obligations |
array of ObligationOut |
yes | What it promises, by line. |
modifications |
array of ContractModificationOut |
Changes of the active contract's terms, accounted for prospectively (ADR-0047), by plan version. | |
created_by |
object | yes | Who created it. |
created_at |
string (date-time) | yes | When (UTC). |
SatisfyInput
| Field | Type | Required | Description |
|---|---|---|---|
on |
string (date) | yes | The day control transferred. |
evidence |
string | yes | What shows it: a delivery note, an acceptance, a go-live record. (min length 3; max length 2000) |
ScheduleLineOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Schedule line id. |
obligation_id |
string (uuid) | yes | The obligation. |
line_no |
integer | yes | The obligation's line number. |
plan_version |
integer | yes | The plan version it belongs to. |
trigger |
"event" | "time" |
yes | time or event. |
period_start |
string (date) or null | yes | A time line: the first day it covers. |
period_end |
string (date) or null | yes | A time line: the last day it covers. |
recognise_on |
string (date) or null | yes | The day it is revenue; an event line: null until the obligation is satisfied. |
amount |
string | yes | Revenue, in the contract's currency. |
superseded |
boolean | yes | Replaced by a later plan version. |
recognised |
boolean | yes | Posted as revenue. |
journal_entry_id |
string (uuid) or null | yes | The recognition entry. |
WaitingItem
| Field | Type | Required | Description |
|---|---|---|---|
contract |
string | yes | Number of the revenue contract. |
contract_id |
string (uuid) | yes | Id of the revenue contract. |
currency |
string | yes | The contract's currency. |
rate_date |
string (date) | yes | The day whose ECB rate the run needs and does not have yet (the period end): run again once it is published. |
lines |
integer | yes | Schedule lines due that wait (revenue recognised before it is billed is booked at the period end's rate); 0 when only the remeasurement waits. |
remeasurement |
boolean | yes | Whether the unbilled receivable's remeasurement at the closing rate waits. |
note |
string | yes | The same, in English words (also in notes). |
note_reason |
Reason |
yes | note by code (periodClose.recognitionNote.waiting_lines, waiting_remeasurement or waiting_both, with contract, currency, date and, for lines, their count). |