Revenue contracts
A revenue contract is an agreement with a customer accounted for under IFRS 15: a period, a fixed
transaction price (net of VAT, in the contract's currency, by default the entity's functional
currency; a subscription's contracts are in the currency it bills in) and the performance
obligations it promises, each with its standalone selling price (SSP). A second person approves
the terms; on approval the price is allocated to the obligations by relative SSP and each
obligation's recognition schedule is written: when its share is revenue. A person records when a
point-in-time obligation is satisfied. The design is
ADR-0027 with the monthly method of
ADR-0036; this guide is ADR-0036 slice 3
as built. An invoice line that names an obligation bills it: it credits the contract liability, not
revenue. The period-end recognition run then moves what the schedule says is revenue from the
liability to the obligation's revenue account, and presents a contract that has recognised more than it
billed as a contract asset (or an unbilled receivable). The controls are in the
control matrix (section "Revenue contracts", RC1 to RC10).
Everything is a REST operation first (/v1, tag "revenue contracts" in the
generated reference); the MCP tool list_revenue_contracts reads the
same operations, and preview_revenue_recognition shows what the period's run would post.
The contract
New contract says under each field what goes in it, offers a last day 1, 2 or 3 years after the first, and the usual ways a promise's own price is set (our list price, what comparable customers pay, expected cost plus margin). Suggest with AI proposes, for each promise from its description, whether its revenue comes over time or on delivery and how it is spread, each with its reason; check them, then save.
POST /v1/revenue-contracts {entity, party, title, start_date, end_date, transaction_price, right_to_consideration?, right_basis?, obligations}
GET /v1/revenue-contracts ?entity&status&party_id
GET /v1/revenue-contracts/{contract_id}
POST /v1/revenue-contracts/{contract_id}/cancel {reason}
A contract is created as a draft by anyone who may draft, an agent too, and numbered
RC-2026-000001 per entity and year of its start. Its terms are never edited: cancel a draft (or one
waiting for approval, which withdraws the request) and create a new one. A contract is never deleted.
An active contract is not cancelled (409 contract_active_not_cancelled): it is
ended early or modified.
right_to_consideration records the judgement IFRS 15.107-108 asks for: whether 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). It decides the presentation in the next part.
Obligations
Each obligation is over time (revenue as the service is given, from service_start to service_end,
by default the contract's period and always inside it) or at a point in time (revenue on the day a
person records that control transferred). An over-time obligation is spread by one of two methods:
| Method | Each month of the service period gets | 12000 over 2027 |
|---|---|---|
straight_line (default) |
its days over the days of the period | January 1019.18, February 920.55, March 1019.17 |
monthly |
the same, a part of a month its days over the days of that month (a subscription's months run from its start day: from the 15th, the 15th to the 14th) | 1000.00 every month |
Amounts are rounded cumulatively, so the months always sum to the obligation's price exactly. monthly
suits a service priced per month: it avoids a contract asset and liability that swing between long and
short months for no economic reason.
The SSP is given with its basis (ssp, ssp_basis), or taken from a product (product): the
product's SSP in force on the contract's start day, times quantity, and for a recurring product times
the months (or years) of the service period; the basis then records how it was worked out (SEAT SSP from 2025-01-01: 10 x 50 CHF per month x 12 months. List price per seat and month). The product also
gives the revenue account unless the obligation names one.
Activation
POST /v1/revenue-contracts/{contract_id}/activate
GET /v1/revenue-contracts/{contract_id}/schedule
Activating decides when revenue is booked, so it is a human decision: the policy action
activate_revenue_contract denies agents (403), and by default a second person approves
(POST /v1/approvals/{approval_id}/approve), never the one who created the contract or asked. The
approval is bound to the digest of the terms (terms_digest). On approval:
- the transaction price is allocated by relative SSP (IFRS 15.76), the cents left over by rounding going to the largest remainders, so the allocations sum to the price exactly: 12000.00 for SSPs 11000 and 2000 is 10153.85 and 1846.15;
- each over-time obligation gets a schedule line per month of service, recognised on the month's last day (or the service end);
- each point-in-time obligation gets one event line, with no day until it is satisfied.
A one-person organization allows activate_revenue_contract in its approval policy, and then a person
with the approve scope activates at once. The customer must be active (409).
Satisfying a point-in-time obligation
POST /v1/revenue-obligations/{obligation_id}/satisfy {on, evidence}
A person records the day control transferred (a delivery, an acceptance, a go-live) and what shows it:
the policy action satisfy_obligation, denied to agents. Its event line is revenue from that day. Only an
open point-in-time obligation of an active contract, once, on a day not in the future and not before the
contract starts. The policy either allows the person to record it or refuses: a rule that asks for a
second person's approval refuses it too (403), because no approval request is made for a
satisfaction yet.
Billing a contract
A sales invoice or credit note line names the obligation it bills:
POST /v1/entities/{entity}/documents {kind: "sales_invoice", party, lines: [{obligation, description, quantity, unit_price, ...}]}
The line is booked to the entity's contract_liability account (it names no other account), VAT as
usual, and its journal line names the contract, so the contract's position is the balance of the
lines that name it: invoices credit it, recognition debits it. The line must bill an obligation of an
active (or completed) contract of the same customer and currency, not cancelled, as a service (goods
wait for inventory), and not to another entity of the organization. On posting, the database refuses a
document that would bring a contract's billed amount (invoices less credit notes, net of VAT) above
its transaction price, or credit an obligation more than was billed. The cap is the contract's: a bundle
may invoice each part at its list price, above or below its allocation, as long as the total stays within
the price. Two documents posted at the same time take turns on the contracts they bill, so the cap holds
for both. A credit note of an invoice that bills obligations credits them by name: each of its lines
names an obligation the invoice bills (a credit note without lines copies them), or credits the
invoice's other lines, on their account and within what they invoiced and was not credited yet. A
credit note is checked when it is drafted and again when it posts, so two drafts of one invoice cannot
both take what is left. A line that bills no obligation is never booked to a contract account
(the contract liability, the contract asset or the unbilled receivable); otherwise lines without an
obligation behave as before.
The recognition run
GET /v1/periods/{period_id}/revenue-recognition/preview
POST /v1/periods/{period_id}/revenue-recognition
Run in a regular, open period, dated its last day, it posts through the approval policy (source rev).
It recognises only revenue that is earned: run before the period's last day (preparing the close early
is normal), it takes the lines whose day has passed, through yesterday (recognised_through), and
leaves the period's later lines for a run after their day (left_for_later; the preview says so). Run
on 11 October, it books nothing of October's monthly revenue; the run on 1 November does.
- the recognition entry: for each obligation, its schedule lines due by the period end and not yet
recognised (time lines of the months through it, the event line of an obligation satisfied by then),
Dr contract liability / Cr the obligation's revenue account. Revenue belongs to its own period: while
an earlier period of the entity is open with lines due in it, the run is refused (
409 run 2026-01 first; the preview says so in its notes). Only a line of a closed period is caught up, counted as such; if that period is reopened, itsrevenue_recognisedwarns that its revenue was caught up later (it does not hold the period open: nothing can move the revenue back); - the presentation entry: each contract whose position is a debit (revenue recognised and not yet
billed) is presented as a
contract_asset, or as anunbilled_receivablewhen its right to consideration is unconditional (IFRS 15.107-108), Cr contract liability. It is reversed on the next period's first day, so each period presents its own end.
A contract in a foreign currency (one other than the entity's functional currency) is billed in its
currency and booked in the functional currency
(ADR-0050): revenue that billing in
advance covers is booked at the rate the contract liability is carried at, the rate of the invoices
(IFRIC 22), and revenue recognised before it is billed at the ECB rate of the period end; each part has
its own pair of lines, both in the contract's currency, with the rate that books it, and the run shows
it as functional_amount and billed_part. The position is never remeasured, except that a debit
position presented as an unbilled_receivable (a monetary item) is remeasured at the closing rate
against unrealised exchange differences, reversed with the presentation (remeasured). When billing
(or a credit note) meets the position at a rate other than it is carried at, the run moves the
difference from the contract liability to the exchange gain or loss account with the recognition entry
(exchange_differences), so that a contract billed and recognised in full carries nothing in either
currency. The ECB publishes the period end's rate on that day: a run before it leaves a foreign
contract that needs the rate waiting (its lines due stay due, its unbilled receivable is not
remeasured; waiting and a note name the contract and the day), recognises everything else, and a run
after the period end recognises what waited. The close checks fail meanwhile.
Once its entries are posted, the run marks completed every active contract whose last day is on or
before the period's end and before today, and whose schedule lines in force are all recognised by posted entries, and
names them in completed (ADR-0047). A run
whose recognition entry waits for approval completes nothing; the next posted run does. A completed
contract has nothing left to recognise and is not modified again, but it can still be billed (an
invoice in arrears for its last period) and its position is presented until it is zero. The database
refuses to complete a contract while a line in force is not recognised.
The run is incremental: it may repeat while the period is open, and recognises only what is newly due
and presents only the change (an invoice posted late in the period changes the presentation alone). An
agent may run it; its entries then wait for a person under the usual policy, and a further run waits
(409) while one does. Two runs of an entity at the same time take turns, so a change is presented
once. A rejected recognition entry releases its lines, so the next run takes them
again. The entity needs the three contract accounts; the chart templates carry them, and an older
entity gives three accounts these roles (account roles); the run names a missing one. The step revenue_recognition
of the MCP tool run_period_end runs it; the close job does not run it yet.
The close checklist gains revenue_recognised (every line due is recognised, the period's own in the
period), contract_position_presented
(each debit position is presented as above) and contract_control_reconciled (no posting on a contract
account without a contract). The year-end close is refused while the recognition of the year's last
regular period is to do.
Ending early and modifying
POST /v1/revenue-contracts/{contract_id}/changes/preview {kind, end_date?, price?, effective_date?, reason}
POST /v1/revenue-contracts/{contract_id}/changes {kind, end_date?, price?, effective_date?, reason}
GET /v1/revenue-contracts/{contract_id}/changes
An active contract that no subscription bills is changed by a change a person asks and a second person approves (ADR-0074), as SAP RAR and Oracle Revenue Management do with a contract modification (IFRS 15.18-21). The contract's dialog has End early and Modify; each shows what the change does before it is asked.
end: the contract ends onend_date, before its last day. Its price is by default what the schedule earned through that day (the months through it, and the month it falls in by its days), so the customer pays for the service given; a higherprice(a fee for ending early) is revenue on the last day.modify: a newpriceand/orend_date, earlier or later, fromeffective_date, the day the change was agreed (the dialog's Takes effect on, today by default). The service before that day keeps the old price: what the schedule earned through the day before stays, even in months the recognition run has not reached yet (a part of a month by its days), and the rest of the new price is spread from that day over the remaining months. The day is not before the first day whose revenue is not recognised and lies within the term. A further promise at its own standalone selling price is a new contract (IFRS 15.20), not a modification.
Recognised revenue never changes. When the new price is below what the contract invoiced, the
difference is credited with the change: a credit note per invoice, newest first, with a line per
invoice line it credits, newest line first, each naming the obligation with that invoice line's own
VAT, posted under the change's approval; it settles what is open on the invoice, and what is paid
stays open for a refund. The credit notes are dated the day the change is approved (in the next
open period when that one is closed), never earlier: a reduction of the price belongs to the VAT
period it is granted in. When the new price is above what was invoiced, the rest is invoiced later
(to_bill), and revenue recognised before it is a contract asset. The preview names billed,
credit_total, to_bill, the credit per invoice and the recurring invoices that stop.
A recurring invoice whose lines bill the contract stops with it: when the change gives the contract an earlier last day, the recurring invoice gets that day too, so it invoices nothing after it. One that still has a due date by then keeps running to it, still approved; one with nothing left to invoice ends; one waiting for approval goes back to draft. A recurring invoice that also bills other items keeps billing them: only its lines for the contract stop after the contract's last day (the recurring invoice shows "Invoiced through" that day under the line), or are removed when nothing is left to invoice for them.
CHF 12,000.00 of maintenance for 2026 (1,000.00 a month), invoiced in full on 1 January and recognised through April, ended on 30 April: price 4,000.00, a credit note of 8,000.00 plus 8.1 % VAT (8,648.00), the schedule from May superseded; the contract liability is then zero. Modified to 10,000.00 instead, agreed on 1 May: 2,000.00 credited and 750.00 a month from May. Extended to 31 March 2027 at 15,000.00 from 1 May: nothing credited, 3,000.00 to invoice, 1,000.00 a month from May 2026 to March 2027.
The same contract recognised through February only, modified to 10,800.00 on 12 October: March to September stay at 1,000.00 a month, 1 to 11 October is 354.84 (11/31 of October), and the rest, 1,445.16, is spread from 12 October: 352.48 for the rest of October, 546.34 in November and in December. 1,200.00 plus VAT is credited, dated 12 October. Agreed on 1 October instead: 600.00 a month for October to December.
Agents may not ask (403); the person who asked may not approve. Refused (409): a subscription's
contract (contract_of_subscription: end or change the subscription), an invoice or credit note of
the contract not posted (contract_documents_unposted), a payment or credit note waiting on an
invoice to credit (credit_settlement_waiting), revenue recognised after the new last day, a
promise not delivered, a change already waiting, a modification from a day whose revenue is
recognised already (contract_change_recognised); approving refuses the same when revenue was
recognised past the day since, and (terms_changed) when revenue was recognised or an invoice
posted since it was asked. 422 for an early end not before the last day
(contract_end_not_earlier), a modification from a day outside the term
(contract_change_outside_term) or a change that changes nothing.
Modifications
A subscription's contract is modified when its term is ended early
(ADR-0047, the subscription guide),
prospectively (IFRS 15.21(a)): a revenue_contract_modification records the next plan version, the
effective day, the price and end before and after, and each obligation's new allocated price. The
schedule lines not yet recognised are superseded and new lines of the plan version spread what is left
to recognise (the new price less what is recognised) over the obligations' remaining days by relative
SSP; recognised revenue never changes. A stand-alone contract is modified by its changes (above). The
database changes a contract's price, end and plan version,
and an obligation's allocated price and service end, only together with that row. The contract lists
its modifications (modifications), and its dialog shows them: plan version, from which day, price
and last day before and after, and why.
Not yet:
- impairment of a contract asset, a change of a promise's scope (other than the contract's price and term), and the split of a contract liability into its current and non-current part;
- December's run needs the next fiscal year opened: the presentation is reversed on 1 January, and the run is refused as a whole (recognition included) until that period exists;
- a line backdated into a closed December while the year's adjustment period is still open is caught up in January of the next year, not in the adjustment period (a run is for regular periods only); the run's catch-up count shows it;
- variable consideration (IFRS 15.50): a cash discount taken or a bad-debt write-off on a contract invoice goes to its own account and does not reduce the contract's position or price.
Data export
The tables revenue_contract, performance_obligation, revenue_schedule_line and
revenue_recognition_run, and trade_document_line.obligation_id, are in the data export, with the
documented schema.