API

Für Kassensysteme, Warenwirtschaft und alles, was Daten liefert oder abholt.

Basisadresse https://<tenant>.<cluster>.api.bum2.fun/api/v1

Alle Antworten sind JSON in UTF-8. Es gibt keinen CORS-Kopf: das hier ist eine Maschinenschnittstelle, und ein Schlüssel, den ein Browser mitschickt, steht im Quelltext einer Webseite.

Anmeldung

Jeder Aufruf braucht einen API-Schlüssel im Kopf Authorization. Erzeugt wird er im Portal unter Einstellungen → API-Schlüssel.

curl https://<tenant>.<cluster>.api.bum2.fun/api/v1/artikel \
  -H 'Authorization: Bearer ak_…'

Der Schlüssel bestimmt den Tenant.

Er steht in keinem Aufrufparameter und lässt sich nicht wählen. Wer einen Schlüssel hat, sieht die Daten des Tenants, zu dem er gehört — und keine anderen.

Der Schlüssel wird einmal angezeigt und danach nie wieder; gespeichert ist nur sein SHA-256-Hash. Geht er verloren, legt man einen neuen an und sperrt den alten. Ein Schlüssel kann lesen oder lesen und schreiben — eine Auswertung, die Zahlen abholt, braucht das Schreibrecht nicht.

Nicht als Abfrageparameter mitgeben: der stünde in jedem Zugriffsprotokoll des Webservers, in jedem Proxy und in der Browserhistorie.

Objekte

Sechs Objekte, in dieser Reihenfolge aufeinander aufbauend. Preise, Bestände und Verkäufe verweisen auf Artikel und Standorte — die müssen also zuerst da sein.

PfadObjektZweck
/api/v1/store Standorte Betriebe, Filialen, Restaurants. Muss VOR Preisen und Beständen hochgeladen werden — die verweisen darauf.
/api/v1/artikel Artikel Der Artikelstamm. Preise und Bestände kommen in eigenen Dateien — ein Artikel kostet je Standort verschieden.
/api/v1/preis Preise Welcher Artikel kostet an welchem Standort wie viel. Ein Artikel ohne Zeile hier ist an diesem Standort nicht im Angebot.
/api/v1/bestand Bestände Momentaufnahme je Artikel und Standort. Keine Historie — ein neuer Upload ersetzt den Stand.
/api/v1/kunde Kunden Stammkunden. Für die Gastronomie meist zweitrangig, für den Handel die Grundlage jeder Kundenauswertung.
/api/v1/lieferant Lieferanten Wer liefert. Aus SAP derselbe Business Partner wie der Kunde, nur in einer anderen Rolle — deshalb eine eigene Tabelle und keine Spalte am Kunden.
/api/v1/verkauf Verkäufe Bons und ihre Positionen. EINE ZEILE JE POSITION — ein Bon mit drei Artikeln steht dreimal darin, jedes Mal mit derselben Bonnummer. Ohne diese Daten gibt es keine Auswertung, keine ABC-Analyse und keine Prognose.

Lesen

GET /api/v1/artikel?limit=100&offset=0
GET /api/v1/artikel/100-001

limit geht bis 1000 (Vorgabe 100). Einzeln lesbar sind nur Objekte mit eigener Nummer: store, artikel, kunde, lieferant, verkauf. Preis und Bestand hängen an Artikel und Standort und haben keine eigene — sie kommen über den Artikel.

Schreiben

POST /api/v1/verkauf
Content-Type: application/json

{"daten": [
  {"bonnummer": "2026-0001", "zeitpunkt": "2026-08-15 19:42",
   "storenummer": "RIVA", "artikelnummer": "100-001",
   "menge": 2, "einzelpreis": 33.00}
]}

Alles oder nichts.

Ist eine Zeile fehlerhaft, wird keine geschrieben, und die Antwort nennt die betroffenen Zeilen einzeln. Der Gegenentwurf — gute Zeilen übernehmen, schlechte melden — hinterlässt einen halb übertragenen Bon, und niemand weiss hinterher, welche Hälfte.

Kein Vollabgleich

Über die API wird ergänzt und aktualisiert, nie stillgelegt. Ein Vollabgleich, der fehlende Sätze abschaltet, bleibt der Upload-Seite im Portal vorbehalten.

Der Grund ist die Betriebsart: eine Kasse schickt Teilmengen — den Umsatz einer Schicht, die geänderten Preise von heute. Was darin fehlt, ist nicht abgeschafft. Würde die API stilllegen, was sie nicht sieht, legte der erste Aufruf einer Kasse das halbe Sortiment still, und die Antwort wäre ein freundliches 200.

Felder

Aus derselben Beschreibung wie die Excel-Vorlagen. Mit Pflicht gekennzeichnete Felder müssen in jedem Datensatz stehen.

Standorte /api/v1/store

