REST-API
Zuletzt geändert: 18.07.2026 21:17

EULANDA REST-API #

Die EulandaXrest-API macht die komplette EULANDA-Warenwirtschaft per HTTPS zugänglich - vom Artikelstamm über den Belegfluss bis zu Lager, Einkauf, Seriennummern, Kasse und Statistik. Sie ist die Grundlage für Shop-Anbindungen, mobile Apps, Field-Service-Lösungen und eigene Frontends.

Drei Grundideen ziehen sich durch die ganze API:

  1. Die Business-Logik bleibt in EULANDA. Die API rechnet nie selbst - Preise, Steuern, Nummernkreise, Lagerbuchungen und Belegwandlungen laufen über die geprüfte EULANDA-SQL-API. Was über REST passiert, verhält sich exakt wie in der EULANDA-Oberfläche.
  2. Gehärtet by default. Bearer-Keys mit feingranularen Scopes, parametrisiertes SQL mit Feld-Whitelists, problem+json-Fehler ohne Interna, Audit-Log mit Korrelations-Id. Bestände werden nie gesetzt, sondern immer gebucht - die Datenbank erzwingt das selbst.
  3. Englische, stabile Oberfläche. Pfade, Feldnamen, Scopes und Fehlercodes sind englisch und nach dem Einfrieren unveränderlich; Neues kommt nur additiv dazu. Jede Ressourcen-Seite enthält das Glossar zur deutschen DB-Spalte.

Basis-URL:

http(s)://<server>:<port>/api/v1

Das Ganze auf einen Blick #

Alle 220 Endpunkte auf einer Seite - mit Prozess-Diagrammen für Belegfluss, Einkauf, Kasse und Zahlungen: API-Landkarte.

BereichRessourcenSeite
StammdatenAdressen + Kontakte, Artikel (Preisfindung, Bestand, Bilder), Lieferanten + Lieferantenartikel, MitarbeiterAdressen, Artikel, Lieferanten, Mitarbeiter
Verkaufs-BelegflussAngebote -> Aufträge -> Lieferscheine -> Rechnungen: anlegen, wandeln, buchen (inkl. SN-Erfassung), Teillieferung, Sammelrechnung, drei Gutschriftsfälle, PDF-AusgabeBelege
EinkaufBestellungen, Bestellvorschlag aus dem Auftrag, WareneingangEinkauf
LagerLagergruppen/-orte, Bestand je Ort, Bewegungen, Bestand setzen als Differenzbuchung, Umlagerung, Inventur, Stücklisten (Jumbo), Seriennummern/Chargen mit RückverfolgungLager
GeräteverwaltungServiceartikel: Geräte beim Kunden mit kompletter Vorgangshistorie, Wartungsterminen und Serviceauftrag direkt am GerätServiceartikel
MerkmaleDer EULANDA-Merkmalbaum als categories: Baum lesen, Listen filtern (UND/ODER/NICHT), Datensätze zuordnen, Baum pflegenMerkmale
KasseKomplette POS-Bedienung: Bons, Kassieren mit TSE, Storno-Bon, Einlage/Entnahme, Z-Abschluss mit ZählungKasse
ZahlungenZahlungseingänge je Rechnung, Offene-Posten-Liste mit Skonto und Mahn-Sicht, Restbetrag ausbuchenZahlungen
StatistikUmsatz-/Ertrags-Cube (Rechnung + Kasse), Kennzahlen-Kacheln, WarengruppenstatistikStatistik
QuerschnittKonstanten-Kataloge, SQL-Registry, DMS-Dateien, BilderLookups, Registry, DMS, Bilder

Der typische Verkaufsfluss über die API:

Angebot ── convert-to-order ──> Auftrag ── book ──> Lieferschein(e)
                                  │                    │  book (+ SN/Chargen)
                                  │                    v
                                  │                 Rechnung / Sammelrechnung ──> PDF
                                  │                    │
                                  └── reopen <── actions/credit (Gutschrift)

Und der Einkauf: Auftrag -> Bestellvorschlag -> Bestellung (je Lieferant) -> buchen -> Wareneingang -> Lager.

Authentifizierung #

Jede App erhält einen API-Client mit eigenem Key und Scopes. Der Key wird bei der Anlage genau einmal angezeigt und als Header übergeben:

Authorization: Bearer exr_<clientId>_<secret>

Anlage auf dem Server (der Key ist danach nicht mehr rekonstruierbar):

