Aller au contenu
Thème
Français

API de validation de factures électroniques

Vérifiez vos factures électroniques par API selon les règles sélectionnées, avec les indications disponibles. Gratuit dans la limite quotidienne.

Cette fonction est en bêta : elle fonctionne et elle est testée, mais des détails peuvent encore changer.

Sur cette page

L’API de validation de factures électroniques de Klarbon contrôle les factures UBL, CII, Factur-X et Peppol BIS selon les règles applicables, en un appel REST. Elle déduit le jeu de règles du fichier ou prend celui que vous indiquez, et renvoie les résultats des contrôles sélectionnés avec les codes de règle, les gravités et les indications disponibles. Les fichiers sont traités pour la requête, sans archive documentaire.

Ce que fait l’API

  • POST /api/v1/check accepte le XML ou le PDF hybride, avec ruleset=auto par défaut.
  • country=FR applique les règles attendues en France, dont les règles CTC.
  • Les routes de production de factures électroniques renvoient aussi des résultats de vérification ; consultez-les avant d'utiliser le fichier.

Ce qu’elle ne fait pas

Un résultat valide signifie que le fichier a passé les contrôles sélectionnés. Il ne garantit pas l’acceptation par le destinataire et ne constitue pas un conseil fiscal ou juridique. La validation est en bêta et gratuite dans la limite quotidienne de votre offre. Pour convertir, voyez l’API de conversion de factures électroniques.

Démarrage rapide

Chaque exemple fait le même appel. Avant publication, chacun est exécuté contre un serveur Klarbon local et le vrai moteur de validation.

Créez une clé sur la page de votre compte et placez-la dans KLARBON_API_KEY. Ne la mettez jamais dans l’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;

La réponse

La réponse à l’appel curl ci-dessus, enregistrée lors de cette exécution avec 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
  }
}

Tarifs

Des packs API prépayés, achetés par carte sur la page de votre compte : 1 000 appels pour 39,00 €, 5 000 appels pour 129,00 €, 25 000 appels pour 449,00 €, 100 000 appels pour 990,00 €, sans date d’expiration. Votre première clé apporte 25 appels gratuits. Les appels payants consomment d’abord le solde API, puis les mêmes crédits que le site. Un appel qui échoue ne coûte rien.

Appel Coût
POST /open Gratuit, usage raisonnable jusqu’à 1 000 par jour et par compte
POST /check Gratuit dans la limite quotidienne de votre offre (20 par jour avec l’offre gratuite)
POST /convert, POST /make 1 appel, seulement si le fichier produit est valide
POST /bank/convert 1 appel par tranche entamée de 500 opérations, seulement après conversion

Limites

  • Fichiers jusqu’à 20 Mo, PDF jusqu’à 50 pages.
  • 60 requêtes par minute et par clé, 120 par compte. Une réponse 429 indique Retry-After.
  • Jusqu’à 10 clés par compte. Idempotency-Key est respecté pendant 24 heures : une nouvelle tentative n’est jamais facturée deux fois.

Erreurs

Chaque erreur a la forme { ok: false, error, message } : un code stable et une phrase claire.

Statut Code Quand
401 auth Aucune clé, ou une clé inconnue ou révoquée.
402 no_credits Solde API et crédits tous deux insuffisants. Rien n’a été fait ; topUpUrl indique où en acheter.
413 too_large Plus de 20 Mo ou de 50 pages PDF.
415 unsupported Un format de fichier que la route n’accepte pas.
422 invalid Le fichier est illisible, ou le fichier produit n’est pas valide. Non facturé.
429 limit Une limite de débit, ou les appels gratuits du jour.
503 engine_unavailable La validation est brièvement indisponible. Non facturé.

Confidentialité

Les fichiers sont traités pour la requête, sans archive de documents. Les journaux de l'application Klarbon ne contiennent ni nom de fichier, ni contenu, ni clé. La clé complète n'est pas conservée, seulement son empreinte HMAC et un court préfixe d'affichage.

Pour aller plus loin

La documentation pour les développeurs est en anglais.

Questions fréquentes

Quels jeux de règles puis-je appliquer ?

EN 16931 en UBL et CII, les profils Factur-X et ZUGFeRD, Peppol BIS Billing, XRechnung, NLCIUS et les règles CTC françaises. GET /api/v1/formats liste ce qui est disponible.

Comment l’API choisit-elle le jeu de règles ?

D’après l’identifiant de personnalisation et le conteneur du fichier, sauf si vous passez ruleset ou country. Le rapport indique le jeu utilisé.

Quelle différence entre erreur bloquante, avertissement et information ?

Une erreur bloquante fait échouer le contrôle sélectionné. Les avertissements et informations ne le font pas échouer, mais peuvent rester importants pour le destinataire.

Klarbon remplace-t-il ma plateforme agréée ?

Non. Klarbon contrôle et convertit les fichiers ; la transmission dans la réforme passe par une plateforme agréée.

Sources

Mis à jour le 5 novembre 2026