LazyBill API

Koppel je eigen systemen aan LazyBill: maak relaties en facturen aan, verstuur ze, boek uren en kosten, en krijg een seintje zodra een factuur betaald is. De API is beschikbaar op elk plan, ook tijdens de gratis proef.

Introductie

De LazyBill API is een REST-API die JSON praat. Alles wat je koppelt werkt op het niveau van één organisatie: je API-token hoort bij een organisatie en ziet alleen de relaties, facturen en uren van die organisatie.

  • Basis-URL - https://lazybill.nl/api/v1
  • Formaat - JSON in en uit; stuur Content-Type: application/json en Accept: application/json mee.
  • Versie - v1 zit in het pad. Binnen v1 breken we niets: er komen hooguit velden bij.

Typische toepassingen: na een geleverde dienst automatisch een concept-factuur klaarzetten, gewerkte uren doorsturen vanuit je eigen systeem, of je administratie bijwerken zodra een factuur betaald is.

Snelstart

In drie stappen van niets naar een verstuurde factuur. Maak eerst een token aan via Instellingen > API in LazyBill en vul dat hieronder in voor {token}.

1. Maak een relatie aan

Request
curl -X POST https://lazybill.nl/api/v1/relations \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "company",
    "name": "Acme B.V.",
    "email": "administratie@acme.nl"
  }'

2. Maak een concept-factuur

Gebruik het id uit stap 1 als relation_id.

Request
curl -X POST https://lazybill.nl/api/v1/invoices \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "relation_id": 12,
    "invoice_date": "2026-07-23",
    "lines": [
      { "description": "Consultancy juli", "quantity": 8, "unit_price": "95.00", "vat_rate": 21 }
    ]
  }'

3. Verstuur de factuur

De factuur krijgt nu een definitief nummer en wordt gemaild naar het adres van de relatie. Liever eerst controleren? Sla deze stap over: het concept staat gewoon in LazyBill klaar.

Request
curl -X POST https://lazybill.nl/api/v1/invoices/88/send \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{}'

Authenticatie

Alle endpoints vereisen een API-token. De eigenaar van een organisatie maakt tokens aan via Instellingen > API. Het token wordt daar één keer getoond; bewaar het als een wachtwoord (bv. in een secrets-manager, nooit in je frontend of repository).

Stuur het token mee als Bearer-token in de Authorization-header:

Header
Authorization: Bearer {token}
  • Zonder (geldig) token krijg je 401 Unauthorized.
  • Een token geeft volledige toegang tot de data van zijn organisatie - en tot niets anders. Records van andere organisaties bestaan voor jouw token niet (404).
  • Een token intrekken kan op dezelfde instellingenpagina; koppelingen die het gebruiken stoppen dan per direct.
  • Gebruik per koppelend systeem een eigen token, dan kun je er later één intrekken zonder de rest te raken.

Conventies

Datums en tijdstippen

  • Datums stuur en ontvang je als ISO 8601: 2026-07-23.
  • Tijdstippen (zoals sent_at en paid_at) komen terug als RFC 3339: 2026-07-23T10:00:00+02:00.

Bedragen en btw

Bedragen zijn altijd exclusief btw, met een punt als decimaalteken: "95.00". Dit geldt ongeacht hoe de organisatie in LazyBill zelf prijzen invoert (incl. of excl.) - de API is daar bewust onafhankelijk van. In responses is subtotal exclusief en total inclusief btw.

Btw geef je per regel op als kaal percentage in vat_rate: 21, 9 of 0 - de drie Nederlandse tarieven. Een ander percentage wordt afgekeurd met een 422, zodat een typefout nooit stilletjes een verkeerde factuur oplevert. Responses geven hetzelfde kale percentage terug.

Daarnaast zijn er drie bijzondere regimes die alle drie 0% rekenen maar elk een eigen, verplichte vermelding op de factuur hebben - een kaal percentage kan dat verschil niet uitdrukken. Stuur daarvoor vat_scheme mee op de regel (vat_rate mag dan weg); LazyBill zet de juiste vermelding automatisch op het factuurdocument en de PDF. In responses zie je vat_scheme op elke regel terug (null bij een gewoon percentage).

vat_schemeBetekenisVermelding op de factuur
reverse_chargedBtw verlegd (bouw/onderaanneming, EU B2B)"Btw verlegd."
exemptVrijgesteld van btw (bv. KOR, zorg, onderwijs)"Vrijgesteld van btw."
marginMargeregeling (handel in gebruikte goederen)"Bijzondere regeling - gebruikte goederen (margeregeling)."

