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

Billing groups

A billing group is several of one customer's subscriptions billing onto one shared invoice: one document, one invoice number, one amount owed. Each member keeps its own products, its own stable id and its own lifecycle; the group decides only that they are invoiced together, on the group's billing dates, and paid from the group's card when it has one.

A group is not a parent-child relationship. Parent-child links two customer records so that one customer pays for another's subscriptions. A group sits inside one customer. The two do not combine: a customer whose subscriptions are billed to a parent cannot hold a group, and a customer holding a group cannot be billed to a parent. Either attempt answers 400 SUBSCRIPTION_BILLING_GROUP_CUSTOMER_BILLED_TO_PARENT.

A group is not a subscription with add-ons either. An add-on bills inside its parent's section, and a subscription whose only companions are its add-ons reports billingGroupId: null. A member of a group can have add-ons: they are listed under it, bill inside its section of the shared invoice, and each uses one of the group's slots.

Over the public API you check whether a set of subscriptions could group, form the group, add and remove members, create a subscription straight onto a group, and read a group and the documents it issues. Every subscription id on this surface is the stable id. The group's card is the buyer's to save on the shared invoice; this API has no card verb. Full schemas live under the Billing Groups tag in the API Reference; the endpoints take the same scopes and license as the rest of the subscription API.

I want to…Call
Ask whether a set could form or join a groupPOST /v1/billing-groups/eligibility
Form a group from existing subscriptionsPOST /v1/billing-groups
Read a group and its current membersGET /v1/billing-groups/{id}
Add existing subscriptions to a groupPOST /v1/billing-groups/{id}/members
Take a subscription off a groupDELETE /v1/billing-groups/{id}/members/{subscriptionId}
Create a subscription onto a group, or form a group around an existing onePOST /v1/subscriptions with joinBillingGroupId or formBillingGroupWithSubscriptionId, see Assign a subscription

Form a group

POST /v1/billing-groups takes a lead subscription and 1 to 19 other subscriptions of the same customer. The lead's next billing date becomes the day the set starts billing together, and its payment terms, invoice delivery and send offset are written onto every member.

A group uses at most 20 slots: one per member and one per add-on a member brings along. A subscription with add-ons joins with them, and the response lists them under it.

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/billing-groups \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "leadSubscriptionId": "0190…", "memberSubscriptionIds": ["0190…", "0190…"] }'
Code
{ "billingGroupId": "0197…", "billingActiveFrom": "2026-10-01", "leadSubscriptionId": "0190…", "members": [ { "subscriptionId": "0190…", "addons": [] }, { "subscriptionId": "0190…", "addons": [] }, { "subscriptionId": "0190…", "addons": [] } ] }

billingActiveFrom is in the future: until that day each member keeps billing on its own. Two optional fields decide how the shared invoice renders: invoiceTemplateId names a consolidated document template, and includeMemberDetailsPdf: true attaches one detail document per member to each send. Both can be left out.

Add subscriptions to a group

POST /v1/billing-groups/{id}/members adds 1 to 19 existing subscriptions of the group's customer. The group's billing date does not move. A joiner with add-ons brings them along, each using a slot, and the response lists them under it in joiners[].addons. Joining changes the subscription without a separate call:

  • Its billing dates move onto the group's. A subscription whose cycles already break where the group's do keeps them.
  • Its payment terms, invoice delivery department and contact, and send offset become the group's.
  • Its card is discarded and automaticBilling turns off. The group's card, when the buyer has attached one, pays for it.
  • billingGroupId on the subscription reports the group at once, before the first shared billing date.

Currency, billing direction, revenue deferral, direct-debit collection and the payment-method mode are never rewritten. A subscription that differs from the set on any of them is refused with a SUBSCRIPTION_BILLING_GROUP_*_MISMATCH code, and the eligibility check below names it.

Check before you write

POST /v1/billing-groups/eligibility asks whether a set could form a group (mode: "form", with leadSubscriptionId and memberSubscriptionIds) or join one (mode: "join", with billingGroupId and subscriptionIds) and writes nothing. It reports every refusal at once, where the write itself stops at the first: at most one per subscription, named by subscriptionId, plus at most one about the whole set, with subscriptionId: null.

Code
{ "eligible": false, "refusals": [ { "code": "SUBSCRIPTION_BILLING_GROUP_BILLING_DIRECTION_MISMATCH", "subscriptionId": "0190…" }, { "code": "SUBSCRIPTION_BILLING_GROUP_MEMBER_LIMIT", "subscriptionId": null } ] }

