ERP Invoice API — Rechnungen per REST-API erstellen

Externe Portale und Shops können über diese API automatisch Rechnungen im ERP erstellen. Jedes System erhält einen eigenen API-Schlüssel.

1. API-Schlüssel erstellen

1

ERP öffnen

Navigieren Sie zu ERP → Seitenleiste → SystemAPI-Schlüssel.

2

Neuen Schlüssel anlegen

Klicken Sie auf „Neuer API-Schlüssel" und vergeben Sie einen Namen (z. B. „Webshop Hauptseite").

3

Schlüssel kopieren

Der Schlüssel wird nur einmal angezeigt. Kopieren Sie ihn sofort und speichern Sie ihn sicher ab.

Wichtig: Der API-Schlüssel kann nach dem Erstellen nicht mehr angezeigt werden. Bei Verlust muss ein neuer Schlüssel generiert werden.

Optional können Sie beim Erstellen konfigurieren:

  • Rate-Limit — Maximale Requests pro Minute (Standard: 60)
  • IP-Whitelist — Nur bestimmte IP-Adressen zulassen
  • Callback-URL — Webhook für Statusänderungen (geplant)

2. Endpoint & Authentifizierung

POST https://it-dashboard.de/api/erp/invoices

HTTP-Header

Header Wert
Authorization Bearer eak_IHR_API_KEY
Content-Type application/json
Accept application/json

3. Request-Body — Alle Felder

Kontakt (Pflicht — eine der beiden Varianten)

Variante A: Bestehenden Kontakt per ID referenzieren:

{
  "erp_contact_id": 15
}

Variante B: Kontaktdaten inline senden:

{
  "contact": {
    "email": "kunde@example.de",
    "company_name": "Musterfirma GmbH",
    "first_name": "Max",
    "last_name": "Mustermann",
    "street": "Musterstraße 1",
    "zip": "12345",
    "city": "Berlin",
    "country": "DE",
    "ust_id_nr": "DE123456789",
    "phone": "+49 123 456789",
    "payment_terms_days": 14
  }
}

Kontakt-Auflösung: Bei Variante B wird zuerst nach einem bestehenden Kontakt mit gleicher E-Mail gesucht. Wird einer gefunden, werden die Adressdaten aktualisiert. Wird keiner gefunden, wird ein neuer Kontakt angelegt.

Nur contact.email ist ein Pflichtfeld bei Variante B. Alle anderen Felder sind optional.

Rechnungsdaten (alle optional)

Feld Typ Standard Beschreibung
title string Rechnungstitel, z. B. „Bestellung #12345"
type string invoice invoice oder credit_note
invoice_date string heute Rechnungsdatum (YYYY-MM-DD)
due_date string +14 Tage Fälligkeitsdatum (YYYY-MM-DD)
service_period_start string Leistungszeitraum Beginn (YYYY-MM-DD)
service_period_end string Leistungszeitraum Ende (YYYY-MM-DD)
discount_percent number 0 Gesamtrabatt in Prozent (0–100)
external_reference string Externe Bestell-ID — empfohlen für Deduplication
introduction_text string ERP-Setting Einleitungstext auf der Rechnung
conclusion_text string ERP-Setting Schlusstext auf der Rechnung
notes string Interne Notizen (nicht auf der Rechnung sichtbar)

Positionen (Pflicht — mindestens 1)

{
  "line_items": [
    {
      "name": "Webhosting Premium",
      "unit_price": 29.90,
      "quantity": 1,
      "unit": "Monat",
      "tax_rate": 19.00,
      "description": "Inkl. 50 GB SSD, SSL",
      "discount_percent": 0,
      "position": 1,
      "product_sku": "WH-PREM-001"
    }
  ]
}
Feld Pflicht Standard Beschreibung
name Ja Positionsname
unit_price Ja Einzelpreis netto in Euro
quantity Nein 1 Menge
unit Nein Stück Einheit (Stück, Monat, Stunde, Pauschal, ...)
tax_rate Nein 19.00 Steuersatz in Prozent
description Nein Zusatztext zur Position
discount_percent Nein 0 Positionsrabatt in Prozent
position Nein auto Reihenfolge (wird automatisch nummeriert)
product_sku Nein Produkt per SKU aus dem Produktkatalog verknüpfen