FeldTyp BeispielHinweis
nummer Pflicht text RIVA Eindeutiges Kürzel, z. B. RIVA. Unveränderlich — es verbindet alle anderen Dateien mit diesem Standort.
name Pflicht text Riva Wie der Standort im Haus heisst.
typ auswahl
filiale · restaurant · hotel · online · lager · sonstige
restaurant Leer = filiale.
strasse text Ketschauerhofstr. 1
plz text 67146 Als TEXT eintragen — führende Nullen gehen sonst verloren.
ort text Deidesheim
land text DE Zwei Buchstaben nach ISO 3166, z. B. DE, AT, CH.
waehrung text EUR Leer = EUR. Gilt für alle Preise dieses Standorts.
sap_werk text 1041 Das Werk (Plant) aus SAP. Nur nötig, wenn dieser Standort Bestände aus S/4HANA bekommt — die Nachricht nennt kein Standortkürzel, sondern diese Nummer. Leer = keine Zuordnung.
sprache text DE Leer = DE. Zwei Buchstaben nach ISO 639-1. Entscheidet, welche Bezeichnung aus einer mehrsprachigen Lieferung übernommen wird — SAP schickt jeden Artikelnamen in allen gepflegten Sprachen.

Artikel /api/v1/artikel

FeldTyp BeispielHinweis
nummer Pflicht text 100-001 Die Artikelnummer aus deinem System. Sie verbindet Preise, Bestände und Verkäufe mit diesem Artikel.
bezeichnung Pflicht text 2023 Riesling „Herrgottsacker"
warengruppe text Wein Flasche weiß Frei wählbar, aber EINHEITLICH schreiben — „Wein weiss" und „Weisswein" sind zwei Gruppen.
hersteller text Reichsrat von Buhl
ean text 4006381333931 Als TEXT eintragen, sonst macht Excel 4,00612E+12 daraus.
basiseinheit text FL Leer = ST. Sonst FL, KG, L, PORTION …
gewicht zahl 1,25 In KILOGRAMM, immer. 750 g sind 0,75 — eine Spalte für die Einheit gibt es bewusst nicht, sonst stehen Gramm und Kilo in derselben Summe. Für Versandkosten und Liefergewicht.
jahrgang zahl 2023 Nur bei Wein. Derselbe Wein ist 2019 ein anderer Artikel als 2020.
status auswahl
aktiv · auslaufend · gesperrt
aktiv Leer = aktiv.
beschreibung text

Preise /api/v1/preis

FeldTyp BeispielHinweis
artikelnummer Pflicht text 100-001 Muss in der Artikeldatei vorkommen.
storenummer Pflicht text RIVA Muss in der Standortdatei vorkommen.
preis Pflicht zahl 33,00 Bruttoverkaufspreis. Komma oder Punkt, beides geht.
gueltig_ab datum 01.03.2026 Leer = ab heute. Ein früherer Preis wird zu diesem Zeitpunkt beendet, nicht überschrieben.
gueltig_bis datum 31.12.2026 Leer = bis auf Weiteres. Mit Datum steht der Zeitraum FEST — dann endet der vorige Preis nicht automatisch, und mehrere Zeiträume desselben Artikels stehen nebeneinander. Der Tag zählt mit.

Bestände /api/v1/bestand

FeldTyp BeispielHinweis
artikelnummer Pflicht text 100-001
storenummer Pflicht text RIVA
menge Pflicht zahl 12 Darf 0 sein. Leer ist etwas anderes als 0 und wird abgewiesen.
einheit text FL Leer = die Basiseinheit des Artikels.
mindestmenge zahl 6 Ab wann nachbestellt wird. Ohne diesen Wert kann der Assistent nicht sagen, was knapp wird.
stand datumzeit 14.08.2026 18:00 Zeitpunkt der Zählung. Leer = jetzt.

Kunden /api/v1/kunde

FeldTyp BeispielHinweis
nummer Pflicht text K-1001
name Pflicht text Bessie Wilhelm
email text [email protected]
strasse text Hauptstr. 3
plz text 67146 Als TEXT — sonst fehlen führende Nullen.
ort text Deidesheim
land text DE
angelegt_am datum 11.09.2023 Seit wann Kunde. Ohne dieses Datum lässt sich Neukundengewinnung nicht auswerten.

Lieferanten /api/v1/lieferant

FeldTyp BeispielHinweis
nummer Pflicht text 24300273
name Pflicht text Weingut Bassermann-Jordan
email text [email protected]
strasse text Hauptstr. 3
plz text 67146 Als TEXT — sonst fehlen führende Nullen.
ort text Deidesheim
land text DE
angelegt_am datum 26.02.2026 Seit wann Lieferant. Aus SAP der Beginn der Rollengültigkeit.

Verkäufe /api/v1/verkauf

