LedgerBee Developer
  • Getting started
  • Conventions
  • Products
  • Advisor firms
  • Configuration
  • API Reference
Subscriptions
    OverviewAssign a subscriptionData modelLifecycle & statusesBilling & cadenceProrationUsage commitmentsReporting usageMonthly revenue & unbilled usageChange flowsParent & childAdd-onsBilling groupsWebhooks
Payment flowCard paymentsProducts & PricingDiscountsProduct Entitlements
Billing documents
Accounting
WebhooksPartner-Hosted Checkout
Customer Portal
Subscriptions

Add-ons

An add-on is a subscription of its own — its own plan, products, status and stable id — that bills on another subscription's invoice, inside that subscription's section. The subscription it bills with is its parent. An add-on has no invoices of its own, and cancelling the parent ends it.

Create add-ons together with a subscription, or add one to a subscription that already runs. Read the relation from either side, and remove an add-on by cancelling it. Every endpoint below takes the scopes and license of the rest of the subscription API (see Subscriptions); full schemas are in the API Reference.

I want to…Call
Create a subscription and its add-ons in one callPOST /v1/subscriptions with addons
Add an add-on to an existing subscriptionPOST /v1/subscriptions/{id}/addons
Turn existing subscriptions into add-onsPOST /v1/subscriptions/{id}/addons/attach
Check first whether they can be attachedPOST /v1/subscriptions/{id}/addons/eligibility
List a subscription's add-onsGET /v1/subscriptions/{id} or GET /v1/subscriptions, field addons
Find an add-on's parentGET /v1/subscriptions/{id} or GET /v1/subscriptions, field addonOfSubscriptionId
See which invoice lines are an add-on'sGET /v1/invoices/{id}, line fields subscriptionId and addonOfSubscriptionId
Remove an add-onPOST /v1/subscriptions/cancel with the add-on's id
End a subscription and its add-onsPOST /v1/subscriptions/cancel with the parent's id

Read add-ons

GET /v1/subscriptions/{id} on a parent lists its add-ons under addons, oldest first. On an add-on, addonOfSubscriptionId and addonOfSubscriptionName name the parent and addons is empty.

Code
{ "id": "0197a943-2325-7829-b835-b6c71a293065", "subscriptionName": "Premium Plan", "addonOfSubscriptionId": null, "addonOfSubscriptionName": null, "billingGroupId": null, "addons": [ { "subscriptionId": "0197a944-1111-7aaa-8bbb-000000000001", "subscriptionName": "Extra seats", "planId": "550e8400-e29b-41d4-a716-446655440000", "status": "active", "addedOn": "2026-09-15", "effectiveNextBillingDate": "2026-10-01T00:00:00.000Z", "monthlyRevenue": "99.00", "currency": "DKK" } ] }
Field on addons[]Meaning
subscriptionIdThe add-on's own stable id. Read it, or cancel it, by this id.
subscriptionNameThe add-on's current name.
planIdThe plan the add-on runs on, or null for an add-on created without a plan.
statusThe add-on's own status.
addedOnThe day (YYYY-MM-DD) the add-on was added to the parent.
effectiveNextBillingDateWhen the add-on's next charge is created. It lands on the parent's invoice of that date.
monthlyRevenueThe add-on's own monthly recurring revenue. The parent's monthlyRevenue does not include it: the parent's full recurring amount is its own plus its add-ons'.
currencyAlways the parent's currency.

GET /v1/subscriptions carries addonOfSubscriptionId and addonOfSubscriptionName on every row, and addons naming each add-on by subscriptionId and subscriptionName only. Read a parent's detail for each add-on's plan, status and next billing date.

billingGroupId is null on an add-on, and on a parent whose only companions are its add-ons: a subscription with add-ons is not a billing group. On an add-on, scheduledChurnDate reports the parent's scheduled cancellation, since that is the day the add-on ends.

