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 group | POST /v1/billing-groups/eligibility |
| Form a group from existing subscriptions | POST /v1/billing-groups |
| Read a group and its current members | GET /v1/billing-groups/{id} |
| Add existing subscriptions to a group | POST /v1/billing-groups/{id}/members |
| Take a subscription off a group | DELETE /v1/billing-groups/{id}/members/{subscriptionId} |
| Create a subscription onto a group, or form a group around an existing one | POST /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.
Code
Code
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
automaticBillingturns off. The group's card, when the buyer has attached one, pays for it. billingGroupIdon 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
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
| Field | Meaning |
|---|---|
billsTogether | true 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. |
billsTogetherUntil | When 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. |
members | The 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[].addons | The 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. |
slotsUsed | One per member plus one per add-on. This is what the cap counts. 0 when billsTogether is false. |
maxSlots | The most slots a group can use, add-ons included: 20. |
chargesByCard | true 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. |
invoiceTemplateId | The consolidated document template the shared invoice renders from. null uses the customer's template, then your default. |
includeMemberDetailsPdf | Whether 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
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:
customerSubscriptionId | billingGroupId | isConsolidated | The document is |
|---|---|---|---|
| set | null | false | one subscription's own invoice |
null | set | true | a billing group's shared invoice |
null | null | true | a parent customer's invoice covering its child customers' subscriptions |
null | null | false | an 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.
| Change | Response |
|---|---|
Cancel with refundBehavior: last_invoice | 400 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 member | 400 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 edit | 400 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 quantity | 400 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 reset | Refused 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.
| Condition | What you observe | Fix |
|---|---|---|
| The customer is billed to a parent customer and a group is formed for it, or the reverse | 400 SUBSCRIPTION_BILLING_GROUP_CUSTOMER_BILLED_TO_PARENT | Choose 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 ran | 400 SUBSCRIPTION_BILLING_GROUP_MEMBERSHIP_CHANGED | Nothing was changed. Read the group again and retry. |
| Taking an add-on off the group on its own | 400 SUBSCRIPTION_ADDON_CANNOT_LEAVE_ALONE, details.subscriptionId | Cancel the add-on with POST /v1/subscriptions/cancel, or take its parent off the group. |
| Cancelling a member that has add-ons | One of the SUBSCRIPTION_ADDON_CASCADE_* codes | See Add-ons → Cancel a parent. |
N subscription.billed events for one invoice | Expected: one per member, each carrying the whole total | Deduplicate on invoiceId; read the total from the invoice, not from a sum of events. |
| Forming or joining with a subscription of another customer | 400 SUBSCRIPTION_BILLING_GROUP_DIFFERENT_CUSTOMER | A group sits inside one customer. |
| A subscription that already holds a place in a group | 400 SUBSCRIPTION_BILLING_GROUP_ALREADY_IN_A_GROUP | Take 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 set | 400 SUBSCRIPTION_BILLING_GROUP_CURRENCY_MISMATCH, …_BILLING_DIRECTION_MISMATCH, …_DEFER_REVENUE_MISMATCH, …_LS_COLLECTION_MISMATCH or …_PAYMENT_METHOD_MODE_MISMATCH | Joining 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-ons | 400 SUBSCRIPTION_BILLING_GROUP_MEMBER_LIMIT, details.slotsUsed, details.slotsRequested, details.maxSlots | Form a second group. |
| Adding to, or creating a subscription onto, a dissolved group | 400 SUBSCRIPTION_BILLING_GROUP_NOT_JOINABLE, details.dissolvedAt | Form a new group. |
| Removing a subscription that is not a current member of the group named in the path | 404 SUBSCRIPTION_BILLING_GROUP_MEMBER_NOT_FOUND, details.groupDissolvedAt | Read 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 card | 400 SUBSCRIPTION_BILLING_GROUP_LEAVE_BLOCKED_BY_UNPAID_INVOICE, details.invoiceId | Wait 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 together | 404 SUBSCRIPTION_BILLING_GROUP_NOT_FOUND | The 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 it | 409 SUBSCRIPTION_BILLING_GROUP_GRID_MOVED_DURING_ASSIGN | Nothing was created. Retry the request. |