DOHandbuch · Die Offensive
Neuerungen API Zur App
Entwickler
Warenwirtschaft: verfügbar

API-Dokumentation

PersonalCockpit Cloud stellt die Artikeldaten der Warenwirtschaft über eine REST-Schnittstelle bereit — für die Anbindung eines Onlineshops oder eines anderen Programms.

Der schnellste Weg

Der Betreiber legt in seiner Installation unter Einstellungen → Anbindungen → „REST-API für WaWi" einen Schlüssel an. Direkt neben dem Menüpunkt öffnet ein -Knopf die vollständige Referenz mit der echten Basis-Adresse dieser Installation und kopierfertigen Beispielen. Diese Seite hier ist die gleiche Referenz zum Nachlesen ohne Zugang.

Warenwirtschaft (WaWi) — REST

Basis-Adresse und Authentifizierung

Die Schnittstelle liegt unter der Domain der jeweiligen Installation. Jeder Aufruf braucht den Schlüssel als Kopfzeile — beide Varianten sind gleichwertig:

https://IHRE-DOMAIN/api/wawi_api.php

Authorization: Bearer pc_wawi_…
X-API-Key: pc_wawi_…

Anfragen und Antworten sind JSON in UTF-8. GET braucht Leserecht, alles andere Schreibrecht; beide werden je Schlüssel getrennt vergeben. Fehler kommen als JSON mit error und — wo hilfreich — hint.

Der Mandant steckt im Schlüssel

Es gibt keinen Parameter für den Betrieb. Der Schlüssel bestimmt, welche Daten sichtbar sind; fremde id- oder EAN-Werte ergeben 404, nicht die Daten eines anderen Betriebs. Der Schlüssel selbst wird nur als Prüfsumme gespeichert und ist nach der Anlage nicht mehr abrufbar.

Endpunkte

Methode & PfadRechtZweck
GET /metalesenSelbsttest: Rechte und Feldlisten des Schlüssels. Bester erster Aufruf.
GET /productslesenArtikelliste, seitenweise. Standard: nur die für den Shop freigegebenen Artikel.
GET /products/{id}lesenEin Artikel, alternativ über ?ean= oder ?article_number=.
POST /productsschreibenArtikel anlegen. Pflicht: article_number, name.
PATCH /products/{id}schreibenEinzelne Felder ändern; nicht gesendete Felder bleiben unberührt.
GET /stocklesenBestände je Artikel und Standort.
PATCH /stockschreibenBestand buchen — relativ (change) oder absolut (quantity).
GET /availabilitylesenStand der Lieferanten-Verfügbarkeit: Zählwerte, veraltete Angaben, letzte Prüfung.
PATCH /availabilityschreibenVerfügbarkeit schreiben — state für den ganzen Bereich oder Ausnahmen-Listen.

Jeder Endpunkt ist auf zwei Wegen erreichbar. Die Query-Form funktioniert auf jedem Server und ist der verlässliche Weg:

GET /api/wawi_api.php/products/123          # Pfad-Form
GET /api/wawi_api.php?resource=products&id=123   # Query-Form

Löschen ist 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

ParameterStandardBedeutung
online_shop11 = nur freigegebene, 0 = nur nicht freigegebene, all = alle.
searchSucht in Name, Shop-Name, Artikelnummer und EAN.
updated_sinceNur seit diesem Zeitpunkt geänderte Artikel — die Basis für den laufenden Abgleich.
page / limit1 / 50Blättern; limit maximal 200.

Netto oder brutto — die wichtigste Regel

Der Shop muss die Mehrwertsteuer selbst aufschlagen

purchase_price und selling_price sind Netto-Preise. uvp_price ist dagegen brutto — so veröffentlichen die Hersteller ihre Preisempfehlung. Ein Streichpreis wird deshalb nur angezeigt, wenn uvp_price über dem gerechneten Brutto-Preis liegt.

selling_price = 21.90   (netto)
tax_class_id  → 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

Lesbar (38): 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, supplier_available, supplier_checked_at, supplier_note, availability, availability_reason.

Schreibbar (16): 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.

Absichtlich gesperrt: article_number, ean und der Lieferant. Das sind die Identitätsmerkmale des Artikels in Wareneingang, Kasse und Buchhaltung — würde der Shop sie ändern, passen Bestellungen und Kassenbelege nicht mehr zusammen.

Nützlich für die Darstellung: 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 im Shop nie erscheinen.

Lieferanten-Verfügbarkeit

Drei Felder am Artikel sagen, ob der Lieferant gerade liefern kann — unabhängig vom eigenen Lagerbestand:

