LedgerBee Developer
  • Getting started
  • Conventions
  • Products
  • Configuration
  • API Reference
Subscriptions
    OverviewAssign a subscriptionData modelLifecycle & statusesBilling & cadenceProrationUsage commitmentsReporting usageChange flowsParent & childBilling groupsWebhooks
Payment flowCard paymentsProducts & PricingProduct 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.

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.

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…" }, { "subscriptionId": "0190…" }, { "subscriptionId": "0190…" }] }

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. 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.

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 needs two members. 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. Some members can only be taken off by the account's operator in the LedgerBee app; for those the request answers 400 SUBSCRIPTION_BILLING_GROUP_MEMBER_NOT_REMOVABLE. 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 dissolved group

A dissolved group keeps answering GET /v1/billing-groups/{id}, with dissolvedAt set and members: [], because the invoices and credit notes it issued keep naming it. 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. Form a new group instead.

Recognize a grouped subscription

GET /v1/subscriptions/{id} and GET /v1/subscriptions carry billingGroupId: the group the subscription bills 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.

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.

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.

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.
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. A group that falls below two members dissolves, and the remaining subscription bills on its own from its next billing date.

Errors & failure states

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.
Cancelling a member is blocked by the state of another member of the set400 SUBSCRIPTION_BILLING_GROUP_CANCEL_BLOCKED_BY_MEMBER, details.blockingSubscriptionIdResolve the named subscription's state first, or ask the account's operator.
The group's membership changed while a request ran: a member was added, removed or cancelled concurrently, or the subscription being removed had moved to another group400 SUBSCRIPTION_BILLING_GROUP_MEMBERSHIP_CHANGEDNothing was changed. Read the group again and retry.
Cancelling with a refund that would credit more than one invoice400 SUBSCRIPTION_BILLING_GROUP_CANCEL_REFUND_SPANS_INVOICESCancel with refundBehavior: none, or cancel the members separately.
The member can only be taken off its group in the LedgerBee app400 SUBSCRIPTION_BILLING_GROUP_MEMBER_NOT_REMOVABLEAsk the account's operator to take it off there.
Any other change to the set this API has no verb for400 SUBSCRIPTION_BILLING_GROUP_OPERATION_NOT_ALLOWEDThe operation is operator-only today.
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.
More than 20 subscriptions in one group400 SUBSCRIPTION_BILLING_GROUP_MEMBER_LIMITForm 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 id404 SUBSCRIPTION_BILLING_GROUP_NOT_FOUNDThe id is the billingGroupId a subscription or an invoice reports.
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 September 13, 2026
Parent & childWebhooks
On this page
  • Form a group
  • Add subscriptions to a group
  • Check before you write
  • Take a subscription off a group
    • A dissolved group
  • Recognize a grouped subscription
  • Recognize a group's documents
  • Reconcile the shared invoice
  • What a grouped subscription refuses
  • Errors & failure states
JSON
JSON
JSON