Create a subscription with add-ons

POST /v1/subscriptions takes addons, up to 19 entries, each naming a plan, optional product overrides and an optional cadence (see Cadence). The subscription and its add-ons are created in one transaction and billed by one billing run, on one invoice. A refused add-on creates nothing, the subscription included.

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/subscriptions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "0197a943-0f1e-7a2b-9c3d-4e5f6a7b8c9d", "planId": "550e8400-e29b-41d4-a716-446655440100", "startDate": "2026-09-15", "addons": [ { "planId": "550e8400-e29b-41d4-a716-446655440000" }, { "planId": "550e8400-e29b-41d4-a716-446655440001", "productOverrides": [{ "priceId": "550e8400-e29b-41d4-a716-446655440002", "quantity": 5 }] } ] }'

Each add-on takes everything except its plan and product overrides from the subscription in the request: the customer, startDate, endDate, prorationBehavior, billingDirection, the invoice department and contact, and the subscription's payment terms, revenue deferral and Leverandørservice collection. The response is the new subscription, with its add-ons listed under addons. Their first charge rides the subscription's first invoice.

addons cannot be combined with joinBillingGroupId or formBillingGroupWithSubscriptionId. Either pair answers 400 SHARED_VALIDATION_FAILED with details.reason: "ADDONS_WITH_BILLING_GROUP_PLACEMENT" and details.fields naming both fields, and nothing is created.

Add an add-on to an existing subscription

POST /v1/subscriptions/{id}/addons creates one add-on of the subscription {id} names and answers 201 with the new add-on, not the parent. Read the parent afterwards to see it listed under addons.

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/subscriptions/0197a943-2325-7829-b835-b6c71a293065/addons \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "planId": "550e8400-e29b-41d4-a716-446655440000", "startDate": "2026-09-15", "prorationBehavior": "none" }'
FieldRequiredEffect
planIdyesThe plan the add-on is created from. It must bill in the parent's currency.
productOverridesnoQuantity overrides for products in the add-on's plan, the same shape as on Assign a subscription.
cadencenoHow often the add-on bills, such as month-1 or year-1. Defaults to the plan's own cadence. See Cadence.
startDatenoWhen the customer gets the add-on. Defaults to today.
prorationBehaviornoHow a partial period from startDate to the add-on's first full cycle is handled. Default none. See Proration.

The parent supplies the customer, billing direction, revenue deferral, Leverandørservice collection, send offset, invoice department and contact, payment terms, allowed payment providers and payment details.

The add-on bills on the parent's invoices. Its cycles break on the parent's billing dates, stepped back on the add-on's own cadence to the first one on or after startDate:

  • An add-on on the parent's cadence that starts on one of the parent's billing dates - for example today, when the parent billed today - bills its first full period from that date. The invoice is stamped with the parent and carries only the add-on's lines, because the parent's own period is already billed.
  • An add-on that starts between two of the parent's billing dates has a partial period up to its first cycle date, handled by prorationBehavior.
  • An add-on on a shorter cadence than its parent - a monthly add-on on a yearly subscription - bills on its own cycle dates, each on an invoice stamped with the parent.

A parent that is itself an add-on answers 400 SUBSCRIPTION_ADDON_PARENT_IS_AN_ADDON. A parent with a scheduled cancellation answers 400 SUBSCRIPTION_ADDON_PARENT_HAS_SCHEDULED_END (with details.endsOn) or, while the cancellation is still a queued change, 400 SUBSCRIPTION_BILLING_GROUP_MEMBER_HAS_PENDING_CHANGES. Either way nothing is created.

Cadence

An add-on bills at its plan's own cadence unless the request names another with cadence, written <interval>-<count>: month-1, month-3, year-1. The value must be the plan's own cadence or one of its alternative cadences; any other answers 400 SHARED_VALIDATION_FAILED with details.reason: "ADDON_CADENCE_NOT_ON_PLAN" and details.allowedCadences listing the ones the plan offers, and nothing is created.