Paginering

Lijst-endpoints zijn gepagineerd. Stuur page en per_page (standaard 25, maximaal 100) mee; de response bevat naast data ook links en meta met onder andere meta.current_page, meta.last_page en meta.total.

Voorbeeld
GET /api/v1/invoices?status=sent&per_page=50&page=2

Rate limiting

Maximaal 60 requests per minuut per organisatie. Daarboven krijg je 429 Too Many Requests; de Retry-After-header vertelt hoeveel seconden je moet wachten. Synchroniseer je veel data, bouw dan een kleine pauze in.

Fouten

Fouten komen altijd als JSON terug met een message. De statuscodes:

StatusBetekenis
401Geen of ongeldig API-token.
402De proefgrens van het account is bereikt: versturen kan pas weer na het activeren van een abonnement. Concepten aanmaken blijft gewoon werken.
403Deze actie is niet toegestaan.
404Niet gevonden - ook wanneer het record bij een andere organisatie hoort.
409Conflict met de huidige status, bv. een al verzonden factuur nogmaals versturen.
422Validatiefout; zie het formaat hieronder.
429Rate limit bereikt; probeer het na de Retry-After opnieuw.

Bij een validatiefout (422) staat per veld wat er mis is:

422-response
{
  "message": "Vul de naam van de relatie in.",
  "errors": {
    "name": ["Vul de naam van de relatie in."],
    "lines.0.unit_price": ["Vul een geldig bedrag in."]
  }
}

Relaties

Relaties zijn je klanten: bedrijven of particulieren. Elke factuur en elke geboekte post hangt aan een relatie.

Relaties ophalen

GET/api/v1/relations
ParameterUitleg
searchZoekt in de naam, bv. ?search=acme.
per_page / pagePaginering (zie Conventies).
Response
{
  "data": [
    {
      "id": 12,
      "type": "company",
      "name": "Acme B.V.",
      "email": "administratie@acme.nl",
      "phone": null,
      "website": null,
      "address_street": "Dorpsstraat 1",
      "address_postal": "1234 AB",
      "address_city": "Amsterdam",
      "address_country": "nl",
      "kvk_number": "12345678",
      "vat_number": null,
      "hourly_rate": null,
      "send_reminders": true,
      "notes": null,
      "created_at": "2026-07-01T09:12:33+02:00",
      "updated_at": "2026-07-01T09:12:33+02:00"
    }
  ],
  "links": { "...": "..." },
  "meta": { "current_page": 1, "last_page": 1, "total": 1 }
}

Relatie aanmaken

POST/api/v1/relations
VeldTypeUitleg
typeverplichtstringcompany of person.
nameverplichtstringNaam van het bedrijf of de persoon.
emailstringFacturen en herinneringen gaan naar dit adres.
phone / websitestringOptionele contactgegevens.
address_street / address_postal / address_city / address_countrystringAdres; land als tweeletterige code (bv. nl).
kvk_numberstring8 cijfers.
vat_numberstringBtw-nummer.
hourly_ratedecimalUurtarief-override voor deze relatie (excl. btw); leeg = het standaardtarief van de organisatie.
send_remindersbooleanBetaalherinneringen voor deze relatie (standaard aan).
notesstringInterne notitie.
Request
{
  "type": "company",
  "name": "Acme B.V.",
  "email": "administratie@acme.nl",
  "kvk_number": "12345678"
}
Response - 201
{
  "data": {
    "id": 12,
    "type": "company",
    "name": "Acme B.V.",
    "email": "administratie@acme.nl",
    "kvk_number": "12345678",
    "...": "..."
  }
}

Eén relatie ophalen

GET/api/v1/relations/{id}

Geeft dezelfde velden terug als de lijst.

Producten

Producten en diensten beheer je in LazyBill zelf; via de API kun je ze opzoeken om een product_id mee te geven op een factuurregel. Dat koppelt de regel aan het product (handig voor rapportage), de omschrijving en prijs geef je per regel gewoon zelf op.

GET/api/v1/products
Response
{
  "data": [
    {
      "id": 4,
      "name": "Consultancy",
      "description": "Advies en implementatie",
      "price": "95.0000",
      "vat_rate": 21,
      "vat_scheme": null,
      "unit": "hour",
      "created_at": "2026-06-12T14:20:11+02:00",
      "updated_at": "2026-06-12T14:20:11+02:00"
    }
  ],
  "links": { "...": "..." },
  "meta": { "...": "..." }
}

price is exclusief btw. unit is een van piece, hour, day, week, month of year.

