Zum Inhalt springen

API zur Umwandlung von E-Rechnungen

E-Rechnungen per REST-Aufruf zwischen UBL, CII, XRechnung und Peppol BIS umwandeln. Berechnet nur, wenn die erzeugte Datei gültig ist.

Auf dieser Seite

Die API zur Umwandlung von E-Rechnungen von Klarbon wandelt Rechnungen mit einem REST-Aufruf zwischen UBL, CII, XRechnung und Peppol BIS um. Sie liest die Quelle in das Rechnungsmodell nach EN 16931, schreibt das Zielformat und prüft das Ergebnis vor der Ausgabe. Berechnet wird nur eine Datei, die die Prüfung besteht. Die Dateien werden für die Anfrage verarbeitet, ohne Dokumentenarchiv.

Was die API macht

  • POST /api/v1/convert mit target auf ubl, cii, xrechnung oder peppol-bis-billing.
  • Die Antwort enthält die neue Datei, den Prüfbericht und einen credits-Block mit dem Verbrauch. Bezahlte Aufrufe nutzen zuerst das API-Guthaben, danach Credits aus dem Abo oder gekaufte Credits.
  • losses nennt Felder, die beim Schreiben des Zielformats geändert, weggelassen oder angenommen wurden. Nicht aus der Quelle übernommene Angaben erfasst die Liste nicht. Vergleichen Sie das Ergebnis mit dem Original.
  • Mit Idempotency-Key sind Wiederholungen sicher: Dieselbe Anfrage wird innerhalb eines Tages nie doppelt berechnet.

Was sie nicht macht

Sie versendet keine Rechnungen und liest keine PDF-Rechnungen ohne strukturierte Daten. Für hybride PDFs gibt es die ZUGFeRD-API.

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

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.

Für die Seite gekürzt: 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

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

Zwischen welchen Formaten kann ich umwandeln?

UBL, CII, XRechnung und Peppol BIS Billing, in beide Richtungen. GET /api/v1/formats zeigt jedes Format mit den möglichen Richtungen.

Was passiert mit Angaben, die das Zielformat nicht kennt?

Die Liste losses nennt Felder, die beim Schreiben des Zielformats geändert, weggelassen oder angenommen wurden. Angaben, die nicht aus der Quelldatei gelesen wurden, erfasst sie nicht. Vergleichen Sie das Ergebnis mit dem Original.

Wird eine ungültige Umwandlung berechnet?

Nein. Eine Datei, die die Prüfung nicht besteht, wird nicht ausgegeben. Der Aufruf antwortet mit 422 und dem Bericht.

Wie wiederhole ich einen Aufruf, ohne doppelt zu zahlen?

Mit dem Header Idempotency-Key. Dieselbe Anfrage mit demselben Schlüssel führt die Umwandlung innerhalb von 24 Stunden ohne erneute Berechnung aus. Die neue Datei muss nicht Byte für Byte identisch sein.

Quellen

Aktualisiert am 6. November 2026