The cadence can differ from the parent's. A monthly add-on of a yearly subscription bills every month on invoices stamped with the parent; each invoice dated when the parent is not due carries only the add-on lines due that day.

Turn existing subscriptions into add-ons

POST /v1/subscriptions/{id}/addons/attach turns subscriptions the customer already has into add-ons of {id}. Each keeps its own id, plan and products, and from then on bills on the parent's invoice, inside its section, and ends when the parent is cancelled.

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/subscriptions/0197a943-2325-7829-b835-b6c71a293065/addons/attach \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "subscriptionIds": ["0197a944-1111-7aaa-8bbb-000000000001"] }'

Attaching cannot be undone. An add-on cannot be detached to bill on its own again; it ends only by being cancelled. Each attached subscription takes the parent's invoice terms and payment settings and moves onto the parent's billing dates; one that bills on other dates is prorated onto them.

An attached subscription loses its own saved card, because its invoice is now the parent's, which only the parent's card pays. If the parent bills together with others in a billing group and the attach leaves that group billing only the parent, the group's card is removed too, and the buyer is told. The response is the parent's detail, with the attached subscriptions under addons, plus cardsRemoved:

Code
{ "id": "0197a943-2325-7829-b835-b6c71a293065", "addons": [{ "subscriptionId": "0197a944-1111-7aaa-8bbb-000000000001", "subscriptionName": "Extra seats" }], "cardsRemoved": [ { "subscriptionId": "0197a944-1111-7aaa-8bbb-000000000001", "billingGroupId": null, "paymentMethodId": "0197a945-2222-7ccc-8ddd-000000000002" } ] }

A card removed from a billing group names the group in billingGroupId and carries subscriptionId: null.

Check before attaching

POST /v1/subscriptions/{id}/addons/eligibility answers what the attach would, without changing anything: every refusal at once, where the attach stops at the first, and the cards it would remove. Omit subscriptionIds to ask only whether {id} can take add-ons at all.

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/subscriptions/0197a943-2325-7829-b835-b6c71a293065/addons/eligibility \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "subscriptionIds": ["0197a944-1111-7aaa-8bbb-000000000001"] }'
Code
{ "eligible": false, "refusals": [ { "code": "SUBSCRIPTION_ADDON_PARENT_MISMATCH", "subscriptionId": "0197a944-1111-7aaa-8bbb-000000000001", "details": { "field": "currency", "value": "EUR", "parentValue": "DKK" } } ], "cardsRemoved": [] }

Each refusal carries the code and details the attach would answer with. It needs the subscriptions-read scope; the attach needs subscriptions-write. An unknown subscription is a 404 on both.

Remove an add-on

An add-on is removed by cancelling it: POST /v1/subscriptions/cancel with the add-on's id and any strategy. It bills on its parent's invoice, so refundBehavior: "last_invoice" answers 400 SUBSCRIPTION_ADDON_REFUND_LAST_INVOICE_NOT_SUPPORTED; use prorated or none. The same code answers a parent cancelled with last_invoice while it has add-ons and bills on its own invoice, since that invoice carries their lines; a parent that is a member of a billing group answers SUBSCRIPTION_BILLING_GROUP_REFUND_LAST_INVOICE_NOT_SUPPORTED.

An add-on cannot be detached from its parent to bill on its own. Taking it off a billing group with DELETE /v1/billing-groups/{id}/members/{subscriptionId} answers 400 SUBSCRIPTION_ADDON_CANNOT_LEAVE_ALONE.

Cancel a parent

Cancelling a parent cancels its add-ons with it. The cancel response is the parent's detail plus cascadedAddonSubscriptionIds, the add-ons the cancellation takes along:

