Naar de inhoud

E-factuur-conversie-API

Zet e-facturen om tussen UBL, CII, XRechnung en Peppol BIS met één REST-aanroep. Alleen berekend als het gemaakte bestand geldig is.

Op deze pagina

De e-factuur-conversie-API van Klarbon zet facturen met één REST-aanroep om tussen UBL, CII, XRechnung en Peppol BIS. Hij leest de bron in het EN 16931-factuurmodel, schrijft het doelformaat en valideert het resultaat voordat je het krijgt. Je betaalt dus alleen voor een bestand dat de controle doorstaat. Bestanden worden voor het verzoek verwerkt, zonder documentarchief.

Wat de API doet

  • POST /api/v1/convert met target op ubl, cii, xrechnung of peppol-bis-billing.
  • Het antwoord bevat het nieuwe bestand, het validatierapport en een credits-blok met de verbruikte eenheden. Betaalde aanroepen gebruiken eerst het API-saldo, daarna abonnementscredits of gekochte credits.
  • losses meldt velden die bij het schrijven zijn gewijzigd, weggelaten of aangenomen. Het meldt geen bronvelden die de lezer niet heeft overgenomen; vergelijk het resultaat met je origineel.
  • Met Idempotency-Key is opnieuw proberen veilig: hetzelfde verzoek wordt binnen een dag nooit twee keer berekend.

Wat de API niet doet

Hij verstuurt geen facturen en leest geen pdf-facturen zonder gestructureerde gegevens. Hybride pdf-uitvoer heeft een eigen pagina: de ZUGFeRD- en Factur-X-API (Engels).

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/convert

curl + jq
curl -sS https://klarbon.app/api/v1/convert \
  -H "Authorization: Bearer $KLARBON_API_KEY" \
  -H "Idempotency-Key: invoice-123456XX-cii" \
  -F "file=@invoice.xml;type=application/xml" \
  -F target=cii \
  -o result.json
jq -e '.ok' result.json > /dev/null || { jq -r '.error + ": " + .message' result.json; exit 1; }
jq -r '.file.content' result.json | base64 --decode > "$(jq -r '.file.filename' 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 Converted = { ok: true; file: KlarbonFile; credits: { spent: number; balance: number } }

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

