Standort-Server REST-API v1
Diese API läuft direkt auf jedem Standort-Server (.NET 8, Repo
de.profipos.server) und ist im
LAN unter http://<server-ip>:8080 erreichbar. Sie wird live verwendet von
allen Endgeräten der Welle-1C-/Welle-2-Domain (Kasse, Kundendisplay, goapp,
kitchendisplay, lieferapp, selfservice, werbedisplay).
Die hier dokumentierten Endpoints wurden mit Build #22 (Commit
fb67297b6af, 13.06.2026) auf main ausgerollt. Die ältere
Cloud-REST-API v6 für Bestellportal und Onboarding existiert
parallel weiter.
Basis-URL
http://<server-ip>:8080/api/v1
Die Server-IP wird typischerweise per UDP-Discovery
ermittelt (Magic PROFIPOS-DISCOVER-V6, Port 34567).
Authentifizierung
Die Endpoints sind in zwei Gruppen aufgeteilt:
| Gruppe | Auth | Endpoints |
|---|---|---|
| Secure | Bearer-Token (pp_<43-base64url>) im Header Authorization | /auth/device/*, /tische/{uuid}, /tische/{uuid}/status |
| Public | keine (Übergangslösung, wird in einer Folge-Welle abgesichert) | Warenkorb, Artikel-Suche, Bondruck, Schublade |
Token-Schema, Generierung und Widerruf siehe Geräte-Auth.
Authorization: Bearer pp_xR4dKw9V…
Bei fehlendem oder ungültigem Token antworten Secure-Endpoints mit:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{ "fehler": "unauthorized", "grund": "unbekanntes_oder_abgelaufenes_token" }
Mögliche grund-Werte: kein_bearer_token, ungueltiges_token_format,
unbekanntes_oder_abgelaufenes_token, interner_fehler.
JSON-Konvention
- Server und Client serialisieren konsequent snake_case (per
JsonNamingPolicy.SnakeCaseLowerglobal aktiviert). - Deserialisierung ist case-insensitive.
- DTOs brauchen daher keine
[JsonPropertyName]-Attribute mehr.
Endpoint-Übersicht
| Methode | Pfad | Auth | Zweck |
|---|---|---|---|
POST | /api/v1/auth/device/login | Secure | Geräte-Token mit Server-Status validieren |
GET | /api/v1/auth/device/me | Secure | Aktueller Geräte-Kontext (Mandant/Standort/Gerät) |
GET | /api/v1/tische/{uuid} | Secure | Tisch-Detail inkl. aktivem Bon |
POST | /api/v1/tische/{uuid}/status | Secure | Tisch-Status setzen (+ MQTT-Publish) |
POST | /api/v1/bons/{uuid}/positionen | Public | Position zum Warenkorb hinzufügen |
PATCH | /api/v1/bonpositionen/{uuid} | Public | Menge einer Position ändern |
DELETE | /api/v1/bonpositionen/{uuid} | Public | Position stornieren |
GET | /api/v1/bons/{uuid}/zahlungen | Public | Zahlungen zu einem Bon listen |
GET | /api/v1/bestellungen | Public | Offene Bestellungen filtern |
GET | /api/v1/artikel/suche | Public | Artikel-Volltext-Suche |
POST | /api/v1/bons/{uuid}/druck | Public | Bon drucken (ESC/POS, TCP/9100) |
POST | /api/v1/schublade/oeffnen | Public | Geldschublade öffnen |
Auth-Endpoints
POST /api/v1/auth/device/login
Bestätigt einen Geräte-Token gegenüber dem Server und liefert den aktuellen Kontext. Wird vor allem genutzt, um auf einem frisch provisionierten Gerät zu prüfen, ob der hinterlegte Token noch gültig ist.
Request-Header:
Authorization: Bearer pp_xR4dKw9V…
Response 200:
{
"geraet_uuid": "0f7c…",
"mandant_uuid": "a31e…",
"standort_uuid": "6c91…",
"geraete_typ": "kasse",
"bezeichnung": "Kasse 1 Bar",
"gueltig_am": "2026-06-13T14:22:18Z"
}
GET /api/v1/auth/device/me
Identisch zur Response von /login, aber idempotenter „Wer bin ich?“-Call —
wird typischerweise beim App-Start oder Reconnect getriggert.
curl -H "Authorization: Bearer pp_xR4dKw9V…" \
http://10.1.1.20:8080/api/v1/auth/device/me
Tische
GET /api/v1/tische/{uuid}
Liefert Tisch-Detail inkl. aktivem Bon + Positionen.
Response 200:
{
"tisch_uuid": "11ab…",
"raum_uuid": "22cd…",
"nummer": 12,
"name": "Tisch 12",
"status": "belegt",
"kapazitaet": 4,
"aktiver_bon": {
"bon_uuid": "33ef…",
"bon_nummer": 1042,
"summe_brutto": 47.30,
"summe_offen": 47.30,
"eroeffnet_am": "2026-06-13T11:48:02Z"
},
"aktive_positionen": [
{ "position_uuid": "…", "menge": 2, "bezeichnung": "Apfelschorle 0,4l", "einzelpreis": 3.80 },
{ "position_uuid": "…", "menge": 1, "bezeichnung": "Schnitzel Wiener Art", "einzelpreis": 18.90 }
]
}
POST /api/v1/tische/{uuid}/status
Setzt den Tisch-Status und löst ein retained MQTT-Publish auf
profipos/{mandant}/{standort}/tisch/{kurz}/status aus.
Request:
{ "status": "belegt" }
Erlaubte Werte: frei, belegt, reserviert, wartung, geparkt.
Response 204 (No Content).
Warenkorb / Bons
POST /api/v1/bons/{uuid}/positionen
Fügt eine neue Position zu einem offenen Bon hinzu. Prüft den Bon-Status und
antwortet 409 Conflict, wenn der Bon bereits bezahlt oder storniert
ist.
Request:
{
"artikel_uuid": "…",
"menge": 2,
"einzelpreis": 3.80,
"bezeichnung": "Apfelschorle 0,4l",
"kueche_status": "neu",
"bediener_uuid": "…"
}
Response 201:
{
"position_uuid": "44gh…",
"position_nr": 17
}
Die position_nr wird serverseitig via SELECT MAX(position_nr) + 1
ermittelt.
PATCH /api/v1/bonpositionen/{uuid}
Ändert die Menge einer bereits angelegten Position. Triggert
RecalcBonSummenAsync (Netto = brutto / (1 + steuersatz/100)).
{ "menge": 3 }
Response 204 bei Erfolg, 404 wenn Position nicht existiert.
DELETE /api/v1/bonpositionen/{uuid}
Storniert eine Position. Hängt einen Storno-Grund + Bediener-UUID per Query-String an:
DELETE /api/v1/bonpositionen/44gh…?bediener=…&grund=Falsch+gebucht
Response 204.
GET /api/v1/bons/{uuid}/zahlungen
Listet alle Zahlungen zu einem Bon (Bar, Karte, Trinkgeld separat).
[
{
"zahlung_uuid": "…",
"zahlungsart": "bar",
"betrag": 50.00,
"trinkgeld": 2.70,
"bezahlt_am": "2026-06-13T12:05:11Z"
}
]
Bestellungen
GET /api/v1/bestellungen
Listet aktive Bestellungen. Default-Filter:
status IN ('offen','in_bezahlung','geparkt','lieferung_offen','abholung_offen').
| Query | Default | Beispiel |
|---|---|---|
status | (Default-Liste) | ?status=offen |
limit | 100 | ?limit=50 |
curl http://10.1.1.20:8080/api/v1/bestellungen?status=geparkt&limit=20
Artikel-Suche
GET /api/v1/artikel/suche
Volltext-Suche mit Boolean-FULLTEXT + LIKE-Fallback. Mindestlänge 2
Zeichen (sonst 400 mit q_mindestens_2_zeichen).
| Query | Default |
|---|---|
q | — (Pflicht) |
limit | 50 |
curl "http://10.1.1.20:8080/api/v1/artikel/suche?q=schnitzel&limit=10"
Response 200:
[
{ "artikel_uuid": "…", "bezeichnung": "Schnitzel Wiener Art", "preis_brutto": 18.90, "warengruppe": "Hauptgerichte" },
{ "artikel_uuid": "…", "bezeichnung": "Pommes", "preis_brutto": 4.50, "warengruppe": "Beilagen" }
]
Bondruck
POST /api/v1/bons/{uuid}/druck
Erzeugt einen ESC/POS-Bondruck (40-Zeichen-Zeilen) und sendet ihn per TCP an
den angegebenen Drucker (Default-Port 9100).
Request:
{
"drucker_host": "192.168.1.50",
"drucker_port": 9100,
"kopf": "ProfiPOS Cafe",
"fuss": "Vielen Dank!",
"schneiden": true
}
Response 200 mit Bon-Statistik:
{
"bon_nr": 1042,
"zeilen": 18,
"bytes": 740,
"gesendet_an": "192.168.1.50:9100",
"dauer_ms": 87
}
Response 502 bei Druck-Fehler:
{ "fehler": "druck_fehlgeschlagen", "details": "connection refused" }
Stornierte Positionen (kueche_status = "storniert") werden ausgelassen.
Werden Rabatt, Trinkgeld oder Steueraufschlüsselung mitgegeben, kommen sie als
zusätzliche Zeilen unter die Summe.
POST /api/v1/schublade/oeffnen
Öffnet die Geldschublade über den per ESC/POS angeschlossenen Drucker.
Request:
{ "drucker_host": "192.168.1.50", "drucker_port": 9100 }
Response 204. Sendet intern die Drawer-Kick-Sequenz
1B 70 00 32 FA (Pin 2, On=50 ms, Off=250 ms) — kompatibel mit Epson, Star
und generischen ESC/POS-Druckern.
Fehlerformat
{ "fehler": "<kurz_kennung>", "details": "<optional>" }
| HTTP | fehler | Bedeutung |
|---|---|---|
| 400 | q_mindestens_2_zeichen | Suche zu kurz |
| 401 | unauthorized | Token fehlt/ungültig (siehe grund) |
| 404 | nicht_gefunden | UUID existiert nicht |
| 409 | bon_bereits_bezahlt / bon_storniert | Position kann nicht mehr angehängt werden |
| 502 | druck_fehlgeschlagen | ESC/POS-Verbindung fehlgeschlagen |