FeldTyp BeispielHinweis
bonnummer Pflicht text 2026-0001 Gleiche Nummer = gleicher Bon.
zeitpunkt Pflicht datumzeit 14.08.2026 19:42 Datum UND Uhrzeit. Nur ein Datum macht jede Tageszeitauswertung unmöglich.
storenummer Pflicht text RIVA
artikelnummer Pflicht text 100-001
menge Pflicht zahl 2
einzelpreis Pflicht zahl 33,00 Der TATSÄCHLICH bezahlte Preis, nicht der Listenpreis. Sonst ändert sich der Umsatz der Vergangenheit bei jeder Preispflege.
rabatt zahl 0 Leer = 0.
kundennummer text Leer = anonym. Im Restaurant der Normalfall.
storniert ja_nein nein ja/nein. Leer = nein. Stornierte Bons zählen nicht zum Umsatz, bleiben aber sichtbar.

SAP S/4HANA

Wer Artikel und Preise aus S/4HANA repliziert, muss sie nicht erst in unser Format bringen. Die Bulk-Nachricht ProductMerchandiseViewReplicationBulkRequest wird direkt angenommen — als XML, so wie SAP sie sendet.

POST https://<tenant>.<cluster>.api.bum2.fun/api/v1/sap/produkte?store=RIVA
Content-Type: application/xml
Authorization: Bearer ak_…

Der Standort kommt aus der Adresse.

Im XML steht er nicht: die Nachricht kennt SalesOrganisationID und DistributionChannelCode, aber keine Filiale. ?store= muss eine Standortnummer sein, die es in diesem Tenant schon gibt — sonst kommt ein 422, und es wird nichts geschrieben.

Optional ?sprache=DE für die Bezeichnung (Vorgabe DE). Gibt es sie in dieser Sprache nicht, wird die erste vorhandene genommen und das in der Antwort gesagt.

Aus einer Nachricht entstehen zwei Importe: erst die Artikel, dann die Preise. Übernommen wird der Verkaufspreis, der heute gilt — künftige Preise sendet SAP mit, und die gehören noch nicht in die Kasse. Artikel ohne heute gültigen Preis kommen trotzdem an; sie sind an diesem Standort nur nicht im Angebot.

{
  "store": "RIVA",
  "artikel": { "gelesen": 812, "neu": 14, "aktualisiert": 798 },
  "preis":   { "gelesen": 806, "neu": 9,  "aktualisiert": 797 },
  "hinweise": ["6 Artikel ohne heute gültigen Verkaufspreis — Stammdaten übernommen, Preis nicht."]
}

Die Währung wird gegen den Standort geprüft. Weicht sie ab, wird nichts geschrieben: tenant_listung führt keine Währung mit, und 4,40 USD stünden sonst als 4,40 EUR am Regal. Umgerechnet wird nicht — welcher Kurs gilt, ist keine Entscheidung einer Schnittstelle.

Fehler

Nach RFC 9457 (Problem Details) — ein festes Format, damit ein Aufrufer nicht raten muss: title für Menschen, type zum Verzweigen im Code, errors für die Zeilen, die nicht durchkamen.

{
  "type": "https://api.bum2.fun/fehler/anmeldung",
  "title": "Der API-Schlüssel wird nicht angenommen.",
  "status": 401
}
CodetypeBedeutung
401…/anmeldung Kein oder ungültiger Schlüssel. Ein gesperrter Schlüssel bekommt dieselbe Meldung wie ein erfundener — ein Unterschied verriete, dass es ihn gibt.
403…/recht Schlüssel darf nur lesen, der Aufruf will schreiben.
404…/objekt Unbekanntes Objekt. moeglich listet die gültigen.
404…/nichtgefunden Diese Nummer gibt es in diesem Tenant nicht.
400…/daten Zeilen fehlerhaft. errors nennt sie mit Zeilennummer.
503…/datenbank Keine Datenbankverbindung. Später erneut versuchen.

Postman

Eine Sammlung mit allen Aufrufen, erzeugt aus derselben Beschreibung wie diese Seite — sie kann also nicht veralten, solange die Schnittstelle sich nicht ändert.

Sammlung herunterladen

In Postman: Import → Datei wählen. Danach in der Sammlung unter Variables den Wert schluessel auf den eigenen API-Schlüssel setzen; er gilt dann für alle Aufrufe.

Grenzen

Grösse eines Aufrufs16 MB
Zeilen je POST keine feste Grenze — praktisch begrenzt das Zeitlimit
Antwortzeit Cloudflare bricht nach 100 Sekunden ab. Bei sehr vielen Zeilen in Teilmengen schicken.
Aufrufe je Minutederzeit nicht begrenzt

Objektkatalog

Die Schnittstelle beschreibt sich selbst — hinter der Anmeldung:

curl https://<tenant>.<cluster>.api.bum2.fun/api/v1/ \
  -H 'Authorization: Bearer ak_…'

Der Katalog steht bewusst hinter der Anmeldung. Er stand einmal davor, und GET /api/v1/ antwortete deshalb mit 200 auch auf einen erfundenen Schlüssel — damit taugte der Endpunkt nicht mehr zum Prüfen der Einrichtung.