Flags (optional)

Flag Standard Beschreibung
auto_mark_sent false Rechnung sofort als „Versendet" markieren + in der Buchhaltung buchen
auto_send_email false Rechnung per E-Mail an Kontakt versenden (PDF + ZUGFeRD als Anhang)

Hinweis: auto_send_email benötigt auto_mark_sent: true — eine Rechnung im Entwurf-Status wird nicht per E-Mail versendet.

4. Antwort (Erfolg — HTTP 201)

{
  "success": true,
  "invoice": {
    "id": 42,
    "invoice_number": "RE-2026-00042",
    "status": "sent",
    "type": "invoice",
    "title": "Bestellung #12345",
    "external_reference": "SHOP-ORDER-12345",
    "contact": {
      "id": 15,
      "customer_number": "KD-2026-015",
      "display_name": "Musterfirma GmbH",
      "email": "kunde@example.de"
    },
    "invoice_date": "2026-03-03",
    "due_date": "2026-03-17",
    "subtotal_net": 29.90,
    "tax_amount": 5.68,
    "total_gross": 35.58,
    "discount_percent": 0,
    "line_items": [
      {
        "position": 1,
        "name": "Webhosting Premium",
        "quantity": 1,
        "unit": "Monat",
        "unit_price": 29.90,
        "tax_rate": 19.00,
        "net_total": 29.90,
        "gross_total": 35.58
      }
    ],
    "pdf_url": "https://it-dashboard.de/erp/invoices/42/pdf",
    "created_at": "2026-03-03T10:30:00+00:00"
  },
  "warnings": []
}

Das warnings-Array enthält Hinweise, z. B. wenn eine product_sku nicht gefunden wurde. Die Rechnung wird trotzdem erstellt.

5. Fehlercodes

HTTP Code Bedeutung
401 missing_token Kein Authorization: Bearer Header gesendet
401 invalid_format Token beginnt nicht mit eak_
401 invalid_key API-Schlüssel nicht gefunden oder gelöscht
403 key_disabled API-Schlüssel ist deaktiviert
403 ip_blocked IP-Adresse nicht in der Whitelist
409 duplicate_reference external_reference existiert bereits — bestehende Rechnung wird zurückgegeben
422 validation_error Validierungsfehler — Details im details-Objekt
429 rate_limit Rate-Limit überschritten — Retry-After Header beachten
500 creation_failed Interner Fehler bei der Rechnungserstellung

Beispiel: Validierungsfehler (422)

{
  "error": "Validierungsfehler.",
  "code": "validation_error",
  "details": {
    "contact": ["Entweder erp_contact_id oder contact-Objekt ist erforderlich."],
    "line_items.0.unit_price": ["Einzelpreis ist ein Pflichtfeld."]
  }
}

Beispiel: Duplikat (409)

{
  "error": "Rechnung mit dieser external_reference existiert bereits.",
  "code": "duplicate_reference",
  "invoice": {
    "id": 42,
    "invoice_number": "RE-2026-00042",
    "status": "sent",
    "..."
  }
}

6. Praxisbeispiele

Minimaler Request — nur Pflichtfelder

curl -X POST https://it-dashboard.de/api/erp/invoices \
  -H "Authorization: Bearer eak_IHR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact": {
      "email": "info@kunde.de"
    },
    "line_items": [
      {
        "name": "Dienstleistung",
        "unit_price": 100.00
      }
    ]
  }'

Erstellt eine Rechnung im Status Entwurf mit 1 Position, 19 % MwSt. und 14 Tagen Zahlungsziel.

Shop-Bestellung mit automatischem E-Mail-Versand