New-XrestApiClient -Udl 'C:\Eulanda\Mandant.udl' -ClientId 'shop' `
    -Name 'Shop-Anbindung' -Scope 'addresses:read', 'articles:read'

Nur /api/v1/health, /api/v1/info und /api/v1/openapi.json sind anonym erreichbar. Nach fünf fehlgeschlagenen Anmeldeversuchen wird die IP 60 Sekunden gesperrt (429).

Schnellstart in fünf Schritten #

1. Läuft der Server? (anonym)

Invoke-RestMethod 'http://localhost:8100/api/v1/health'
# -> status: ok, version: ...
curl.exe "http://localhost:8100/api/v1/health"

2. Key hinterlegen - einmal pro Session:

$key = 'exr_shop_...'
$h = @{ Authorization = "Bearer $key" }

3. Erste Liste - Artikel mit Paging und schlanker Feldauswahl:

Invoke-RestMethod 'http://localhost:8100/api/v1/articles?limit=5&fields=id,articleNumber,name,price' -Headers $h
curl.exe -H "Authorization: Bearer %KEY%" "http://localhost:8100/api/v1/articles?limit=5&fields=id,articleNumber,name,price"

4. Ein Datensatz samt Bestand und Preis:

Invoke-RestMethod 'http://localhost:8100/api/v1/articles/by-number/1100' -Headers $h
Invoke-RestMethod 'http://localhost:8100/api/v1/articles/1/stock' -Headers $h
Invoke-RestMethod 'http://localhost:8100/api/v1/articles/1/price?customerId=17&quantity=5' -Headers $h

5. Erster Beleg - Auftrag anlegen, Position einstellen, PDF holen:

$order = Invoke-RestMethod 'http://localhost:8100/api/v1/sales-orders' -Method Post -Headers $h `
    -ContentType 'application/json' -Body '{"customerId":17}'