strategycascadedAddonSubscriptionIds
immediatelyThe add-ons that ended now.
end_of_cycle / specific_dateThe add-ons that will end with the parent, as they stand when the request is made.
clear_scheduleAlways [].
Code
{ "id": "0197a943-2325-7829-b835-b6c71a293065", "status": "active", "scheduledChurnDate": "2026-12-01T00:00:00.000Z", "cascadedAddonSubscriptionIds": ["0197a944-1111-7aaa-8bbb-000000000001"] }

A scheduled cancellation is stamped on the parent alone, and the add-ons to end are resolved again on the day it takes effect. Until then each add-on keeps its current status and reports the parent's date as its scheduledChurnDate. With end_of_cycle, the parent and its add-ons end together, on the latest period end among them, so an add-on on another cadence does not bill a stub period.

An immediate cancellation settles the parent and its add-ons on one document, and has three refusals of its own:

  • 400 SUBSCRIPTION_ADDON_CASCADE_MEMBER_NOT_CHURNABLE — an add-on is in a status it cannot be cancelled from. details.addonSubscriptionId names it. An add-on you cancelled immediately a moment earlier is still settling its final charge, and is named with details.currentStatus: "cancelled" until that settlement has run. Retry shortly. A scheduled cancellation is not affected: it leaves such an add-on out.
  • 400 SUBSCRIPTION_ADDON_CASCADE_MIXED_CREDITED_INVOICES — the refund would credit more than one invoice. details.participants lists each subscription with the creditedInvoiceNumber it would credit. Cancel with refundBehavior: "none", or cancel the add-ons first.
  • 400 SUBSCRIPTION_ADDON_CASCADE_SET_CHANGED — an add-on was added or removed while the request ran. Nothing was cancelled; read the parent again and retry.

Rules

RuleWhat it means
One level deepAn add-on cannot have add-ons of its own, and an add-on cannot be a parent.
Same terms as the parentAn add-on belongs to the parent's customer, bills in the parent's currency and has the parent's billing direction, revenue deferral and Leverandørservice collection. One that differs is refused with SUBSCRIPTION_ADDON_PARENT_MISMATCH, details.field naming the setting and details.value / details.parentValue the two values.
Billed on the parent's invoiceThe add-on's lines appear inside the parent's section of the parent's invoice, after the parent's own lines. The invoice's customerSubscriptionId is the parent's; the add-on has no invoices of its own. See Find an add-on's invoices.
Its own cadenceAn add-on may bill on a different cadence from its parent. A monthly add-on of a yearly parent bills every month, on the parent's invoice, on its own due dates. An invoice dated when the parent is not due carries only the lines of the add-ons due that day.
No card of its ownThe add-on is paid with the parent's invoice. automaticBilling: true on an add-on answers 400 SUBSCRIPTION_ADDON_DETAILS_FOLLOW_PARENT. A parent whose only companions are its add-ons keeps its own card, which pays for the add-ons' lines too.
The parent's billing grid is fixedThe add-ons are anchored to the parent's billing dates. While a parent has add-ons, a direction or cadence change, pause, resume, billing-anchor reset, send-offset change or merge on it is refused with SUBSCRIPTION_ADDON_PARENT_GRID_LOCKED. Cancel the add-ons to lift it. These are operator-driven flows today; see Change flows.
The add-on's grid and details are its parent'sThe same changes on an add-on are refused with SUBSCRIPTION_ADDON_FOLLOWS_PARENT_GRID, and a change to its invoice terms or payment settings with SUBSCRIPTION_ADDON_DETAILS_FOLLOW_PARENT: change them on the parent, which passes them on to its add-ons.
Ends with the parentCancelling the parent cancels every add-on, on the parent's date.
Not a billing groupA parent with add-ons reports billingGroupId: null. It can join or form a billing group like any other subscription, and its add-ons come with it.

Add-ons and billing groups

