API-Dokumentation
REST-API für Erstellung, Validierung und Konvertierung von E-Rechnungen. Alle Endpunkte nutzen JSON und Bearer-Token-Authentifizierung.
Authentifizierung
Jeder API-Request erfordert einen gültigen API-Schlüssel im
Authorization-Header als Bearer-Token.
Header:
Authorization: Bearer erec_lv_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
API-Schlüssel können im Kundenbereich unter
API-Schlüssel verwaltet werden.
Schlüssel beginnen mit erec_lv_ (Live) oder
erec_sb_ (Sandbox).
Live & Sandbox
Die API unterscheidet zwischen Live- und Sandbox-Umgebungen. Die Umgebung wird durch den verwendeten API-Schlüssel bestimmt.
Live
- Schlüssel-Präfix:
erec_lv_ - Verbraucht Live-Credits
- Echte Rechnungserzeugung
- Produktive Nutzungszahlen
- Alle Request-Felder werden verarbeitet
Sandbox
- Schlüssel-Präfix:
erec_sb_ - Verbraucht keine Live-Credits
- Gleiche API-Formate und Endpoints
- Verwendet vordefinierte Beispieldatensätze
- Limit: 100 Aufrufe pro Schlüssel (konfigurierbar)
Jede Response enthält den Header X-Environment: live
oder X-Environment: sandbox. Sandbox-Responses
tragen zusätzlich X-Sandbox-Mode: true und
X-Sandbox-Remaining (verbleibende Aufrufe).
Beispieldatensätze für die Sandbox
In der Sandbox werden vordefinierte Rechnungsdatensätze verwendet,
die unterschiedliche Szenarien abbilden. Wählen Sie einen Datensatz
über das Feld "sample" im Request-Body aus. In der
Live-API wird dieser Wert ignoriert.
| Wert | Szenario | Beschreibung |
|---|---|---|
standard |
Standard-Rechnung | Beratungsleistung, 1 Position, 19 % MwSt, B2B Inland |
kleinstbetrag |
Kleinstbetrag-Rechnung | Software-Lizenz, geringer Betrag, 0 % MwSt (intragemeinschaftliche Lieferung EU) |
gutschrift |
Gutschrift | Type 381 (Credit Note), 2 Positionen, 7 % und 19 % MwSt |
Wird kein sample-Feld übergeben, wird standard
verwendet.
Eigene Werte übernehmen
Um ein positives Test-Erlebnis zu ermöglichen, können Sie im Sandbox-Modus
die Rechnungsnummer (document.number) und das
Rechnungsdatum (document.date) selbst übergeben.
Diese Werte ersetzen die Standardwerte aus dem Beispieldatensatz. Alle
anderen Felder stammen aus dem gewählten Datensatz.
Sandbox-Request mit Overrides:
{
"sample": "standard",
"document": {
"number": "RE-TEST-2026-042",
"date": "2026-08-18"
}
}
Wenn Sie document.number oder document.date
nicht angeben, werden die Standardwerte aus dem Beispieldatensatz
verwendet.
Sandbox-Aufruflimit
Jeder Sandbox-Schlüssel hat ein Standard-Limit von 100 Aufrufen. Dieses Limit ist pro Schlüssel in der Datenbank hinterlegt und kann von einem Administrator pro Schlüssel angepasst werden. Bei Erreichen des Limits erhalten Sie folgende Fehlermeldung:
{
"status": "error",
"error": {
"code": "sandbox_limit_exceeded",
"category": "rate_limit",
"message": "Das Sandbox-Aufruflimit für diesen API-Schlüssel ist erreicht.",
"sandbox_used": 100,
"sandbox_limit": 100,
"sandbox_remaining": 0
}
}
Der Response-Header X-Sandbox-Remaining zeigt die
verbleibenden Aufrufe für den aktuellen Schlüssel.
Endpoints
/api/v1/credits
Aktuellen Credit-Stand abfragen.
Response 200
{
"status": "success",
"data": {
"used": 42,
"limit": 1000,
"remaining": 958,
"environment": "live"
},
"meta": {
"user_id": 7,
"timestamp": "2026-08-13T14:05:00+00:00"
}
}
Response-Header: X-Environment, ggf. X-Sandbox-Mode
/api/v1/credits/consume
Credit verbrauchen (Sandbox verbraucht 0 Credits).
Request-Body
{
"environment": "live"
}
Response 200
{
"status": "success",
"data": {
"consumed": 1,
"used": 43,
"limit": 1000,
"remaining": 957,
"environment": "live"
},
"meta": {
"user_id": 7,
"timestamp": "2026-08-13T14:06:00+00:00"
}
}
/api/v1/invoices/xrechnung
XRechnung-XML aus strukturierten Daten erzeugen.
Request-Body (Live – vollständige Daten)
{
"document": {
"number": "RE-2026-0001",
"type_code": "380",
"date": "2026-08-13",
"currency": "EUR",
"buyer_reference": "BA-2026-001"
},
"seller": {
"name": "Beispiel GmbH",
"address": {
"line1": "Musterstr. 1",
"postcode": "10115",
"city": "Berlin",
"country": "DE"
},
"tax_numbers": [
{"type": "VA", "id": "DE123456789"}
]
},
"buyer": {
"name": "Kunde AG",
"address": {
"line1": "Kaufstr. 42",
"postcode": "20095",
"city": "Hamburg",
"country": "DE"
}
},
"lines": [
{
"line_id": "1",
"product_name": "Beratungsleistung",
"net_price": 500.00,
"quantity": 1,
"unit_code": "C62",
"tax": {
"category_code": "S",
"type_code": "VAT",
"rate_percent": 19
},
"line_total": 500.00
}
],
"tax_breakdown": [
{
"category_code": "S",
"type_code": "VAT",
"basis_amount": 500.00,
"calculated_amount": 95.00,
"rate_percent": 19
}
],
"summation": {
"grand_total": 595.00,
"due_payable": 595.00,
"line_total": 500.00,
"tax_basis_total": 500.00,
"tax_total": 95.00
},
"payment": {
"iban": "DE89370400440532013000",
"terms_description": "Zahlbar innerhalb 14 Tagen ohne Abzug"
}
}
Request-Body (Sandbox – Beispieldatensatz mit Overrides)
{
"sample": "standard",
"document": {
"number": "RE-TEST-2026-042",
"date": "2026-08-18"
}
}
Im Sandbox-Modus wird das Feld sample ausgewertet
(standard, kleinstbetrag, gutschrift).
Die Felder document.number und document.date
überschreiben die Standardwerte. In der Live-API wird sample
ignoriert und der vollständige Request-Body verarbeitet.
Response 200 (Live)
Content-Type: application/xml
X-Environment: live
X-Credits-Remaining: 956
X-Credits-Limit: 1000
<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice ...>
<!-- XRechnung 3.0 XML -->
</rsm:CrossIndustryInvoice>
Response 200 (Sandbox)
Content-Type: application/xml
X-Environment: sandbox
X-Sandbox-Mode: true
X-Sandbox-Remaining: 99
<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice ...>
<!-- XRechnung 3.0 XML aus Beispieldatensatz -->
</rsm:CrossIndustryInvoice>
Fehler 429 – Sandbox-Limit erreicht
{
"status": "error",
"error": {
"code": "sandbox_limit_exceeded",
"category": "rate_limit",
"message": "Das Sandbox-Aufruflimit für diesen API-Schlüssel ist erreicht.",
"sandbox_used": 100,
"sandbox_limit": 100,
"sandbox_remaining": 0
}
}
Fehler 400 – Ungültiger Beispieldatensatz
{
"status": "error",
"error": {
"code": "sandbox_invalid_sample",
"category": "validation",
"message": "Der angegebene Beispieldatensatz ist nicht verfügbar.",
"valid_samples": ["standard", "kleinstbetrag", "gutschrift"]
}
}
Fehler 422 – Validierung
{
"status": "error",
"error": {
"code": "validation_failed",
"category": "validation",
"message": "Die übergebenen Daten sind ungültig.",
"fields": [
{
"field": "document.number",
"code": "field_required",
"message": "Feld ist erforderlich."
}
]
}
}
/api/v1/invoices/zugferd
ZUGFeRD-PDF/A-3 aus XML und bestehendem PDF erzeugen.
Request-Body
{
"invoice": {
"xml": "<?xml ...?><rsm:CrossIndustryInvoice ...>...</rsm:CrossIndustryInvoice>",
"pdf": "JVBERi0xLjQKMSAwIG9iag..."
}
}
xml: XRechnung-/ZUGFeRD-XML als Zeichenkette.
pdf: Quell-PDF als Base64-kodierte Zeichenkette.
Response 200
Content-Type: application/pdf
X-Environment: live
X-Credits-Remaining: 955
X-Credits-Limit: 1000
(binary PDF data)
/api/v1/validate
XML gegen XSD-Schema validieren (verbraucht keine Credits im Sandbox-Modus).
Request-Body
{
"invoice": "<?xml ...?><rsm:CrossIndustryInvoice ...>...</rsm:CrossIndustryInvoice>"
}
Response 200
{
"status": "success",
"data": {
"valid": true,
"errors": [],
"warnings": []
}
}
Response 200 mit Fehlern
{
"status": "success",
"data": {
"valid": false,
"errors": ["Element 'ram:SellerTradeParty' fehlt"],
"warnings": []
}
}
Code-Beispiele
cURL
# Credit-Stand abfragen
curl -X GET https://example.com/api/v1/credits \
-H "Authorization: Bearer erec_lv_a1b2c3..."
# XRechnung erzeugen
curl -X POST https://example.com/api/v1/invoices/xrechnung \
-H "Authorization: Bearer erec_lv_a1b2c3..." \
-H "Content-Type: application/json" \
-d '{"document":{"number":"RE-2026-0001",...}}'
# Sandbox-Request mit Beispieldatensatz
curl -X POST https://example.com/api/v1/invoices/xrechnung \
-H "Authorization: Bearer erec_sb_a1b2c3..." \
-H "Content-Type: application/json" \
-d '{"sample":"standard","document":{"number":"RE-TEST-001","date":"2026-08-18"}}'
# Sandbox: Gutschrift-Beispiel ohne Overrides
curl -X POST https://example.com/api/v1/invoices/xrechnung \
-H "Authorization: Bearer erec_sb_a1b2c3..." \
-H "Content-Type: application/json" \
-d '{"sample":"gutschrift"}'
PHP
<?php
$ch = curl_init('https://example.com/api/v1/invoices/xrechnung');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer erec_lv_a1b2c3...',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'document' => [
'number' => 'RE-2026-0001',
'type_code' => '380',
'date' => '2026-08-13',
'currency' => 'EUR',
],
// ... weitere Felder
]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP-Status: $status\n";
echo $response;
JavaScript
const response = await fetch('https://example.com/api/v1/invoices/xrechnung', {
method: 'POST',
headers: {
'Authorization': 'Bearer erec_lv_a1b2c3...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
document: {
number: 'RE-2026-0001',
type_code: '380',
date: '2026-08-13',
currency: 'EUR',
},
// ... weitere Felder
}),
});
const environment = response.headers.get('X-Environment');
const data = await response.text();
console.log('Environment:', environment);
console.log('Status:', response.status);
console.log('Data:', data);
Fehlercodes
Alle Fehlerantworten folgen dem gleichen JSON-Format mit
code, category und message.
Es werden keine Stacktraces oder sensiblen Daten preisgegeben.
{
"status": "error",
"error": {
"code": "auth_missing",
"category": "authentication",
"message": "Authentifizierung erforderlich. Bitte gültigen Bearer-Token übergeben."
}
}
Authentifizierung
| Code | HTTP | Beschreibung |
|---|---|---|
auth_missing |
401 | Authentifizierung erforderlich. Bitte gültigen Bearer-Token übergeben. |
auth_invalid |
401 | Authentifizierung fehlgeschlagen. Der API-Schlüssel ist ungültig. |
auth_expired |
401 | Die Sitzung ist abgelaufen. Bitte erneut authentifizieren. |
auth_forbidden |
403 | Zugriff verweigert. Keine Berechtigung für diese Ressource. |
API-Schlüssel
| Code | HTTP | Beschreibung |
|---|---|---|
api_key_not_found |
401 | Der angegebene API-Schlüssel wurde nicht gefunden. |
api_key_revoked |
403 | Der API-Schlüssel wurde widerrufen. |
api_key_disabled |
403 | Der API-Schlüssel ist deaktiviert. |
api_key_environment |
403 | Der API-Schlüssel ist für diese Umgebung nicht gültig. |
Account / Abonnement
| Code | HTTP | Beschreibung |
|---|---|---|
account_not_found |
404 | Das Konto wurde nicht gefunden. |
account_inactive |
403 | Das Konto ist nicht aktiv. |
account_suspended |
403 | Das Konto wurde gesperrt. |
account_deletion_pending |
403 | Das Konto ist zur Löschung vorgemerkt. |
no_active_subscription |
403 | Es liegt kein aktives Abonnement vor. |
Credits
| Code | HTTP | Beschreibung |
|---|---|---|
credits_insufficient |
402 | Nicht genügend Credits verfügbar. |
credits_limit_exceeded |
429 | Das Credit-Kontingent für diesen Monat ist erschöpft. |
credits_usage_error |
500 | Credits konnten nicht verarbeitet werden. |
Rate-Limiting / Sandbox-Limit
| Code | HTTP | Beschreibung |
|---|---|---|
rate_limit_exceeded |
429 | Das Rate-Limit für diesen API-Schlüssel wurde überschritten. |
sandbox_limit_exceeded |
429 | Das Sandbox-Aufruflimit für diesen API-Schlüssel ist erreicht. |
Validierung
| Code | HTTP | Beschreibung |
|---|---|---|
sandbox_invalid_sample |
400 | Der angegebene Beispieldatensatz ist nicht verfügbar. |
validation_failed |
400 | Die übergebenen Daten sind ungültig. |
JSON / Body
| Code | HTTP | Beschreibung |
|---|---|---|
invalid_content_type |
415 | Content-Type muss application/json sein. |
empty_body |
400 | Request-Body darf nicht leer sein. |
body_too_large |
413 | Request-Body überschreitet das maximale Limit. |
invalid_json |
400 | Request-Body ist kein gültiges JSON. |
invalid_json_structure |
400 | JSON-Root muss ein Objekt sein. |
E-Rechnungserzeugung
| Code | HTTP | Beschreibung |
|---|---|---|
invoice_build_failed |
422 | Die Rechnung konnte nicht erzeugt werden. |
invoice_xml_invalid |
422 | Die erzeugte XML-Struktur ist ungültig. |
invoice_pdf_invalid |
422 | Das übergebene PDF ist ungültig. |
invoice_pdf_merge_failed |
422 | Das ZUGFeRD-PDF konnte nicht erzeugt werden. |
invoice_xsd_validation_failed |
422 | Die XML-Struktur entspricht nicht dem XSD-Schema. |
Interne Fehler
| Code | HTTP | Beschreibung |
|---|---|---|
internal_error |
500 | Ein interner Serverfehler ist aufgetreten. |
internal_database_error |
503 | Die Datenbank ist derzeit nicht verfügbar. |
internal_service_error |
502 | Ein erforderlicher Dienst ist derzeit nicht verfügbar. |
Routing
| Code | HTTP | Beschreibung |
|---|---|---|
not_found |
404 | Der angeforderte Endpunkt existiert nicht. |
method_not_allowed |
405 | Die HTTP-Methode ist für diesen Endpunkt nicht erlaubt. |
Request-ID
Jeder API-Request wird mit einer eindeutigen Request-ID protokolliert
(RFC 4122 v4 UUID). Die ID ist im Response-Body unter
meta.request_id verfügbar und dient der Nachverfolgung
bei Support-Anfragen.
{
"status": "success",
"data": { ... },
"meta": {
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": 7,
"timestamp": "2026-08-13T14:05:00+00:00"
}
}
Bei Fehlern kann die Request-ID dem Support mitgeteilt werden, um die Anfrage im System zu lokalisieren. Die ID enthält keine Rechnungsinhalte – es werden ausschließlich technische Metadaten gespeichert (Endpoint, HTTP-Status, Environment, Dauer, IP-Adresse).