BillKit for developers
BillKit is a subscription billing API for European businesses. It runs plans, checkout, invoices, VAT, dunning and the customer portal on top of your own Mollie account.
What BillKit is, and what it sits on
BillKit does not process payments. Your tenant account connects a Mollie account, and BillKit drives it: it creates the payments, stores the resulting mandates, schedules and charges renewals, closes periods, computes VAT, produces invoices and PDFs, retries failures and emits webhooks. Money moves through Mollie and settles into your Mollie balance.
That split explains most of the design. Card and SEPA details never reach BillKit. Anything Mollie decides (whether a charge needs Strong Customer Authentication, whether a SEPA debit eventually bounces) is reported to BillKit asynchronously, which is why almost every outcome you care about arrives as a webhook rather than in a response body.
How the API is shaped
REST over HTTPS, JSON in and JSON out, including errors. Amounts are integers in the smallest
unit of the currency (amount_cents). Timestamps are Unix seconds. Object ids carry a type
prefix, so sub_... is always a subscription.
| Base URL | https://api.billkit.eu |
| Auth | Bearer secret key: bk_live_… or bk_test_… |
| Request body | application/json. Unknown fields, and unknown query parameters, are rejected with 400 parameter_unknown. |
| Updates | Omitted fields are left alone. null clears a field that can be empty; on a field that cannot, it is refused with 400 naming the field. |
| Success status | 200 on every route, including creates |
| Lists | Cursor paginated: limit (default 10, max 100) and starting_after |
| Idempotency | Idempotency-Key header on any write |
There is no API version header. Live and test mode are selected by which key you send, not by a parameter.
curl https://api.billkit.eu/v1/ping \
-H "Authorization: Bearer $BILLKIT_API_KEY"{
"tenant_id": "7b0f1e2c-9a41-4d6e-8f30-1c5a2b7d4e88",
"mode": "test",
"object": "ping"
}Id prefixes
| Prefix | Object |
|---|---|
| cus_ | Customer |
| prod_ | Product |
| price_ | Price |
| cs_ | Checkout session |
| sub_ | Subscription |
| pay_ | Payment |
| inv_ | Invoice |
| re_ | Refund |
| cn_ | Credit note |
| coup_ | Coupon |
| tr_ | Tax rate |
| ur_ | Usage record |
| evt_ | Event |
| we_ | Webhook endpoint |
| dp_ | Dispute |
| tc_ | Test clock |
| osp_ | One-off payment |
| bps_ | Billing portal session |
| ili_ | Invoice line item |
| ak_ | API key |
| al_ | Audit log entry |
| whd_ | Webhook delivery |
Treat every id as an opaque string. Most are short and mixed-case; API keys, audit log entries and webhook deliveries end in 32 hex digits instead. Both forms keep their prefix.
The one thing that surprises people
You cannot POST /v1/subscriptions. A subscription is a side effect of a paid checkout: you
create a checkout session, the customer pays, Mollie tells BillKit the payment settled, and
BillKit creates the subscription and emits subscription.created. The reason is the mandate.
A recurring charge needs an authorised mandate, and only a real first payment through Mollie
produces one, so a subscription cannot exist before that payment lands.
Everything starts in test mode. Test-mode objects are stored separately, carry
livemode: false, and are invisible to a live key. See Test mode.
Core concepts
Products and prices
What you sell, what it costs and how often.
Customers
Who you bill, their country and VAT id.
Subscriptions
Status, renewal state, plan changes and cancellation.
Payments
Each charge against a customer, and how it ended.
Invoices and credit notes
The documents each charge and refund produces.
Events and audit log
What happened in your account, and who did it.
BillKit