A subscription refused for two reasons reports the first; the second appears once the first is fixed. A dissolved group answers eligible: false with a set-level SUBSCRIPTION_BILLING_GROUP_NOT_JOINABLE; an unknown group is a 404. The check needs only the subscriptions-read scope and ignores idempotency keys, so a caller can poll it as the set changes.

Read a group

GET /v1/billing-groups/{id} returns the group and the subscriptions billed together on its document, each with its own next billing date, monthly revenue and add-ons.

Code
{ "id": "0197…", "customerId": "0190…", "billingActiveFrom": "2026-10-01", "dissolvedAt": null, "billsTogether": true, "billsTogetherUntil": null, "slotsUsed": 3, "maxSlots": 20, "chargesByCard": false, "invoiceTemplateId": null, "includeMemberDetailsPdf": false, "members": [ { "subscriptionId": "0190…", "subscriptionName": "Premium Plan", "billingActiveFrom": "2026-10-01", "effectiveNextBillingDate": "2026-10-01", "monthlyRevenue": "499.00", "currency": "DKK", "addons": [ { "subscriptionId": "0190…", "subscriptionName": "Extra seats", "planId": "0190…", "addedOn": "2026-09-15", "effectiveNextBillingDate": "2026-10-01", "monthlyRevenue": "99.00", "currency": "DKK" } ] }, { "subscriptionId": "0190…", "subscriptionName": "Basic Plan", "billingActiveFrom": "2026-10-01", "effectiveNextBillingDate": "2026-10-01", "monthlyRevenue": "199.00", "currency": "DKK", "addons": [] } ] }
FieldMeaning
billsTogethertrue while two or more subscriptions hold a place in the group, including one that starts billing on it later. false once the group has dissolved or only one subscription is left in it.
billsTogetherUntilWhen the group stopped billing together: its dissolvedAt, or otherwise when the last subscription to leave it left. null while billsTogether is true. Turning a member into another member's add-on records no date, so a group that stopped billing together that way reports the last departure before it, or null when none left.
membersThe subscriptions billed together, ordered by the day they start billing on the group and then by id. At least two while billsTogether is true; empty when it is false.
members[].addonsThe member's add-ons, oldest first. Each is a subscription of its own, with its own stable id, billed inside the member's section. See Add-ons.
slotsUsedOne per member plus one per add-on. This is what the cap counts. 0 when billsTogether is false.
maxSlotsThe most slots a group can use, add-ons included: 20.
chargesByCardtrue when the next shared invoice is charged to the group's card; false when it is sent for bank payment. The card is the group's; members hold none of their own.
invoiceTemplateIdThe consolidated document template the shared invoice renders from. null uses the customer's template, then your default.
includeMemberDetailsPdfWhether each send also attaches a zip with one detail document per member.

A member's monthlyRevenue does not include its add-ons'; each add-on carries its own.

Take a subscription off a group

DELETE /v1/billing-groups/{id}/members/{subscriptionId} takes one subscription off the group, effective at once. It keeps the billing date and the fields the group gave it and bills on its own from its next billing date.

Code
{ "billingGroupId": "0197…", "subscriptionId": "0190…", "groupDissolved": false }

A group is two or more subscriptions billed together. The departure that leaves fewer dissolves the group, and the response says so with groupDissolved: true; the remaining subscription bills on its own from its next billing date. The subscription must be a current member of the group named in the path: otherwise the request answers 404 SUBSCRIPTION_BILLING_GROUP_MEMBER_NOT_FOUND and nothing changes, wherever the subscription actually bills. Taking a member off takes its add-ons out with it. An add-on cannot be taken off on its own: the request answers 400 SUBSCRIPTION_ADDON_CANNOT_LEAVE_ALONE, with details.subscriptionId naming it, and the add-on ends by being cancelled. While a shared invoice is still being collected on the group's card, no member can leave: the request answers 400 SUBSCRIPTION_BILLING_GROUP_LEAVE_BLOCKED_BY_UNPAID_INVOICE naming the invoice in details.invoiceId, because the leaver could no longer see or pay the document its payment state points at.

A group that no longer bills together

A group that has dissolved, or has one subscription left in it, answers GET /v1/billing-groups/{id} with billsTogether: false, members: [] and slotsUsed: 0, as long as an invoice or credit note names it: the documents it issued keep naming it. A dissolved group also has dissolvedAt set, and billsTogetherUntil says when it stopped billing together. An id that bills nothing together and that no invoice or credit note names answers 404 SUBSCRIPTION_BILLING_GROUP_NOT_FOUND, as an unknown id does.