Facturen

Een factuur begint als concept: zonder nummer, nog volledig te bewerken in LazyBill. Pas bij het versturen krijgt hij een definitief, opvolgend nummer en wordt hij bevroren. Zo blijft de nummerreeks altijd sluitend.

Facturen ophalen

GET/api/v1/invoices
ParameterUitleg
statusFilter: concept, sent, overdue, reminder_1, reminder_2, reminder_final of paid.
relation_idAlleen facturen van deze relatie.
per_page / pagePaginering; gesorteerd nieuwste eerst.

Eén factuur ophalen

GET/api/v1/invoices/{id}
Response
{
  "data": {
    "id": 88,
    "number": "2026-0012",
    "status": "sent",
    "is_credit": false,
    "credit_of_invoice_id": null,
    "relation": { "id": 12, "name": "Acme B.V.", "...": "..." },
    "invoice_date": "2026-07-23",
    "due_date": "2026-08-22",
    "reference": "Order 554",
    "notes": null,
    "subtotal": "760.00",
    "vat_total": "159.60",
    "total": "919.60",
    "lines": [
      {
        "id": 301,
        "product_id": 4,
        "description": "Consultancy juli",
        "quantity": "8.00",
        "unit_price": "95.0000",
        "vat_rate": 21,
        "vat_scheme": null,
        "line_total": "760.00",
        "position": 0
      }
    ],
    "pay_url": "https://lazybill.nl/pay/abc123...",
    "sent_at": "2026-07-23T10:00:00+02:00",
    "paid_at": null,
    "created_at": "2026-07-23T09:58:11+02:00",
    "updated_at": "2026-07-23T10:00:00+02:00"
  }
}
  • subtotal is exclusief, total inclusief btw.
  • pay_url is de publieke betaallink voor de klant; null zolang de factuur niet online betaalbaar is (bv. concept, al betaald, of geen betaalprovider gekoppeld).
  • number is null bij een concept.

Concept-factuur aanmaken

POST/api/v1/invoices
VeldTypeUitleg
relation_idverplichtintegerDe klant; moet een relatie van jouw organisatie zijn.
invoice_dateverplichtdateFactuurdatum, YYYY-MM-DD.
due_datedateVervaldatum. Weglaten = afgeleid uit de betaaltermijn van de relatie (of de standaardtermijn uit de instellingen).
referencestringJouw kenmerk; doorzoekbaar in LazyBill.
notesstringOpmerking op de factuur.
linesverplichtarrayMinstens één regel.
lines[].descriptionverplichtstringOmschrijving van de regel.
lines[].quantityverplichtdecimalAantal.
lines[].unit_priceverplichtdecimalStukprijs, exclusief btw.
lines[].vat_rateverplichtnumberBtw-percentage: 21, 9 of 0.
lines[].vat_schemestringBijzonder regime: reverse_charged, exempt of margin; vat_rate mag dan weg. Zie Conventies.
lines[].product_idintegerOptionele koppeling aan een product.
Request
{
  "relation_id": 12,
  "invoice_date": "2026-07-23",
  "due_date": "2026-08-22",
  "reference": "Order 554",
  "lines": [
    {
      "description": "Consultancy juli",
      "quantity": 8,
      "unit_price": "95.00",
      "vat_rate": 21
    }
  ]
}
Response - 201
{
  "data": {
    "id": 88,
    "number": null,
    "status": "concept",
    "subtotal": "760.00",
    "vat_total": "159.60",
    "total": "919.60",
    "...": "..."
  }
}

Factuur versturen

POST/api/v1/invoices/{id}/send

Maakt het concept definitief (nummer toegekend, bevroren) en mailt de factuur als PDF naar de klant, inclusief betaallink als de organisatie online betalen heeft gekoppeld.

