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

GET

/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

POST

/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"
    }
}
POST

/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."
            }
        ]
    }
}
POST

/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)
POST

/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).