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:
- 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.
- 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.
- 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.
| Bereich | Ressourcen | Seite |
|---|---|---|
| Stammdaten | Adressen + Kontakte, Artikel (Preisfindung, Bestand, Bilder), Lieferanten + Lieferantenartikel, Mitarbeiter | Adressen, Artikel, Lieferanten, Mitarbeiter |
| Verkaufs-Belegfluss | Angebote -> Aufträge -> Lieferscheine -> Rechnungen: anlegen, wandeln, buchen (inkl. SN-Erfassung), Teillieferung, Sammelrechnung, drei Gutschriftsfälle, PDF-Ausgabe | Belege |
| Einkauf | Bestellungen, Bestellvorschlag aus dem Auftrag, Wareneingang | Einkauf |
| Lager | Lagergruppen/-orte, Bestand je Ort, Bewegungen, Bestand setzen als Differenzbuchung, Umlagerung, Inventur, Stücklisten (Jumbo), Seriennummern/Chargen mit Rückverfolgung | Lager |
| Geräteverwaltung | Serviceartikel: Geräte beim Kunden mit kompletter Vorgangshistorie, Wartungsterminen und Serviceauftrag direkt am Gerät | Serviceartikel |
| Merkmale | Der EULANDA-Merkmalbaum als categories: Baum lesen, Listen filtern (UND/ODER/NICHT), Datensätze zuordnen, Baum pflegen | Merkmale |
| Kasse | Komplette POS-Bedienung: Bons, Kassieren mit TSE, Storno-Bon, Einlage/Entnahme, Z-Abschluss mit Zählung | Kasse |
| Zahlungen | Zahlungseingänge je Rechnung, Offene-Posten-Liste mit Skonto und Mahn-Sicht, Restbetrag ausbuchen | Zahlungen |
| Statistik | Umsatz-/Ertrags-Cube (Rechnung + Kasse), Kennzahlen-Kacheln, Warengruppenstatistik | Statistik |
| Querschnitt | Konstanten-Kataloge, SQL-Registry, DMS-Dateien, Bilder | Lookups, 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,cityliefert nur diese Felder. - Sortierung:
?sort=namebzw.?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:00liefert nur seither geänderte Datensätze;GET /<ressource>/idsliefert 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. Dercodebleibt 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 #
| Scope | Bedeutung |
|---|---|
addresses:read | Adressen, Kontakte und Adressbilder lesen |
addresses:write | Adressen, Kontakte und Adressbilder schreiben |
articles:read | Artikel lesen inkl. Preisfindung, Bestand und Bilder |
articles:write | Artikel anlegen, ändern, löschen, Bilder schreiben |
employees:read | Mitarbeiter lesen (read-only) |
suppliers:read | Lieferanten und Lieferantenartikel lesen (inkl. Einkaufspreise!) |
suppliers:write | Lieferanten freischalten, Konditionen und Lieferantenartikel pflegen |
registry:read | SQL-Registry lesen (nur innerhalb der Pfad-Allowlist des Clients) |
registry:write | SQL-Registry schreiben (nur Schreib-Präfixe der Allowlist) |
documents:read | Belege lesen, Formulare listen, PDF-Ausgabe |
documents:write | Belege anlegen, Positionen einstellen, Wandlungen (Angebot->Auftrag, Auftrag->Lieferschein, Lieferschein->Rechnung) |
documents:book | Aufträge und Lieferscheine buchen, SN-Erfassung, Auftrag wieder öffnen |
documents:cancel | Rechnungen stornieren und Direktgutschriften |
dms:read | DMS-Dateien listen und herunterladen |
dms:write | DMS-Dateien hochladen |
dms:delete | DMS-Dateien löschen |
categories:manage | Merkmalbaum pflegen: Knoten anlegen, umbenennen, verschieben, löschen (siehe Merkmale) |
service-articles:read | Serviceartikel (Geräte) lesen inkl. Historie und Bilder |
service-articles:write | Serviceartikel anlegen, ändern, löschen |
stock:read | Lager lesen: Gruppen, Orte, Bestände, Bewegungen, SN/Chargen |
stock:write | Lager buchen: set-quantity (Differenzbuchung), Warenbewegung, Umlagerung, Inventur |
purchase-orders:read | Bestellungen und Bestellvorschlag lesen (inkl. Einkaufspreise!) |
purchase-orders:write | Bestellungen anlegen, buchen, Wareneingang erzeugen |
lookups:read | Konstanten-Kataloge (Mengeneinheiten, Länder, Zahlungsarten, …) |
cash:read | Kassenbelege, Kassenabschlüsse und TSE-Daten lesen |
cash:write | Kasse bedienen: Bons, Kassieren, Einlage/Entnahme, Z-Abschluss |
payments:read | Zahlungen je Rechnung und Offene-Posten-Liste lesen |
payments:write | Zahlungen erfassen/löschen, Restbeträge ausbuchen |
statistics:read | Umsatz-/Ertragsstatistiken und Kennzahlen |
admin | Alles (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.