Zum Inhalt springen

API zur Prüfung von E-Rechnungen

E-Rechnungen per API nach EN 16931, XRechnung, Peppol und Factur-X prüfen. Meldungen verständlich erklärt, kostenlos im Tageslimit.

Diese Funktion ist in der Beta: Sie funktioniert und ist getestet, Details können sich noch ändern.

Auf dieser Seite

Die API zur Prüfung von E-Rechnungen von Klarbon prüft UBL, CII, XRechnung, Peppol BIS, Factur-X und ZUGFeRD mit einem REST-Aufruf gegen die jeweils geltenden veröffentlichten Regeln. Sie erkennt das Regelwerk aus der Datei oder nimmt das, das Sie angeben, und liefert die Meldungen der gewählten Prüfungen mit Regel-ID, Schweregrad und Hinweisen zur Behebung. Die Dateien werden für die Anfrage verarbeitet, ohne Dokumentenarchiv.

Was die API macht

  • POST /api/v1/check nimmt die XML-Datei oder das hybride PDF, standardmäßig mit ruleset=auto.
  • country wählt ein verfügbares Länderregelwerk, wenn Sie das Ziel der Rechnung kennen. Empfängerspezifische Anforderungen prüft es nicht vollständig.
  • Die Routen, die E-Rechnungen schreiben, liefern ebenfalls Prüfergebnisse. Sehen Sie sich die Meldungen an, bevor Sie die Datei verwenden.

Was sie nicht macht

Ein gültiges Ergebnis heißt, dass die Datei die ausgewählten Prüfungen besteht. Es heißt nicht, dass der Empfänger sie annimmt, und es ist keine Steuer- oder Rechtsberatung. Die Prüfung ist in der Beta und im Tageslimit Ihres Tarifs kostenlos. Zum Umwandeln gibt es die API zur Umwandlung von E-Rechnungen.

Schnellstart

Jedes Beispiel macht denselben Aufruf. Vor der Veröffentlichung läuft jedes einzelne gegen einen lokalen Klarbon-Server und die echte Validierung.

Legen Sie auf Ihrer Kontoseite einen Schlüssel an und setzen Sie ihn als KLARBON_API_KEY. Der Schlüssel gehört nie in die Adresse.

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" \
  -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")

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")},
        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'),
    ],
]);
$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") { 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;

Die Antwort

Die Antwort auf den curl-Aufruf oben, aufgezeichnet bei diesem Lauf mit KoSIT XRechnung test suite, business case 01.01a (UBL), Apache-2.0.

{
  "ok": true,
  "format": "xrechnung",
  "report": {
    "detected": {
      "syntax": "ubl",
      "customizationId": "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0",
      "profile": null,
      "container": "xml"
    },
    "ruleset": {
      "id": "xrechnung-3.0-ubl",
      "name": "XRechnung 3.0 (UBL)",
      "version": "3.0.2"
    },
    "valid": true,
    "engineVersion": "1.0.0",
    "durationMs": 11,
    "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": "/*:Invoice[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'][1]",
        "test": "cac:Delivery/cbc:ActualDeliveryDate or cac:InvoicePeriod or (every $line in (cac:InvoiceLine | cac:CreditNoteLine) satisfies $line/cac:InvoicePeriod)",
        "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
      }
    ]
  },
  "invoice": {
    "kind": "invoice",
    "typeCode": "380",
    "number": "123456XX",
    "issueDate": "2016-04-04",
    "currency": "EUR",
    "buyerReference": "04011000-12345-03",
    "paymentTerms": "Zahlbar sofort ohne Abzug.",
    "processId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
    "specificationId": "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0",
    "notes": [
      {
        "text": "Es gelten unsere Allgem. Geschäftsbedingungen, die Sie unter […] finden.",
        "subjectCode": "ADU"
      }
    ],
    "precedingInvoices": [],
    "seller": {
      "name": "[Seller name]",
      "tradingName": "[Seller trading name]",
      "identifiers": [],
      "legalRegistration": {
        "value": "[HRA-Eintrag]"
      },
      "vatId": "DE 123456789",
      "legalInformation": "123/456/7890, HRA-Eintrag in […]",
      "electronicAddress": {
        "value": "seller@email.de",
        "scheme": "EM"
      },
      "address": {
        "line1": "[Seller address line 1]",
        "city": "[Seller city]",
        "postCode": "12345",
        "country": "DE"
      },
      "contact": {
        "name": "nicht vorhanden",
        "telephone": "+49 1234-5678",
        "email": "seller@email.de"
      }
    },
    "buyer": {
      "name": "[Buyer name]",
      "identifiers": [
        {
          "value": "[Buyer identifier]"
        }
      ],
      "electronicAddress": {
        "value": "buyer@info.de",
        "scheme": "EM"
      },
      "address": {
        "line1": "[Buyer address line 1]",
        "city": "[Buyer city]",
        "postCode": "12345",
        "country": "DE"
      }
    },
    "payment": {
      "meansCode": "58",
      "creditTransfers": [
        {
          "iban": "DE79000000001234567890"
        }
      ]
    },
    "allowances": [],
    "charges": [],
    "totals": {
      "lineTotal": "314.86",
      "taxExclusive": "314.86",
      "tax": "22.04",
      "taxInclusive": "336.9",
      "payable": "336.9"
    },
    "vatBreakdown": [
      {
        "category": "S",
        "rate": "7",
        "taxableAmount": "314.86",
        "taxAmount": "22.04"
      }
    ],
    "attachments": [],
    "lines": [
      {
        "id": "Zeitschrift [...]",
        "note": "Die letzte Lieferung im Rahmen des abgerechneten Abonnements erfolgt in 12/2016 Lieferung erfolgt / erfolgte direkt vom Verlag",
        "quantity": "1",
        "unit": "XPP",
        "netAmount": "288.79",
        "orderLineReference": "6171175.1",
        "period": {
          "start": "2016-01-01",
          "end": "2016-12-31"
        },
        "allowances": [],
        "charges": [],
        "price": {
          "amount": "288.79"
        },
        "vat": {
          "category": "S",
          "rate": "7"
        },
        "item": {
          "name": "Zeitschrift [...]",
          "description": "Zeitschrift Inland",
          "sellerId": "246",
          "classifications": [
            {
              "value": "0721-880X",
              "scheme": "IB"
            }
          ],
          "attributes": []
        }
      },
      {
        "id": "Porto + Versandkosten",
        "quantity": "1",
        "unit": "XPP",
        "netAmount": "26.07",
        "allowances": [],
        "charges": [],
        "price": {
          "amount": "26.07"
        },
        "vat": {
          "category": "S",
          "rate": "7"
        },
        "item": {
          "name": "Porto + Versandkosten",
          "classifications": [],
          "attributes": []
        }
      }
    ],
    "nationalTerms": [],
    "source": {
      "format": "xrechnung",
      "syntax": "ubl",
      "customizationId": "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0"
    }
  },
  "usage": {
    "used": 1,
    "limit": 20
  }
}