FeldWerteBedeutung
supplier_available1 / 0 / null1 = lieferbar, 0 = nicht lieferbar, null = keine Aussage.
supplier_checked_atZeitstempel oder nullWann geprüft wurde. Ohne Datum ist die Angabe wertlos.
supplier_noteText, max. 120 ZeichenWoher die Angabe kommt, z. B. „Lieferantenliste 24.07.2026".
availabilityverfuegbar / nicht_lieferbar / unbekanntVom Server gerechnetes Ergebnis — genau das soll der Shop auswerten.
availability_reasonText oder nullWarum „unbekannt": „nie geprüft", „kein Prüfdatum", „Angabe älter als 7 Tage".

Drei Zustände, nicht zwei — und eine Verfallszeit

null heißt nicht „nicht lieferbar". Eine Lieferantenliste deckt nie den ganzen Artikelstamm ab; würde man Ungeprüftes als gesperrt behandeln, hätte der Shop über Nacht fast nichts mehr zu verkaufen. Der Shop sperrt nur, was ausdrücklich als nicht lieferbar gemeldet wurde.

Zusätzlich altern die Angaben: Ist supplier_checked_at älter als 7 Tage (oder fehlt), liefert availability wieder unbekannt. Steht der Prüflauf eine Woche still, sperrt er also nicht weiter mit den Zahlen von letzter Woche. Der Wert steht als availability_max_age_days in GET /meta.

PATCH /availability schreibt die Verfügbarkeit. Es gibt zwei Formen, eine davon muss angegeben sein:

FormBodyWirkung
state — ein Zustand für den ganzen Bereich{"scope":{"eans":["…"]},"state":"nicht_lieferbar"}Der einfache Weg, auch für einzelne Artikel. Erlaubt sind verfuegbar (→ 1), nicht_lieferbar (→ 0) und unbekannt (→ null) — dieselben Werte, die availability ausgibt.
Ausnahmen-Listen — für einen ganzen Abgleich{"scope":{"brands":["…"]},"unavailable":[…],"unknown":[…]}Gesendet werden nur die Ausnahmen. Alles im Bereich, was in keiner Liste steht, wird lieferbar.

Beides zusammen — oder keins von beidem — ist ein Fehler

state und unavailable/unknown gleichzeitig sind widersprüchlich und werden mit 422 abgewiesen. Fehlen beide, ebenfalls: Ein Aufruf mit nur scope würde sonst 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 erfolgreich gemeldeten Treffers ins Gegenteil. Die Antwort spiegelt zusätzlich mit mode und state zurück, wie der Aufruf verstanden wurde.

Weiteres FeldPflichtBedeutung
scopejaGeltungsbereich: {"brands":["Kerastase"]}, {"supplier_id":3}, {"eans":[…]} (max. 5000) oder {"all":true} — letzteres nur zusammen mit confirm_all: true.
noteneinHerkunft der Angabe, wird an allen berührten Artikeln hinterlegt.
match_byneinauto (Standard), ean oder article_number.
forceneinHebt die Sicherheitsschranke auf (siehe unten).

Warum ein Geltungsbereich Pflicht ist: „alles, was nicht in der Liste steht, ist lieferbar" gilt nur innerhalb des Bereichs, den die Liste tatsächlich abdeckt. Ohne scope würde ein Abgleich, der nur eine Marke enthält, den ganzen Stamm auf „lieferbar" setzen.

Sicherheitsschranke: Wären mehr als 50 % des Geltungsbereichs nicht lieferbar, bricht der Aufruf mit 422 ab und schreibt nichts — das ist das typische Bild eines abgebrochenen Crawls (Login-Seite, Zeitüberschreitung). Mit force: true wird trotzdem gespeichert. Zusätzlich meldet die Antwort je Marke einen Hinweis, wenn ab fünf Artikeln mindestens 80 % als nicht lieferbar gelten.

Die Schranke greift erst ab fünf Artikeln im Geltungsbereich. Bei kleineren Bereichen ist ein Prozentwert bedeutungslos — „dieser eine Artikel ist nicht lieferbar" sind 1 von 1 und damit 100 % — und ein so kleiner Aufruf kann auch kein Sortiment leeren.

Mehrfach belegte EANs werden nicht geraten, sondern in ambiguous_ean gemeldet; unbekannte Kennungen stehen in not_found. Steht eine Kennung in beiden Listen, gewinnt „nicht lieferbar".