VeldTypeUitleg
tostringE-mailadres van de ontvanger. Weggelaten? Dan gebruikt LazyBill het adres van de relatie of het hoofdcontact.
messagestringPersoonlijk bericht bovenin de e-mail.
skip_emailbooleantrue = alleen definitief maken, niet mailen (bv. als je 'm zelf verstuurt).
Response - 200
{
  "data": { "id": 88, "number": "2026-0012", "status": "sent", "...": "..." },
  "meta": { "mailed_to": "administratie@acme.nl", "mail_error": null }
}
  • Een mislukte e-mail blokkeert het versturen niet: de factuur is dan wél definitief en de foutmelding staat in meta.mail_error.
  • Op een factuur die geen concept meer is krijg je 409.
  • Is de proefgrens van het account bereikt, dan krijg je 402 en blijft de factuur een concept.

Uren & kosten

Uren en losse kosten zijn declarabele posten: je boekt ze los, en LazyBill bundelt alle onbefactureerde posten automatisch tot één concept-factuur per relatie in de periodieke facturatie (samen met eventuele abonnementen). Je hoeft er dus zelf geen facturen van te maken - al kan dat natuurlijk wel.

POST/api/v1/billables

Uren

Bij uren bepaalt LazyBill het tarief server-side: het uurtarief van de relatie als dat er is, anders het standaardtarief van de organisatie. Een eigen unit_price of vat_rate meesturen is dan een validatiefout - zo kan een koppelend systeem nooit per ongeluk een afwijkend tarief boeken.

Request
{
  "kind": "hours",
  "relation_id": 12,
  "date": "2026-07-23",
  "description": "Sprintwerk",
  "quantity": 1.5
}
Response - 201
{
  "data": {
    "id": 51,
    "kind": "hours",
    "relation_id": 12,
    "date": "2026-07-23",
    "description": "Sprintwerk",
    "quantity": "1.50",
    "unit_price": "95.0000",
    "vat_rate": 21,
    "vat_scheme": null,
    "total": "142.50",
    "invoice_id": null,
    "created_at": "2026-07-23T16:41:09+02:00"
  }
}

Kosten

Bij kosten geef je zelf een (netto) bedrag op. quantity is optioneel (standaard 1), vat_rate ook (standaard 21).

Request
{
  "kind": "cost",
  "relation_id": 12,
  "date": "2026-07-23",
  "description": "Reiskosten",
  "unit_price": "45.00"
}
VeldTypeUitleg
kindverplichtstringhours of cost.
relation_idverplichtintegerDe relatie waarvoor je boekt.
dateverplichtdateDatum van de post, YYYY-MM-DD.
descriptionverplichtstringOmschrijving; komt straks op de factuurregel.
quantitydecimalUren: verplicht, als decimaal (1,5 uur = 1.5). Kosten: optioneel aantal.
unit_pricedecimalAlleen bij kosten (verplicht): het netto bedrag. Bij uren verboden.
vat_ratenumberAlleen bij kosten (optioneel): 21, 9 of 0 (standaard 21). Bij uren verboden.
vat_schemestringAlleen bij kosten: reverse_charged, exempt of margin, zie Conventies. Bij uren verboden.

In de response zie je het toegepaste unit_price en het regeltotaal. invoice_id blijft null tot de post gefactureerd is.

Webhooks

Wil je direct weten wanneer een factuur betaald is? Stel via Instellingen > API een webhook-URL in. LazyBill stuurt dan een POST naar die URL zodra een factuur op betaald gaat - via de betaallink, via automatische incasso of handmatig in de app.

Payload

POST naar jouw URL
{
  "event": "invoice.paid",
  "occurred_at": "2026-07-23T14:03:07+02:00",
  "invoice": {
    "id": 88,
    "number": "2026-0012",
    "status": "paid",
    "total": "919.60",
    "relation": { "id": 12, "name": "Acme B.V.", "...": "..." },
    "...": "..."
  }
}

invoice bevat het volledige factuur-object, precies zoals bij GET /api/v1/invoices/{id}.

Signature verifiëren

Elke aflevering is ondertekend, zodat je zeker weet dat de melding echt van LazyBill komt. De header X-Lazybill-Signature bevat een HMAC-SHA256 van de rauwe request-body, berekend met het webhook-secret van je organisatie (te vinden op de API-instellingenpagina).

Verificatie (PHP)
$rawBody   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_LAZYBILL_SIGNATURE'] ?? '';

$expected = hash_hmac('sha256', $rawBody, $secret);

if (! hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}
Bereken de HMAC altijd over de rauwe body (niet over opnieuw geëncodeerde JSON - de volgorde van velden zou dan kunnen verschillen) en vergelijk met een timing-safe functie zoals hash_equals.

Afleveren en opnieuw proberen

  • Beantwoord een webhook binnen 10 seconden met een 2xx-status. Zwaar werk? Zet de melding in een queue en antwoord meteen.
  • Mislukte afleveringen worden niet opnieuw geprobeerd; de mislukking is wel zichtbaar in de geschiedenis van de factuur in LazyBill. Bouw je iets kritisch, poll dan als vangnet periodiek GET /api/v1/invoices?status=paid.
  • Behandel meldingen idempotent: verwerk dezelfde factuur-id niet twee keer.