Invoices
An invoice is a booked accounting document: sending allocates its number and
posts it to the books. Reads require the invoices-read scope and writes
invoices-write — one scope pair covers invoices and
credit notes. The shared line model,
list/get/PDF reads, and send channels are in
Billing documents; this page covers what is
specific to invoices.
Lifecycle
| Phase | What happens |
|---|---|
| Create | POST /v1/invoices returns a draft. The draft has no number yet (number is null). |
| Update | PATCH /v1/invoices/{id} changes an invoice that has not been sent. |
| Delete | DELETE /v1/invoices/{id} removes a draft. |
| Send | POST /v1/invoices/{id}/send books the document, allocates the invoice number, and delivers it over the chosen channel. |
| Download | GET /v1/invoices/{id}/pdf streams the rendered PDF at any point. Before send, pass ?draft=true for a watermarked preview. |
| Track | GET /v1/invoices/{id}/delivery-status returns the aggregate send summary plus a per-attempt log. |
Create an invoice
POST /v1/invoices returns the draft as PublicInvoice. customerId is
required, and the document must carry at least one product line (see
Document lines). A billable
line sends a priceId and a quantity; you may override unitPrice (it defaults
to the price's current amount in the document currency). totalPrice and VAT
(vatRate / vatAmount, derived from the product and the customer's VAT zone)
are computed server-side — do not send them. totalPrice is
quantity * unitPrice for a flat-rate price or an overridden unitPrice, and
what the price's tiers charge for the quantity on a volume, graduated or package
price.
The invoice number stays null until the document is sent.
dueDate is optional. When omitted it is derived from the payment terms
(date plus paymentTerms days; for cash terms the due date is the invoice
date). An explicit dueDate may equal date but must not be earlier — see
Errors.
subject and notes print on the PDF; description never does. See
Text on the document.
Code
Update an invoice
PATCH /v1/invoices/{id} changes an invoice whose status is draft or
scheduled and returns the updated PublicInvoice. Send null to clear a
field the API Reference marks as nullable, such as reference,
subject or notes.
Code
| Request | Result |
|---|---|
| A field is left out | Its stored value is kept, except where the rules below change it. |
A nullable field is null | The field is cleared. |
Any other field is null | 400. |
lines is sent | Every stored line is replaced. Send the full intended set: a line left out of the array is removed. |
lines is left out | The stored lines are kept. |
The invoice must still carry at least one product line. Prices, totals and VAT are worked out again from the lines you send.
Changing customerId or currency requires lines in the same request: line
prices are derived from the currency and line VAT from the customer's VAT zone.
A customer change also removes the stored customerDepartmentId and
customerContactId unless the request states new ones, and removes any extra
email recipients added to the invoice in the app.
A request with date or paymentTerms and no dueDate sets the due date
again from the invoice's payment terms.
An approved invoice whose content changes loses its approval. If it was
scheduled to send, it returns to draft.
lines cannot be replaced on the invoices below. The request is refused with
INVOICE_LINE_UPDATE_NOT_SUPPORTED, and details.reason says why:
details.reason | The invoice |
|---|---|
subscription | was created by subscription billing (it has a customerSubscriptionId or a billingGroupId, or isConsolidated set), or one of its lines bills a subscription. |
self_billing | is a self-billing invoice. |
shipment | has lines a shipment refers to. |
The other fields of such an invoice can still be changed, except customerId
and currency, which require lines.
A line that defers its revenue (recognitionPeriodDays) is replaced like any
other line. Its revenue schedule is written again from the lines and the date
the invoice ends up with, so the period a replacement line states is the one
that is recognised.
A sent invoice is booked. Correct it with a credit note.
Delete an invoice
DELETE /v1/invoices/{id} removes an invoice whose status is draft and
returns 204 with no body. The Errors table lists the drafts that
are kept.
Code
Deleting a draft that subscription billing created removes the invoice only. Subscription billing does not bill that period again.
Status values
Invoice status is one of draft, scheduled, sending, sent, paid,
partially_paid, cancelled, uncollectible, credited, ls_rejected.
ls_rejected appears only on an invoice collected through Leverandørservice
(Mastercard Payment Services): the payer's bank rejected or charged back the
collection while the invoice was sending, so the invoice was not paid that
way.
Send
POST /v1/invoices/{id}/send books the document and allocates its number. What
happens next is the deliveryType channel's business — Manual stops there and
leaves delivery to you. Invoices accept every channel; see
Sending.
Code
overrideEmail and overrideEanNumber apply to the single send unless
updateCustomerRecord is true, which persists them onto the customer record.
documentTemplateId overrides the PDF template for this send. The response
carries success, deliveryType, and sproomDocumentId (the id of the document
created on the Sproom network, for a Sproom send).
Delivery status
GET /v1/invoices/{id}/delivery-status returns a summary (send count, last
channel, last recipient, last send time) and a chronological entries log of
every attempt with its status (success / failed), deliveryType, recipient,
sentAt, and errorCode on failure.
A Sproom send rejected by the network does not fail the request synchronously;
it records a failed delivery entry with its errorCode. Poll delivery-status
after a Sproom send to confirm the outcome.
Errors
| Condition | Response | Fix |
|---|---|---|
| Missing or wrong scope | 403 | Grant the matching read or write scope. |
| Non-UUID id / unknown id | 400 / 404 | Pass a UUID id that exists for your tenant. |
dueDate earlier than date | 400 INVOICE_DUE_DATE_BEFORE_ISSUE_DATE | Send a due date on or after the invoice date, or omit dueDate to derive it from the payment terms. |
| Update of an invoice that has been sent | 400 INVOICE_MODIFICATION_FORBIDDEN | Issue a credit note against it. |
Update that changes customerId or currency without lines | 400 INVOICE_CUSTOMER_OR_CURRENCY_CHANGE_REQUIRES_LINES | Send the full line set in the same request. On an invoice whose lines cannot be replaced, the customer and currency cannot be changed. |
Update with lines on an invoice whose lines cannot be replaced | 400 INVOICE_LINE_UPDATE_NOT_SUPPORTED | Leave lines out of the request. details.reason names the cause; see Update an invoice. |
Update with lines, a department or a contact on an invoice that has no customer | 400 INVOICE_MISSING_CUSTOMER | Send customerId together with lines. |
| Delete of an invoice that is not a draft, or of a draft whose revenue recognition has already posted | 400 INVOICE_DELETION_FORBIDDEN | Issue a credit note against a sent invoice. |
| Update or delete of an invoice waiting for an approver | 400 INVOICE_APPROVAL_PENDING | Retry once the approval is decided. |
| Delete of a draft a shipment refers to | 400 INVOICE_DELETION_BLOCKED_BY_SHIPMENT | Cancel the shipment first. |
| Delete of a draft with a card payment that took money, may still take it, or holds an amount | 400 INVOICE_DELETION_BLOCKED_BY_CARD_CHARGE | Keep the draft. |
scheduledSendAt on cash payment terms | 400 INVOICE_CASH_CANNOT_BE_SCHEDULED | Cash invoices are paid on send; remove scheduledSendAt or use day-count terms. |
| Card payment providers with no customer | 400 INVOICE_CARD_PAY_REQUIRES_CUSTOMER | Set customerId when enabling allowedPaymentProviders. |
Subscription and billing-group invoices
Three fields on an invoice say who it bills:
| Field | Set when |
|---|---|
customerSubscriptionId | The invoice bills exactly one subscription. |
billingGroupId | The invoice is a billing group's shared document. Never set together with customerSubscriptionId. |
isConsolidated | Several subscriptions bill onto this one document: a billing group's shared invoice, or a parent customer's invoice covering its child customers' subscriptions. With both ids null it is the parent-paid kind. |
A manually created invoice carries neither id. List a group's documents with
GET /v1/invoices?billingGroupId=<uuid>.
customerSubscriptionId names the exact version of the subscription that issued
the invoice. Once a subscription has been changed (a new plan, a scheduled edit)
that id can differ from the subscription's own id; the per-line
subscriptionId below and the subscriptionId list filter always use the
subscription's own id, and the filter accepts either.
Which subscription each line was billed for
One invoice can carry several subscriptions' lines: an
add-on bills on its parent's invoice, a billing
group's members share one document, and a parent customer's invoice covers its
children's subscriptions. Each line on GET /v1/invoices/{id} says whose it is:
| Line field | Value |
|---|---|
subscriptionId | The subscription the line was billed for. An add-on's lines name the add-on, not the parent the invoice is addressed to. null on a line that belongs to no subscription, such as a line on a manually created invoice. |
addonOfSubscriptionId | On an add-on's lines, the parent it is billed with. null on every other line. |
The same two fields are on credit-note lines.
A parent with one add-on, billed on one invoice addressed to the parent (abridged — other line fields omitted):
Code
Find the invoices a subscription was billed on
GET /v1/invoices?subscriptionId=<uuid> returns every invoice the subscription
was billed on: the ones addressed to it, and the ones addressed to another
subscription or to a billing group that carry its lines. Filtering by an add-on
therefore returns its parent's invoices. An unknown subscription returns an empty
list, the same as an unknown customerId; a malformed id returns 400.
Code
List items carry no lines; read an invoice for its per-line subscriptionId.
Webhook events
Subscription-driven invoices (those the billing engine sends) emit the
billing.invoice_sent topic. On a billing group's shared invoice the payload's
subscriptionId is null; read the invoice for its billingGroupId. Invoices you
create and send directly over this API are booked the same way but are not
subscription events. Webhooks covers endpoint setup, the
signed-envelope format, and the full topic list.
| Topic | Fires when |
|---|---|
billing.invoice_sent | Fired when an invoice document is sent to the customer. |
billing.credit_note_sent | Fired when a credit note document is sent to the customer. |
billing.invoice_updated | Fired when an invoice is changed, in the app or through the API alike: its fields, its lines or their order, or its status. A draft scheduled or cancelled fires it, and so does an invoice reaching sent once it is delivered, beside billing.invoice_sent. A save that changes nothing fires nothing; changedFields names what moved. |
billing.invoice_deleted | Fired when a draft invoice is deleted, in the app or through the API alike. Only drafts can be deleted; a sent invoice is corrected with a credit note instead. |
billing.invoice_paid | Fired when a customer invoice becomes paid in full: by payments, by a settlement, or by a credit note applied to it. Only the invoice fires it — the credit note it was settled against does not, and neither does a reminder fee or an advance. A partial payment fires nothing. An invoice reopened (a payment reverted) and paid again fires again. Invoices brought in by an import do not fire it. |
A change to an invoice emits billing.invoice_updated, whether it is made over
this API or in the LedgerBee app. The payload is a summary of the invoice after
the change, not the whole invoice: its id, number, customer, status, totals and
currency, with changedFields, the names of the fields that changed. It does not
carry the lines; read the invoice for them. Webhooks lists
every field of the payload. Changing lines, or only their order, lists
lines, plus subtotal, vat and amount when the totals moved. Sending an
invoice emits it as well, with
changedFields set to ["status"] once the invoice is delivered and its
status is sent. A PATCH that changes nothing emits no event. Deleting a
draft emits the delete topic in the table above, carrying the invoice's id, its
number (null until the invoice is sent) and its customer.