Ihre Artikeldaten liegen in der Warenwirtschaft — Namen, Beschreibungen, Preise, Bilder und Bestände. Über die REST-API kann ein Onlineshop diese Daten automatisch übernehmen, statt dass jemand sie ein zweites Mal eintippt. Verkauft der Shop etwas, kann er den Bestand sogar zurückmelden.
Was ist eine API — in einem Satz?
Eine API ist eine Tür, durch die ein anderes Programm Ihre Daten abholen darf. Sie vergeben dafür einen Schlüssel und entscheiden, ob dieser Schlüssel nur schauen (lesen) oder auch etwas verändern (schreiben) darf.
Dieser Artikel hat zwei Teile: Der erste Teil ist für Sie als Betreiber — Schlüssel anlegen und verwalten. Der zweite Teil ist die technische Referenz, die Sie Ihrem Shop-Entwickler weitergeben können.
Teil 1: Für den Betreiber
Wo finde ich das?
Gehen Sie auf Einstellungen → Anbindungen → „REST-API für WaWi". Direkt neben dem Menüpunkt sitzt ein kleines ⓘ-Symbol — es öffnet die vollständige technische Dokumentation im Fenster, inklusive der Internet-Adresse Ihrer Installation und fertiger Beispiele zum Kopieren. Genau diese Ansicht ist es, die Ihr Shop-Entwickler braucht.
Schlüssel anlegen — Schritt für Schritt
„Neuer Schlüssel" anklicken.
Eine Bezeichnung eingeben, zum Beispiel „Onlineshop". Der Name erscheint später bei jeder Bestandsbuchung in der Artikel-Historie — so sehen Sie, welches System gebucht hat.
Rechte wählen. „Lesen" braucht jeder Shop. „Schreiben" nur, wenn der Shop Bestände zurückmelden oder Artikel anlegen soll. Wenn Sie unsicher sind: erst nur Lesen — nachträglich umstellen ist jederzeit möglich.
Schlüssel sofort kopieren. Er erscheint genau einmal. Danach ist er nicht mehr abrufbar — bei uns liegt nur eine Prüfsumme, so wie bei einem Passwort.
Den Schlüssel an den Shop-Entwickler geben — am besten nicht per normaler E-Mail, sondern über einen Passwort-Manager oder telefonisch.
Schlüssel verloren?
Kein Problem und kein Datenverlust: Legen Sie einen neuen an und widerrufen Sie den alten. Ein Anzeigen des alten Schlüssels ist absichtlich nicht möglich — genau das schützt Sie, falls jemand Fremdes Zugriff auf Ihr Konto bekäme.
Was die Liste Ihnen zeigt
Zu jedem Schlüssel sehen Sie: die Bezeichnung, den Anfang des Schlüssels (zum Auseinanderhalten), die vergebenen Rechte, wer ihn angelegt hat, wann er zuletzt benutzt wurde und wie viele Aufrufe es gab. Wird ein Schlüssel nie benutzt, ist er entweder falsch hinterlegt — oder er wird nicht mehr gebraucht und kann weg.
Widerrufen oder löschen?
- Widerrufen (Ein/Aus-Symbol): Der Schlüssel ist sofort ungültig, bleibt aber in der Liste. Praktisch, wenn Sie später nachvollziehen wollen, welcher Zugang wann aktiv war.
- Löschen: entfernt den Eintrag vollständig. Der Schlüssel funktioniert danach ebenfalls nicht mehr.
Beides greift sofort beim nächsten Aufruf des Shops — es gibt keine Wartezeit.
Was der Shop überhaupt sieht
Nur Artikel, bei denen im Produktfenster im Reiter „OnlineShop" das Häkchen „Im Onlineshop zeigen" gesetzt ist. Alles andere — Kabinettware, Verbrauchsmaterial, Altbestände — bleibt unsichtbar. Sie steuern das Shop-Sortiment also weiterhin in der Warenwirtschaft, nicht im Shop.
Ihre Daten bleiben Ihre Daten
Der Betrieb steckt im Schlüssel selbst. Ein Schlüssel kann deshalb technisch keine Daten eines anderen Betriebs sehen — auch dann nicht, wenn jemand fremde Artikelnummern durchprobiert. Und: Einkaufspreise werden zwar ausgeliefert, gehören aber nie in den Shop; Ihr Entwickler sollte das Feld gar nicht verwenden.
Wenn etwas nicht klappt
| Der Entwickler meldet … | Das prüfen Sie |
|---|---|
| „Schlüssel unbekannt oder deaktiviert" | Steht der Schlüssel in der Liste auf aktiv? Wurde er versehentlich gelöscht oder beim Kopieren abgeschnitten? |
| „kein Leserecht" / „kein Schreibrecht" | Recht in der Liste umstellen — es wirkt sofort. |
| „Ich sehe nur wenige Artikel" | Häkchen „Im Onlineshop zeigen" bei den fehlenden Artikeln setzen (geht auch per Excel-Massenpflege). |
| „Ich sehe keine Bilder" | Artikel ohne Bild werden mit leerer Bildliste geliefert. Bilder pflegen oder per ZIP-Import hochladen. |
| „Der Bestand stimmt nicht" | In der Artikel-Historie steht jede Buchung mit dem Schlüssel-Namen. Dort sehen Sie, was der Shop gebucht hat. |
Teil 2: Technische Referenz (für den Shop-Entwickler)
Dieselben Angaben stehen in der App unter Einstellungen → Anbindungen hinter dem ⓘ-Symbol — dort mit der echten Basis-Adresse der Installation und Kopier-Schaltflächen.
Basis-Adresse und Authentifizierung
https://IHRE-DOMAIN/api/wawi_api.php # Schlüssel als Kopfzeile, eine der beiden Varianten: Authorization: Bearer pc_wawi_… X-API-Key: pc_wawi_…
Anfragen und Antworten sind JSON in UTF-8. GET braucht Leserecht, alles andere
Schreibrecht. Fehler kommen als JSON mit error und – wo hilfreich – hint.
Zwei gleichwertige Schreibweisen
# Pfad-Form GET /api/wawi_api.php/products/123 # Query-Form — funktioniert garantiert auf jedem Server GET /api/wawi_api.php?resource=products&id=123
Alle Endpunkte
| Methode & Pfad | Recht | Zweck |
|---|---|---|
GET /meta | lesen | Selbsttest: Rechte und Feldlisten des Schlüssels. Bester erster Aufruf. |
GET /products | lesen | Artikelliste, seitenweise. Standard: nur freigegebene Artikel. |
GET /products/{id} | lesen | Ein Artikel, alternativ über ?ean= oder ?article_number=. |
POST /products | schreiben | Artikel anlegen. Pflicht: article_number, name. |
PATCH /products/{id} | schreiben | Einzelne Felder ändern; nicht gesendete Felder bleiben unberührt. |
GET /stock | lesen | Bestände je Artikel und Standort. |
PATCH /stock | schreiben | Bestand buchen — relativ (change) oder absolut (quantity). |
GET /availability | lesen | Stand der Lieferanten-Verfügbarkeit: Zählwerte, veraltete Angaben, letzte Prüfung. |
PATCH /availability | schreiben | Verfügbarkeit schreiben — state für den ganzen Bereich oder Ausnahmen-Listen. |
Löschen ist bewusst nicht vorgesehen. Statt zu löschen setzt
der Shop online_shop: 0 — der Artikel verschwindet aus dem Sortiment, bleibt
aber für Kasse, Bestellungen und Buchhaltung erhalten.
Parameter der Artikelliste
| Parameter | Standard | Bedeutung |
|---|---|---|
online_shop | 1 | 1 = nur freigegebene, 0 = nur nicht freigegebene, all = alle. |
search | — | Sucht in Name, Shop-Name, Artikelnummer, EAN. |
updated_since | — | Nur seit diesem Zeitpunkt geänderte Artikel — die Basis für den laufenden Abgleich. |
page / limit | 1 / 50 | Blättern; limit maximal 200. |
Beispiel-Antwort
{
"pagination": { "page": 1, "limit": 50, "total": 412, "pages": 9, "showing": 50 },
"products": [
{
"id": "1204",
"article_number": "80612345",
"ean": "3474636612345",
"name": "Serie Expert Absolut Repair Shampoo 300ml",
"b2c_name": "Absolut Repair Shampoo",
"selling_price": 21.9,
"tax_class_id": "1",
"uvp_price": 29.9,
"online_shop": 1,
"short_description": "Aufbauendes Shampoo für strapaziertes Haar.",
"long_description": "…",
"application": "Ins feuchte Haar einmassieren, ausspülen.",
"ingredients": "Aqua, Sodium Laureth Sulfate, …",
"warnings": "Kontakt mit den Augen vermeiden.",
"image_url": "https://…/uploads/shop/abc.jpg",
"images": ["https://…/uploads/shop/abc.jpg", "https://…/uploads/shop/abc-2.jpg"],
"min_stock": 2,
"updated_at": "2026-07-24 18:03:11"
}
]
}
Felder zum Lesen
id, article_number, ean, name,
b2c_name, category, unit, purchase_price,
selling_price, uvp_price, tax_class_id,
online_shop, short_description, long_description,
application, ingredients, warnings,
image_url, images, kab_vk, sparte,
min_stock, nettogew_g, bruttogew_g,
laenge_mm, breite_mm, hoehe_mm,
stufe1_geschaeftsbereich, stufe2_produktserie,
stufe3_submarke, supplier_id, created_at,
updated_at.
Wichtig für die Shop-Anzeige: b2c_name vor
name verwenden (wenn gefüllt); images statt einzelner Bildfelder
nutzen (Hauptbild steht vorn); Maße und Gewichte für die Versandkosten;
purchase_price ist der Einkaufspreis und darf nie sichtbar werden.
Netto oder brutto? Die wichtigste Regel
purchase_price und selling_price sind Netto-Preise —
genau wie in der Warenwirtschaft. Der Shop schlägt die Mehrwertsteuer selbst auf:
Shop-Preis = selling_price × (1 + MwSt/100). Der passende Satz steckt in
tax_class_id.
uvp_price ist dagegen brutto — so
veröffentlichen die Hersteller ihre Preisempfehlung. Ein Streichpreis wird deshalb nur
gezeigt, wenn uvp_price gefüllt und größer als der gerechnete
Brutto-Preis ist. Wer ihn versehentlich mit selling_price vergleicht,
zeigt eine um die Mehrwertsteuer zu hohe Ersparnis.
selling_price = 21,90 (netto) MwSt = 19 % Shop-Preis = 21,90 × 1,19 = 26,06 € (brutto) uvp_price = 29,90 (brutto, unverändert übernehmen) Ersparnis = (29,90 − 26,06) / 29,90 = 13 %
Felder zum Schreiben
name, b2c_name, category, unit,
purchase_price, selling_price, uvp_price,
online_shop, min_stock, short_description,
long_description, application, ingredients,
warnings, image_url, image_urls.
Ein leerer Text ("") leert das Feld. Nicht
mitgesendete Felder bleiben unverändert. image_urls erwartet eine
JSON-Liste als Text, z. B. ["https://…/a.jpg","https://…/b.jpg"].
Absichtlich gesperrt: Artikelnummer, EAN, Lieferant
Diese drei sind die Identität des Artikels in Wareneingang, Kasse und Buchhaltung. Würde der Shop sie ändern, passen Bestellungen und Kassenbelege nicht mehr zusammen. Sie werden ausschließlich in der Warenwirtschaft gepflegt.
Bestand buchen
# relativ — empfohlen, z. B. ein Stück nach einem Shop-Verkauf
PATCH ?resource=stock { "product_id": 1204, "location_id": 2, "change": -1 }
# absolut — nur für Inventur / vollständigen Abgleich
PATCH ?resource=stock { "product_id": 1204, "location_id": 2, "quantity": 7 }
Die Antwort enthält quantity_before und quantity. Fehlt die
Bestandszeile, wird sie angelegt. Ohne location_id wird auf den Bestand ohne
Standort-Zuordnung gebucht. Jede Buchung landet mit dem Schlüssel-Namen in der
Bestands-Historie.
Warum change und nicht quantity?
Verkaufen Shop und Kasse im selben Moment, überschreibt ein absolutes
quantity die zweite Buchung — die Ware fehlt dann im Regal, nicht im
System. change: -1 rechnet dagegen immer auf den aktuellen Stand.
Lieferanten-Verfügbarkeit
Ob der Lieferant gerade liefern kann, ist etwas anderes als der eigene
Lagerbestand. Der Shop soll dafür das fertig gerechnete Feld availability
auswerten und nicht selbst rechnen:
| Wert | Was der Shop tun soll |
|---|---|
verfuegbar | Normal verkaufen. |
nicht_lieferbar | „Aktuell nicht verfügbar" anzeigen, Kauf-Button ausblenden. |
unbekannt | Normal verkaufen. availability_reason nennt den Grund. |
Die Rohwerte stehen zusätzlich in supplier_available (1 / 0 / leer),
supplier_checked_at (wann geprüft) und supplier_note (Herkunft
der Angabe).
Drei Zustände — „leer" heißt nicht „gesperrt"
Eine Lieferantenliste deckt nie den ganzen Artikelstamm ab. Würde ein ungeprüfter Artikel als nicht lieferbar gelten, hätte der Shop über Nacht fast nichts mehr zu verkaufen. Gesperrt wird nur, was der Lieferant ausdrücklich als nicht lieferbar gemeldet hat.
Zusätzlich verfallen die Angaben: Ist die Prüfung älter als sieben Tage,
liefert availability wieder unbekannt. Ein Prüflauf, der eine
Woche still steht, sperrt also nicht weiter mit alten Zahlen. Die Frist steht als
availability_max_age_days in GET /meta.
Zum Schreiben gibt es zwei Formen, eine davon muss angegeben sein.
Für einzelne Artikel ist state der einfache Weg:
# A) state — ein Zustand für den ganzen Geltungsbereich
PATCH ?resource=availability
{
"scope": { "eans": ["3474636612345"] },
"state": "nicht_lieferbar",
"note": "Telefonat Lieferant 25.07.2026"
}
# B) Ausnahmen-Listen — für einen ganzen Abgleich.
# Alles im Bereich, was in keiner Liste steht, wird lieferbar.
PATCH ?resource=availability
{
"scope": { "brands": ["Kerastase", "Redken"] },
"unavailable": ["3474636612345"],
"unknown": ["3474639999999"],
"note": "Lieferantenliste 24.07.2026"
}
Erlaubte state-Werte sind verfuegbar (→ 1),
nicht_lieferbar (→ 0) und unbekannt (→ leer) — dieselben Werte,
die availability ausgibt.
Beides zusammen — oder keins von beidem — ist ein Fehler
state gleichzeitig mit unavailable/unknown ist
widersprüchlich und wird mit 422 abgewiesen. Fehlen beide,
ebenfalls: Ein Aufruf mit nur scope würde stillschweigend „alles lieferbar"
bedeuten — und genau das ergibt ein unvollständiger Body.
Aus dem gleichen Grund werden unbekannte Felder abgewiesen: Wer
status statt state schreibt, bekommt einen Fehler statt eines
als Erfolg gemeldeten Treffers ins Gegenteil. Die Antwort spiegelt mit mode
und state zurück, wie der Aufruf verstanden wurde.
scope ist Pflicht — erlaubt sind brands,
supplier_id, eans (max. 5000) oder all zusammen mit
confirm_all. Der Grund: „alles, was nicht in der Liste steht, ist lieferbar"
gilt nur innerhalb des Bereichs, den die Liste wirklich abdeckt. Ohne Geltungsbereich
würde eine Liste mit einer einzigen Marke den ganzen Stamm auf „lieferbar" setzen.
Wären mehr als 50 % des Geltungsbereichs nicht lieferbar, bricht der Aufruf mit
422 ab und schreibt nichts — das typische Bild eines abgebrochenen
Prüflaufs. Mit force: true wird trotzdem gespeichert. Die Schranke greift erst
ab fünf Artikeln im Bereich: bei einem einzelnen Artikel ist „1 von 1" immer 100 %, und so
ein Aufruf kann kein Sortiment leeren. Mehrfach belegte EANs
werden nicht geraten, sondern in ambiguous_ean gemeldet; unbekannte
Kennungen stehen in not_found.
GET ?resource=availability liefert den Stand: Zählwerte je Zustand,
stale_count (veraltete Angaben) und last_checked_at. Damit
lässt sich überwachen, ob der Prüflauf noch arbeitet.
Fehlercodes
| Code | Ursache und Abhilfe |
|---|---|
| 401 | Kopfzeile fehlt, oder Schlüssel falsch / widerrufen / deaktiviert. |
| 403 | Recht fehlt (lesen bzw. schreiben) — wird in der App je Schlüssel vergeben. |
| 404 | Artikel gehört nicht zu diesem Betrieb / existiert nicht, oder unbekannte Ressource. |
| 405 | Methode nicht vorgesehen (z. B. DELETE). |
| 409 | Artikelnummer beim Anlegen schon vergeben — stattdessen PATCH nutzen. |
| 422 | Pflichtfeld fehlt oder kein schreibbares Feld dabei; hint nennt die erlaubten Felder. |
| 500 / 503 | Buchung zurückgerollt bzw. Datenbank nicht erreichbar. Mit wachsender Wartezeit erneut versuchen (2 s, 4 s, 8 s) — nicht in einer engen Schleife. |
Grenzen und empfohlener Takt
- Artikel je Seite: maximal 200 (Standard 50).
- Bestandszeilen je Aufruf: maximal 500 (Standard 200).
- Aktive Schlüssel je Betrieb: 20.
- Katalog stündlich per
updated_since, Bestand bei Bedarf. Kein Dauer-Abruf im Sekundentakt — die Warenwirtschaft bedient gleichzeitig Kasse und Salon-Displays.
Ablauf einer Anbindung
GET /meta aufrufen — bestätigt Schlüssel und Rechte, ohne etwas zu verändern.
Vollständigen Katalog holen (page bis pages hochzählen) und Bilder in den Shop übernehmen.
Zeitpunkt des Lauf-Endes speichern und künftig nur noch updated_since abfragen.
Artikel mit online_shop: 0 im Shop ausblenden — nicht löschen, damit Bestellhistorie und Links erhalten bleiben.
Optional: nach jedem Shop-Verkauf PATCH /stock mit change: -1.
Die Schnittstelle ist über HTTPS erreichbar und arbeitet ausschließlich mit dem Betrieb, zu dem der Schlüssel gehört. Lesende Aufrufe sind auch aus dem Browser erlaubt; schreibende Aufrufe sollten immer vom Shop-Server ausgehen, damit der Schlüssel nicht öffentlich wird.