Katalog synchronisieren (API)
Über die Catalog-Sync-API übertragen Sie den kompletten Katalog eines Standorts — Artikel, Kategorien, Artikeloptionen und Menüs — in einer Anfrage an bessa. Die Anfrage wird sofort geprüft, anschließend werden die Daten im Hintergrund in einer einzigen Transaktion geschrieben. Diese Schnittstelle richtet sich an Entwickler, die ein Kassen- oder Fremdsystem an bessa anbinden.
Für die manuelle Pflege Ihres Katalogs benötigen Sie diese Schnittstelle nicht. Artikel, Kategorien und Optionen pflegen Sie komfortabel im bessa Kassa Manager.
Überblick
Sie laden den gesamten Katalog in einer Anfrage hoch. Die Anfrage wird synchron validiert — eine fehlerhafte Anfrage wird sofort mit 400 abgelehnt. Die eigentlichen Schreibvorgänge laufen danach als Hintergrund-Job. Der Client fragt anschließend einen Status-Endpunkt ab, bis der Job abgeschlossen ist. Ein identischer erneuter Upload wird erkannt (Deduplizierung) und löst keine weitere Arbeit aus.
|
Basis-URL |
|
|
Authentifizierung |
Token-Authentifizierung als API-Benutzer mit Upload-Berechtigung des Standorts ( |
|
Rate-Limit |
60 Anfragen pro Minute und Upload-Benutzer. Ein identischer erneuter Upload (gleicher Body-Hash) umgeht das Limit und liefert eine Dedup-Antwort. |
Endpunkte
|
Methode |
Pfad |
Zweck |
|---|---|---|
|
|
|
Validieren, deduplizieren und Katalog-Übernahme einreihen |
|
|
|
Status eines Jobs abfragen ( |
POST /v1/web/pos/catalog/sync/
Validiert die gesamte Nutzlast, speichert sie und reiht die Hintergrund-Übernahme ein.
Anfrage-Body
{
"mode": "update", // "update" (Standard) | "create"
"articles": [
{
"id": "4001", // Pflicht, ≤128 Zeichen — die Artikelnummer (PLU/UUID)
"name": "Pizza Salami", // Pflicht
"gross": 899, // Pflicht, ganzzahlig in kleinster Einheit → 8,99
"tax": 1000, // Pflicht, ganzzahlig → 10,00 (%)
"is_active": true, // optional, Standard true
"description": "…", // optional
"allergens": "A.G.L", // optional
"type": 4, // optional ArticleType: 1 Unbekannt, 2 Sonstiges, 3 Getränk, 4 Speise
"course_group": 1, // optional, ganzzahlige Gang-Nummer (wird angelegt/aktualisiert)
"available": 10, // optional, absoluter Lagerbestand als Basiswert
"tracking": true, // optional, Standard false — Lagerführung aktivieren
"dispenser_id": "D-42", // optional, ≤32 Zeichen
"prices": [ // optional, ≤10 Einträge, einer je Bestellart
{ "order_type": 1, "price": 899 }, // order_type = Order.Type, price = kleinste Einheit
{ "order_type": 2, "price": 999 }
],
"translations": { // optional, je Sprachcode (≤5 Zeichen)
"de": { "nutrition_facts": "…" },
"en": { "name": "Pizza Salami", "description": "…", "allergens": "…", "nutrition_facts": "…" }
},
"modifiers": ["159434e3-…"] // optional, IDs aus der obersten Liste "modifiers"
}
],
"categories": [
{
"id": "Pizza", // Pflicht, ≤64 Zeichen — Kategorie-Referenz (je Standort eindeutig)
"name": "Pizza", // Pflicht
"from_hour": "11:00", // optional, "HH:MM" oder null
"to_hour": null, // optional, "HH:MM" oder null
"articles": ["4001", "4002"], // geordnete Artikel-IDs dieser Kategorie
"translations": { "en": { "name": "Pizza", "description": "…" } } // optional
}
],
"modifiers": [
{
"id": "159434e3-…", // Pflicht, ≤128 Zeichen — Options-Nummer (je Standort eindeutig)
"name": "Fleisch", // Pflicht
"min": 0, // optional, Standard 1
"max": 3, // optional, Standard 1 (muss ≥ min sein)
"ignore_price": true, // optional, Standard false
"articles": ["e400bb4a-…"], // geordnete Artikel-IDs = die wählbaren Optionen
"translations": { "en": { "name": "Meat", "description": "…" } } // optional
}
],
"menus": [
{
"name": "Abholung", // Pflicht
"description": "", // optional
"type": 1, // Pflicht, Order.Type (siehe unten); je Standort eindeutig
"categories": ["Pizza"] // geordnete Kategorie-IDs (== categories[].id)
}
],
"tables": [
{
"id": "T1", // Pflicht — Tisch-Referenz (je Standort eindeutig)
"name": "Table 1", // Pflicht
"is_active": true // optional, Standard true
}
]
}
Alle fünf obersten Listen (articles, categories, modifiers, menus, tables) sind optional und standardmäßig leer.
Wichtige Regeln
-
Reihenfolge ergibt sich aus der Array-Position — es gibt nirgends ein
sort-Feld. Die Reihenfolge voncategories[],menus[].categories[],modifiers[].articles[]undcategories[].articles[]bestimmt die angezeigte bzw. gespeicherte Sortierung. -
mode:-
update(Standard): aktualisiert bzw. legt an, was in der Nutzlast enthalten ist. Nicht genannte Objekte und Verknüpfungen bleiben unangetastet. -
create: vollständiger Neuaufbau — entfernt zusätzlich alle Strukturen, die in der Nutzlast fehlen (Menüs, Kategorien, Optionen und deren Verknüpfungen). Artikel werden nie gelöscht (Bestellungen und Kundenbindung verweisen darauf); ein in der Nutzlast fehlender Artikel wird lediglich entkoppelt.
-
-
Preise werden als explizite
{order_type, price}-Liste angegeben (ein Eintrag je Bestellart).priceist ganzzahlig in der kleinsten Währungseinheit. Maximal 10 Einträge; einorder_typedarf innerhalb eines Artikels nicht doppelt vorkommen. Preise werden ausschließlich aktualisiert/angelegt — ein Preis für eine Bestellart, die in einer späteren Nutzlast wegfällt, bleibt erhalten. -
Übersetzungen sind Vollzustand je Sprache und je Sync: Senden Sie pro Sprache jedes Mal den vollständigen Feldsatz. Ein in einem Sprach-Eintrag weggelassenes Feld wird nicht aus einem vorherigen Sync übernommen — es fällt auf den flachen Wert des Artikels zurück (und
nutrition_facts, das kein flaches Feld besitzt, wird leer). Übersetzungen werden nur aktualisiert/angelegt (eine weggelassene Sprache bleibt erhalten). Artikel-Übersetzungen enthaltenname/description/allergens/nutrition_facts; Kategorie- und Options-Übersetzungen enthaltenname/description. Menüs haben keine Übersetzungen. -
Lagerbestand:
available/trackingsetzen beim Anlegen den absoluten Basiswert. Beim Aktualisieren wirdavailablenur angewendet, wenn der Artikel nicht lagergeführt ist — der Live-Bestand eines lagergeführten Artikels gehört der separaten Lagerbestands-Schnittstelle (Stock-Delta).
Validierung (liefert 400, kein Job, keine Schreibvorgänge)
Folgende Fälle werden sofort abgelehnt — es wird nichts geschrieben:
-
Unbekannte Referenzen innerhalb der Nutzlast: eine
modifiers[]-ID eines Artikels, die nicht inmodifiers[]enthalten ist; einearticles[]-ID einer Option oder Kategorie, die nicht inarticles[]enthalten ist; einecategories[]-ID eines Menüs, die nicht incategories[]enthalten ist. -
Doppelte IDs innerhalb von
articles/categories/modifiersoder ein doppelter Menü-type. -
prices: mehr als 10 Einträge oder ein wiederholterorder_typeinnerhalb eines Artikels. -
mingrößer alsmaxbei einer Option. -
from_hour/to_hournicht im FormatHH:MM. -
Ein Sprachschlüssel einer Übersetzung länger als 5 Zeichen.
-
Ein
type/order_type/modeaußerhalb des gültigen Bereichs.
Fehler werden als Standard-Fehlerobjekt zurückgegeben, geschlüsselt nach dem betroffenen Abschnitt bzw. Feld.
Antworten
|
Status |
Wann |
Body |
|---|---|---|
|
|
Neue Nutzlast angenommen und eingereiht |
|
|
|
Identische Nutzlast bereits bekannt (dedupliziert, keine neue Arbeit). Zusätzlich Header |
|
|
|
Validierung fehlgeschlagen |
Fehlerobjekt; es wurde nichts geschrieben |
|
|
Token fehlt/ungültig oder keine Upload-Berechtigung |
— |
|
|
Rate-Limit überschritten (nicht dedupliziert) |
— |
GET /v1/web/pos/catalog/sync/{id}/
Liefert den Job-Status. Auf den Standort des Aufrufers beschränkt — die Job-ID eines anderen Standorts liefert 404.
Antwort 200
{
"id": "csj_…",
"mode": "update",
"state": "done",
"message": "",
"stats": { "articles_created": 3, "articles_updated": 0, "...": 0 },
"created": "2026-06-26T08:00:00Z",
"updated": "2026-06-26T08:00:03Z"
}
|
Feld |
Bedeutung |
|---|---|
|
|
Job-ID (entspricht der POST-Antwort) |
|
|
|
|
|
|
|
|
Ergebnis- bzw. Fehlermeldung (bei Fehlern eine generische, übersetzte Meldung) |
|
|
Zähler je Objekttyp nach erfolgreicher Übernahme (angelegt/aktualisiert/verknüpft usw.); |
|
|
Zeitstempel |
Empfohlener Ablauf für Clients
-
Katalog per
POSTsenden → zurückgegebeneidmerken. -
Bei
400abbrechen und die Nutzlast korrigieren (denselben Body nicht erneut senden — er schlägt wieder fehl). -
Bei
202/200den EndpunktGET …/{id}/abfragen, bisstategleichdoneoderfailedist. -
Bei
faileddiemessageanzeigen und nach Korrektur erneut hochladen — nicht endlos abfragen.
Referenz: Order.Type-Werte
|
Wert |
Bestellart |
|---|---|
|
0 |
Vor Ort |
|
1 |
Abholung |
|
2 |
Lieferung |
|
3 |
Selbstbedienung |
|
4 |
Schankanlage |
|
5 |
Drittanbieter-Abholung |
|
6 |
Drittanbieter-Lieferung |
|
7 |
Kantine |
Vom Kassensystem abhängiges Verhalten (informativ)
Die folgenden Punkte werden automatisch anhand der Anbieter-Konfiguration des Standorts angewendet; der Client steuert sie nicht je Anfrage:
-
Text ignorieren: Beschreibung und Allergene aus der Nutzlast werden ignoriert (bestehende Werte bleiben erhalten).
-
Sortierung ignorieren: Bei einer reinen Umsortierung bleibt die Reihenfolge Kategorie→Artikel unverändert.
-
Artikeltyp ignorieren: Bei bestimmten Anbieter-Konfigurationen wird der
typedes Artikels ignoriert.
Verhältnis zu den bisherigen Endpunkten
Diese Route ersetzt die bisherigen Endpunkte POST /v1/web/pos/articles/, POST /v1/web/pos/menus/ und POST/PUT/PATCH /v1/web/pos/sync/articles/{plu}/. Diese Endpunkte sind veraltet (deprecated) — verwenden Sie für neue wie bestehende Anbindungen ausschließlich die Catalog-Sync-API.
Häufige Fragen
Wie viele Anfragen darf ich pro Minute senden? 60 Anfragen pro Minute und Upload-Benutzer. Ein identischer erneuter Upload (gleicher Body-Hash) wird dedupliziert und zählt nicht gegen das Limit.
Werden Artikel im Modus create gelöscht? Nein. Artikel werden nie gelöscht, weil Bestellungen und die Kundenbindung darauf verweisen. Im Modus create werden fehlende Artikel lediglich aus Kategorien und Optionen entkoppelt; Menüs, Kategorien und Optionen, die in der Nutzlast fehlen, werden hingegen entfernt.
Warum ist ein übersetztes Feld nach dem Sync leer? Übersetzungen sind Vollzustand je Sprache: Senden Sie jedes Mal den vollständigen Feldsatz pro Sprache. Ein weggelassenes Feld fällt auf den flachen Artikelwert zurück; nutrition_facts hat keinen flachen Wert und wird daher leer.
Wie setze ich den Lagerbestand eines lagergeführten Artikels? Beim Aktualisieren wird available nur für nicht lagergeführte Artikel angewendet. Der Live-Bestand eines lagergeführten Artikels wird über die separate Lagerbestands-Schnittstelle (Stock-Delta) gepflegt.
Fehlerbehebung
-
400 Bad Request— Die Nutzlast hat die Validierung nicht bestanden. Prüfen Sie das zurückgegebene Fehlerobjekt (geschlüsselt nach Abschnitt/Feld), korrigieren Sie die Nutzlast und senden Sie sie erneut. Senden Sie nicht denselben Body erneut — er schlägt wieder fehl. -
401 / 403— Token fehlt oder ist ungültig, oder der API-Benutzer hat keine Upload-Berechtigung für den Standort. -
429 Too Many Requests— Das Rate-Limit wurde überschritten. Warten Sie und drosseln Sie die Sendefrequenz. -
state: failed— Die Hintergrund-Übernahme ist fehlgeschlagen. Zeigen Sie diemessagean, korrigieren Sie die Ursache und laden Sie erneut hoch. Fragen Sie den Status nicht endlos ab. -
404beim Statusabruf — Die Job-ID gehört zu einem anderen Standort oder existiert nicht. Verwenden Sie dieidaus Ihrer eigenen POST-Antwort.