Invoke-RestMethod ("http://localhost:8100/api/v1/sales-orders/$($order.id)/items") -Method Post -Headers $h `
    -ContentType 'application/json' -Body '{"articleNumber":"1100","quantity":2}'
Invoke-RestMethod ("http://localhost:8100/api/v1/sales-orders/$($order.id)/pdf") -Headers $h -OutFile 'auftrag.pdf'

Von hier aus ist der Rest Muster-Wiederholung: jede Ressource hat dieselben Listen-Parameter, dieselben Fehlercodes und dieselbe Glossar-Tabelle auf ihrer Doku-Seite. Für Sync-Clients (Shop, App) ist das empfohlene Muster: initial Vollabzug mit Paging, danach zyklisch ?changedSince= plus GET /<ressource>/ids für den Lösch-Abgleich.

Konventionen #

  • JSON UTF-8, Feldnamen camelCase, Datum ISO 8601 (2026-07-18T10:06:30).
  • Listen: { "items": [...], "total": n, "limit": l, "offset": o }, Default-Limit 50, Maximum 500.
  • Sparse fieldsets: ?fields=id,match,city liefert nur diese Felder.
  • Sortierung: ?sort=name bzw. ?sort=-changedAt (absteigend).
  • Filter: ?city=Mainz (Gleichheit auf jedes definierte Feld), ?q= für die Suche über Match, Name und Ort; Merkmalfilter über ?categoryAnd/Or/Not (siehe Merkmale).
  • Delta-Sync: ?changedSince=2026-07-18T06:00:00 liefert nur seither geänderte Datensätze; GET /<ressource>/ids liefert die komplette Schlüsselliste für den Lösch-Abgleich.
  • Idempotenz: POST-Endpunkte akzeptieren einen Idempotency-Key-Header - die Wiederholung desselben Requests liefert die gespeicherte Antwort, statt doppelt anzulegen (wichtig für Belegerzeugung über instabile Verbindungen).
  • Sprache: Accept-Language: de (oder ?lang=de) schaltet die Fehlertexte auf Deutsch; Default ist Englisch. Der code bleibt immer sprachneutral.

Fehlerformat #

Fehler kommen als application/problem+json (RFC 7807):

{
  "type": "https://sdk.eulanda.eu/rest/errors/#validation_field_unknown",
  "title": "Validation failed",
  "status": 422,
  "code": "VALIDATION_FIELD_UNKNOWN",
  "detail": "Field 'strasse' does not exist on resource 'addresses'.",
  "correlationId": "125641a6cf96438492ba8cc7315ca3c6",
  "errors": [ { "field": "strasse", "code": "UNKNOWN_FIELD" } ]
}

Der code ist stabiler API-Vertrag - Clients verzweigen auf ihn, nie auf die Texte. Die correlationId steht auch im Antwort-Header X-Correlation-Id und in allen Server-Logs - bei Supportfragen immer mit angeben.

Scopes #

ScopeBedeutung
addresses:readAdressen, Kontakte und Adressbilder lesen
addresses:writeAdressen, Kontakte und Adressbilder schreiben
articles:readArtikel lesen inkl. Preisfindung, Bestand und Bilder
articles:writeArtikel anlegen, ändern, löschen, Bilder schreiben
employees:readMitarbeiter lesen (read-only)
suppliers:readLieferanten und Lieferantenartikel lesen (inkl. Einkaufspreise!)
suppliers:writeLieferanten freischalten, Konditionen und Lieferantenartikel pflegen
registry:readSQL-Registry lesen (nur innerhalb der Pfad-Allowlist des Clients)
registry:writeSQL-Registry schreiben (nur Schreib-Präfixe der Allowlist)
documents:readBelege lesen, Formulare listen, PDF-Ausgabe
documents:writeBelege anlegen, Positionen einstellen, Wandlungen (Angebot->Auftrag, Auftrag->Lieferschein, Lieferschein->Rechnung)
documents:bookAufträge und Lieferscheine buchen, SN-Erfassung, Auftrag wieder öffnen
documents:cancelRechnungen stornieren und Direktgutschriften
dms:readDMS-Dateien listen und herunterladen
dms:writeDMS-Dateien hochladen
dms:deleteDMS-Dateien löschen
categories:manageMerkmalbaum pflegen: Knoten anlegen, umbenennen, verschieben, löschen (siehe Merkmale)
service-articles:readServiceartikel (Geräte) lesen inkl. Historie und Bilder
service-articles:writeServiceartikel anlegen, ändern, löschen
stock:readLager lesen: Gruppen, Orte, Bestände, Bewegungen, SN/Chargen
stock:writeLager buchen: set-quantity (Differenzbuchung), Warenbewegung, Umlagerung, Inventur
purchase-orders:readBestellungen und Bestellvorschlag lesen (inkl. Einkaufspreise!)
purchase-orders:writeBestellungen anlegen, buchen, Wareneingang erzeugen
lookups:readKonstanten-Kataloge (Mengeneinheiten, Länder, Zahlungsarten, …)
cash:readKassenbelege, Kassenabschlüsse und TSE-Daten lesen
cash:writeKasse bedienen: Bons, Kassieren, Einlage/Entnahme, Z-Abschluss
payments:readZahlungen je Rechnung und Offene-Posten-Liste lesen
payments:writeZahlungen erfassen/löschen, Restbeträge ausbuchen
statistics:readUmsatz-/Ertragsstatistiken und Kennzahlen
adminAlles (nur für Verwaltungswerkzeuge)

Empfehlung: je Client nur die tatsächlich benötigten Scopes vergeben - ein Shop braucht z.B. articles:read, addresses:write, documents:write und lookups:read, aber weder Einkauf noch Registry.

Beispiele in dieser Doku #

Jedes Beispiel steht immer in PowerShell und in curl. Achtung in Windows PowerShell 5.1: curl ist dort ein Alias für Invoke-WebRequest - deshalb schreiben die curl-Beispiele konsequent curl.exe (das echte curl liegt bei Windows 10/11 bei). In cmd und PowerShell 7 funktioniert auch das nackte curl.

Health und Spezifikation #

GET /api/v1/health          -> { "status": "ok", "version": "0.1.0" }
GET /api/v1/info            -> { "product": "EulandaXrest", "apiVersion": "v1", ... }
GET /api/v1/openapi.json    -> OpenAPI-3.0-Spezifikation (generiert aus der Routen-Tabelle)

Die OpenAPI-Spezifikation entsteht direkt aus Routen-Tabelle und Ressourcen-Definitionen des Servers - sie kann also nicht vom Code abweichen. Der benötigte Scope steht je Operation in x-required-scope; die Spezifikation eignet sich direkt für Client-Generatoren und API-Tools wie Postman oder Bruno.

String-Felder tragen ihre maximale Länge als maxLength - live aus dem SQL-Schema gelesen, nie von Hand gepflegt. Felder mit Zeichenregeln (etwa match und articleNumber: nur Großbuchstaben, Ziffern, dateinamen-sichere Zeichen) haben zusätzlich pattern und description. Ein Client sollte diese Constraints schon bei der Eingabe durchsetzen: maxLength als maxlength-Attribut des Eingabefelds, pattern als Live-Validierung, description als Hilfetext - dann entstehen ungültige Werte gar nicht erst, und bei einer Schema-Änderung zieht der Client automatisch nach.

Beide anonym und bewusst ohne Systemdetails; für Watchdogs gibt es serverseitig Test-XrestHealth.