Naar de inhoud

Peppol BIS-validatie-API

Controleer Peppol BIS Billing 3.0-facturen via de REST-API op geselecteerde regels, met uitleg bij de meldingen. Klarbon verstuurt geen facturen.

Deze functie is in bèta: ze werkt en is getest, maar details kunnen nog veranderen.

Op deze pagina

De Peppol BIS-validatie-API van Klarbon controleert een Peppol BIS Billing 3.0-factuur of creditfactuur met één REST-aanroep tegen de gekozen Peppol- en EN 16931-regels, met uitleg bij de meldingen waar beschikbaar. Gebruik de API voordat je het bestand aan je accesspoint geeft. Klarbon is geen accesspoint: het verstuurt, ontvangt en registreert niets op het Peppol-netwerk.

Wat de API doet

  • POST /api/v1/check kiest de regels op basis van het bestand. Kies Peppol expliciet met ruleset=peppol-bis3-invoice voor een factuur of ruleset=peppol-bis3-creditnote voor een creditfactuur.
  • Meldingen bevatten een regel-id en ernst, met een XML-locatie en uitleg waar beschikbaar.
  • Dezelfde controle zit in de online tool, zodat iemand een afgekeurd bestand ook zelf kan bekijken.

Wat de API niet doet

Wie naar een Peppol-API zoekt, wil meestal facturen versturen. Daarvoor is een accesspoint nodig; Klarbon controleert het bestand vooraf. Validatie is in bèta en telt mee voor de daglimiet van je abonnement.

Snel aan de slag

Elk voorbeeld doet dezelfde aanroep. Voor publicatie draait elk voorbeeld tegen een lokale Klarbon-server en de echte validatie.

Maak op uw accountpagina een sleutel aan en zet die in KLARBON_API_KEY. Zet de sleutel nooit in het adres.

POST /api/v1/check

curl + jq
curl -sS https://klarbon.app/api/v1/check \
  -H "Authorization: Bearer $KLARBON_API_KEY" \
  -F "file=@invoice.xml;type=application/xml" \
  -F ruleset=peppol-bis3-invoice \
  -o result.json
jq -e '.ok' result.json > /dev/null || { jq -r '.error + ": " + .message' result.json; exit 1; }
jq '{valid: .report.valid, findings: [.report.findings[] | {ruleId, severity, message}]}' result.json
TypeScript, Node.js 22.18+
import { readFile, writeFile } from "node:fs/promises"

type KlarbonFile = { content: string; filename: string; mime: string; bytes: number }
type Failure = { ok: false; error: string; message: string }
type Checked = { ok: true; report: { valid: boolean; findings: { ruleId: string; severity: string; message: string }[] } }

const form = new FormData()
form.append("file", new Blob([await readFile("invoice.xml")], { type: "application/xml" }), "invoice.xml")
form.append("ruleset", "peppol-bis3-invoice")