const response = await fetch("https://klarbon.app/api/v1/convert", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.KLARBON_API_KEY}`,
    "Idempotency-Key": "invoice-123456XX-cii",
  },
  body: form,
})
const result = (await response.json()) as Converted | Failure
if (!result.ok) throw new Error(`${response.status} ${result.error}: ${result.message}`)
await writeFile(result.file.filename, Buffer.from(result.file.content, "base64"))
console.log(`${result.file.filename} written, ${result.credits.balance} credits left`)
Python 3.9+, requests
import base64
import os

import requests

with open("invoice.xml", "rb") as upload:
    response = requests.post(
        "https://klarbon.app/api/v1/convert",
        headers={
            "Authorization": f"Bearer {os.environ['KLARBON_API_KEY']}",
            "Idempotency-Key": "invoice-123456XX-cii",
        },
        files={"file": ("invoice.xml", upload, "application/xml")},
        data={"target": "cii"},
        timeout=120,
    )
result = response.json()
if not result["ok"]:
    raise SystemExit(f"{response.status_code} {result['error']}: {result['message']}")
with open(result["file"]["filename"], "wb") as output:
    output.write(base64.b64decode(result["file"]["content"]))
print(result["file"]["filename"], "written,", result["credits"]["balance"], "credits left")
PHP 8+, curl
<?php
$curl = curl_init('https://klarbon.app/api/v1/convert');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('KLARBON_API_KEY'),
        'Idempotency-Key: invoice-123456XX-cii',
    ],
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile('invoice.xml', 'application/xml', 'invoice.xml'),
        'target' => 'cii',
    ],
]);
$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);
}
file_put_contents($result['file']['filename'], base64_decode($result['file']['content']));
echo "{$result['file']['filename']} written, {$result['credits']['balance']} credits left\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/convert?target=cii") { Content = upload };
request.Headers.Add("Idempotency-Key", "invoice-123456XX-cii");
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 written = result.GetProperty("file");
await File.WriteAllBytesAsync(written.GetProperty("filename").GetString()!,
    Convert.FromBase64String(written.GetProperty("content").GetString()!));
Console.WriteLine($"{written.GetProperty("filename")} written, {result.GetProperty("credits").GetProperty("balance")} credits left");
return 0;

Het antwoord

Het antwoord op de curl-aanroep hierboven, vastgelegd bij die run met KoSIT XRechnung test suite, business case 01.01a (UBL), Apache-2.0.

Ingekort voor de pagina: file.content: base64 cut

{
  "ok": true,
  "file": {
    "content": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgi...",
    "filename": "123456XX-cii.xml",
    "mime": "application/xml",
    "bytes": 6867
  },
  "report": {
    "detected": {
      "syntax": "cii",
      "customizationId": "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0",
      "profile": "xrechnung",
      "container": "xml"
    },
    "ruleset": {
      "id": "xrechnung-3.0-cii",
      "name": "XRechnung 3.0 (CII)",
      "version": "3.0.2"
    },
    "valid": true,
    "engineVersion": "1.0.0",
    "durationMs": 8,
    "findings": [
      {
        "ruleId": "BR-DE-TMP-32",
        "severity": "info",
        "message": "[BR-DE-TMP-32] Eine Rechnung sollte zur Angabe des Liefer-/Leistungsdatums entweder BT-72 \"Actual delivery date\", BG-14 \"Invoicing period\" oder in jeder Rechnungsposition BG-26 \"Invoice line period\" enthalten.",
        "location": "/rsm:CrossIndustryInvoice[1]/rsm:SupplyChainTradeTransaction[1]",
        "test": "ram:ApplicableHeaderTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime or ram:ApplicableHeaderTradeSettlement/ram:BillingSpecifiedPeriod or (every $line in ram:IncludedSupplyChainTradeLineItem satisfies $line/ram:SpecifiedLineTradeSettlement/ram:BillingSpecifiedPeriod)",
        "title": "BR-DE-TMP-32",
        "what": "[BR-DE-TMP-32] Eine Rechnung sollte zur Angabe des Liefer-/Leistungsdatums entweder BT-72 \"Actual delivery date\", BG-14 \"Invoicing period\" oder in jeder Rechnungsposition BG-26 \"Invoice line period\" enthalten.",
        "fix": "",
        "link": null,
        "officialUrl": null
      }
    ]
  },
  "losses": [],
  "credits": {
    "spent": 1,
    "balance": 197,
    "plan": 0,
    "bought": 197
  }
}

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

Tussen welke formaten kan ik omzetten?

UBL, CII, XRechnung en Peppol BIS Billing, in beide richtingen. GET /api/v1/formats toont elk formaat en de mogelijke richtingen.

Wat gebeurt er met gegevens die het doelformaat niet kent?

De lijst losses meldt velden die bij het schrijven zijn gewijzigd, weggelaten of aangenomen. Velden die niet uit het origineel zijn ingelezen, staan niet in die lijst. Vergelijk het resultaat met het origineel.

Betaal ik voor een ongeldige omzetting?

Nee. Een bestand dat de controle niet doorstaat, wordt niet teruggegeven. De aanroep antwoordt met 422 en het rapport.

Hoe probeer ik opnieuw zonder dubbel te betalen?

Met de header Idempotency-Key. Hetzelfde verzoek met dezelfde sleutel voert de omzetting binnen 24 uur opnieuw uit zonder extra kosten. Het nieuwe bestand is niet gegarandeerd byte voor byte gelijk.

Bronnen

Bijgewerkt op 6 november 2026