Billing documents
LedgerBee has four customer-facing documents, all built on the same line model and rendered to PDF. Two are booked accounting documents and two are pre-sales documents that convert forward into a booked invoice:
| Document | Booked? | Page |
|---|---|---|
| Invoice | yes — posts to the books on send | Invoices |
| Credit note | yes — posts to the books on send | Credit notes |
| Quote | no — a price offer | Quotes |
| Order confirmation | no — confirms an agreed order | Order confirmations |
All endpoints live under https://api.ledgerbee.com/api/v1 and authenticate with
the x-api-key header. Full request/response schemas live in the
API Reference. This page covers what the four share; each document's own
page covers its lifecycle, creation, and type-specific operations.
Scopes
Each resource has its own scope pair (see API Keys). Invoices and credit notes share one pair:
| Resource | Read | Write |
|---|---|---|
| Invoices & credit notes | invoices-read | invoices-write |
| Quotes | quotes-read | quotes-write |
| Order confirmations | order-confirmations-read | order-confirmations-write |
Read scopes cover list, get, and PDF; write scopes cover create, update, send, and convert.
Document lines
A line is one of two kinds, and what you send depends on the kind. The same rules apply to invoices, credit notes, quotes, and order confirmations.
| Line kind | How to send it | Amounts & VAT |
|---|---|---|
| Product line | Set priceId (required) and quantity. Optionally override unitPrice, and set description for custom line text. | Derived server-side: totalPrice = quantity * unitPrice for a flat-rate price or an overridden unitPrice; for a volume, graduated or package price, totalPrice is what the price's tiers charge for the quantity. VAT from the product and the customer's VAT zone. Do not send totalPrice / vatRate / vatAmount. |
| Text / separator line | Set lineType to text or separator. priceId and quantity are not required for these lines — omit them. | None — do not send totalPrice / vatRate / vatAmount. |
Every billable line must reference a priceId from the
product catalogue. A document must contain at least
one product line; text / separator lines may accompany it but do not satisfy the
rule on their own. There is no hand-typed amount line — a billable line always
bills a catalogue price.
Lines are returned in the order you list them in the lines array: the API
stamps your submission order onto each line. There is no separate sort field; to
reorder, send the array in the order you want.
Creation does not dedupe retried requests. When a timeout leaves the outcome unknown, list recently created documents before re-creating. See Idempotency.
Text on the document
A document has three free-text fields. Two of them print on the PDF; the third is for your own records.
| Field | Printed on the PDF | Use it for |
|---|---|---|
subject | Yes, in bold, directly above notes | A heading that says what the document is about, such as Website maintenance, March. At most 255 characters. |
notes | Yes, under subject | A message to the customer. |
description | Never | Your own reference. The customer never sees it. Invoices and credit notes only. |
In the standard layout subject and notes print under the customer's
address, above the lines.
Code
On an update, leave subject out to keep it, or send null to clear it.
Converting a quote or an order confirmation carries its subject to the
document it becomes.
Discounts
A discount is a record in LedgerBee: a name, a type (percentage, fixed or per
unit), a scope (line or document) and the products, customers and dates it
applies to. A document applies a discount by naming its id in discountIds.
There is no hand-typed discount on a line. Creating discount records and
assigning them is covered in Discounts.
| Where | Takes | Applies |
|---|---|---|
A line's discountIds | Line-scope discounts | To that line, in the order sent |
The document's discountIds | Document-scope discounts | Once, after every line discount, to the lines whose product it applies to (every line when it names no product) |
Each id is checked for the line's product, the document's customer and the document date. A document converted from a quote is checked at the quote's date. A fixed or per-unit discount takes its amount in the document's currency; a discount with no amount in that currency is refused rather than converted.
Acme buys 2 × Widget at 1,000.00 and 1 × Gadget at 500.00, both at 25 % VAT.
"Loyalty 10%" is a line-scope percentage discount assigned to Acme for both
products. POST /v1/invoices:
Code
The invoice comes to 2,250.00 net and 562.50 VAT. Each line reports what each
discount took off in appliedDiscounts, the document reports its
document-scope discounts in documentDiscounts, and totalDiscounted is the
sum of both:
Code
A line's totalPrice stays its amount before discounts. totalDiscounted also
counts a line discount typed by hand in LedgerBee, and is null on a document
that names no discount record.
On an update, a line's discountIds travel with the restated lines: a line sent
without them has no line discounts. The document's discountIds stay as they
are when left out, and an empty array removes them. A credit note that names an
invoice takes no discounts of its own. It gives back the discounts that invoice
used on each line that names the invoice line it credits — see
Credit specific invoice lines
and Credit a whole invoice.
A discount that cannot be applied refuses the request with 400:
| Code | Cause |
|---|---|
DISCOUNT_NOT_APPLICABLE | No such discount, inactive, not assigned to this product and customer, or outside its dates. details.reason says which. |
DISCOUNT_WRONG_SCOPE | A document-scope id on a line, or a line-scope id on the document. |
DISCOUNT_REFERENCED_TWICE | The same id twice on one line or on the document. |
DISCOUNT_NOT_COMBINABLE | A discount that may not share a line with another. |
DISCOUNT_NO_AMOUNT_IN_CURRENCY | A fixed or per-unit discount with no amount in the document's currency. |
DISCOUNT_LINE_WITHOUT_PRODUCT | A discount on a text or separator line. |
DISCOUNT_EXCEEDS_LINE / DISCOUNT_EXCEEDS_DOCUMENT | The discounts would take the line or the document below zero. |
DISCOUNT_REPRICE_NEEDS_LINES | An invoice update changed its discounts, or the customer, date or currency of an invoice carrying them, without sending lines. |
DISCOUNT_CREDIT_NOTE_FOLLOWS_INVOICE | A credit note that names invoiceNumber sent discount ids. |
Reading documents
Every resource exposes the same read surface:
| Action | Path |
|---|---|
| List | GET /v1/{resource} |
| Get one | GET /v1/{resource}/{id} |
| Download PDF | GET /v1/{resource}/{id}/pdf |
Lists paginate (page, default 1; limit, default 25, max 100) and filter by
customerId and status (plus free-text search on invoices/credit notes, and
sortBy / sortOrder on quotes/order confirmations). The status filter matches
the display status, so server-derived states work too — each document page
lists its own status values. List items omit the lines breakdown and the
discounts; get a single document to read them. Every {id} path segment is a UUID.
GET /v1/{resource}/{id}/pdf streams application/pdf as an attachment. Pass
?draft=true to render a watermarked draft preview before the document is sent.
Sending
POST /v1/{resource}/{id}/send allocates the document number (if not already
set), renders the PDF, and delivers it over the chosen channel. The body's
deliveryType selects the channel:
deliveryType | Behaviour |
|---|---|
Email | Emails the rendered PDF. The recipient resolves from the request's email field, else the customer contact, else the customer record. |
Sproom | Invoices and credit notes only. Delivers over the Sproom e-invoicing network. There is no legal e-invoice type for a quote or order confirmation. |
Manual | Allocates the number without delivering. Download the PDF and deliver it yourself. |
Leverandorservice | Invoices only. Collects the amount by Danish direct debit over Leverandørservice. The collection request is generated on send; the customer is emailed the invoice — carrying a "will be collected, do not pay manually" notice — once Mastercard accepts the collection. It requires your company to be configured as a creditor, the customer to be enrolled with payment details, and an active mandate — otherwise the send fails with LS_CREDITOR_NOT_CONFIGURED, LS_CUSTOMER_NOT_SET_UP, LS_PAYMENT_DETAILS_REQUIRED, or LS_MANDATE_REQUIRED. |
When you send by email to an address that isn't already a contact of the customer,
that email is created as a customer contact and linked to the document (matching
the gated app); an email that already matches a contact reuses it.
documentTemplateId overrides the PDF template for a single send. The exact
recipient field names differ by resource — see each document's page.
Errors
| Condition | Response | Fix |
|---|---|---|
Invalid request (e.g. non-UUID {id}) | 400 | Pass a UUID id and a body matching the schema. |
| Invalid or missing API key | 401 | Send a valid x-api-key header. |
| API key missing required scopes | 403 | Grant the key the resource's read / write scope. |
| Email send with no resolvable recipient | the send fails | Pass the recipient email, or set an email on the customer / contact. |
Type-specific errors (conversion, locked-after-convert, …) are on each document's page.