A member of a billing group can have add-ons. They bill inside that member's section of the group's shared invoice.

  • GET /v1/billing-groups/{id} lists each member's add-ons under members[].addons. An add-on is never a member of its own.
  • Each add-on uses one of the group's slots. slotsUsed counts members plus their add-ons against maxSlots (20). A write that would exceed it answers 400 SUBSCRIPTION_BILLING_GROUP_MEMBER_LIMIT with details.slotsUsed, details.slotsRequested and details.maxSlots.
  • Forming a group with, or adding, a subscription that has add-ons brings the add-ons along. The form and add responses list them under the member they bill with.
  • POST /v1/subscriptions/{id}/addons on a group member adds the add-on to the member's group, where it takes a slot.
  • Taking a member off the group takes its add-ons with it. An add-on cannot be taken off alone.
  • An add-on reports billingGroupId: null even when its parent is a member of a group; read the parent for the group.

POST /v1/subscriptions cannot create add-ons and place the subscription on a group in the same call. Create the subscription onto the group, then add the add-ons with POST /v1/subscriptions/{id}/addons.

A group whose other members all become add-ons of one of them stops billing together: GET /v1/billing-groups/{id} then answers billsTogether: false, and its card, if it had one, is removed and named in the attach's cardsRemoved.

Find an add-on's invoices

An add-on has no invoices of its own: its lines ride on its parent's invoice, which is addressed to the parent. Each line on GET /v1/invoices/{id} names the subscription it was billed for in subscriptionId (an add-on's lines name the add-on), and an add-on's lines name the parent in addonOfSubscriptionId. To find every invoice an add-on was billed on, list GET /v1/invoices?subscriptionId=<add-on id>: it returns the parent's invoices that carry the add-on's lines. See Invoices.

Selling add-ons at checkout

A plan item in the catalogue lists the add-ons a buyer may take with it, and the partner-hosted checkout sells them with the plan, on the plan's invoice. See Partner-Hosted Checkout → Add-ons.

Support matrix

POST /v1/subscriptions with addonsPOST /v1/subscriptions/{id}/addonsPOST /v1/subscriptions/{id}/addons/attachPartner checkout addons
CreatesThe subscription and its add-onsOne add-on of an existing subscriptionNothing: existing subscriptions become add-onsThe plan's subscription and its add-ons
Add-ons per callUp to 191Up to 19Up to 19, each plan once
Add-on startsOn the subscription's startDateOn startDate, default todayAlready runningOn the session's start date
First billedOn the subscription's first invoiceOn the parent's invoice on the add-on's first cycle dateOn the parent's invoice on its next billing dateOn the subscription's first invoice
Cadencecadence, default the add-on plan's owncadence, default the add-on plan's ownKeptThe plan item's period
CardNoneNoneIts own card is removedNone
UndoCancel the add-onCancel the add-onCancel the add-on; it cannot be detachedCancel the add-on
ResponseThe subscription, with addonsThe new add-onThe parent, with addons and cardsRemovedThe confirm names the plan's subscription

Errors & failure states

