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 & Pfad | Recht | Zweck |
|---|---|---|
GET /meta | lesen | Selbsttest: Rechte und Feldlisten des Schlüssels. Bester erster Aufruf. |
GET /products | lesen | Artikelliste, seitenweise. Standard: nur die für den Shop freigegebenen 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. |
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
| Parameter | Standard | Bedeutung |
|---|---|---|
online_shop | 1 | 1 = nur freigegebene, 0 = nur nicht freigegebene, all = alle. |
search | — | Sucht in Name, Shop-Name, Artikelnummer und 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. |
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:
| Feld | Werte | Bedeutung |
|---|---|---|
supplier_available | 1 / 0 / null | 1 = lieferbar, 0 = nicht lieferbar, null = keine Aussage. |
supplier_checked_at | Zeitstempel oder null | Wann geprüft wurde. Ohne Datum ist die Angabe wertlos. |
supplier_note | Text, max. 120 Zeichen | Woher die Angabe kommt, z. B. „Lieferantenliste 24.07.2026". |
availability | verfuegbar / nicht_lieferbar / unbekannt | Vom Server gerechnetes Ergebnis — genau das soll der Shop auswerten. |
availability_reason | Text oder null | Warum „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:
| Form | Body | Wirkung |
|---|---|---|
| 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 Feld | Pflicht | Bedeutung |
|---|---|---|
scope | ja | Geltungsbereich: {"brands":["Kerastase"]}, {"supplier_id":3}, {"eans":[…]} (max. 5000) oder {"all":true} — letzteres nur zusammen mit confirm_all: true. |
note | nein | Herkunft der Angabe, wird an allen berührten Artikeln hinterlegt. |
match_by | nein | auto (Standard), ean oder article_number. |
force | nein | Hebt 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
| 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 — 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.