BillKit/DocsConsole
Core concepts

Invoices and credit notes

BillKit issues the documents. You read them, render them, re-send them, and void the ones that should never have existed.

Updated Oct 14, 2026·OpenAPI v1.0.0

There is no create endpoint

An invoice is written for you, at the moment a charge settles. That covers a subscription’s first payment and every renewal, the charge for a plan change, a one-off sale, and a metered period close. A payment-method verification charge sells nothing and gets no invoice, and that includes the small charge that starts a trial or a metered subscription: the first invoice there is the charge at trial_end, or the first period close. invoice.created fires, then invoice.paid once the money is in.

Before a licensed renewal is charged, invoice.upcoming announces it: three days ahead, seven for a yearly plan, and again if the amount changes before the charge. A SEPA direct debit is only collected once the customer has been emailed the amount, at least three days earlier. An amount that changes inside that window, or a payment method that switches to SEPA after the notice went out, is announced again and the debit moves to three days after that email. Its data is an upcoming_invoice object carrying subscription_id, customer_id, amount_due_cents (VAT included), tax_cents, currency, price_id, coupon_id, period_start and next_payment_attempt, the time of the charge. No invoice exists yet at that point; the invoice is written when the renewal settles. A metered SEPA subscription is announced when its period closes, with the new invoice’s invoice_id, and debited three days later.

The consequence is that an invoice is a record of something that happened, not a request for payment you assemble yourself. BillKit does not do quotes, drafts, purchase orders or net-30 terms. If you need to bill somebody on paper and collect by bank transfer, that is work outside BillKit.

The document

shell
curl https://api.billkit.eu/v1/invoices/inv_Wq3Ye8nB5tX1rKcMzv \
-H "Authorization: Bearer $BILLKIT_API_KEY"
FieldNotes
numberstringYYYY-NNNNNN, allocated per account and per mode. The series is gapless and restarts each January, which is what most member states expect of a sales-invoice sequence.
statusstringopen, paid, void or uncollectible. See below.
customer_id / subscription_idsubscription_id is null on a one-off sale.
currency / subtotal_cents / tax_cents / total_centsNet before discount, VAT, and what was charged, in minor units. discount_cents is what a coupon took off, ex VAT, so subtotal_cents - discount_cents + tax_cents = total_cents on a VAT-inclusive price as much as on an exclusive one.
tax_country_code / tax_reverse_chargedThe country the VAT was decided for, and whether the supply was reverse-charged. See Charge and record EU VAT.
payment_id / one_shot_payment_idExactly one is set on an invoice that documents a charge. Both are null on a metered invoice that is still open, because nothing has been charged for it yet.
due_at / paid_at / voided_atinteger | nullUnix seconds.
linesarrayLine items: description, amount_cents, quantity, tax_rate_basis_points and the period_start / period_end they cover. Returned on the retrieve and empty on the list, to keep that response small.
*_at_invoice_timeThe buyer as invoiced: name, email, business name, VAT number, billing address. Snapshots, not live reads.
pdf_urlnullNot populated. Fetch the document from GET /v1/invoices/{id}/pdf.

GET /v1/invoices lists newest first and narrows with customer_id, subscription_id, payment_id and status. status takes a single value, and an unknown one is a 400 naming the valid ones. There is no draft: every invoice is issued, numbered, open or paid. Both routes take expand=customer, which adds who the customer is today beside the snapshot of who was invoiced. For a deleted or purged customer it is null, and the *_at_invoice_time snapshot is what remains.

The buyer fields are frozen. Tax is worked out before the money moves, written onto the charge, and copied from there onto the invoice. Renaming a customer or correcting their VAT number afterwards does not restate a document you have already issued, which is the behaviour you want and also why a correction is a credit note rather than an edit.

Statuses

statusMeaning
openIssued and not settled. A metered period close opens one before it charges the mandate, and invoice.payment_failed fires each time that charge fails.
paidSettled. A subscription charge and a one-off sale both land here directly.
uncollectibleCollection was abandoned, because the retries ran out or the invoice stayed unpaid past the payment method’s limit, and invoice.marked_uncollectible fired. Metered only: a metered invoice is issued before it is charged, while a licensed renewal is invoiced only once it is paid.
voidNever owed. See below.

The PDF

GET /v1/invoices/{id}/pdf is the document you hand a customer or their accountant. It is rendered on the first request and reused afterwards, so that first call is slower than the rest.

Follow redirects. Depending on how the deployment stores documents you either get the bytes back directly or a 302 to a short-lived download link. If PDF rendering is not enabled you get a 501 with code rendering_pending. The invoice itself is still available from GET /v1/invoices/{id}, which carries everything you need to typeset your own.