ConditionWhat you observeFix
addons sent with joinBillingGroupId or formBillingGroupWithSubscriptionId400 SHARED_VALIDATION_FAILED, details.reason: "ADDONS_WITH_BILLING_GROUP_PLACEMENT"Create the subscription onto the group first, then add the add-ons.
More than 19 entries in addons400 SHARED_VALIDATION_FAILEDSend at most 19.
The parent named in /v1/subscriptions/{id}/addons is itself an add-on400 SUBSCRIPTION_ADDON_PARENT_IS_AN_ADDON, details.subscriptionId (the add-on you named)Add the add-on to that add-on's parent instead.
The parent has a scheduled cancellation400 SUBSCRIPTION_ADDON_PARENT_HAS_SCHEDULED_END, details.endsOnClear the cancellation with clear_schedule, or leave the parent to end.
The parent has a queued change400 SUBSCRIPTION_BILLING_GROUP_MEMBER_HAS_PENDING_CHANGES, details.pendingChangeIdsWait for the change to take effect, or cancel it from the parent's scheduled changes — each id is a changeId that GET /v1/subscriptions/{id}/scheduled-changes lists.
The add-on's plan, or a subscription being attached, differs from the parent in customer, currency, billing direction, revenue deferral or Leverandørservice collection400 SUBSCRIPTION_ADDON_PARENT_MISMATCH, details.field, details.value, details.parentValuePick a plan, or a subscription, that matches the parent.
cadence is not one the add-on's plan offers400 SHARED_VALIDATION_FAILED, details.reason: "ADDON_CADENCE_NOT_ON_PLAN", details.allowedCadencesSend one of allowedCadences, or omit cadence.
The parent is named among the subscriptions to attach400 SUBSCRIPTION_ADDON_SELF_ATTACH, details.subscriptionIdLeave the parent out.
A subscription to attach is already an add-on of another subscription400 SUBSCRIPTION_ADDON_TARGET_ALREADY_AN_ADDON, details.addonOfSubscriptionIdAttach it to that parent, or cancel it.
A subscription to attach has add-ons of its own400 SUBSCRIPTION_ADDON_JOINER_HAS_ADDONS, details.addonCountAdd-ons are one level deep; attach its add-ons' parent instead of it, or cancel its add-ons.
A subscription to attach is billed together with others in a different billing group400 SUBSCRIPTION_BILLING_GROUP_ALREADY_IN_A_GROUPTake it off that group first.
The parent's set, or its group, would exceed 20 slots400 SUBSCRIPTION_BILLING_GROUP_MEMBER_LIMIT, details.slotsUsed, details.slotsRequested, details.maxSlotsCancel an add-on or remove a member first.
Taking an add-on off a billing group400 SUBSCRIPTION_ADDON_CANNOT_LEAVE_ALONE, details.subscriptionIdCancel the add-on, or take its parent off the group.
Cancelling an add-on, or a parent with add-ons outside a billing group, with refundBehavior: "last_invoice"400 SUBSCRIPTION_ADDON_REFUND_LAST_INVOICE_NOT_SUPPORTED (a group member: SUBSCRIPTION_BILLING_GROUP_REFUND_LAST_INVOICE_NOT_SUPPORTED)Use prorated or none.
Cancelling a parent immediately while an add-on cannot be cancelled400 SUBSCRIPTION_ADDON_CASCADE_MEMBER_NOT_CHURNABLE, details.addonSubscriptionIdResolve the named add-on's state first.
Cancelling a parent immediately with a refund that spans invoices400 SUBSCRIPTION_ADDON_CASCADE_MIXED_CREDITED_INVOICES, details.participantsCancel with refundBehavior: "none", or cancel the add-ons first.
An add-on was added or removed while a cancellation ran400 SUBSCRIPTION_ADDON_CASCADE_SET_CHANGEDNothing was cancelled. Read the parent again and retry.
automaticBilling: true, or another invoice or payment setting, on an add-on400 SUBSCRIPTION_ADDON_DETAILS_FOLLOW_PARENT, details.fieldChange it on the parent, or set up card billing there.
Moving the parent's billing grid while it has add-ons400 SUBSCRIPTION_ADDON_PARENT_GRID_LOCKEDCancel the add-ons first.

A refusal answered outside /v1/billing-groups never carries details.billingGroupId, whatever the code.

Last modified on November 6, 2026
Parent & childBilling groups
On this page
  • Read add-ons
  • Create a subscription with add-ons
  • Add an add-on to an existing subscription
    • Cadence
  • Turn existing subscriptions into add-ons
    • Check before attaching
  • Remove an add-on
  • Cancel a parent
  • Rules
  • Add-ons and billing groups
  • Find an add-on's invoices
  • Selling add-ons at checkout
  • Support matrix
  • Errors & failure states
JSON
JSON
JSON
JSON