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.
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
curl https://api.billkit.eu/v1/invoices/inv_Wq3Ye8nB5tX1rKcMzv \
-H "Authorization: Bearer $BILLKIT_API_KEY"| Field | Notes |
|---|---|
| numberstring | YYYY-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. |
| statusstring | open, paid, void or uncollectible. See below. |
| customer_id / subscription_id | subscription_id is null on a one-off sale. |
| currency / subtotal_cents / tax_cents / total_cents | Net 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_charged | The country the VAT was decided for, and whether the supply was reverse-charged. See Charge and record EU VAT. |
| payment_id / one_shot_payment_id | Exactly 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 | null | Unix seconds. |
| linesarray | Line 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_time | The buyer as invoiced: name, email, business name, VAT number, billing address. Snapshots, not live reads. |
| pdf_urlnull | Not 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
| status | Meaning |
|---|---|
| open | Issued and not settled. A metered period close opens one before it charges the mandate, and invoice.payment_failed fires each time that charge fails. |
| paid | Settled. A subscription charge and a one-off sale both land here directly. |
| uncollectible | Collection 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. |
| void | Never 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.
curl -X POST https://api.billkit.eu/v1/invoices/inv_Wq3Ye8nB5tX1rKcMzv/email \
-H "Authorization: Bearer $BILLKIT_API_KEY"{
"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.
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.
curl "https://api.billkit.eu/v1/credit_notes?invoice_id=inv_Wq3Ye8nB5tX1rKcMzv" \
-H "Authorization: Bearer $BILLKIT_API_KEY"| Field | Notes |
|---|---|
| number | CN-YYYY-NNNNNN. Its own series, separate from the invoice one, because a tax authority treats them as different document classes. |
| invoice_id / customer_id | The invoice being credited, which is always set, and whose it was. |
| refund_id / reason | The settled refund that triggered it, and that refund’s reason. |
| subtotal_cents / tax_cents / total_cents | Positive 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_charged | Carried 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.
BillKit