Kosten

Vorausbezahlte API-Pakete, per Karte auf Ihrer Kontoseite gekauft: 1.000 Aufrufe für 39,00 €, 5.000 Aufrufe für 129,00 €, 25.000 Aufrufe für 449,00 €, 100.000 Aufrufe für 990,00 €, ohne Ablaufdatum. Ihr erster Schlüssel bringt 25 kostenlose Aufrufe. Kostenpflichtige Aufrufe verbrauchen zuerst das API-Guthaben, danach dieselben Credits wie die Website. Ein fehlgeschlagener Aufruf kostet nichts.

Aufruf Kosten
POST /open Kostenlos, faire Nutzung bis 1.000 am Tag pro Konto
POST /check Kostenlos im Tageslimit Ihres Tarifs (20 am Tag im kostenlosen Tarif)
POST /convert, POST /make 1 Aufruf, nur wenn die erzeugte Datei gültig ist
POST /bank/convert 1 Aufruf je angefangene 500 Umsätze, nur bei erfolgreicher Umwandlung

Grenzen

  • Dateien bis 20 MB, PDFs bis 50 Seiten.
  • 60 Anfragen pro Minute je Schlüssel, 120 je Konto. Eine 429 enthält Retry-After.
  • Bis zu 10 Schlüssel pro Konto. Idempotency-Key gilt 24 Stunden, ein erneuter Versuch wird also nie doppelt berechnet.

Fehler

Jeder Fehler ist { ok: false, error, message }: ein fester Code und ein klarer Satz.

Status Code Wann
401 auth Kein Schlüssel oder ein unbekannter oder widerrufener Schlüssel.
402 no_credits API-Guthaben und Credits reichen beide nicht. Nichts wurde ausgeführt; topUpUrl nennt die Kaufseite.
413 too_large Über 20 MB oder 50 PDF-Seiten.
415 unsupported Ein Dateiformat, das die Route nicht annimmt.
422 invalid Die Datei war nicht lesbar oder die erzeugte Datei ist nicht gültig. Nicht berechnet.
429 limit Ein Ratenlimit oder die kostenlosen Aufrufe des Tages.
503 engine_unavailable Die Validierung ist kurz nicht erreichbar. Nicht berechnet.

Datenschutz

Dateien werden für die Anfrage verarbeitet, ohne ein Dokumentenarchiv anzulegen. Die Anwendungsprotokolle von Klarbon enthalten weder Dateinamen noch Inhalte noch Schlüssel. Der vollständige Schlüssel wird nicht gespeichert, nur ein HMAC davon und ein kurzes Anzeigepräfix.

Weiter

Die Entwicklerdokumentation ist auf Englisch.

Häufige Fragen

Gegen welche Regelwerke kann ich prüfen?

EN 16931 in UBL und CII, XRechnung, Peppol BIS Billing, die Profile von ZUGFeRD und Factur-X, NLCIUS und die französischen CTC-Regeln. GET /api/v1/formats zeigt, was verfügbar ist.

Wie wählt die API das Regelwerk?

Aus der Customization-ID und dem Container der Datei, wenn Sie weder ruleset noch country angeben. Der Bericht nennt das verwendete Regelwerk.

Was ist der Unterschied zwischen Fehler, Warnung und Hinweis?

Ein Fehler bedeutet, dass die Datei eine Regel der gewählten Prüfung nicht besteht. Warnungen und Hinweise lassen diese Prüfung nicht scheitern, können für den Empfänger aber wichtig sein.

Nimmt mein Kunde eine gültige Rechnung sicher an?

Nicht sicher. Gültig heißt, dass die ausgewählten Prüfungen bestanden sind. Empfänger und Portale können eigene Anforderungen haben.

Quellen

Aktualisiert am 6. November 2026