A rendered PDF is cached once the document is final (paid, void, or a credit note); an open or uncollectible invoice is rendered fresh on each request, so its PDF always shows where it stands. Changing your logo or brand colours under Settings → Branding affects invoices rendered from then on, and does not restyle one a customer has already downloaded.

POST /v1/invoices/{id}/email sends the customer the invoice again, worded for where it stands. A paid invoice gets the same branded receipt BillKit sends on issue. An open one gets a request for payment: the amount due, the due date, and a link to pay with a different payment method when the invoice belongs to a subscription. An uncollectible one gets a notice that it is unpaid. A void invoice is owed by nobody, so the call is refused with 409 state_conflict and nothing is sent. Every version carries fresh links, because the ones in the original may have expired. It comes from your own domain if you have set one up, and otherwise from a BillKit address carrying your name. It goes to the address captured on the invoice rather than the customer’s current one: this is a copy of a document issued to somebody, and quietly redirecting it would make the resend a different act from the original send. An invoice with no address on file is a 400 rather than a reported send that never happened.

shell
curl -X POST https://api.billkit.eu/v1/invoices/inv_Wq3Ye8nB5tX1rKcMzv/email \
-H "Authorization: Bearer $BILLKIT_API_KEY"
Response · 200 OK
{
"object": "invoice.email",
"invoice_id": "inv_Wq3Ye8nB5tX1rKcMzv",
"recipient": "ada@example.com",
"sent": true
}

recipient is where it actually went. A 200 means BillKit queued the message for delivery, not that a mailbox accepted it.

Voiding

POST /v1/invoices/{id}/void states that the sale was never owed. Use it for a period metered wrong, a customer billed by mistake, or an amount you have decided not to pursue. The invoice keeps its number and stays readable, because a gapless series cannot have a row taken out of it. It stops being a receivable, and invoice.voided fires.

shell
curl https://api.billkit.eu/v1/invoices/inv_Wq3Ye8nB5tX1rKcMzv/void \
-H "Authorization: Bearer $BILLKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "metered period double-counted"}'

The body is optional. reason goes on the audit entry only, because it is written for an operator and has no place on a customer’s PDF. The response is the voided invoice.

A paid invoice cannot be voided. Voiding claims nothing was ever owed, which stops being true once the money has moved, so the call is a 409 with code invoice_not_voidable. Refund the payment instead. An open or uncollectible invoice can be voided. Re-voiding an already-void invoice returns it unchanged and emits nothing a second time, and a charge still in flight against the invoice is refused with 409 operation_pending.

Usage already attached to a voided invoice stays attached to it and is never re-billed on a later period close.

Credit notes

A credit note is the document that reverses part or all of an invoice that was genuinely owed. It is what you issue when money has already moved, which is exactly the case voiding cannot cover.

You do not write one. BillKit issues it when a refund settles, which means nothing is issued while a refund is pending and nothing is issued if it fails. Refunds against invoiced subscription charges and against one-off sales both produce one. The checkout charge that starts a trial or a metered subscription has no invoice, so refunding it issues no credit note. credit_note.created fires when it is issued. GET /v1/credit_notes lists newest first and filters by invoice_id or customer_id.

shell
curl "https://api.billkit.eu/v1/credit_notes?invoice_id=inv_Wq3Ye8nB5tX1rKcMzv" \
-H "Authorization: Bearer $BILLKIT_API_KEY"
FieldNotes
numberCN-YYYY-NNNNNN. Its own series, separate from the invoice one, because a tax authority treats them as different document classes.
invoice_id / customer_idThe invoice being credited, which is always set, and whose it was.
refund_id / reasonThe settled refund that triggered it, and that refund’s reason.
subtotal_cents / tax_cents / total_centsPositive magnitudes. The direction is carried by the document being a credit note, and the PDF renders the minus sign.
tax_country_code / tax_rate_basis_points / tax_reverse_chargedCarried across from the sale, so a zero-rated supply is withdrawn on the same basis it was made.

A partial refund credits its share of the VAT rather than all of it or none of it, so the credit notes against one sale add up to exactly the tax that sale charged. GET /v1/credit_notes/{id}/pdf renders the document, on the same terms as an invoice PDF.

How long these are kept

Indefinitely. Invoices, credit notes, payments and refunds are the issued accounting record, and you are required to keep them for years after the fact, so nothing sweeps them. That is the opposite of events and delivery attempts, which age out.

GET /v1/tenant/export streams the whole account, invoices and their line items included, in one request. It needs the data:export scope.