On a dissolved group, everything that would put a subscription on it is refused with 400 SUBSCRIPTION_BILLING_GROUP_NOT_JOINABLE: adding members, creating a subscription onto it, and the eligibility check reports the same code as a set-level refusal. On a group with one subscription left, the member verbs answer 404 SUBSCRIPTION_BILLING_GROUP_NOT_FOUND. Form a new group instead.

Recognize a grouped subscription

GET /v1/subscriptions/{id} and GET /v1/subscriptions carry billingGroupId: the group the subscription is billed together with others on, or null when it bills on its own. A subscription that has joined a group but not yet reached its first shared billing date already reports the group, because the relationship exists before it bills.

billingGroupId is null for a subscription whose only companions are its add-ons: it bills its own document, on its own terms, and keeps its own card. It is null on an add-on too, including one whose parent is a member of a group; read the parent for the group. Two or more subscriptions billed together is what makes a group, so a group id reaches a subscription read only while that is true.

A grouped subscription reports paymentMethodId: null and automaticBilling: false. The card is the group's, not the member's, and only the buyer attaches one. Setting automaticBilling: true on a member answers 400 SUBSCRIPTION_BILLING_GROUP_MEMBER_DETAILS_LOCKED, and on a member's add-on 400 SUBSCRIPTION_ADDON_DETAILS_FOLLOW_PARENT.

Recognize a group's documents

An invoice says who it bills through three fields, described in Invoices:

customerSubscriptionIdbillingGroupIdisConsolidatedThe document is
setnullfalseone subscription's own invoice
nullsettruea billing group's shared invoice
nullnulltruea parent customer's invoice covering its child customers' subscriptions
nullnullfalsean invoice created by hand

billingGroupId and customerSubscriptionId are never both set. List a group's invoices with GET /v1/invoices?billingGroupId=<uuid>; the filter combines with status, customerId and paging like every other list filter.

The first row also covers a subscription with add-ons: the document names that one subscription, and its lines include its add-ons', which have no invoices of their own. On a group's shared invoice, a member's add-ons bill inside that member's section.

A credit note carries the same pair without isConsolidated: one issued for a group's shared invoice carries billingGroupId, one issued for a single subscription carries customerSubscriptionId. A group's credit notes render as one flat document; the id is a payer identity, not a document shape. See Credit notes.

The invoice's billingGroupId outlives the group. A subscription that has left its group reads billingGroupId: null, and a dissolved group has no current members, but every document the group issued keeps naming it. To find which group a past invoice belonged to, read the invoice, not the subscription.

Reconcile the shared invoice

One shared invoice produces one subscription.billed per member, and every one of those events carries the same invoiceId and invoiceNumber and the whole document total as amount, never that member's share. Deduplicate on invoiceId, attribute the set through billingGroupId, and never sum amount across the events of one invoice. A three-member group billed 1000.00 emits three events that each say 1000.00; the invoice is 1000.00.

billing.invoice_sent for a group's document carries subscriptionId: null and the group in billingGroupId. The credit-note counterpart does the same.

When the group pays by card, each member receives the payment-succeeded event when the group's charge clears, with the whole charged amount, the shared billingGroupId, and that member's own paidThroughDate on the shared invoice. If the group's retries run out, each member receives the exhausted-retries error. A member never receives the per-decline charge error and never enters awaiting_payment; the charge is the group's. Gate a member's access on those two events. The topics and their payloads are on Webhooks & reconciliation.

What a grouped subscription refuses

Members bill on one shared document, on one set of billing dates, so a change that would give one member its own document or move it off the group's dates is refused. The refusals below are behaviour of the subscription itself; over the public API they surface on cancellation and on the two update fields.

ChangeResponse
Cancel with refundBehavior: last_invoice400 SUBSCRIPTION_BILLING_GROUP_REFUND_LAST_INVOICE_NOT_SUPPORTED — the last invoice is the group's and covers other subscriptions. Use prorated or none.
automaticBilling: true on a member400 SUBSCRIPTION_BILLING_GROUP_MEMBER_DETAILS_LOCKED — the card and the automatic charge belong to the group. On a member's add-on, 400 SUBSCRIPTION_ADDON_DETAILS_FOLLOW_PARENT.
prorationBehavior: always_invoice on a plan replacement or products edit400 SUBSCRIPTION_BILLING_GROUP_IMMEDIATE_PRORATION_NOT_ALLOWED — omit the field; the adjustment lands on the group's next shared invoice.
Taking on a usage commitment while a member: a new commitment, or a plan replacement onto a plan that commits a quantity400 SUBSCRIPTION_BILLING_GROUP_PREPAID_COMMITMENT_NOT_SUPPORTED. A commitment the subscription already holds is a different matter: it only stops the subscription from being prorated onto another billing date (SUBSCRIPTION_BILLING_GROUP_PREPAID_COMMITMENT_BLOCKS_REANCHOR at eligibility), so such a subscription can lead a group, or join one that already bills on its date.
Direction or cadence change, pause, resume, merge, billing-anchor resetRefused with a SUBSCRIPTION_BILLING_GROUP_* code naming the change. These flows are operator-driven today; see Change flows.