curl -X POST https://it-dashboard.de/api/erp/invoices \
  -H "Authorization: Bearer eak_IHR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact": {
      "email": "max@musterfirma.de",
      "company_name": "Musterfirma GmbH",
      "first_name": "Max",
      "last_name": "Mustermann",
      "street": "Industriestr. 10",
      "zip": "80333",
      "city": "München"
    },
    "title": "Bestellung #WS-2026-0815",
    "external_reference": "WS-2026-0815",
    "line_items": [
      {
        "name": "Webhosting Business",
        "description": "Inkl. 100 GB SSD, 10 Domains, SSL",
        "quantity": 12,
        "unit": "Monate",
        "unit_price": 14.90
      },
      {
        "name": "Domain-Registrierung example.de",
        "quantity": 1,
        "unit_price": 9.90
      },
      {
        "name": "Einrichtungspauschale",
        "quantity": 1,
        "unit": "Pauschal",
        "unit_price": 49.00
      }
    ],
    "auto_mark_sent": true,
    "auto_send_email": true
  }'

Rechnung wird erstellt, sofort als Versendet markiert, in der Buchhaltung gebucht und als PDF per E-Mail an den Kontakt geschickt.

PHP-Integration (Laravel / Guzzle)

$response = Http::withToken('eak_IHR_API_KEY')
    ->post('https://it-dashboard.de/api/erp/invoices', [
        'contact' => [
            'email' => $order->customer_email,
            'company_name' => $order->company_name,
            'street' => $order->street,
            'zip' => $order->zip,
            'city' => $order->city,
        ],
        'title' => "Bestellung #{$order->id}",
        'external_reference' => "SHOP-{$order->id}",
        'line_items' => collect($order->items)->map(fn ($item) => [
            'name' => $item->name,
            'quantity' => $item->quantity,
            'unit_price' => $item->price,
            'tax_rate' => 19.00,
        ])->toArray(),
        'auto_mark_sent' => true,
        'auto_send_email' => true,
    ]);

if ($response->status() === 201) {
    $invoiceNumber = $response->json('invoice.invoice_number');
    // → "RE-2026-00042"
} elseif ($response->status() === 409) {
    // Duplikat — Rechnung existiert bereits
    $invoiceNumber = $response->json('invoice.invoice_number');
}

Produkte per SKU referenzieren

curl -X POST https://it-dashboard.de/api/erp/invoices \
  -H "Authorization: Bearer eak_IHR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "erp_contact_id": 15,
    "title": "Monatliche Abrechnung März 2026",
    "service_period_start": "2026-03-01",
    "service_period_end": "2026-03-31",
    "line_items": [
      {
        "product_sku": "WH-PREM-001",
        "name": "Webhosting Premium",
        "unit_price": 29.90,
        "quantity": 1
      }
    ],
    "auto_mark_sent": true
  }'

Falls die SKU im Produktkatalog existiert, wird die Position mit dem Produkt verknüpft. Falls nicht, kommt eine Warnung im warnings-Array — die Rechnung wird trotzdem erstellt.

7. Deduplication (Doppel-Schutz)

Wenn external_reference gesetzt ist, prüft die API ob bereits eine Rechnung mit der gleichen Referenz und dem gleichen API-Key existiert:

  • Neue Referenz → Rechnung wird erstellt (HTTP 201)
  • Bestehende Referenz → Keine neue Rechnung, bestehende wird zurückgegeben (HTTP 409)
  • Stornierte Rechnung mit gleicher Referenz → Wird ignoriert, neue Rechnung wird erstellt

Empfehlung: Setzen Sie immer external_reference mit der Shop-Bestell-ID, um bei Netzwerkfehlern oder Retries keine Duplikate zu erzeugen.

8. Rate-Limiting

Standard: 60 Requests pro Minute pro API-Key (konfigurierbar im Admin-Bereich).

Bei Überschreitung antwortet die API mit:

HTTP/1.1 429 Too Many Requests
Retry-After: 42

{
  "error": "Rate-Limit überschritten.",
  "code": "rate_limit",
  "retry_after": 42
}

Warten Sie die im Retry-After Header angegebenen Sekunden ab, bevor Sie einen neuen Request senden.