Der Abgleich schreibt keine Einträge in die Artikel-Historie — ein nächtlicher Lauf über tausend Artikel würde sie unbrauchbar machen. Nachvollziehbar ist er über supplier_checked_at und supplier_note.

Beispiele

# 1. Selbsttest — bestätigt Schlüssel und Rechte, ändert nichts
curl -H "Authorization: Bearer pc_wawi_…" \
  "https://IHRE-DOMAIN/api/wawi_api.php?resource=meta"

# 2. Erste Seite der Shop-Artikel
curl -H "Authorization: Bearer pc_wawi_…" \
  "https://IHRE-DOMAIN/api/wawi_api.php?resource=products&online_shop=1&limit=50&page=1"

# 3. Nur die Änderungen seit dem letzten Abgleich
curl -H "Authorization: Bearer pc_wawi_…" \
  "https://IHRE-DOMAIN/api/wawi_api.php?resource=products&updated_since=2026-07-25%2006:00:00"

# 4. Ein Stück verkauft — relativ buchen (sicher gegen gleichzeitige Kassenverkäufe)
curl -X PATCH "https://IHRE-DOMAIN/api/wawi_api.php?resource=stock" \
  -H "Authorization: Bearer pc_wawi_…" -H "Content-Type: application/json" \
  -d '{ "product_id": 1204, "location_id": 2, "change": -1 }'

# 5. Ein einzelner Artikel ist nicht lieferbar (state-Kurzform)
curl -X PATCH "https://IHRE-DOMAIN/api/wawi_api.php?resource=availability" \
  -H "Authorization: Bearer pc_wawi_…" -H "Content-Type: application/json" \
  -d '{
        "scope": { "eans": ["3474636612345"] },
        "state": "nicht_lieferbar",
        "note": "Telefonat Lieferant 25.07.2026"
      }'

# 6. Ganzer Abgleich: nur die Ausnahmen senden, der Rest gilt als lieferbar
curl -X PATCH "https://IHRE-DOMAIN/api/wawi_api.php?resource=availability" \
  -H "Authorization: Bearer pc_wawi_…" -H "Content-Type: application/json" \
  -d '{
        "scope": { "brands": ["Kerastase", "Redken"] },
        "unavailable": ["3474636612345", "3474630123456"],
        "unknown": ["3474639999999"],
        "note": "Lieferantenliste 24.07.2026"
      }'

# 6. Stand abfragen — läuft der Prüflauf noch?
curl -H "Authorization: Bearer pc_wawi_…" \
  "https://IHRE-DOMAIN/api/wawi_api.php?resource=availability"
{
  "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.",
      "images": ["https://…/abc.jpg", "https://…/abc-2.jpg"],
      "min_stock": 2,
      "updated_at": "2026-07-24 18:03:11"
    }
  ]
}

Fehlercodes

CodeUrsache und Abhilfe
401Kopfzeile fehlt, oder Schlüssel falsch / widerrufen / deaktiviert.
403Recht fehlt (lesen bzw. schreiben) — wird in der App je Schlüssel vergeben.
404Artikel gehört nicht zu diesem Betrieb / existiert nicht, oder unbekannte Ressource.
405Methode nicht vorgesehen (z. B. DELETE).
409Artikelnummer beim Anlegen schon vergeben — stattdessen PATCH nutzen.
422Pflichtfeld fehlt oder kein schreibbares Feld dabei; hint nennt die erlaubten Felder.
500 / 503Buchung 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 — dieselbe Datenbank bedient gleichzeitig Kasse und Salon-Displays.

Vollständige Anleitung im Handbuch

Der Artikel „REST-API für die Warenwirtschaft (Onlineshop anbinden)" enthält zusätzlich den Betreiber-Teil (Schlüssel anlegen, widerrufen, Fehlersuche) und den empfohlenen Ablauf einer Erst-Anbindung.

Noch nicht öffentlich

Die App selbst spricht intern mit api/index.php?endpoint=<Bereich>&action=<Aktion>. Diese Endpunkte sind keine öffentliche Schnittstelle: Sie sind an die Sitzung der Anwendung gebunden, nicht versioniert und können sich mit jedem Release ändern. Bitte nicht dagegen entwickeln.

Was für eine spätere Öffnung in Frage kommt — ohne Terminversprechen:

  • Termine & Buchung
  • Kasse (POS) inklusive TSE-Bezug
  • Personal & Zeiterfassung
  • Webhooks statt Abfragen im Takt

Wenn Sie eine dieser Anbindungen konkret brauchen, melden Sie sich — die Reihenfolge richtet sich nach dem Bedarf.