Cancelling a member is allowed with any other strategy, and cancels its add-ons with it. A group that falls below two subscriptions billed together ends, and the remaining subscription bills on its own from its next billing date.

Errors & failure states

A refusal names a group in details.billingGroupId only on the /v1/billing-groups endpoints, and only a group GET /v1/billing-groups/{id} would answer for. A refusal on any other endpoint carries no details.billingGroupId.

ConditionWhat you observeFix
The customer is billed to a parent customer and a group is formed for it, or the reverse400 SUBSCRIPTION_BILLING_GROUP_CUSTOMER_BILLED_TO_PARENTChoose one: bill the customer to its parent, or group its subscriptions.
The subscription addressed through the group left it, or moved to another set, while the request ran400 SUBSCRIPTION_BILLING_GROUP_MEMBERSHIP_CHANGEDNothing was changed. Read the group again and retry.
Taking an add-on off the group on its own400 SUBSCRIPTION_ADDON_CANNOT_LEAVE_ALONE, details.subscriptionIdCancel the add-on with POST /v1/subscriptions/cancel, or take its parent off the group.
Cancelling a member that has add-onsOne of the SUBSCRIPTION_ADDON_CASCADE_* codesSee Add-ons → Cancel a parent.
N subscription.billed events for one invoiceExpected: one per member, each carrying the whole totalDeduplicate on invoiceId; read the total from the invoice, not from a sum of events.
Forming or joining with a subscription of another customer400 SUBSCRIPTION_BILLING_GROUP_DIFFERENT_CUSTOMERA group sits inside one customer.
A subscription that already holds a place in a group400 SUBSCRIPTION_BILLING_GROUP_ALREADY_IN_A_GROUPTake it off its group first, or leave it out of this one.
Currency, billing direction, revenue deferral, direct-debit collection or payment-method mode differ across the set400 SUBSCRIPTION_BILLING_GROUP_CURRENCY_MISMATCH, …_BILLING_DIRECTION_MISMATCH, …_DEFER_REVENUE_MISMATCH, …_LS_COLLECTION_MISMATCH or …_PAYMENT_METHOD_MODE_MISMATCHJoining never rewrites these. Align them first, or group only the subscriptions that agree; the eligibility check names the odd one out. A subscription created or attached as an add-on answers SUBSCRIPTION_ADDON_PARENT_MISMATCH instead, with details.field.
More than 20 slots in one group, counting members and their add-ons400 SUBSCRIPTION_BILLING_GROUP_MEMBER_LIMIT, details.slotsUsed, details.slotsRequested, details.maxSlotsForm a second group.
Adding to, or creating a subscription onto, a dissolved group400 SUBSCRIPTION_BILLING_GROUP_NOT_JOINABLE, details.dissolvedAtForm a new group.
Removing a subscription that is not a current member of the group named in the path404 SUBSCRIPTION_BILLING_GROUP_MEMBER_NOT_FOUND, details.groupDissolvedAtRead the subscription's billingGroupId for the group it is in.
Removing a member while a shared invoice is still being collected on the group's card400 SUBSCRIPTION_BILLING_GROUP_LEAVE_BLOCKED_BY_UNPAID_INVOICE, details.invoiceIdWait for the charge to clear, or for its retries to end.
An unknown group id; on a read, an id that bills nothing together and that no invoice or credit note names; on a member verb, an id naming fewer than two subscriptions billed together404 SUBSCRIPTION_BILLING_GROUP_NOT_FOUNDThe id is the billingGroupId a subscription or an invoice reports; a subscription reports one only while it is billed together with others.
The group's billing date moved while a subscription was being created onto it409 SUBSCRIPTION_BILLING_GROUP_GRID_MOVED_DURING_ASSIGNNothing was created. Retry the request.
Last modified on November 6, 2026
Add-onsWebhooks
On this page
  • Form a group
  • Add subscriptions to a group
  • Check before you write
  • Read a group
  • Take a subscription off a group
    • A group that no longer bills together
  • Recognize a grouped subscription
  • Recognize a group's documents
  • Reconcile the shared invoice
  • What a grouped subscription refuses
  • Errors & failure states
JSON
JSON
JSON
JSON