Subscriptions
Subscriptions: a committed term of recurring products billed monthly, quarterly or yearly, in advance or in arrears. A second person approves the terms, which activates the term's revenue contract; the run bills each period against the contract's obligations.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/subscriptions |
List subscriptions | read |
|
POST /v1/subscriptions |
Create a subscription | draft |
required |
GET /v1/subscriptions/metrics |
Get MRR, ARR and churn | read |
|
POST /v1/subscriptions/run |
Bill the subscription periods that are due | draft |
required |
GET /v1/subscriptions/{subscription_id} |
Get a subscription | read |
|
POST /v1/subscriptions/{subscription_id}/activate |
Ask for a subscription's terms to be approved | draft |
|
GET /v1/subscriptions/{subscription_id}/amendments |
List a subscription's amendments | read |
|
POST /v1/subscriptions/{subscription_id}/amendments |
Ask for an amendment of an active subscription | draft |
|
POST /v1/subscriptions/{subscription_id}/amendments/preview |
Preview an amendment of a subscription | read |
|
POST /v1/subscriptions/{subscription_id}/cancel |
Cancel a subscription | draft |
|
GET /v1/subscriptions/{subscription_id}/invoices |
List a subscription's billed periods | read |
GET /v1/subscriptions
List subscriptions
Subscriptions, the newest first, each with its lines. Read-only.
| Required scope | read (admin implies every scope) |
| Operation id | list_subscriptions_v1_subscriptions_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this entity's subscriptions (code or id). | |
| query | status |
"active" | "cancelled" | "draft" | "ended" | "pending_approval" or null |
Only subscriptions with this status. | |
| query | party_id |
string (uuid) or null | Only this customer's subscriptions. | |
| query | limit |
integer | How many, the newest first. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/subscriptions" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of SubscriptionOut |
| 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)
[
{
"auto_renew": false,
"auto_submit": false,
"billing_frequency": "monthly",
"billing_timing": "advance",
"created_at": "2026-10-05T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_term_end": "2027-12-31",
"current_term_start": "2027-01-01",
"entity": "CH01",
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"lines": [],
"number": "SUB-000001",
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"renewal_notice_days": 30,
"start_date": "2027-01-01",
"status": "draft",
"term_amount": "6000.00",
"term_months": 12,
"title": "Platform, 10 seats"
}
]
POST /v1/subscriptions
Create a subscription
Create a subscription as a draft: the customer, the start day, the committed term (a
whole number of billing periods), how it is billed (monthly, quarterly or yearly; in advance or
in arrears) and its lines, each a recurring product with a quantity, a price per month or year
(by default the list price in force on the start day) and a discount. It bills in currency:
by default the won opportunity's, else the customer's, else the entity's functional currency;
prices are taken in that currency, never converted. Every product needs an SSP in it on the
start day: it allocates the term's revenue contract. The first invoice is built (not
kept) to check the customer's tax treatment and the accounts. Nothing is billed until a second
person approves the terms (POST .../{subscription_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 for a product that is not recurring, inactive, goods, or without a price or SSP
in the currency (subscription_currency_unpriced names the currencies it is priced in), or a
term that is not a whole number of billing periods. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_subscription_v1_subscriptions_post |
Request body
application/json (required): SubscriptionInput
| 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 is subscribed. (min length 1; max length 200) |
currency |
string or null | ISO 4217 code of the currency it bills in: its prices, invoices, credit notes and revenue contract are in it. Default: the won opportunity's currency, else the customer's, else the entity's functional currency. Each product needs a list price (or the line a unit_price) and an SSP in it: a price is never converted. (pattern ^[A-Z]{3}$) |
|
start_date |
string (date) | yes | The first day of the first term. |
term_months |
integer | yes | The committed term in months: a whole number of billing periods. (min 1; max 60) |
billing_frequency |
"monthly" | "quarterly" | "yearly" |
How often it is invoiced; the periods are counted from the term start. (default "monthly") |
|
billing_timing |
"advance" | "arrears" |
advance (invoiced on a period's first day) or arrears (on the day after its last). (default "advance") |
|
auto_renew |
boolean | Whether the approved terms renew themselves: the first billing run after the term's last day starts the next term, as long and with its own revenue contract, at the prices in force raised by renewal_uplift_percent. Without it a person asks for a renewal (POST .../amendments, kind renew) or the subscription ends. (default false) |
|
renewal_notice_days |
integer | Days before the term's last day the renewal is due (renewal_status due). (min 0; max 365; default 30) |
|
renewal_uplift_percent |
decimal string or null | By how much a renewal raises each line's price (each price rounded to the currency's minor unit); none keeps the prices. At most 4 decimals. (min 0; max 100) | |
payment_terms_days |
integer or null | Days to pay an invoice; default the customer's. (min 0; max 365) | |
auto_submit |
boolean | True: the system submits each invoice under the approved terms; false: the run leaves drafts for a person. (default false) |
|
opportunity |
string (uuid) or null | The won opportunity it came from (GET /v1/opportunities). |
|
lines |
array of SubscriptionLineInput |
yes | What is sold. (min items 1; max items 50) |
Example request
curl -X POST "$ERP_URL/v1/subscriptions" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"auto_renew": false,
"billing_frequency": "monthly",
"billing_timing": "advance",
"entity": "CH01",
"lines": [
{
"product": "PLATFORM",
"quantity": "10"
}
],
"party": "C-CH",
"start_date": "2027-01-01",
"term_months": 12,
"title": "Platform, 10 seats"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | SubscriptionOut |
| 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)
{
"auto_renew": false,
"auto_submit": false,
"billing_frequency": "monthly",
"billing_timing": "advance",
"created_at": "2026-10-05T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_term_end": "2027-12-31",
"current_term_start": "2027-01-01",
"entity": "CH01",
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"lines": [],
"number": "SUB-000001",
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"renewal_notice_days": 30,
"start_date": "2027-01-01",
"status": "draft",
"term_amount": "6000.00",
"term_months": 12,
"title": "Platform, 10 seats"
}
GET /v1/subscriptions/metrics
Get MRR, ARR and churn
Monthly recurring revenue (MRR) at each month's end (today for the current month), read
from the subscription lines in force that day, never from the ledger: quantity x the price per
month (a yearly price over 12), less the discount. ARR is 12 x MRR. Each month explains the
change from the month before with each customer's movement: new, expansion,
contraction, churn and reactivation, naming the subscriptions that moved, and gives
the customer churn rate, the gross MRR churn rate and the net MRR retention. Amounts are kept
in each subscription's currency and converted into the entity's functional currency at the
ECB fixing of each month's day; the change of rates is the month's currency_effect. At most
36 months; a month to come is refused (422 month_in_future). Read-only.
| Required scope | read (admin implies every scope) |
| Operation id | get_subscription_metrics_v1_subscriptions_metrics_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string | yes | Entity code (or id). |
| query | from_month |
string or null | First month YYYY-MM; default the twelfth month back from to_month. (pattern `^\d{4}-(0[1-9] |
|
| query | to_month |
string or null | Last month YYYY-MM, at the latest the current one; default the current one. (pattern `^\d{4}-(0[1-9] |
Example request
curl "$ERP_URL/v1/subscriptions/metrics?entity=CH01" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringRevenueReport |
| 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)
{
"currency": "CHF",
"entity": "CH01",
"from_month": "2026-10",
"months": [
{
"arr": {
"amount": "140424.00",
"by_currency": [
{
"amount": "90000.00",
"currency": "CHF"
},
{
"amount": "54360.00",
"currency": "EUR"
}
]
},
"as_of": "2026-10-23",
"churn": {
"amount": "-1000.00",
"by_currency": [
{
"amount": "-1000.00",
"currency": "CHF"
}
]
},
"churned_customers": 1,
"closing": {
"amount": "11702.00",
"by_currency": [
{
"amount": "7500.00",
"currency": "CHF"
},
{
"amount": "4530.00",
"currency": "EUR"
}
]
},
"contraction": {
"amount": "0.00",
"by_currency": []
},
"currency_effect": "-12.00",
"customer_churn_rate": "8.3",
"customers": 12,
"customers_opening": 12,
"expansion": {
"amount": "214.00",
"by_currency": [
{
"amount": "230.00",
"currency": "EUR"
}
]
},
"fixing_date": "2026-10-23",
"month": "2026-10",
"movements": [
{
"amount": "-1000.00",
"converted": "-1000.00",
"currency": "CHF",
"kind": "churn",
"party_code": "C-1002",
"party_id": "0b9c2f4e-3d1a-4c55-9a77-6f1e2d3c4b5a",
"party_name": "Alpen Logistik AG",
"subscriptions": [
{
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"number": "SUB-000004"
}
]
}
],
"mrr_churn_rate": "8.4",
"net_mrr_retention": "93.4",
"new": {
"amount": "500.00",
"by_currency": [
{
"amount": "500.00",
"currency": "CHF"
}
]
},
"new_customers": 1,
"opening": {
"amount": "12000.00",
"by_currency": [
{
"amount": "8000.00",
"currency": "CHF"
},
{
"amount": "4300.00",
"currency": "EUR"
}
]
},
"reactivated_customers": 0,
"reactivation": {
"amount": "0.00",
"by_currency": []
}
}
],
"to_month": "2026-10",
"today": "2026-10-23",
"unconverted": []
}
POST /v1/subscriptions/run
Bill the subscription periods that are due
Make the invoice of every billing period of an active subscription whose invoice date is on
or before through (never after today where the subscription's company is): in advance on
the period's first day, in arrears on the day after its last; when that day is in a closed
period, on the first day of the next open one. Each line bills its obligation in the term's
revenue contract (to the contract liability) at the period's part of the term's amount, so the
periods of a term add up to its transaction price exactly.
Each invoice is a draft for a person to submit, or, with auto_submit, is submitted by the
system through the normal path (the approval policy, the open period, the numbering), which the
approval of the terms authorised. A draft's customer, products, quantities, prices and
obligations cannot be changed (409); a draft voided instead is made again by the next run. It
is idempotent: one invoice per subscription and period, enforced by the database; an
Idempotency-Key replays the first answer. A period that cannot be made (an inactive
customer, the contract's billing cap, terms that no longer match the approved digest) is
failed with its error and stops that subscription at that period, not the run; the next run
tries it again. At most twelve periods per subscription are billed in one run: more_due says
to run again. The first run after a fully billed term's last day renews it when its terms renew
it (auto_renew) or a renewal was approved (the next term with its own revenue contract, whose
due periods it bills too), and otherwise marks it ended. A person's request: 403 for an
agent. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | run_subscriptions_v1_subscriptions_run_post |
Request body
application/json (required): SubscriptionRunInput
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | Bill the periods whose invoice date is on or before this day (inclusive). It may not lie more than a day after the server's date (UTC). |
entity |
string or null | Only this entity (code or id). | |
subscription |
string (uuid) or null | Only this subscription (it must be active). |
Example request
curl -X POST "$ERP_URL/v1/subscriptions/run" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"through": "2027-01-31"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SubscriptionRunResult |
| 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)
{
"through": "2026-09-21",
"made": 1,
"failed": 1,
"items": [
{
"subscription_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"period_start": "2026-09-21",
"period_end": "2026-09-21",
"invoice_date": "2026-09-21",
"status": "drafted",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"net": "string",
"error": "string"
}
],
"more_due": true
}
GET /v1/subscriptions/{subscription_id}
Get a subscription
One subscription with its lines, who approved its terms, its term's revenue contract and each line's obligation in it. 404 when it does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | get_subscription_v1_subscriptions__subscription_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | subscription_id |
string (uuid) | yes | Id of the subscription (GET /v1/subscriptions). |
Example request
curl "$ERP_URL/v1/subscriptions/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SubscriptionOut |
| 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)
{
"auto_renew": false,
"auto_submit": false,
"billing_frequency": "monthly",
"billing_timing": "advance",
"created_at": "2026-10-05T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_term_end": "2027-12-31",
"current_term_start": "2027-01-01",
"entity": "CH01",
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"lines": [],
"number": "SUB-000001",
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"renewal_notice_days": 30,
"start_date": "2027-01-01",
"status": "draft",
"term_amount": "6000.00",
"term_months": 12,
"title": "Platform, 10 seats"
}
POST /v1/subscriptions/{subscription_id}/activate
Ask for a subscription's terms to be approved
Ask for the terms of a draft subscription to be approved. A second person always
decides (POST /v1/approvals/{approval_id}/approve): never the person who drafted the
subscription or asked, and never an agent (the policy action approve_subscription; an
agent's request is refused with 403, naming the rule). The approval is bound to the digest of
the terms. Approving creates and activates the term's revenue contract in the same
transaction: one over-time obligation per line, recognised month by month, its SSP the
product's over the term; the transaction price is what the term bills. The subscription is then
active and the run bills it from its first period. 409 when it is not a draft or the customer
is not active.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | activate_subscription_v1_subscriptions__subscription_id__activate_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | subscription_id |
string (uuid) | yes | Id of the subscription (GET /v1/subscriptions). |
Example request
curl -X POST "$ERP_URL/v1/subscriptions/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/activate" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SubscriptionActivationOut |
| 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)
{
"subscription": {
"auto_renew": false,
"auto_submit": false,
"billing_frequency": "monthly",
"billing_timing": "advance",
"created_at": "2026-10-05T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_term_end": "2027-12-31",
"current_term_start": "2027-01-01",
"entity": "CH01",
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"lines": [],
"number": "SUB-000001",
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"renewal_notice_days": 30,
"start_date": "2027-01-01",
"status": "draft",
"term_amount": "6000.00",
"term_months": 12,
"title": "Platform, 10 seats"
},
"decision": "allow",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
GET /v1/subscriptions/{subscription_id}/amendments
List a subscription's amendments
The amendments asked for the subscription, the oldest first, each with the amounts it named and whether it was applied or rejected. 404 when the subscription does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | list_subscription_amendments_v1_subscriptions__subscription_id__amendments_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | subscription_id |
string (uuid) | yes | Id of the subscription (GET /v1/subscriptions). |
Example request
curl "$ERP_URL/v1/subscriptions/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/amendments" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of AmendmentOut |
| 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",
"subscription_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "cancel",
"end_date": "2026-09-21",
"effective_date": "2026-09-21",
"proration": "none",
"reason": "string",
"term_start": "2026-09-21",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amounts": {
"old_price": "string",
"new_price": "string",
"earned": "string",
"credit_total": "string",
"credits": [
null
],
"next_term_start": "2026-09-21",
"next_term_end": "2026-09-21",
"next_price": "string",
"first_period_start": "2026-09-21",
"first_period_end": "2026-09-21",
"first_charge": "string"
},
"status": "applied",
"modification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lines": [
{
"product_id": null,
"product_code": null,
"description": null,
"quantity": null,
"unit_price": null,
"price_period": null,
"discount_percent": null,
"monthly_amount": null
}
],
"new_contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"approved_by": {},
"approved_at": "2026-09-21T09:30:00Z",
"created_by": {},
"created_at": "2026-09-21T09:30:00Z"
}
]
POST /v1/subscriptions/{subscription_id}/amendments
Ask for an amendment of an active subscription
Ask a second person to approve an amendment of an active subscription's current term.
cancel ends it early: end_date is the last day of service (the term's last day ends it there
without renewing). A second person always decides (POST /v1/approvals/{approval_id}/approve), never the person who asked, and never an agent (403,
the policy action approve_subscription). The request names the amounts, worked out from what
is posted now; approving refuses (409 terms_changed) when they changed in between. Approving,
in one transaction: billing stops after end_date (the period it falls in is billed by its days
if not billed yet); the term's revenue contract is modified prospectively (its price becomes
what the customer now pays, the schedule from the first day not recognised is rewritten,
recognised revenue stays); with proration prorate, the system posts a credit note per period
billed past end_date for its unused part, naming the obligations; with none, what was billed
is kept and its part for service not given is revenue on end_date. The run then ends the
subscription after end_date. 409 when the subscription is not active or already ends, has
an invoice not posted, revenue recognised after end_date, or an amendment waiting; 422 when
end_date is outside the current term or leaves nothing to bill.
renew (for a subscription whose terms do not renew it, auto_renew false) renews it into the
next term: as long as the term, its months counted from the start day, at the prices in force
raised by renewal_uplift_percent, with its own revenue contract activated in the approver's
name. Approved before the term ends, the first billing run after its last day starts the next
term; approved after, it starts at once. 409 when the subscription is not active, ends early,
renews itself (renewal_automatic) or its renewal is approved already.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | request_subscription_amendment_v1_subscriptions__subscription_id__amendments_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | subscription_id |
string (uuid) | yes | Id of the subscription (GET /v1/subscriptions). |
Request body
application/json (required): AmendmentInput
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"cancel" | "change" | "renew" |
cancel: end the current term early, on end_date. renew: renew it into the next term (for a subscription whose terms do not renew it, auto_renew false). change: bill lines instead of the current lines from effective_date to the term's end (an upgrade or a downgrade). (default "cancel") |
|
end_date |
string (date) or null | cancel: the last day of service, from the current term's first day to its last (its last day ends it at the term end without renewing). |
|
proration |
"none" | "prorate" |
cancel: prorate credits the unused part of every period billed past end_date (a credit note per invoice); none keeps what was billed, a non-refundable prepayment, recognised as revenue on end_date. (default "prorate") |
|
effective_date |
string (date) or null | change: the first day of the new lines, after the first day of the terms in force and at the latest the term's last day (today, the first day of the next billing period, or another day). Every period billed past the day before is credited for the part the new lines replace, and the first period of the new lines bills its days from this day. |
|
lines |
array of SubscriptionLineInput or null |
change: every line from effective_date (a line that stays as it is is named again), each priced as given or at the product's list price in force that day, and with an SSP that day. (min items 1; max items 50) |
|
reason |
string | yes | Why, kept with the change. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/subscriptions/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/amendments" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"end_date": "2026-06-20",
"kind": "cancel",
"proration": "prorate",
"reason": "The customer closes its Zürich office"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | AmendmentRequestOut |
| 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)
{
"amendment": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"subscription_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "cancel",
"end_date": "2026-09-21",
"effective_date": "2026-09-21",
"proration": "none",
"reason": "string",
"term_start": "2026-09-21",
"contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amounts": {
"old_price": "string",
"new_price": "string",
"earned": "string",
"credit_total": "string",
"credits": [
null
],
"next_term_start": null,
"next_term_end": null,
"next_price": null,
"first_period_start": null,
"first_period_end": null,
"first_charge": null
},
"status": "applied",
"modification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lines": [
null
],
"new_contract_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"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",
"subscription": {
"auto_renew": false,
"auto_submit": false,
"billing_frequency": "monthly",
"billing_timing": "advance",
"created_at": "2026-10-05T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_term_end": "2027-12-31",
"current_term_start": "2027-01-01",
"entity": "CH01",
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"lines": [],
"number": "SUB-000001",
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"renewal_notice_days": 30,
"start_date": "2027-01-01",
"status": "draft",
"term_amount": "6000.00",
"term_months": 12,
"title": "Platform, 10 seats"
}
}
POST /v1/subscriptions/{subscription_id}/amendments/preview
Preview an amendment of a subscription
What an amendment would change, worked out from what is posted now; nothing is written. For
cancel: the term's new price (the revenue contract's transaction price), the part earned by
the service through end_date, and a credit per period billed past it. For renew: the next
term's days and price (new_price). The errors are those of the request below.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | preview_subscription_amendment_v1_subscriptions__subscription_id__amendments_preview_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | subscription_id |
string (uuid) | yes | Id of the subscription (GET /v1/subscriptions). |
Request body
application/json (required): AmendmentInput
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"cancel" | "change" | "renew" |
cancel: end the current term early, on end_date. renew: renew it into the next term (for a subscription whose terms do not renew it, auto_renew false). change: bill lines instead of the current lines from effective_date to the term's end (an upgrade or a downgrade). (default "cancel") |
|
end_date |
string (date) or null | cancel: the last day of service, from the current term's first day to its last (its last day ends it at the term end without renewing). |
|
proration |
"none" | "prorate" |
cancel: prorate credits the unused part of every period billed past end_date (a credit note per invoice); none keeps what was billed, a non-refundable prepayment, recognised as revenue on end_date. (default "prorate") |
|
effective_date |
string (date) or null | change: the first day of the new lines, after the first day of the terms in force and at the latest the term's last day (today, the first day of the next billing period, or another day). Every period billed past the day before is credited for the part the new lines replace, and the first period of the new lines bills its days from this day. |
|
lines |
array of SubscriptionLineInput or null |
change: every line from effective_date (a line that stays as it is is named again), each priced as given or at the product's list price in force that day, and with an SSP that day. (min items 1; max items 50) |
|
reason |
string | yes | Why, kept with the change. (min length 3; max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/subscriptions/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/amendments/preview" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"end_date": "2026-06-20",
"kind": "cancel",
"proration": "prorate",
"reason": "The customer closes its Zürich office"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | AmendmentAmounts |
| 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)
{
"old_price": "string",
"new_price": "string",
"earned": "string",
"credit_total": "string",
"credits": [
{
"period_start": "2026-09-21",
"period_end": "2026-09-21",
"invoice_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amount": "string"
}
],
"next_term_start": "2026-09-21",
"next_term_end": "2026-09-21",
"next_price": "string",
"first_period_start": "2026-09-21",
"first_period_end": "2026-09-21",
"first_charge": "string"
}
POST /v1/subscriptions/{subscription_id}/cancel
Cancel a subscription
Cancel a draft subscription, or one waiting for approval (its request is withdrawn; only a
person withdraws it), with a reason kept in the audit trail. An active subscription is not
cancelled here: it is ended early by an amendment (POST .../{subscription_id}/amendments).
409 otherwise.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | cancel_subscription_v1_subscriptions__subscription_id__cancel_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | subscription_id |
string (uuid) | yes | Id of the subscription (GET /v1/subscriptions). |
Request body
application/json (required): SubscriptionCancelInput
| 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/subscriptions/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/cancel" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "The customer chose another plan"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SubscriptionOut |
| 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)
{
"auto_renew": false,
"auto_submit": false,
"billing_frequency": "monthly",
"billing_timing": "advance",
"created_at": "2026-10-05T08:00:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"current_term_end": "2027-12-31",
"current_term_start": "2027-01-01",
"entity": "CH01",
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"lines": [],
"number": "SUB-000001",
"party_code": "C-CH",
"party_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"party_name": "Zürich Kunde AG",
"renewal_notice_days": 30,
"start_date": "2027-01-01",
"status": "draft",
"term_amount": "6000.00",
"term_months": 12,
"title": "Platform, 10 seats"
}
GET /v1/subscriptions/{subscription_id}/invoices
List a subscription's billed periods
The billing periods the run billed, in order, each with its invoice or, for a failed attempt, the error (the next run tries it again). 404 when the subscription does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | list_subscription_invoices_v1_subscriptions__subscription_id__invoices_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | subscription_id |
string (uuid) | yes | Id of the subscription (GET /v1/subscriptions). |
Example request
curl "$ERP_URL/v1/subscriptions/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/invoices" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of SubscriptionInvoiceOut |
| 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",
"period_start": "2026-09-21",
"period_end": "2026-09-21",
"kind": "credit",
"amendment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "drafted",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"document_status": "string",
"error": "string",
"created_at": "2026-09-21T09:30:00Z"
}
]
Schemas
AmendmentAmounts
| Field | Type | Required | Description |
|---|---|---|---|
old_price |
string | yes | The term's revenue contract price before, net of VAT. |
new_price |
string | yes | Its price after: what the customer pays for the term as amended (renew: what the next term costs, the next contract's price). |
earned |
string | yes | The part of the new price for the service through the last day, recognised month by month; the rest (a kept prepayment) is recognised on the last day. |
credit_total |
string | yes | What the credit notes credit in all, net of VAT. |
credits |
array of AmendmentCredit |
yes | A credit note per billed period. |
next_term_start |
string (date) or null | renew: the next term's first day. |
|
next_term_end |
string (date) or null | renew: its last day. |
|
next_price |
string or null | change: what the new lines bill from the effective date to the term's end, net of VAT: the price of their revenue contract. |
|
first_period_start |
string (date) or null | change: the first day of the new lines' first billing period. |
|
first_period_end |
string (date) or null | change: its last day. |
|
first_charge |
string or null | change: what that first period bills, net of VAT: the new lines for the rest of the period the change falls in (old_price less new_price is what the current lines no longer bill, credit_total of it credited on invoices already made). |
AmendmentCredit
| Field | Type | Required | Description |
|---|---|---|---|
period_start |
string (date) | yes | The billed period credited: its first day. |
period_end |
string (date) | yes | Its last day. |
invoice_id |
string (uuid) | yes | The period's invoice. |
amount |
string | yes | What is credited, net of VAT. |
AmendmentInput
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"cancel" | "change" | "renew" |
cancel: end the current term early, on end_date. renew: renew it into the next term (for a subscription whose terms do not renew it, auto_renew false). change: bill lines instead of the current lines from effective_date to the term's end (an upgrade or a downgrade). (default "cancel") |
|
end_date |
string (date) or null | cancel: the last day of service, from the current term's first day to its last (its last day ends it at the term end without renewing). |
|
proration |
"none" | "prorate" |
cancel: prorate credits the unused part of every period billed past end_date (a credit note per invoice); none keeps what was billed, a non-refundable prepayment, recognised as revenue on end_date. (default "prorate") |
|
effective_date |
string (date) or null | change: the first day of the new lines, after the first day of the terms in force and at the latest the term's last day (today, the first day of the next billing period, or another day). Every period billed past the day before is credited for the part the new lines replace, and the first period of the new lines bills its days from this day. |
|
lines |
array of SubscriptionLineInput or null |
change: every line from effective_date (a line that stays as it is is named again), each priced as given or at the product's list price in force that day, and with an SSP that day. (min items 1; max items 50) |
|
reason |
string | yes | Why, kept with the change. (min length 3; max length 1000) |
AmendmentLineOut
| Field | Type | Required | Description |
|---|---|---|---|
product_id |
string (uuid) | yes | The product. |
product_code |
string | yes | Its code. |
description |
string | yes | What is sold. |
quantity |
string | yes | How many units. |
unit_price |
string | yes | Net price of one unit per price_period. |
price_period |
"month" | "year" |
yes | month or year. |
discount_percent |
string | yes | Discount in percent. |
monthly_amount |
string | yes | What a month of service costs. |
AmendmentOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Amendment id. |
subscription_id |
string (uuid) | yes | The subscription. |
kind |
"cancel" | "change" | "renew" |
yes | What it changes. |
end_date |
string (date) or null | yes | The last day of service of the term amended (renew: the term's last day). |
effective_date |
string (date) | yes | The first day the change applies. |
proration |
"none" | "prorate" or null |
yes | cancel: prorate or none. |
reason |
string | yes | Why. |
term_start |
string (date) | yes | The first day of the term it amends. |
contract_id |
string (uuid) | yes | That term's revenue contract. |
amounts |
AmendmentAmounts |
yes | The amounts the second person approves. |
status |
"applied" | "pending_approval" | "rejected" |
yes | pending_approval (a second person decides in /v1/approvals), applied or rejected. |
modification_id |
string (uuid) or null | yes | The modification of the revenue contract it made, once applied. |
lines |
array of AmendmentLineOut or null |
change: the lines from effective_date. |
|
new_contract_id |
string (uuid) or null | change: the revenue contract of the new lines, once applied. |
|
approved_by |
object or null | yes | Who approved it. |
approved_at |
string (date-time) or null | yes | When. |
created_by |
object | yes | Who asked for it. |
created_at |
string (date-time) | yes | When. |
AmendmentRequestOut
| Field | Type | Required | Description |
|---|---|---|---|
amendment |
AmendmentOut |
yes | The amendment, waiting for approval. |
approval_id |
string (uuid) | yes | The approval request a second person decides (POST /v1/approvals/{approval_id}/approve). |
subscription |
SubscriptionOut |
yes | The subscription, unchanged until then. |
MrrMonth
| Field | Type | Required | Description |
|---|---|---|---|
month |
string | yes | YYYY-MM. |
as_of |
string (date) | yes | The day MRR is read: the month's last day, or today for the current month. |
fixing_date |
string (date) or null | yes | The ECB fixing that converts the month's amounts (the latest on or before as_of); null when there is none (other currencies are then in unconverted). |
opening |
Figure |
yes | MRR at the end of the month before. |
new |
Figure |
yes | MRR of customers who had none ever before. |
expansion |
Figure |
yes | More MRR from customers who had some at both ends. |
contraction |
Figure |
yes | Negative. |
churn |
Figure |
yes | Negative. |
reactivation |
Figure |
yes | MRR of customers who had some before, but not at the month before's end. |
currency_effect |
string | yes | In the report's currency: the opening MRR's change of value from the month before's fixing to this month's (and the cents of converting each customer on its own), so that opening + movements + currency effect = closing. |
closing |
Figure |
yes | MRR at as_of: every subscription line in force that day. |
arr |
Figure |
yes | Annual recurring revenue: 12 x the closing MRR. |
customers_opening |
integer | yes | Customers with MRR at the month before's end. |
customers |
integer | yes | Customers with MRR at as_of. |
new_customers |
integer | yes | Customers with a new movement. |
churned_customers |
integer | yes | Customers with a churn movement. |
reactivated_customers |
integer | yes | Customers with a reactivation movement. |
customer_churn_rate |
string or null | yes | Churned customers over the opening customers, in percent (one decimal); null without opening customers. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$) |
mrr_churn_rate |
string or null | yes | Gross MRR churn: churn and contraction over the opening MRR (both at this month's fixing), in percent; null without opening MRR. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$) |
net_mrr_retention |
string or null | yes | The opening MRR kept, with expansion and less contraction and churn, over the opening MRR (both at this month's fixing), in percent; new and reactivated customers are not counted. Null without opening MRR. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$) |
movements |
array of MrrMovement |
yes | Each customer's movement, the largest first. |
MrrMovement
| Field | Type | Required | Description |
|---|---|---|---|
kind |
"churn" | "contraction" | "expansion" | "new" | "reactivation" |
yes | new: the customer's first MRR; expansion / contraction: more or less MRR from a customer who had some at the month before's end and still has; churn: all of it gone; reactivation: MRR again from a customer who had some before, but not at the month before's end. |
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. |
currency |
string | yes | The currency the customer's subscriptions bill in. |
amount |
string | yes | The change of MRR in that currency (negative: less). |
converted |
string | yes | The change in the report's currency, at the month's fixing. |
subscriptions |
array of SubscriptionRef |
yes | The subscriptions whose MRR changed. |
RecurringRevenueReport
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Entity code. |
currency |
string | yes | The report's currency: the entity's functional currency. |
from_month |
string | yes | The first month, YYYY-MM. |
to_month |
string | yes | The last month, YYYY-MM (the current month by default). |
today |
string (date) | yes | Today where the entity is. |
unconverted |
array of string | yes | Currencies without an ECB rate on a month's day, left out of the converted amounts of that month. |
months |
array of MrrMonth |
yes | Each month, oldest first. |
SubscriptionActivationOut
| Field | Type | Required | Description |
|---|---|---|---|
subscription |
SubscriptionOut |
yes | The subscription 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. |
SubscriptionCancelInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why it is cancelled. (min length 3; max length 1000) |
SubscriptionInput
| 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 is subscribed. (min length 1; max length 200) |
currency |
string or null | ISO 4217 code of the currency it bills in: its prices, invoices, credit notes and revenue contract are in it. Default: the won opportunity's currency, else the customer's, else the entity's functional currency. Each product needs a list price (or the line a unit_price) and an SSP in it: a price is never converted. (pattern ^[A-Z]{3}$) |
|
start_date |
string (date) | yes | The first day of the first term. |
term_months |
integer | yes | The committed term in months: a whole number of billing periods. (min 1; max 60) |
billing_frequency |
"monthly" | "quarterly" | "yearly" |
How often it is invoiced; the periods are counted from the term start. (default "monthly") |
|
billing_timing |
"advance" | "arrears" |
advance (invoiced on a period's first day) or arrears (on the day after its last). (default "advance") |
|
auto_renew |
boolean | Whether the approved terms renew themselves: the first billing run after the term's last day starts the next term, as long and with its own revenue contract, at the prices in force raised by renewal_uplift_percent. Without it a person asks for a renewal (POST .../amendments, kind renew) or the subscription ends. (default false) |
|
renewal_notice_days |
integer | Days before the term's last day the renewal is due (renewal_status due). (min 0; max 365; default 30) |
|
renewal_uplift_percent |
decimal string or null | By how much a renewal raises each line's price (each price rounded to the currency's minor unit); none keeps the prices. At most 4 decimals. (min 0; max 100) | |
payment_terms_days |
integer or null | Days to pay an invoice; default the customer's. (min 0; max 365) | |
auto_submit |
boolean | True: the system submits each invoice under the approved terms; false: the run leaves drafts for a person. (default false) |
|
opportunity |
string (uuid) or null | The won opportunity it came from (GET /v1/opportunities). |
|
lines |
array of SubscriptionLineInput |
yes | What is sold. (min items 1; max items 50) |
SubscriptionInvoiceOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Row id. |
period_start |
string (date) | yes | The billing period's first day. |
period_end |
string (date) | yes | Its last day. |
kind |
"credit" | "period" |
yes | period: the invoice of a billing period; credit: the credit note an amendment made for the unused part of a billed period. |
amendment_id |
string (uuid) or null | credit: the amendment that made it. |
|
status |
"drafted" | "failed" | "pending_approval" | "posted" |
yes | What the run (or amendment) did: drafted (left for a person), posted, pending_approval or failed. It is not updated afterwards: the invoice's own status is document_status. |
document_id |
string (uuid) or null | yes | The invoice or credit note; null when it failed. |
document_status |
string or null | The invoice's status now (draft, pending_approval, posted, rejected or void; a voided draft is made again by the next run); null when it failed. |
|
error |
string or null | yes | Why a failed attempt failed. |
created_at |
string (date-time) | yes | When. |
SubscriptionLineInput
| Field | Type | Required | Description |
|---|---|---|---|
product |
string | yes | Code (or id) of a recurring product of the entity (GET /v1/products). It gives the description, the revenue account, the VAT supply and rate type, the price in force on the start day when unit_price is omitted, and the standalone selling price of the term's revenue contract. |
description |
string or null | Default: the product's name. (min length 1; max length 500) | |
quantity |
decimal string | How many units (seats), at most 6 decimals. (greater than 0; default "1") |
|
unit_price |
decimal string or null | Net price of one unit per the product's price period (month or year), at most 6 decimals; default: the product's list price in force on the start day. (min 0) | |
discount_percent |
decimal string | Discount on the price, in percent, at most 4 decimals. (min 0; default "0") |
|
revenue_account |
string or null | Revenue account (code or id); default the product's. |
SubscriptionLineOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Line id. |
line_no |
integer | yes | Position, from 1. |
product_id |
string (uuid) | yes | The product. |
product_code |
string | yes | Its code. |
description |
string | yes | What is sold. |
quantity |
string | yes | How many units. |
unit_price |
string | yes | Net price of one unit per price_period. |
price_period |
"month" | "year" |
yes | month or year. |
discount_percent |
string | yes | Discount in percent. |
monthly_amount |
string | yes | What a month of service costs, rounded to the currency (quantity times the monthly price, less the discount). |
revenue_account |
string | yes | Code of the revenue account. |
valid_from |
string (date) | yes | The first day it applies. |
valid_to |
string (date) or null | yes | The last day it applies; null while open. |
obligation_id |
string (uuid) or null | yes | Its performance obligation in the current term's revenue contract; null before the subscription is active. |
SubscriptionOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Subscription id. |
entity |
string | yes | Entity code. |
number |
string | yes | SUB-000001, per entity. |
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 is subscribed. |
currency |
string | yes | The currency it bills in (prices, invoices, credit notes, its revenue contracts); revenue is booked in the entity's functional currency (ADR-0050). |
start_date |
string (date) | yes | The first day of the first term. |
term_months |
integer | yes | The committed term in months. |
current_term_start |
string (date) | yes | The current term's first day. |
current_term_end |
string (date) | yes | The current term's last day. |
billing_frequency |
"monthly" | "quarterly" | "yearly" |
yes | How often it is invoiced. |
billing_timing |
"advance" | "arrears" |
yes | advance or arrears. |
auto_renew |
boolean | yes | Whether the approved terms renew themselves. |
renewal_notice_days |
integer | yes | Days before the term end a renewal is due. |
renewal_uplift_percent |
string or null | yes | What a renewal adds to the prices. |
payment_terms_days |
integer or null | yes | Days to pay; null: the customer's. |
auto_submit |
boolean | yes | Whether the system submits each invoice. |
term_amount |
string | yes | What the current term costs, net of VAT: the transaction price of its revenue contract (less, once the term is ended early). |
ends_on |
string (date) or null | The last day of service of a term ended early (an applied cancel amendment): nothing after it is billed; null otherwise. |
|
changes_on |
string (date) or null | The first day of new lines an applied change amendment approved, until they are in force (the billing run moves to them once the current lines are billed through the day before); null otherwise. |
|
terms_from |
string (date) or null | The first day of the lines in force: the current term's, or the day a change started them. |
|
next_bill |
string (date) or null | yes | The first day of the next billing period to invoice; null unless active. |
term_no |
integer | The current term's number, the first being 1. (default 1) |
|
renewal_status |
"approved" | "auto" | "due" | "ends" | "requested" | "upcoming" or null |
An active subscription's next term: auto (its terms renew it), approved (a second person approved its renewal), requested (a renewal waits for approval), due (the notice day has come and nothing renews it), upcoming (not yet due), ends (it ends on ends_on); for a subscription the billing run ended with its term (not early), due or requested: it may still be renewed, and the approval starts the next term at once; null otherwise. |
|
renewal_due_on |
string (date) or null | The day the renewal is due: renewal_notice_days before the term's last day. |
|
renews_on |
string (date) or null | The next term's first day, when it renews (auto, approved). |
|
status |
"active" | "cancelled" | "draft" | "ended" | "pending_approval" |
yes | The status. |
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. |
contract_id |
string (uuid) or null | yes | The current term's revenue contract. |
contract_number |
string or null | yes | Its number. |
opportunity_id |
string (uuid) or null | yes | The opportunity it came from. |
renewal_deal_id |
string (uuid) or null | The renewal deal of the current term, opened when its renewal fell due. | |
renewal_deal_number |
string or null | Its number. | |
cancel_reason |
string or null | yes | Why it was cancelled. |
lines |
array of SubscriptionLineOut |
yes | What is sold. |
created_by |
object | yes | Who created it. |
created_at |
string (date-time) | yes | When. |
SubscriptionRef
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | The subscription's id. |
number |
string | yes | The subscription's number, such as SUB-000004. |
SubscriptionRunInput
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | Bill the periods whose invoice date is on or before this day (inclusive). It may not lie more than a day after the server's date (UTC). |
entity |
string or null | Only this entity (code or id). | |
subscription |
string (uuid) or null | Only this subscription (it must be active). |
SubscriptionRunItem
| Field | Type | Required | Description |
|---|---|---|---|
subscription_id |
string (uuid) | yes | The subscription. |
number |
string | yes | Its number. |
period_start |
string (date) | yes | The billing period's first day. |
period_end |
string (date) | yes | Its last day. |
invoice_date |
string (date) | yes | The invoice's date. |
status |
"drafted" | "failed" | "pending_approval" | "posted" |
yes | drafted (left for a person to submit), posted, pending_approval or failed (nothing was made; error says why, and the next run tries again). |
document_id |
string (uuid) or null | yes | The invoice; null when it failed. |
net |
string or null | yes | Its net amount; null when it failed. |
error |
string or null | yes | Why it failed; null otherwise. |
SubscriptionRunResult
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | The day billed through. |
made |
integer | yes | Invoices made. |
failed |
integer | yes | Periods that failed (tried again by the next run). |
items |
array of SubscriptionRunItem |
yes | What was made, in order. |
more_due |
boolean | yes | True when a run stopped early (at most a year of periods per subscription) and more is due: run again. |