const response = await fetch("https://klarbon.app/api/v1/check", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.KLARBON_API_KEY}`,
  },
  body: form,
})
const result = (await response.json()) as Checked | Failure
if (!result.ok) throw new Error(`${response.status} ${result.error}: ${result.message}`)
console.log(result.report.valid ? "valid" : "not valid")
for (const finding of result.report.findings) console.log(finding.severity, finding.ruleId, finding.message)
Python 3.9+, requests
import os

import requests

with open("invoice.xml", "rb") as upload:
    response = requests.post(
        "https://klarbon.app/api/v1/check",
        headers={
            "Authorization": f"Bearer {os.environ['KLARBON_API_KEY']}",
        },
        files={"file": ("invoice.xml", upload, "application/xml")},
        data={"ruleset": "peppol-bis3-invoice"},
        timeout=120,
    )
result = response.json()
if not result["ok"]:
    raise SystemExit(f"{response.status_code} {result['error']}: {result['message']}")
print("valid" if result["report"]["valid"] else "not valid")
for finding in result["report"]["findings"]:
    print(finding["severity"], finding["ruleId"], finding["message"])
PHP 8+, curl
<?php
$curl = curl_init('https://klarbon.app/api/v1/check');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('KLARBON_API_KEY'),
    ],
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile('invoice.xml', 'application/xml', 'invoice.xml'),
        'ruleset' => 'peppol-bis3-invoice',
    ],
]);
$body = curl_exec($curl);
if ($body === false) {
    fwrite(STDERR, curl_error($curl) . "\n");
    exit(1);
}
$result = json_decode($body, true);
if (!$result['ok']) {
    $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    fwrite(STDERR, "$status {$result['error']}: {$result['message']}\n");
    exit(1);
}
echo $result['report']['valid'] ? "valid\n" : "not valid\n";
foreach ($result['report']['findings'] as $finding) {
    echo "{$finding['severity']} {$finding['ruleId']} {$finding['message']}\n";
}
C#, .NET 10
using System.Net.Http.Headers;
using System.Text.Json;

using var http = new HttpClient();
var key = Environment.GetEnvironmentVariable("KLARBON_API_KEY");
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);

var upload = new ByteArrayContent(await File.ReadAllBytesAsync("invoice.xml"));
upload.Headers.ContentType = new MediaTypeHeaderValue("application/xml");

using var request = new HttpRequestMessage(HttpMethod.Post, "https://klarbon.app/api/v1/check?ruleset=peppol-bis3-invoice") { Content = upload };
using var response = await http.SendAsync(request);
using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var result = json.RootElement;
if (!result.GetProperty("ok").GetBoolean())
{
    Console.Error.WriteLine($"{(int)response.StatusCode} {result.GetProperty("error")}: {result.GetProperty("message")}");
    return 1;
}
var report = result.GetProperty("report");
Console.WriteLine(report.GetProperty("valid").GetBoolean() ? "valid" : "not valid");
foreach (var finding in report.GetProperty("findings").EnumerateArray())
    Console.WriteLine($"{finding.GetProperty("severity")} {finding.GetProperty("ruleId")} {finding.GetProperty("message")}");
return 0;

Het antwoord

Het antwoord op de curl-aanroep hierboven, vastgelegd bij die run met Klarbon synthetic fixture, CC0-1.0.

{
  "ok": true,
  "format": "peppol-bis-billing",
  "report": {
    "detected": {
      "syntax": "ubl",
      "customizationId": "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0",
      "profile": null,
      "container": "xml"
    },
    "ruleset": {
      "id": "peppol-bis3-invoice",
      "name": "Peppol BIS Billing 3.0 (invoice)",
      "version": "2026.5"
    },
    "valid": false,
    "engineVersion": "1.0.0",
    "durationMs": 26,
    "findings": [
      {
        "ruleId": "PEPPOL-COMMON-R043",
        "severity": "fatal",
        "message": "[PEPPOL-COMMON-R043]-Belgian enterprise number MUST be stated in the correct format.",
        "location": "/*:Invoice[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'][1]/cac:AccountingSupplierParty[1]/cac:Party[1]/cbc:EndpointID[1]",
        "test": "matches(normalize-space(), '^[0-9]10$') and u:mod97-0208(normalize-space())",
        "title": "PEPPOL-COMMON-R043",
        "what": "[PEPPOL-COMMON-R043]-Belgian enterprise number MUST be stated in the correct format.",
        "fix": "",
        "link": null,
        "officialUrl": null
      },
      {
        "ruleId": "PEPPOL-COMMON-R043",
        "severity": "fatal",
        "message": "[PEPPOL-COMMON-R043]-Belgian enterprise number MUST be stated in the correct format.",
        "location": "/*:Invoice[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'][1]/cac:AccountingCustomerParty[1]/cac:Party[1]/cbc:EndpointID[1]",
        "test": "matches(normalize-space(), '^[0-9]10$') and u:mod97-0208(normalize-space())",
        "title": "PEPPOL-COMMON-R043",
        "what": "[PEPPOL-COMMON-R043]-Belgian enterprise number MUST be stated in the correct format.",
        "fix": "",
        "link": null,
        "officialUrl": null
      }
    ]
  },
  "invoice": {
    "kind": "invoice",
    "typeCode": "380",
    "number": "TEST-UBL-001",
    "issueDate": "2026-11-05",
    "dueDate": "2026-12-05",
    "currency": "EUR",
    "buyerReference": "TEST-BUYER",
    "processId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
    "specificationId": "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0",
    "notes": [
      {
        "text": "Fictional test data only. Dummy tax ids. Not for payment."
      }
    ],
    "precedingInvoices": [],
    "seller": {
      "name": "Fictional supplier - test only",
      "tradingName": "Fictional supplier - test only",
      "identifiers": [],
      "legalRegistration": {
        "value": "0000000000"
      },
      "vatId": "BE0000000000",
      "electronicAddress": {
        "value": "0000000000",
        "scheme": "0208"
      },
      "address": {
        "line1": "Test street 1",
        "city": "Fictional city",
        "postCode": "1000",
        "country": "BE"
      }
    },
    "buyer": {
      "name": "Fictional buyer - test only",
      "tradingName": "Fictional buyer - test only",
      "identifiers": [],
      "legalRegistration": {
        "value": "0000000000"
      },
      "vatId": "BE0000000000",
      "electronicAddress": {
        "value": "0000000000",
        "scheme": "0208"
      },
      "address": {
        "line1": "Test street 2",
        "city": "Fictional city",
        "postCode": "1000",
        "country": "BE"
      }
    },
    "payment": {
      "meansCode": "30",
      "remittanceInformation": "TEST-UBL-001",
      "creditTransfers": [
        {
          "iban": "TEST-ACCOUNT-NOT-PAYABLE"
        }
      ]
    },
    "allowances": [],
    "charges": [],
    "totals": {
      "lineTotal": "100.00",
      "taxExclusive": "100.00",
      "tax": "21.00",
      "taxInclusive": "121.00",
      "payable": "121.00"
    },
    "vatBreakdown": [
      {
        "category": "S",
        "rate": "21",
        "taxableAmount": "100.00",
        "taxAmount": "21.00"
      }
    ],
    "attachments": [],
    "lines": [
      {
        "id": "1",
        "quantity": "1",
        "unit": "C62",
        "netAmount": "100.00",
        "allowances": [],
        "charges": [],
        "price": {
          "amount": "100.00"
        },
        "vat": {
          "category": "S",
          "rate": "21"
        },
        "item": {
          "name": "Fictional service",
          "classifications": [],
          "attributes": []
        }
      }
    ],
    "nationalTerms": [],
    "source": {
      "format": "peppol-bis-billing",
      "syntax": "ubl",
      "customizationId": "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0"
    }
  },
  "usage": {
    "used": 6,
    "limit": 20
  }
}

Kosten

Vooraf betaalde API-pakketten, per kaart gekocht op uw accountpagina: 1.000 aanroepen voor € 39,00, 5.000 aanroepen voor € 129,00, 25.000 aanroepen voor € 449,00, 100.000 aanroepen voor € 990,00, zonder vervaldatum. Uw eerste sleutel geeft 25 gratis aanroepen. Betaalde aanroepen gebruiken eerst het API-saldo, daarna dezelfde credits als de site. Een mislukte aanroep kost niets.

Aanroep Kosten
POST /open Gratis, redelijk gebruik tot 1.000 per dag per account
POST /check Gratis binnen de daglimiet van uw abonnement (20 per dag in het gratis abonnement)
POST /convert, POST /make 1 aanroep, alleen als het gemaakte bestand geldig is
POST /bank/convert 1 aanroep per begonnen 500 transacties, alleen na omzetting

Limieten

  • Bestanden tot 20 MB, pdf’s tot 50 pagina’s.
  • 60 verzoeken per minuut per sleutel, 120 per account. Een 429 bevat Retry-After.
  • Tot 10 sleutels per account. Idempotency-Key geldt 24 uur, dus een nieuwe poging wordt nooit twee keer berekend.

Fouten

Elke fout is { ok: false, error, message }: een vaste code en één duidelijke zin.

Status Code Wanneer
401 auth Geen sleutel, of een onbekende of ingetrokken sleutel.
402 no_credits API-saldo en credits zijn allebei te laag. Er is niets gedaan; topUpUrl wijst naar de aankooppagina.
413 too_large Meer dan 20 MB of 50 pdf-pagina’s.
415 unsupported Een bestandsformaat dat de route niet aanneemt.
422 invalid Het bestand was niet leesbaar, of het gemaakte bestand is niet geldig. Niet berekend.
429 limit Een snelheidslimiet of de gratis aanroepen van de dag.
503 engine_unavailable De validatie is even niet bereikbaar. Niet berekend.

Privacy

Bestanden worden voor het verzoek verwerkt, zonder documentarchief. De applicatielogs van Klarbon bevatten geen bestandsnaam, geen inhoud en geen sleutel. De volledige sleutel wordt niet bewaard, alleen een HMAC ervan en een kort weergaveprefix.

Verder

De documentatie voor ontwikkelaars is in het Engels.

Veelgestelde vragen

Verstuurt Klarbon facturen via Peppol?

Nee. Klarbon is geen Peppol-accesspoint. Het controleert het bestand; je accesspoint verstuurt het.

Welke regels gebruikt de controle?

De regels van Peppol BIS Billing 3.0 met de EN 16931-basis. Het rapport noemt de regelset en de versie.

Garandeert een geldig resultaat dat de factuur wordt bezorgd?

Nee. Het betekent dat het bestand de gekozen controles doorstaat. Het ontvangende accesspoint en de afnemer kunnen aanvullende eisen stellen.

Kost validatie credits?

Nee. Controleren is gratis binnen de daglimiet van je abonnement; op de prijspagina staan de huidige aantallen.

Bronnen

Bijgewerkt op 6 november 2026