Developer-Dokumentation

15 Seiten

Stand: 6. November 2026

Einstieg

Einführung

APRO ist die modulare Kassenplattform für Restaurant, Hotel, Foodtruck oder Systemgastronomie — offen für alles, was du brauchst. Von Hotelsoftware über Buchhaltung, Zahlungs- und Zutrittssysteme bis zu Reservierungs- und Lieferdiensten: APRO verbindet sich nahtlos mit deiner bestehenden Systemlandschaft, für durchgängige Prozesse und maximalen Automatisierungskomfort.

Diese Seite richtet sich an Entwicklungsteams, die auf dieser Oberfläche aufbauen: Partner mit Reservierungs- oder Warenwirtschaftssystemen, Lieferplattformen, die Bestellungen in die Kassa einsteuern, BI-Tools mit Lesezugriff auf den Belegbestand oder interne Teams, die Reports automatisieren.

Was Sie hier finden

  • REST-APIs. Stabile JSON-über-HTTPS-Endpunkte für die unten genannten Bereiche. Jeder Endpunkt wird über einen einzigen API-Key authentifiziert — kein SDK erforderlich, kein OAuth-Flow zu orchestrieren.
  • SQL-Views. Lesende Views für direkten Datenbankzugriff, ideal für BI-Pipelines und Data Warehouses.
  • Payload-Beispiele. Jedes Beispiel auf dieser Seite liegt zusätzlich als eigenständige JSON-Datei unter /samples/ vor und kann direkt heruntergeladen und abgespielt werden.
  • Vollständige API-Referenz. Der maschinenlesbare Vertrag ist unter api-docs.apro.at veröffentlicht.

Wählen Sie Ihren Einstieg

Ziel

Hier starten

Umsätze, Belege und Zahlungen abfragen

Umsatz API

Bestellungen von extern einspielen

Externe Bestellung API

Menükarten lesen oder bearbeiten

Menükarten API

Daten für BI / Warehouse exportieren

SQL Views

Anfragen signieren

Authentifizierung

Status und Versionierung

Endpunkte unter /api/v1/… gelten als stabil. Breaking Changes werden in den Release Notes auf apro.at angekündigt. Neue optionale Felder und nicht-brechende Ergänzungen in Antworten können jederzeit dazu kommen — Parser sollten unbekannte Keys tolerieren.

Support

  • Vertrieb & Onboarding: apro.at/support
  • Integrationsfragen: über Ihre APRO-Ansprechperson.

Authentifizierung

Alle APRO-REST-Endpunkte verwenden einen einzigen API-Key, übergeben im Request-Header x-api-key. Es gibt keinen OAuth-Flow und kein Token-Refresh — Keys sind langlebig und auf einen Mandanten oder einen einzelnen Standort beschränkt.

Header

POST /api/v1/external/order HTTP/1.1
Host: my.apro.at
x-api-key: sk_live_pLpXZq8S…
content-type: application/json

Key-Scope

Jeder APRO-REST-Key ist an einen Standort gebunden. Ein Request ist nur erfolgreich, wenn der aus dem Endpunkt abgeleitete Standort mit dem Standort des Keys übereinstimmt.

Endpunkt-Stil

Standort wird abgeleitet aus

POST /api/v1/reports/receipts

dem API-Key selbst.

POST /api/v1/locations/{location}/reports/receipts

{location} in der URL — muss mit dem Key-Standort übereinstimmen.

Ein standortgebundener Key versagt sicher, wenn er gegen einen anderen Standort verwendet wird, und ist unbedenklich an Partner oder ein einzelnes Deployment weiterzugeben.

Key beziehen

Keys werden vom APRO Account-Team ausgestellt. Anfrage an Ihre Ansprechperson oder an apro.at/support. Rotation wird unterstützt; der abgelöste Key bleibt eine Karenzzeit lang gültig, damit Sie ohne Ausfall umziehen können.

Keys aufbewahren

  • Niemals in das Versionskontrollsystem einchecken.
  • Keys in einem Secret Manager speichern (Azure Key Vault, 1Password, HashiCorp Vault, GitHub-Actions- oder Azure-DevOps-Secrets).
  • Zur Laufzeit als Umgebungsvariable (APRO_API_KEY) einspielen.

Fehlerantworten

Status

Bedeutung

401 Unauthorized

x-api-key-Header fehlt oder ist ungültig.

403 Forbidden

Key gültig, aber nicht für Standort oder Aktion berechtigt.

429 Too Many Requests

Rate-Limit erreicht. Mit exponentiellem Backoff erneut versuchen.

APIs

Umsatz API

Die Umsatz-API liefert alle umsatzrelevanten Transaktionen eines Zeitraums — samt Positionen, Zahlungen und Steueraufteilung — in einer einzigen Abfrage. Ideal für Buchhaltungsexporte, Umsatzabstimmung, BI-Warehouses oder Ad-hoc-Reports.

Tipp: Umsatz API oder SQL Views?
Die Umsatz-API und die SQL Views liefern
dieselben Daten in unterschiedlicher Form für unterschiedliche
Laufzeiten:

- Umsatz API — über den öffentlichen Cloud-Endpunkt
(my.apro.at). Pro Beleg ein hierarchisches JSON-Objekt mit
Positionen, Zahlungen, Steuersätzen, Bediener und Tisch bereits
verschachtelt. Ideal für Off-Premise-Systeme, SaaS-Integrationen,
Mobile Clients — überall, wo ein fertiger Objektbaum gewünscht ist.
- SQL Views — lesende Views direkt auf der
On-Premise-Kassendatenbank. Flach, relational, mit jedem
SQL-Server-fähigen Tool abfragbar. Ideal für On-Premise-
Integrationen, ETL-Pipelines und **bestehende BI-Workflows
(Power BI, Tableau, Qlik, Metabase)**.

Cloud-API für Realtime-System-zu-System-Integration, SQL-Views für
Bulk-Analytics und Dashboards.
  • Host: my.apro.at
  • Auth: API-Key im x-api-key
  • Content type: application/json

Endpunkte

Variante

Endpunkt

Standort aus

Pfad + Key

POST /api/v1/locations/{location}/reports/receipts

{location} muss mit dem Standort des Keys übereinstimmen.

Nur Key

POST /api/v1/reports/receipts

dem API-Key selbst.

Beide Varianten erfordern einen standortgebundenen API-Key. Der Key selbst bestimmt, welche Standortdaten geliefert werden — einen mandantenweiten Endpunkt gibt es nicht. Wähle, was im Client-Code klarer ist — beide liefern dieselben Daten und benötigen denselben Key.

Request-Body

Feld

Typ

Pflicht

Beschreibung

after

ISO 8601 Timestamp

✅

Nur Belege nach diesem Zeitpunkt.

before

ISO 8601 Timestamp

✅

Nur Belege vor diesem Zeitpunkt.

limit

integer

—

Anzahl zurückgelieferter Belege begrenzen.

afterTransactionID

GUID

—

Cursor — Transaktionen nach dieser ID liefern. Für Paging zusammen mit limit nutzen.

Beispielanfrage

POST /api/v1/reports/receipts

{
  "after":  "2026-01-01T00:00:00Z",
  "before": "2026-02-01T00:00:00Z",
  "limit":  100,
  "afterTransactionID": "f03e11de-6330-4a03-a8a8-b6e5fd4e32a5"
}

Download: revenue-request.json

Antwort

Ein Array von Belegen. Jedes Element ist eine Transaktion mit Positionen, Zahlungen und Metadaten.

Top-Level-Felder

Feld

Typ

Beschreibung

transactionId

GUID

Eindeutige Transaktions-ID.

transactionType

string

Siehe Transaktionstypen.

receiptType

string

Beleg, Ausgangsrechnung oder Lieferschein.

totalAmount

decimal

Bruttogesamtbetrag.

tip

decimal

Im totalAmount enthaltenes Trinkgeld.

discount

decimal

Abgezogener Rabatt.

dateTime

ISO 8601

Zeitpunkt der Transaktion.

business

Business

Unternehmen / Mandant.

location

Location

Der APRO-Standort, für den abgefragt wurde.

staff

Staff

Bediener / Kellner.

table

Table

Tisch oder Bestellziel (z. B. Bar 1, Zimmer 301).

area

Area

Bereich / Zone des Tisches (z. B. Garten, Bar).

customer

Customer · nullable

Rechnungsempfänger. Auf diesem Endpunkt immer null — siehe unten.

paymentGroup

Payment group

Gruppierung, in die die Zahlungsarten zusammenlaufen.

payments

[Payment]

Zahlungen zum Beleg.

items

[Item]

Belegpositionen.

guestCount

integer

Gästeanzahl, summiert über die Positionen des Belegs. 0, wenn nicht erfasst.

Item

Feld

Typ

Beschreibung

id

string

Eindeutige Artikel-ID.

number

integer

Artikelnummer (in APRO). 0, wenn nicht parsebar.

name

string

Artikelname.

uniqueId

string · nullable

Stabiler Identifier je Position. Zum Deduplizieren bzw. Abgleichen über wiederholte Abfragen hinweg. null bei älteren Query-Konfigurationen.

externalId

string · nullable

Referenz aus einem Fremdsystem. null bei älteren Query-Konfigurationen.

bookDate

ISO 8601

Buchungszeitpunkt.

grossAmount

decimal

Bruttopreis.

netAmount

decimal

Nettopreis.

purchasePrice

decimal

Einkaufspreis der Position, für Deckungsbeitrags-Auswertungen. 0 bei älteren Query-Konfigurationen.

quantity

decimal

Stückzahl.

course

integer · nullable

Gang bei mehrgängigem Service. null, wenn nicht zugeordnet.

tax

Tax

Angewendeter Steuersatz.

category

Category

Sparte.

sideDishes

[Side dish] · nullable

Unterartikel / Beilagen zu dieser Position. null, wenn keine vorhanden sind.

Side dish

Unterartikel sind die Beilagen und Modifikatoren, die unter einer Position gebucht werden — die Pommes zum Schnitzel, das ohne Zwiebel zum Burger. Sie werden verschachtelt in der übergeordneten Position zurückgegeben, nie als eigene Positionen.

Ein Unterartikel nutzt dieselbe Item-Struktur, befüllt aber nur die folgenden Felder. Alle übrigen — bookDate, externalId, purchasePrice, course, tax, category, sideDishes — sind null bzw. ihr Default-Wert; Unterartikel verschachteln sich nicht rekursiv.

Feld

Typ

Beschreibung

id

string

Eindeutige Unterartikel-ID.

number

integer

Artikelnummer des Unterartikels. 0, wenn nicht parsebar.

name

string

Bezeichnung des Unterartikels.

uniqueId

string

Stabiler Identifier je Unterartikel. Zugleich der Schlüssel, über den dedupliziert wird.

grossAmount

decimal

Bruttopreis des Unterartikels.

netAmount

decimal

Nettopreis des Unterartikels.

quantity

decimal

Stückzahl des Unterartikels.

Achtung: Unterartikel nicht zu den Summen addieren
Ein Unterartikel mit Aufpreis ist **bereits im grossAmount /
netAmount der übergeordneten Position** und im totalAmount des
Belegs enthalten. Wer sideDishes[].grossAmount zusätzlich aufaddiert,
zählt Umsatz doppelt. Die Beträge sind eine Aufschlüsselung für die
Anzeige, keine eigenständigen Positionen.

sideDishes ist null — nicht [] — wenn eine Position keine Unterartikel hat, ebenso bei älteren Query-Konfigurationen aus der Zeit vor diesem Feature. Daher auf null-oder-leer prüfen, statt ein Array vorauszusetzen.

Tax

Feld

Typ

Beschreibung

id

string

Eindeutige ID des Steuersatzes.

value

decimal

Höhe, z. B. 20.0 für 20 %.

name

string

Bezeichnung, z. B. Mwst 20 %.

Category

Feld

Typ

Beschreibung

id

string

Eindeutige Sparten-ID.

number

integer

Sparten-Nummer.

name

string

Bezeichnung.

Payment

Feld

Typ

Beschreibung

id

string

Eindeutige Zahlungs-ID. Zahlungen werden über diesen Wert dedupliziert.

amount

decimal

Gezahlter Betrag.

paymentType

PaymentType

Verwendetes Zahlungsmittel.

fibuAccountNr

string · nullable

Fibu-Kontonummer, auf die die Zahlung gebucht wird. Für die Zuordnung auf Sachkonten im Export.

Payment type

Feld

Typ

Beschreibung

id

integer · nullable

Eindeutige Zahlungsmittel-ID.

nr

integer · nullable

Zahlungsmittel-Nummer. Der Schlüssel heißt nr, nicht number.

name

string

Bezeichnung, z. B. Bar, Kreditkarte.

Business

Feld

Typ

Beschreibung

id

string

Mandanten-ID aus der Kassendatenbank.

name

string

Firmenname.

Staff

Feld

Typ

Beschreibung

id

string

Eindeutige Mitarbeiter-ID.

number

integer

Personalnummer.

name

string

Mitarbeitername.

Table

Feld

Typ

Beschreibung

id

string

Eindeutige Tisch-ID.

number

integer

Tischnummer.

name

string

Tischbezeichnung, z. B. Bar 1, Restaurant 5.

Location

Feld

Typ

Beschreibung

id

string

APRO-Standort-ID — die locationId, auf die die Abfrage aufgelöst wurde.

name

string · nullable

Standortname.

Area

Der Bereich bzw. die Zone, in der ein Tisch steht.

Feld

Typ

Beschreibung

id

string

Eindeutige Bereichs-ID.

number

integer

Bereichsnummer.

name

string

Bezeichnung, z. B. Garten, Bar.

Payment group

Die Gruppierung, in der die Zahlungsarten zusammenlaufen — für Kassabuch- und Abschluss-Auswertungen.

Feld

Typ

Beschreibung

id

integer

Zahlungsgruppen-ID. 0, wenn nicht gesetzt.

name

string

Bezeichnung der Gruppe.

Customer

Feld

Typ

Beschreibung

id

string

Eindeutige Kunden-ID.

number

integer

Kundennummer.

name

string

Kundenname.

Hinweis: Aktuell immer null
Der Receipts-Endpunkt befüllt customer nicht — das Feld ist im
Response-Contract reserviert, wird von der Abfrage aber nicht gesetzt.
Keine Rechnungsempfänger-Logik darauf aufbauen; Kunden stattdessen über
die eigenen Endpunkte auflösen.
Beispielantwort

Response · zwei direkt aufeinanderfolgende Abrechnungen

[
  {
    "transactionId": "f03e11de-6330-4a03-a8a8-b6e5fd4e32a5",
    "transactionType": "Abrechnen",
    "receiptType": "Beleg",
    "totalAmount": 3.2,
    "tip": 0.0,
    "discount": 0.0,
    "dateTime": "03.10.2021 21:59:57",
    "business": { "id": "1", "name": "APRO Kassensysteme" },
    "location": { "id": "7236", "name": "Wien Haupthaus" },
    "staff":    { "id": "8", "number": 9500, "name": "Onlinekellner" },
    "table":    { "id": "76", "number": 76, "name": "Daniel Z" },
    "area":     { "id": "3", "number": 3, "name": "Garten" },
    "customer": null,
    "paymentGroup": { "id": 2, "name": "Kartenzahlung" },
    "payments": [
      {
        "id": "04b2d2f5-8765-41b5-ad5c-aed802dc23bc",
        "amount": 3.2,
        "paymentType": { "id": 26, "nr": 200, "name": "Smorder Kreditkarte" },
        "fibuAccountNr": "2751"
      }
    ],
    "items": [
      {
        "id": "38",
        "number": 216,
        "name": "Wiener Schnitzel",
        "uniqueId": "0f6c2c14-3f8a-4f0b-9a1e-2b7d5c8e4411",
        "externalId": null,
        "bookDate": "03.10.2021 21:59:55",
        "grossAmount": 3.2,
        "netAmount": 3.2,
        "purchasePrice": 1.1,
        "quantity": 1.0,
        "course": 2,
        "tax": { "id": "5", "value": 5.0, "name": "Mwst 5 %" },
        "category": { "id": "12", "number": 12, "name": "Hauptspeisen" },
        "sideDishes": [
          {
            "id": "512",
            "number": 9001,
            "name": "Pommes",
            "uniqueId": "b41d9a77-55c2-4e3d-8f10-6cc0a2b93d88",
            "grossAmount": 1.5,
            "netAmount": 1.36,
            "quantity": 1.0
          },
          {
            "id": "518",
            "number": 9007,
            "name": "ohne Zwiebel",
            "uniqueId": "c9a70e31-1d44-4a92-b7e5-30f8c1d6a204",
            "grossAmount": 0.0,
            "netAmount": 0.0,
            "quantity": 1.0
          }
        ]
      }
    ],
    "guestCount": 2
  },
  {
    "transactionId": "db2b90d1-ab37-4557-b169-2289c3004a0a",
    "transactionType": "Abrechnen",
    "receiptType": "Beleg",
    "totalAmount": 3.2,
    "tip": 0.0,
    "discount": 0.0,
    "dateTime": "03.10.2021 22:00:12",
    "business": { "id": "1", "name": "APRO Kassensysteme" },
    "location": { "id": "7236", "name": "Wien Haupthaus" },
    "staff":    { "id": "8", "number": 9500, "name": "Onlinekellner" },
    "table":    { "id": "76", "number": 76, "name": "Daniel Z" },
    "area":     { "id": "3", "number": 3, "name": "Garten" },
    "customer": null,
    "paymentGroup": { "id": 2, "name": "Kartenzahlung" },
    "payments": [
      {
        "id": "bb3e6300-0cc3-4922-80c4-a8a4c3530d3c",
        "amount": 3.2,
        "paymentType": { "id": 26, "nr": 200, "name": "Smorder Kreditkarte" },
        "fibuAccountNr": "2751"
      }
    ],
    "items": [
      {
        "id": "38",
        "number": 216,
        "name": "Cola 0,5",
        "uniqueId": "7d2f3b90-6c11-4c8e-93aa-5e4419c7f0d3",
        "externalId": null,
        "bookDate": "03.10.2021 22:00:11",
        "grossAmount": 3.2,
        "netAmount": 3.2,
        "purchasePrice": 0.7,
        "quantity": 1.0,
        "course": null,
        "tax": { "id": "5", "value": 5.0, "name": "Mwst 5 %" },
        "category": { "id": "20", "number": 20, "name": "Alkoholfreie Getränke" },
        "sideDishes": null
      }
    ],
    "guestCount": 1
  }
]

Dieselbe Payload als eigenständige Datei herunterladen: revenue-response.json

Transaktionstypen

Der transactionType unterscheidet, was eine Zeile repräsentiert. Für umsatzrelevante Reports auf Abrechnen filtern (und RechnungZurückholen für Rückholungen).

Typ

Bedeutung

Bonieren

Artikel auf offenen Tisch boniert.

Verschieben

Tischwechsel.

Abrechnen

Abschluss — bezahlter Beleg.

Stornieren

Stornierung einer Buchung.

RechnungZurückholen

Rücknahme einer Abrechnung.

DebitBonieren

Debit-Buchung.

ARAusstellen · ARPreview · ARNachdruck · ARNachdruckOriginal

Ausgangsrechnungs-Lifecycle.

Tagesabschluss · TagesabschlussPreview

Tagesabschluss / Vorschau.

Kellnerabschlag · KellnerabschlagPreview · KellnerabschlagUndo · Kellneruebergabe

Kellnerübergabe / Abschlag.

Startbeleg · Schlussbeleg · Nullbeleg · Monatsbeleg · JahresBeleg · Nacherfassungssammelbeleg

Gesetzliche Fiskalbelege (RKSV).

TischOeffnen · TischSchliessen · GaesteanzahlAendern · GaesteanzahlKorrigieren

Tisch-Lifecycle.

BestellmanagerBestellung · …NextState · …SetFinaleState · …Undo · …Stornieren · …Verschieben · …NaechsterGang · …Statistik

Bestellmanager-Events.

WarenkorbErstellen · WarenkorbUpdate · WarenkorbVerwerfen

Warenkorb-Lifecycle.

DatenExport · UndoDatenExport

Datenexport-Markierungen.

Kassabuch · Bargeldzählung · TerminalKassenschnitt · Lieferantabschlag · LieferantabschlagVorschau · Ansehen · Information · SchankanlageQrCode · Abrechnen HSK · Abrechnen AR · AbrechnenNachdruck

Weitere operative Ereignisse.

Umsatzberechnung

Um den umsatzrelevanten Umsatz einer Antwort zu berechnen, behält man bezahlte Abrechnungen samt Rückholungen und summiert die Netto-Positionen:

response
  .filter(x =>
    x.paymentGroup.name === "Umsatzwirksam" &&
    ["Abrechnen", "RechnungZurückholen"].includes(x.transactionType)
  )
  .map(x =>
    x.items.reduce((sum, item) => sum + item.netAmount * item.quantity, 0)
  )
  .reduce((sum, val) => sum + val, 0);

Paging

Es gibt keinen Cursor in der Antwort — nutzen Sie die deterministische transactionId der zuletzt gesehenen Zeile als afterTransactionID der nächsten Anfrage, after/before bleiben unverändert. So lange iterieren, bis die Antwort weniger Zeilen liefert als Ihr limit.

Fehler

Siehe Authentifizierung → Fehlerantworten.

Externe Bestellung API

Die Externe-Bestellung-API nimmt eine vollständig formulierte Bestellung aus einem beliebigen Fremdsystem entgegen — Lieferaggregator, White-Label- App, Kiosk, Voice-Agent — und steuert sie in die laufende APRO-Kassa am Zielstandort ein. Ein einziger POST genügt; die Bestellung erscheint sofort im Bestellmanager.

  • Endpunkt: POST https://my.apro.at/api/v1/external/order
  • Auth: API-Key im x-api-key
  • Content type: application/json

Bestellung einem Standort zuordnen

Die Bestellung muss genau einem Standort zugeordnet sein. Geben Sie einen der folgenden Werte mit — der erste nicht-leere Wert gewinnt:

  1. LocationID — numerische ID
  2. LocationShortCode — Kurz-Identifier (z. B. vienna-main)
  3. SpotID — konkreter Tisch/Zimmer/Abholpunkt (Standort wird abgeleitet)
  4. QRCode — gescannter APRO-QR-Code (Standort und Spot werden abgeleitet)

Fehlen alle, wird die Anfrage abgelehnt.

Request-Body

Feld

Typ

Pflicht

Beschreibung

guid

string

—

Idempotenz-Schlüssel. Kommt derselbe guid zweimal, wird das Duplikat ignoriert.

locationId

int?

◻︎

Siehe Routing oben.

locationShortCode

string

◻︎

Siehe Routing oben.

spotId

int?

—

ID des Bestellziels (Tisch, Zimmer, Abhol-, Lieferstandort — alles Spots, nur unterschiedliche Typen). Entspricht spot.id der Externer-Katalog-API.

spotNumber

int?

—

Nummer des Spots, wenn die ID unbekannt ist. Aufgelöst über spot.number.

spotName

string

—

Fallback, wenn weder spotId noch spotNumber auflöst. Lookup über spot.name — bevorzugt einen Spot vom Typ Table, danach beliebiger Spot am Standort.

roomNumber

int?

—

Nummer des Zimmer-Spots für Hotel-/Zimmerabrechnungs-Flows. Aufgelöst gegen einen SpotType.Room-Spot über spot.number (verschoben um RoomNrSpotNrOffset des Standorts); Fallback auf spot.alternativeName gegen die rohe Zimmernummer. Bei Treffer wird spotId gesetzt (falls noch nicht vorhanden) und die Bestellung an die aktive Reservierung des Zimmers gebunden.

roomId

int?

—

Für zukünftige Erweiterungen reserviert. Wird aktuell akzeptiert, aber ignoriert — für ein Zimmer den spotId eines Room-Spots verwenden.

userInfo

UserInfo

✅

Wer bestellt — email ist Pflicht.

deliveryInfo

DeliveryInfo

—

Lieferadresse. Pflicht bei Zustellung.

takeaway

bool

✅

true für Mitnehmen, false für Vor Ort.

note

string

—

Freitext-Notiz des Gastes (Allergene, Sonderwünsche).

qrCode

string

—

Siehe Routing.

entries

[Entry]

✅

Bestellte Artikel.

preOrderTime

long?

—

Vorbestellzeitpunkt, Unix-Zeit in Millisekunden UTC.

shortCode

string

—

Abhol-/Lieferreferenz (z. B. zw1356).

tip

decimal?

—

Trinkgeldhöhe.

isPaid

bool

—

true, wenn die Bestellung bereits extern bezahlt wurde.

grandTotal

decimal?

✅

Summe aller Artikel + Tip. Wird serverseitig gegengeprüft.

Entry

Feld

Typ

Pflicht

Beschreibung

productNr

string

✅

Produktnummer in APRO.

name

string

—

Anzeigename — rein informativ; APRO vertraut auf productNr.

preOrderTime

long?

—

Vorbestellzeit pro Artikel.

quantity

double

✅

Bestellmenge.

price

double?

—

Übersteuert den Systempreis dieser Position (Rabatte, Kampagnen).

note

string

—

Artikel-Notiz (z. B. Ohne Zwiebeln).

addons

[Addon]

—

Zusatzoptionen.

Addon

Feld

Typ

Pflicht

Beschreibung

productNr

string

✅

Produktnummer des Addons.

name

string

—

Anzeigename.

quantity

double

✅

Menge.

price

double?

—

Übersteuert den Systempreis.

UserInfo

Feld

Typ

Pflicht

Beschreibung

firstName

string

—


lastName

string

—


email

string

✅

Kontakt-E-Mail für Belege und Status-Updates.

phone

string

—


locale

string

—

IETF-Locale, z. B. de-DE.

nickname

string

—

Kurzname für Besteller/Abholer.

DeliveryInfo

Feld

Typ

Beschreibung

name

string

Bezeichnung der Lieferadresse.

companyName

string

Firmenname.

city

string

Ort.

zip

string

PLZ.

street

string

Straße.

country

string

Land.

floor

string

Stockwerk.

stairway

string

Stiege.

door

string

Türe.

note

string

Hinweis zur Lieferadresse.

Beispiele

Minimalbestellung

POST /api/v1/external/order

{
  "locationShortCode": "vienna-main",
  "takeaway": true,
  "userInfo": { "email": "guest@example.com" },
  "entries": [{ "productNr": "123", "quantity": 1 }],
  "grandTotal": 9.99
}

Download: external-order-minimal.json

Vollständige Bestellung mit Lieferung und Beilagen

POST /api/v1/external/order

{
  "guid": "123e4567-e89b-12d3-a456-426614174000",
  "locationId": 101,
  "spotId": 15,
  "spotNumber": 10,
  "shortCode": "zw1356",
  "userInfo": {
    "firstName": "Max",
    "lastName": "Mustermann",
    "email": "max.mustermann@example.com",
    "phone": "+491234567890",
    "locale": "de-DE",
    "nickname": "Max"
  },
  "takeaway": false,
  "note": "Bitte schnell liefern",
  "qrCode": "QR1234567890",
  "deliveryInfo": {
    "name": "Max Mustermann",
    "city": "Wien",
    "zip": "1010",
    "street": "Stephansplatz 1",
    "country": "AT",
    "floor": "3",
    "stairway": "A",
    "door": "12"
  },
  "entries": [
    {
      "productNr": "123",
      "name": "Wiener Schnitzel",
      "quantity": 2,
      "price": 9.99,
      "note": "Extra scharf",
      "addons": [
        { "productNr": "501", "name": "Pommes", "quantity": 2, "price": 3.5 }
      ]
    },
    { "productNr": "31231", "name": "Burger Classic", "quantity": 1, "note": "Ohne Zwiebeln" }
  ],
  "preOrderTime": 1723574400000,
  "tip": 1.2,
  "isPaid": true,
  "grandTotal": 28.18
}

Download: external-order-full.json

Idempotenz

Setzen Sie guid immer auf eine stabile, client-generierte UUID. APRO nutzt sie zur Deduplizierung bei Retries — geht eine Antwort verloren und Sie senden erneut, ist der zweite POST ein No-Op und liefert Erfolg.

Preisberechnung

APRO berechnet jede Position serverseitig aus productNr neu. price auf einem Entry oder Addon greift nur, wenn explizit übergeben; grandTotal wird mit dem Server-Total abgeglichen, inkonsistente Payloads werden abgelehnt. So wird der Wert zugleich Sicherheitsnetz und Vehikel für Kampagnen-Rabatte.

Externer Katalog API

Der Externe-Katalog-API bietet eine schlanke, nur lesende Sicht auf die Stammdaten eines APRO-Standorts — Artikel, Warengruppen und Spots (Tische, Zimmer, Abholpunkte) — zur Verwendung in White-Label- Apps, Kiosks, externen Speisekarten und Partnerintegrationen.

Die drei Endpunkte teilen sich dieselben Konventionen: Scope per Standort, verschachtelte DTO-Struktur, Filterung über Query-Parameter und Paginierung.

  • Host: my.apro.at
  • Auth: API-Key im x-api-key
  • Content type: application/json
Tipp: In Postman ausprobieren
Lade die fertige Collection herunter:
apro-external-catalog.postman_collection.json.
In Postman importieren, im Variables-Tab der Collection apiKey und
locationShortCode setzen, dann Send drücken.
Achtung: Filter verwenden eine Bracket-Syntax
Alle Filterfelder — inklusive des Pflichtfeldes locationId /
locationShortCode — müssen als filter['<name>']=<value> übergeben
werden, nicht als gewöhnliche Query-Parameter. Schlichtes
?locationShortCode=… wird stillschweigend ignoriert; die Anfrage
schlägt dann mit „location not resolved" fehl. Details unter
Filterung.

Endpunkte

Methode

URL

Liefert

GET

/api/v1/external/product

Paginierte Liste von Product

GET

/api/v1/external/product-category

Paginierte Liste von ProductCategory

GET

/api/v1/external/spot

Paginierte Liste von Spot

Gemeinsame Konventionen

Standort-Scope

Jede Anfrage muss genau einen Standort adressieren. Geben Sie einen der folgenden Werte mit:

  1. locationId — numerische ID
  2. locationShortCode — Kurz-Identifier (z. B. vienna-main)

Lässt sich aus keinem von beiden ein Standort auflösen, wird die Anfrage mit 400 Bad Request abgelehnt.

Paginierung

Query-Parameter

Default

Beschreibung

skip

0

Anzahl der zu überspringenden Datensätze.

take

50

Seitengröße. Limit 500.

Jede Antwort steckt in einem Paged-Envelope:

{
  "page": { "offset": 0, "limit": 50, "total": 1234 },
  "data": [ /* ... */ ]
}
  • offset und limit spiegeln die Anfrage.
  • total ist die Gesamtanzahl aller passenden Datensätze über alle Seiten — Grundlage für skip/take-Schleifen.
Filterung

Filter werden als bracketed Query-Parameter unter dem Schlüssel filter übergeben — filter['<name>']=<value>. Schlichtes ?<name>=<value> wird ignoriert. Paginierung (skip, take) bleibt als gewöhnliche Query-Parameter.

Listen-Filter (productIds, gtins, externalSpotIds, …) werden kommagetrennt übergeben. Literale Kommas mit \, escapen.

GET /api/v1/external/product?filter['locationShortCode']=vienna-main
  &filter['productNrs']=A-100,A-200
  &filter['isVisible']=true
  &filter['search']=schnitzel
  &skip=0&take=100

Hinweise:

  • Der Klammer-Key ist case-insensitive — filter['LocationShortCode'] und filter['locationShortCode'] sind äquivalent.
  • filter["…"] (doppelte Anführungszeichen, URL-kodiert als %22) funktioniert ebenfalls.
  • Unbekannte Filterparameter werden ignoriert.

Artikel

GET https://my.apro.at/api/v1/external/product
Query-Parameter

Parameter

Typ

Beschreibung

locationId

int?

Pflicht (oder locationShortCode).

locationShortCode

string

Pflicht (oder locationId).

cashboxId

int?

Auf eine Kassa einschränken. Muss zum Standort gehören.

search

string

Volltextsuche über Name, externer Name, productNr, GTIN und externe Artikel-ID.

productIds

int[]

Filter nach APRO-Artikel-ID.

productNrs

string[]

Filter nach productNr (der Wert, der bei External-Order-Einträgen zurückgegeben wird).

gtins

string[]

Filter nach GTIN (Barcode).

externalProductIds

string[]

Filter nach externer Artikel-ID (Drittsystem-Referenz).

isVisible

bool?

Filter nach Sichtbarkeit.

isLocked

bool?

Filter nach Sperr-Flag.

Product

Feld

Typ

Beschreibung

id

int

APRO-Cloud-Artikel-ID.

productNr

string

Identifier, mit dem die Bestell-Pipeline diesen Artikel auflöst. Diesen Wert direkt als productNr des Eintrags beim Anlegen einer Bestellung via Externe-Bestellung-API verwenden. Basiert auf der POS-Artikelnummer.

gtin

string

Barcode / GTIN.

externalProductId

string

POS-Artikel-ID — die im lokalen Kassensystem vergebene ID. Informativ; nicht der Wert, gegen den die External-Order-productNr aufgelöst wird.

name

string

Anzeigename.

externalName

string

Name für externe Oberflächen — Fallback auf name, wenn leer.

subtitle

string

Kurz-Untertitel.

description

string

Ausführliche Beschreibung.

pricing

Pricing

Preis- und Steuerinfo.

cashbox

ProductCashbox

Kassa / Eltern-Artikel / Produktionsplatz.

media

Media

Bild-URLs.

flags

Flags

Sichtbarkeit, Sperre, Review-Status.

nutrition

Nutrition

Menge, Kalorien, Allergene, Altersgrenze.

productGroupExternalIds

string[]

Externe IDs der Warengruppen, denen der Artikel angehört.

localizations

Localization[]

Gepflegte Übersetzungen der Artikeltexte und des Bildes, ein Eintrag je Sprache. Leer, wenn keine vorhanden sind.

Pricing

Feld

Typ

Beschreibung

defaultPrice

double?

Standardpreis.

referencePrice

double?

Referenz-/Streichpreis.

taxRateId

int?

APRO-Steuersatz-ID.

allowClientPrice

bool

Ob Clients den Preis überschreiben dürfen.

ProductCashbox

Feld

Typ

Beschreibung

cashboxId

int

Zugehörige Kassa.

productionPlaceId

int?

Küche / Bar, in der zubereitet wird.

parentProductId

int?

Bei Varianten / Template-Ableitungen gesetzt.

Media

Feld

Typ

Beschreibung

imageUrl

string

Öffentliche Bild-URL.

Flags

Feld

Typ

Beschreibung

isVisible

bool

Sichtbar in Storefronts.

isLocked

bool

Gegen automatische Sync-Änderungen gesperrt.

queuedForReview

bool

Wartet auf manuelles Review.

Nutrition

Feld

Typ

Beschreibung

amount

double?

Nettomenge.

amountUnit

string

Einheit, z. B. g, ml.

calories

double?

Kalorien pro amount.

allergens

int (Flags)

Bitflag der deklarierten Allergene.

minimumAge

int

Mindestkäuferalter (0, wenn keines).

ingredients

[Ingredient]

Deklarierte Zutatenliste. Leer-Array, wenn nicht gepflegt.

nutritionalValues

[NutritionalValue]

Strukturierte Nährwerte. Leer-Array, wenn nicht gepflegt.

Ingredient

Feld

Typ

Beschreibung

name

string

Zutaten-Bezeichnung (z. B. Lamm, Weizenmehl).

NutritionalValue

Feld

Typ

Beschreibung

name

string

Wert-Bezeichnung (z. B. Gewicht, Kalorien, Fett).

unit

string

Einheit, freier Text (z. B. g, ml, kcal).

value

string

Numerischer Wert, als String serialisiert, um das Quellformat zu erhalten. Client-seitig parsen, falls nötig.

belongsTo

int?

Optionale Referenz auf eine Unterkomponente / Variante (null, wenn der Wert für das Gesamtprodukt gilt).

Localization

Feld

Typ

Beschreibung

locale

string

Sprach- / Culture-Schlüssel der Übersetzung (z. B. en, it, de-DE).

name

string

Übersetzter Anzeigename.

subtitle

string

Übersetzter Untertitel.

description

string

Übersetzte ausführliche Beschreibung.

imageUrl

string

Sprachspezifisches Bild, sofern gepflegt.

name, subtitle, description und media.imageUrl auf oberster Ebene enthalten weiterhin die Standardwerte (Deutsch) — localizations ersetzt sie nicht, sondern ergänzt nur die vorhandenen Übersetzungen. Einträge ohne Sprachschlüssel werden weggelassen, und einzelne Felder eines Eintrags können null sein, wenn genau dieser Text nicht übersetzt wurde. Zur Anzeige die gewünschte Sprache in localizations suchen und auf das Feld der obersten Ebene zurückfallen.

Beispiel
GET /api/v1/external/product?filter['locationShortCode']=vienna-main&filter['isVisible']=true&take=2
{
  "page": { "offset": 0, "limit": 2, "total": 1247 },
  "data": [
    {
      "id": 9001,
      "productNr": "A-100",
      "gtin": "9001234567890",
      "externalProductId": "wiener-schnitzel",
      "name": "Wiener Schnitzel",
      "externalName": "Wiener Schnitzel",
      "subtitle": "Mit Erdäpfelsalat",
      "description": "Klassisches Wiener Schnitzel vom Kalb.",
      "pricing": {
        "defaultPrice": 22.9,
        "referencePrice": null,
        "taxRateId": 3,
        "allowClientPrice": false
      },
      "cashbox": { "cashboxId": 17, "productionPlaceId": 2, "parentProductId": null },
      "media": { "imageUrl": "https://cdn.smorder.at/locations/7236/p/9001.jpg" },
      "flags": { "isVisible": true, "isLocked": false, "queuedForReview": false },
      "nutrition": {
        "amount": 250,
        "amountUnit": "g",
        "calories": 720,
        "allergens": 67,
        "minimumAge": 0,
        "ingredients": [{ "name": "Kalb" }, { "name": "Weizenmehl" }, { "name": "Ei" }],
        "nutritionalValues": [
          { "name": "Gewicht", "unit": "g", "value": "250", "belongsTo": null },
          { "name": "Fett",    "unit": "g", "value": "30",  "belongsTo": null }
        ]
      },
      "productGroupExternalIds": ["mains", "austrian"],
      "localizations": [
        {
          "locale": "en",
          "name": "Viennese Schnitzel",
          "subtitle": "With potato salad",
          "description": "Classic Viennese veal schnitzel.",
          "imageUrl": null
        },
        {
          "locale": "it",
          "name": "Cotoletta alla viennese",
          "subtitle": null,
          "description": null,
          "imageUrl": null
        }
      ]
    }
  ]
}

Download: external-products-response.json

Warengruppen

GET https://my.apro.at/api/v1/external/product-category

Warengruppen entsprechen den APRO Product Groups — die kassenbezogene Gruppierung, die Reports, Steuersätze und Druckreihenfolge steuert. Das Feld productGroupExternalIds auf einem Artikel referenziert genau die externalId, die hier zurückkommt — damit lassen sich beide Antworten ohne zusätzliche Lookups verbinden.

Query-Parameter

Parameter

Typ

Beschreibung

locationId

int?

Pflicht (oder locationShortCode).

locationShortCode

string

Pflicht (oder locationId).

cashboxId

int?

Auf eine Kassa einschränken. Muss zum Standort gehören.

search

string

Volltextsuche über Name, externer Name und externe ID.

categoryIds

int[]

Filter nach APRO-Warengruppen-ID.

externalIds

string[]

Filter nach externer ID.

ProductCategory

Feld

Typ

Beschreibung

id

int

APRO-Cloud-Warengruppen-ID.

externalId

string

POS-Warengruppen-ID — die im lokalen Kassensystem vergebene ID.

name

string

Anzeigename.

externalName

string

Name für externe Oberflächen.

cashbox

CategoryCashbox

Kassa / Lieferant.

tax

CategoryTax

Steuersatz.

localizations

CategoryLocalization[]

Gepflegte Übersetzungen der Warengruppentexte und des Bildes, ein Eintrag je Sprache. Leer, wenn keine vorhanden sind.

CategoryCashbox

Feld

Typ

Beschreibung

cashboxId

int?

Zugehörige Kassa.

supplierId

int?

Standard-Lieferant für Artikel der Warengruppe.

CategoryTax

Feld

Typ

Beschreibung

taxRateId

int?

Standard-Steuersatz für Artikel der Warengruppe.

vat

double

USt-Prozentsatz (z. B. 20 für 20 %).

CategoryLocalization

Feld

Typ

Beschreibung

locale

string

Sprach- / Culture-Schlüssel der Übersetzung (z. B. en, it, de-DE).

name

string

Übersetzter Anzeigename.

description

string

Übersetzte Beschreibung.

imageUrl

string

Sprachspezifisches Bild, sofern gepflegt.

Es gelten dieselben Regeln wie bei Artikeln: name auf oberster Ebene bleibt der deutsche Standardwert, localizations ergänzt nur das, was vorhanden ist.

Beispiel
GET /api/v1/external/product-category?filter['locationShortCode']=vienna-main
{
  "page": { "offset": 0, "limit": 50, "total": 14 },
  "data": [
    {
      "id": 41,
      "externalId": "mains",
      "name": "Hauptspeisen",
      "externalName": "Mains",
      "cashbox": { "cashboxId": 17, "supplierId": null },
      "tax": { "taxRateId": 3, "vat": 10 },
      "localizations": [
        { "locale": "en", "name": "Mains", "description": null, "imageUrl": null },
        { "locale": "it", "name": "Secondi", "description": null, "imageUrl": null }
      ]
    },
    {
      "id": 42,
      "externalId": "drinks-alcoholic",
      "name": "Getränke (Alkohol)",
      "externalName": "Alcoholic drinks",
      "cashbox": { "cashboxId": 17, "supplierId": null },
      "tax": { "taxRateId": 1, "vat": 20 },
      "localizations": []
    }
  ]
}

Download: external-product-categories-response.json

Spots

GET https://my.apro.at/api/v1/external/spot

Spots sind die physischen / logischen Ziele, an die ein Gast bestellen kann: Tische, Zimmer, Abholtheken, Lieferzonen, Drive-in.

Query-Parameter

Parameter

Typ

Beschreibung

locationId

int?

Pflicht (oder locationShortCode).

locationShortCode

string

Pflicht (oder locationId).

areaId

int?

Auf einen Bereich / ein Zimmer einschränken.

search

string

Volltextsuche über Name, Alternativname, externe Spot-ID und QR-Code.

spotIds

int[]

Filter nach APRO-Spot-ID.

externalSpotIds

string[]

Filter nach externer Spot-ID.

type

enum

Spot / Table / Room / Pickup / Delivery / …

visibleInApp

bool?

Filter nach App-Sichtbarkeit.

supportsTableOrder

bool?

Filter nach Tischbestell-Fähigkeit.

Spot

Feld

Typ

Beschreibung

id

int

APRO-Cloud-Spot-ID.

externalSpotId

string

POS-Spot-ID — die im lokalen Kassensystem vergebene ID.

number

int?

Numerisches Label (z. B. Tischnummer).

name

string

Anzeigename.

alternativeName

string

Sekundärer Name für manche Oberflächen.

description

string

Freitext-Beschreibung.

qrCode

string

QR-Code-Payload des Spots.

type

enum

Spot-Typ.

capacity

int?

Sitzplätze (Tisch) / Maximalgäste (Zimmer).

location

SpotLocation

Zugehöriger Standort.

area

SpotArea

Zugehöriger Bereich, falls vorhanden.

geo

SpotGeo

Geokoordinaten. null, wenn beide Werte fehlen.

flags

SpotFlags

Fähigkeits-Flags.

SpotLocation

Feld

Typ

Beschreibung

locationId

int

Numerische Standort-ID.

locationShortCode

string

Kurzcode für Cross-API-Joins.

SpotArea

Feld

Typ

Beschreibung

areaId

int

APRO-Bereichs-ID.

name

string

Anzeigename des Bereichs.

externalAreaId

string

Externe Referenz des Bereichs.

SpotGeo

Feld

Typ

Beschreibung

latitude

double?

WGS84-Breite.

longitude

double?

WGS84-Länge.

SpotFlags

Feld

Typ

Beschreibung

visibleInApp

bool

Sichtbar in Storefronts.

supportsTableOrder

bool

Akzeptiert Tischbestellungen.

supportsCashPayment

bool

Akzeptiert Bargeld.

defaultToTakeAway

bool

Default-Bestellmodus ist Takeaway.

displayAsButton

bool

Als Button statt Listeneintrag darstellen.

Beispiel
GET /api/v1/external/spot?filter['locationShortCode']=vienna-main&filter['type']=Table&filter['visibleInApp']=true&take=2
{
  "page": { "offset": 0, "limit": 2, "total": 38 },
  "data": [
    {
      "id": 1501,
      "externalSpotId": "table-01",
      "number": 1,
      "name": "Tisch 1",
      "alternativeName": null,
      "description": null,
      "qrCode": "QR-01",
      "type": "Table",
      "capacity": 4,
      "location": { "locationId": 7236, "locationShortCode": "vienna-main" },
      "area": { "areaId": 11, "name": "Gastgarten", "externalAreaId": "garden" },
      "geo": null,
      "flags": {
        "visibleInApp": true,
        "supportsTableOrder": true,
        "supportsCashPayment": true,
        "defaultToTakeAway": false,
        "displayAsButton": false
      }
    }
  ]
}

Download: external-spots-response.json

Cross-API-Joins

Die drei Endpunkte sind so geschnitten, dass sie sich client-seitig verbinden lassen:

  • product.productNr ↔ productNr (Entry / Addon) der Externe-Bestellung-API — case-insensitive, gematcht ausschließlich gegen Artikel auf der Primär-Kassa des Standorts. Wenn productNr nicht auflöst, greift im Bestell-Pipeline ein Fallback über den name-Wert des Entries gegen product.name.
  • product.productGroupExternalIds[i] ↔ productCategory.externalId
  • product.cashbox.cashboxId / productCategory.cashbox.cashboxId ↔ cashboxId-Filter auf beiden Endpunkten
  • spot.location.locationShortCode ↔ locationShortCode-Filter überall
  • spot.id ↔ SpotID der External-Order-API — die id des Spots direkt zurückgeben, um eine Bestellung an denselben Spot zurückzurouten, von dem sie gelesen wurde. spot.qrCode entspricht dem QRCode-Feld der External-Order-API.

Fehler

Status

Bedeutung

400 Bad Request

Weder locationId noch locationShortCode lieferten einen gültigen Standort.

401 Unauthorized

x-api-key fehlt oder ist ungültig.

403 Forbidden

Key nicht für den Standort autorisiert.

429 Too Many Requests

Rate-Limit — mit Jitter zurücknehmen und erneut versuchen.

Menükarten API

Die Menükarten-API liefert die öffentliche Menüstruktur für White-Label-Apps, Selbstbedienungskiosks und QR-Ordering-Flows. Das Lesen erfordert keine Authentifizierung; jede schreibende Aktion schon.

  • Host: my.apro.at
  • Content type: application/json

Menü abrufen

GET https://my.apro.at/api/v1/locations/{locationId}/menus

Die Antwort ist ein Baum. Ebenen von außen nach innen:

  1. Menu — oberste Gruppierung (z. B. Speisen, Getränke, Angebote).
  2. Category — Abschnitt innerhalb eines Menüs (z. B. Hauptspeisen).
  3. MenuEntry — konkreter verkaufbarer Artikel.
  4. MenuEntryFeature — Beilagen-/Modifier-Gruppe zu einem Artikel.
  5. MenuEntryFeatureValue — einzelne Option innerhalb einer Gruppe.
  6. Hours — Gültigkeitsfenster, die an Menüs und Kategorien hängen.
Beispielantwort (gekürzt)

GET /api/v1/locations/{locationId}/menus

[
  {
    "name": "Speisen",
    "badge": 7,
    "imageUrl": "https://cdn.smorder.at/locations/7236/menus/speisen.jpg",
    "description": "Klassiker aus der österreichischen Küche.",
    "priceName": "Hauspreis",
    "orderableHours": [
      { "days": 254, "startTime": "11:00", "endTime": "22:00" }
    ],
    "categories": [
      {
        "name": "Hauptspeisen",
        "menuEntries": [
          {
            "menuEntryID": 4001,
            "name": "Wiener Schnitzel",
            "badge": 7,
            "price": 22.9,
            "features": [
              {
                "name": "Beilage",
                "groupName": "Beilage",
                "minValuesRequired": 1,
                "maxValuesAllowed": 1,
                "valuesForFree": 1,
                "values": [
                  { "name": "Erdäpfelsalat", "priceAdjustment": 0 },
                  { "name": "Pommes frites",  "priceAdjustment": 0 },
                  { "name": "Reis",           "priceAdjustment": 0 }
                ]
              },
              {
                "name": "Ohne",
                "isDeselectFeature": true,
                "values": [
                  { "name": "Preiselbeeren", "priceAdjustment": 0 }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

Vollständiges, ungekürztes Beispiel herunterladen: menu-response.json

1. Menu

Oberste Gruppierung im Menübaum; enthält Kategorien. Beispiele: Speisen, Getränke, Angebote.

Feld

Typ

Beschreibung

name

string

Anzeigename.

badge

enum

Optischer Badge — siehe Badges.

imageUrl

string

URL zum Titelbild.

description

string

Beschreibungstext.

categories

Category[]

Kategorien in diesem Menü.

orderableHours

Hours[]

Zeiten, in denen Bestellungen möglich sind.

preOrderableHours

Hours[]

Zeiten, für die Vorbestellungen möglich sind.

deliverableHours

Hours[]

Zeiten, in denen Zustellung möglich ist.

priceName

string

Zu verwendende Preisebene (z. B. Hauspreis, Happy Hour).

Mutationen (Auth erforderlich):

Aktion

Methode

Pfad

Anlegen

POST

/api/v1/customer/locations/{locationId}/menu

Aktualisieren

PUT

/api/v1/customer/menus/{menuId}

Löschen

DELETE

/api/v1/customer/menus/{menuId}

2. Category

Unterpunkt eines Menüs. Beispiele: Alkoholfreie Getränke, Bier, Hauptspeisen, Nachspeisen.

Feld

Typ

Beschreibung

name

string

Anzeigename.

badge

enum

Siehe Badges.

imageUrl

string

URL zum Titelbild.

description

string

Beschreibungstext.

menuEntries

MenuEntry[]

Einträge in dieser Kategorie.

orderableHours

Hours[]

Bestellzeiten.

preOrderableHours

Hours[]

Vorbestellzeiten.

deliverableHours

Hours[]

Lieferzeiten.

Mutationen (Auth erforderlich):

Aktion

Methode

Pfad

Anlegen

POST

/api/v1/customer/menus/{menuId}/category

Aktualisieren

PUT

/api/v1/customer/menus/{menuId}/category/{id}

Löschen

DELETE

/api/v1/customer/menus/{menuId}/category/{id}

3. MenuEntry

Konkret verkaufbarer Artikel. Beispiele: Wiener Schnitzel, Kaiser Bier.

Feld

Typ

Beschreibung

menuEntryID

integer

Eindeutige ID; wird mit der Bestellung zurückgeschickt.

name

string

Artikelname.

badge

enum

Siehe Badges.

imageUrl

string

URL zum Artikelbild.

subtitle

string

Kurzbeschreibung.

description

string

Beschreibungstext.

features

MenuEntryFeature[]

Beilagen-Gruppen (Größen, Beilagen …).

price

money

Verkaufspreis.

Mutationen (Auth erforderlich):

Aktion

Methode

Pfad

Anlegen

POST

/api/v1/customer/categories/{categoryId}/menuEntry

Aktualisieren

PUT

/api/v1/customer/menuEntries/{id}

Löschen

DELETE

/api/v1/customer/menuEntries/{id}

4. MenuEntryFeature

Beilagen-/Modifier-Gruppe eines MenuEntry. Beispiele: Gebindegröße, Beilage zur Hauptspeise.

Feld

Typ

Beschreibung

name

string

Bezeichnung der Beilage.

description

string

Beschreibungstext.

groupName

string

Gruppierungsschlüssel für seitenweise Anzeige.

isDeselectFeature

boolean

true markiert eine Abwahl-Option (z. B. Ohne Zwiebeln).

maxValuesAllowed

number

Maximalanzahl auswählbarer Optionen.

minValuesRequired

number

Mindestanzahl auswählbarer Optionen.

valuesForFree

number

Anzahl der Optionen ohne Aufpreis.

values

MenuEntryFeatureValue[]

Verfügbare Optionen.

Mutationen (Auth erforderlich):

Aktion

Methode

Pfad

Anlegen

POST

/api/v1/menuentryfeature

Aktualisieren

PUT

/api/v1/menuentryfeature/{featureId}

Löschen

DELETE

/api/v1/menuentryfeature/{featureId}

5. MenuEntryFeatureValue

Einzelne Ausprägung einer Beilage. Beispiele: Bier 0,3 l, Bier 0,5 l, Pommes, Kartoffeln.

Feld

Typ

Beschreibung

name

string

Bezeichnung.

description

string

Beschreibungstext.

imageUrl

string

URL zum Optionsbild.

minAmount

number

Mindeststückzahl bei Auswahl.

maxAmount

number

Maximale Stückzahl.

freeAmount

number

Stückzahl ohne Aufpreis.

priceAdjustment

number

Preisanpassung pro Stück.

Mutationen (Auth erforderlich):

Aktion

Methode

Pfad

Anlegen

POST

/api/v1/menuentryfeaturevalue

Aktualisieren

PUT

/api/v1/menuentryfeaturevalue/{featureValueId}

Löschen

DELETE

/api/v1/menuentryfeaturevalue/{featureValueId}

6. Hours

Gültigkeitsfenster an Menüs oder Kategorien. Sie steuern, wann bestellt, vorbestellt oder geliefert werden darf.

Feld

Typ

Beschreibung

days

enum bitmask

Bitmaske der Wochentage — siehe unten.

startDate

date

Absolutes Startdatum.

endDate

date

Absolutes Enddatum.

startTime

daytime

Startzeit an gültigen Tagen.

endTime

daytime

Endzeit an gültigen Tagen.

exclude

boolean

Fenster ausschließen statt einschließen.

Wochentags-Bitmaske:

Tag

Bit

Wert

Montag

1 << 1

2

Dienstag

1 << 2

4

Mittwoch

1 << 3

8

Donnerstag

1 << 4

16

Freitag

1 << 5

32

Samstag

1 << 6

64

Sonntag

1 << 7

128

Beispiel: Mo–Fr ergibt 2 + 4 + 8 + 16 + 32 = 62.

Badges

Optische Hervorhebungen für Menüs, Kategorien und Einträge:

Bezeichnung

Wert

NEU

1

Empfehlung

2

Reduziert

3

Coming Soon

4

Unser Tip

5

Special

6

Bestseller

7

HOT

8

SALE

9

Hotel API (PMS)

Die Hotel-API ist die bidirektionale Brücke zwischen einer APRO-Kassa und einem Property-Management-System (PMS). Das PMS spielt Reservierungen in die Kassa ein, damit Gäste aufs Zimmer anschreiben können; die Kassa liefert die Zimmerumsätze an das PMS zurück, damit sie auf der Hotelrechnung landen.

Typischer Ablauf

PMS                                       APRO POS
 │ 1. POST  /pms/reservations  ─────────▶ │   (Reservierungen upsert)
 │                                        │
 │                                        │  Gäste schreiben aufs Zimmer …
 │                                        │
 │ 2. GET   /pms/charges?hotelId=…  ─────▶│
 │ ◀─── Charges (optional inkl. Receipts) │
 │                                        │
 │ 3. POST  /pms/confirm-charges  ───────▶│   (ack; keine Wiederholung)
 │                                        │
 │ 4. DELETE /pms/reservations?…  ───────▶│   (bei Check-out / Abschluss)

Schritt 2 ist die Polling-Schleife des PMS. Schritte 1 und 4 werden durch Ereignisse im PMS ausgelöst. Schritt 3 muss jeder erfolgreichen Abfrage folgen — sonst werden dieselben Charges beim nächsten Poll wieder geliefert.

1. Reservierungen upserten

Den kompletten aktiven Reservierungsstand ins POS übergeben. Schlüssel sind hotelId + Id + guestId; bestehende Zeilen werden in-place aktualisiert.

POST https://cloudgateway.apro.at/hotelservice/api/v1/pms/reservations
Request-Body

Feld

Typ

Pflicht

Beschreibung

reservations

Reservation[]

✅

Eine oder mehrere Reservierungen.

Reservation

Feld

Typ

Pflicht

Beschreibung

Id

string

✅

Reservierungs-ID im PMS.

hotelId

string

✅

PMS-Mandant / Hotel.

guestId

string

✅

Gast-ID im PMS.

roomNr

string

✅

Zugewiesene Zimmernummer.

title

string

—

Anrede (Mr., Mrs. …).

firstName

string

—


lastName

string

—


arrival

ISO 8601

✅

Check-in-Zeitpunkt.

departure

ISO 8601

✅

Check-out-Zeitpunkt.

nrOfIndividuals

integer

—

Personenzahl — für Covers/Buffet-Regeln.

identifiers

Identifier[]

—

Keycard-UIDs, NFC-Tokens, Zimmer-Tablets — alle Kennungen, mit denen das POS eine Zimmerbuchung erkennen soll.

Identifier

Feld

Typ

Beschreibung

value

string

Wert der Kennung (Keycard-UID, Token).

Beispielanfrage

POST /hotelservice/api/v1/pms/reservations

{
  "reservations": [
    {
      "Id": "Aabc123",
      "hotelId": "1",
      "guestId": "Abc123",
      "roomNr": "Abc123",
      "title": "Mr.",
      "firstName": "Max",
      "lastName": "Mustermann",
      "arrival": "2026-04-02T14:00:00",
      "departure": "2026-04-04T14:00:00",
      "nrOfIndividuals": 1,
      "identifiers": [{ "value": "keycard-uid" }]
    }
  ]
}

Download: hotel-reservations-upsert.json

Beispielantwort

200 OK

{
  "newReservationsCount": 0,
  "checkedOutReservationsCount": 0,
  "updatedReservationsCount": 1,
  "state": 1,
  "isSuccessfull": true,
  "errorCode": 0,
  "message": null,
  "context": null
}

Download: hotel-reservations-response.json

Tipp
Immer den vollständigen aktiven Stand übertragen, keine Deltas.
checkedOutReservationsCount zeigt an, wie viele bisher bekannte
Reservierungen nicht mehr in der Payload waren — diese werden
automatisch ausgecheckt.

2. Charges abfragen

Holt alle seit dem letzten bestätigten Poll auf Zimmer gebuchten Umsätze.

GET https://cloudgateway.apro.at/hotelservice/api/v1/pms/charges
    ?hotelId=1
    &excludeAlreadyExported=true
Query-Parameter

Name

Typ

Pflicht

Beschreibung

hotelId

string

✅

PMS-Mandant / Hotel.

excludeAlreadyExported

bool

—

true = nur noch nicht bestätigte Einträge (der normale Polling-Modus). false = das gesamte konfigurierte Fenster erneut.

Antwortstruktur

Jedes Element hat ein chargeType, das bestimmt welche Felder befüllt sind:

chargeType

Bedeutung

payments vorhanden?

Charge

Offene Buchung aufs Zimmer (Gast hat noch nicht gezahlt — POS bucht aufs Zimmer-Folio).

nein

Receipt

Geschlossener Beleg (Gast hat an der Kassa bezahlt; PMS kann ignorieren oder abgleichen).

ja

Top-Level:

Feld

Typ

Beschreibung

page.limit

integer

Angewandte Seitengröße.

page.offset

integer

Offset im Backlog.

page.total

integer

Noch nicht bestätigte Einträge insgesamt.

data

Item[]

Charges und Receipts.

Item

Feld

Typ

Beschreibung

chargeType

`"Charge"

"Receipt"`

Siehe Tabelle oben.

bookDate

ISO 8601

Buchungszeitpunkt im POS.

receiptNr

integer

POS-Belegnummer.

userId

integer

Bediener-ID.

userName

string

Bedienername.

outlet

integer

Outlet-ID (1 = Bar, 2 = Restaurant …).

tableNr

integer

Tischnummer.

table

string

Tischbezeichnung.

totalAmount

decimal

Bruttosumme der Zeile.

tip

decimal

Enthaltenes Trinkgeld.

businesscaseId

GUID

APRO-Transaktions-ID — genau dieser Wert wandert als transactionIds in den Confirm-Aufruf.

reservationId

string | null

Reservierungs-ID (bei Charge).

guestId

string | null

Gast-ID.

roomNumber

string | null

Zimmernummer.

charges

LineItem[]

Einzelpositionen.

payments

Payment[]

Nur bei Receipt.

LineItem

Feld

Typ

Beschreibung

articleNr

integer

Artikelnummer.

articleName

string

Artikelname.

productgroupNr

integer

Sparten-Nummer.

productgroupName

string

Sparten-Bezeichnung.

quantity

decimal

Stückzahl.

unitPrice

decimal

Brutto-Einzelpreis.

vatPercent

decimal

Angewendeter Steuersatz.

Beispielantwort

200 OK · Charge + zwei Receipts (gekürzt)

{
  "page": { "limit": 3, "offset": 0, "total": 8096 },
  "data": [
    {
      "chargeType": "Charge",
      "bookDate": "2026-04-15T09:15:04.95",
      "receiptNr": 90,
      "userId": 20,
      "userName": "Johannes",
      "outlet": 1,
      "tableNr": 2,
      "table": "Bar 2",
      "totalAmount": 9.00,
      "tip": 0.30,
      "businesscaseId": "253c225f-7c4a-4d3f-80cd-63de4dbfca9e",
      "reservationId": "   15631",
      "guestId": "    6158",
      "roomNumber": "   414",
      "charges": [
        { "articleNr": 129,  "articleName": "Zipfer 0,2",     "productgroupName": "Bier",        "quantity": 1.00, "unitPrice": 1.10, "vatPercent": 20.00 },
        { "articleNr": 213,  "articleName": "Apfel ltg 0,5",  "productgroupName": "Flaschen AF", "quantity": 1.00, "unitPrice": 2.70, "vatPercent": 20.00 },
        { "articleNr": 1626, "articleName": "Würstel Pommes", "productgroupName": "Hauptspeise", "quantity": 1.00, "unitPrice": 4.90, "vatPercent": 10.00 }
      ]
    }
  ]
}

Vollständiges Beispiel (Charge + zwei Receipts) herunterladen: hotel-charges-response.json

Achtung
String-Felder wie reservationId, guestId oder roomNumber können
links mit Leerzeichen aufgefüllt sein, wenn die Kassa diese Felder
fixer Breite speichert (" 15631"). Im PMS vor dem Matching
trim()en.

3. Charges bestätigen

Bestätigt die erfolgreich verarbeitete Liste. Nach der Bestätigung liefert /charges mit excludeAlreadyExported=true diese businesscaseIds nicht mehr.

POST https://cloudgateway.apro.at/hotelservice/api/v1/pms/confirm-charges
Request-Body

Feld

Typ

Pflicht

Beschreibung

hotelId

string

✅

PMS-Mandant / Hotel.

transactionIds

GUID[]

✅

businesscaseId-Werte aus der vorangegangenen /charges-Antwort.

POST /hotelservice/api/v1/pms/confirm-charges

{
  "hotelId": "1",
  "transactionIds": [
    "69F94B1A-3453-7017-8DDB-94E2175D0365",
    "155FFA87-DC19-EB1F-3DD8-540EF62A115D"
  ]
}

Download: hotel-confirm-charges.json

4. Reservierung entfernen

Explizite Entfernung — sinnvoll beim Check-out oder wenn der Gast nicht mehr aufs Zimmer buchen darf.

DELETE https://cloudgateway.apro.at/hotelservice/api/v1/pms/reservations
    ?hotelId=1
    &reservationId=Abc123
    &guestId=Abc123

Alle drei Query-Parameter sind Pflicht. Die betroffene Reservierung wird entfernt, oder der Call ist ein No-Op, falls sie nicht existiert.

Hinweis
In der Praxis reicht der Voll-Upsert aus Schritt 1 — jede Reservierung,
die nicht mehr in der Payload steht, wird automatisch ausgecheckt
(checkedOutReservationsCount). Diesen Endpunkt nur für gezielte
Out-of-Band-Entfernungen nutzen.

Idempotenz & Retries

  • POST /pms/reservations ist idempotent über { hotelId, Id, guestId }. Gleicher Payload → gleicher Zustand.
  • POST /pms/confirm-charges ist idempotent über transactionIds. Eine erneute Bestätigung derselben ID ist ein No-Op.
  • Poll → Confirm muss in eurem System transaktional sein: zuerst die Charges ins Journal schreiben, dann bestätigen. Schlägt die Bestätigung fehl, liefert der nächste Poll dieselben Zeilen erneut.

Fehler

Siehe Authentifizierung → Fehlerantworten. Bei fachlichen Fehlern liefert die Antwort:

{
  "isSuccessfull": false,
  "errorCode": 42,
  "message": "No reservation found for guestId 'Abc123'.",
  "context": { "guestId": "Abc123" }
}

Gutscheine API

Die Gutschein-API ist die programmatische Schnittstelle zum zentralen APRO-Gutscheinbestand — dieselbe Basis, die Verkaufskarten im Lokal, Online-Gutscheinshops und Treueguthaben speist. Salden sind serverseitig autoritativ; jede Operation liefert den aktualisierten Gutschein zurück, sodass der Aufrufer keinen Zustand lokal tracken muss.

Beträge

Alle Geldbeträge sind Integer in Minor-Units (Cent) — 5000 entspricht € 50,00. balance ist das aktuell verfügbare Guthaben; amount ist der ursprüngliche Nennwert; maxAmount (optional) begrenzt das Aufladelimit.

Voucher-Objekt

Das Voucher-Objekt taucht in jeder Antwort auf. Felder:

Feld

Typ

Beschreibung

id

GUID

Interne APRO-Gutschein-ID (URL-Parameter).

voucherCode

string

Lesbarer Code auf der Karte/dem Beleg.

gift.amount

integer

Ursprünglicher Nennwert (Minor Units).

gift.balance

integer

Verfügbares Guthaben (Minor Units).

gift.maxAmount

integer | null

Aufladelimit; null = unbegrenzt.

codeId

string | null

Optionale Zweit-ID.

startDate

ISO 8601 | null

Vor diesem Zeitpunkt nicht gültig.

expirationDate

ISO 8601 | null

Nach diesem Zeitpunkt nicht gültig.

enabled

bool

false = manuell gesperrt.

comment

string | null

Freitext-Notiz.

categoryId

integer

Gutscheinkategorie (Ausgabekanal).

categoryName

string

Kategoriename.

categoryNrOfTimesRedeemablePerUnit

integer | null

Einlöse-Limit auf Kategorie-Ebene.

categoryExternalPaymentProviderId

string | null

Verweis auf externen Payment-Provider.

createdAt

ISO 8601

Ausgabezeitpunkt.

discriminator

string

"GiftVoucherDto" für Gutschein-Karten.

1. Gutscheine auflisten

GET https://cloudgateway.apro.at/voucherservice/api/v1/vouchers

Liefert alle für den API-Key sichtbaren Gutscheine. Für einzelne Codes den validity-Endpoint nutzen — der ist dafür indiziert.

2. Gültigkeit prüfen

GET https://cloudgateway.apro.at/voucherservice/api/v1/vouchers/validity
    ?voucherCode=560620085
Query-Parameter

Name

Typ

Pflicht

Beschreibung

voucherCode

string

✅

Der auf dem Gutschein aufgedruckte Code.

Beispielantwort

200 OK

{
  "voucher": {
    "gift": { "amount": 5000, "balance": 3880, "maxAmount": null },
    "id": "f5f4d577-3764-4c25-12f8-08db73dad741",
    "voucherCode": "560620085",
    "codeId": null,
    "startDate": null,
    "expirationDate": null,
    "enabled": true,
    "comment": null,
    "categoryId": 4,
    "categoryName": "OnlineGutschein",
    "categoryNrOfTimesRedeemablePerUnit": null,
    "categoryExternalPaymentProviderId": null,
    "createdAt": "2023-06-23T11:13:15.6386753",
    "discriminator": "GiftVoucherDto"
  },
  "errors": [],
  "isValid": true
}

Download: voucher-validity-response.json

isValid: false + befüllter errors-Array sagt, warum der Gutschein gerade nicht einlösbar ist. Häufige Codes:

Fehler

Bedeutung

NotFound

voucherCode existiert nicht.

Expired

expirationDate überschritten.

NotYetStarted

Vor startDate.

Disabled

enabled = false.

NoBalance

balance == 0.

CategoryCapReached

categoryNrOfTimesRedeemablePerUnit erschöpft.

3. Gutschein ausstellen

POST https://cloudgateway.apro.at/voucherservice/api/v1/vouchers/gifts

Legt einen neuen Gutschein an. Bei Erfolg ist der HTTP-Status 201 Created und der Location-Header verweist auf die neue Ressource.

Request-Body

Feld

Typ

Pflicht

Beschreibung

categoryId

integer

✅

Gutscheinkategorie.

voucherCode

string

✅

Aufgedruckter Code. Muss eindeutig sein.

comment

string

—

Freitext-Notiz.

gift.amount

integer

✅

Nennwert in Minor Units.

gift.maxAmount

integer

—

Aufladelimit.

clientType

string

✅

Kennung des aufrufenden Systems. Wird von APRO für deine Integration vergeben — siehe clientType.

clientContext

string (JSON)

—

Serialisierte JSON-Zusatzinfo (Outlet etc.).

startDate

ISO 8601

—

Gültig ab.

expirationDate

ISO 8601

—

Gültig bis.

Beispielanfrage

POST /voucherservice/api/v1/vouchers/gifts

{
  "categoryId": 1,
  "voucherCode": "2131231",
  "comment": "Ersatzkarte — Original verloren",
  "gift": { "amount": 50, "maxAmount": 10000 },
  "clientType": "<von-apro-vergeben>",
  "clientContext": "{\"outletId\":\"231231\"}",
  "startDate": "2026-01-01T00:00:00",
  "expirationDate": "2028-12-31T23:59:59"
}

Download: voucher-issue-request.json

Beispielantwort

201 Created

{
  "data": {
    "gift": { "amount": 50, "balance": 50, "maxAmount": 10000 },
    "id": "e961c17d-d037-4d27-734d-08ddda944e39",
    "voucherCode": "2131231",
    "categoryId": 1,
    "categoryName": "Standard",
    "enabled": true,
    "comment": "Ersatzkarte — Original verloren",
    "startDate": "2026-01-01T00:00:00",
    "expirationDate": "2028-12-31T23:59:59",
    "createdAt": "2026-04-20T21:33:00.000+02:00",
    "discriminator": "GiftVoucherDto"
  }
}

Download: voucher-issue-response.json

4. Gutschein einlösen

POST https://cloudgateway.apro.at/voucherservice/api/v1/vouchers/{voucherId}/redemption

Zieht amount Minor Units vom Guthaben ab. Der Pfadparameter ist die interne id (nicht der voucherCode) — zu erhalten über /validity.

Request-Body

Feld

Typ

Pflicht

Beschreibung

redemption.amount

integer

✅

Abzubuchender Betrag in Minor Units.

clientReference

string

—

Opake Trace-Zeichenkette (z. B. "{ \"waiterId\": 10 }"). Wird persistiert.

clientContext

string (JSON)

—

Zusatzinfo.

comment

string

—

Freitext-Notiz.

clientType

string

✅

Kennung des aufrufenden Systems. Wird von APRO vergeben — siehe clientType.

Beispielanfrage

POST /voucherservice/api/v1/vouchers/{id}/redemption

{
  "redemption": { "amount": 50 },
  "clientReference": "{ \"waiterId\": 10 }",
  "clientContext": "{\"outletId\":\"231231\"}",
  "comment": "Eingelöst an Bar 2 zu Beleg 5906",
  "clientType": "<von-apro-vergeben>"
}

Download: voucher-redeem-request.json

Beispielantwort

200 OK

{
  "data": {
    "redemption": { "amount": 50, "items": [] },
    "success": true,
    "errors": [],
    "transactionId": 21142,
    "voucher": {
      "gift": { "amount": 5000, "balance": 3830, "maxAmount": null },
      "id": "f5f4d577-3764-4c25-12f8-08db73dad741",
      "voucherCode": "560620085",
      "categoryId": 4,
      "categoryName": "OnlineGutschein",
      "enabled": true,
      "discriminator": "GiftVoucherDto"
    }
  }
}

Download: voucher-redeem-response.json

Die transactionId (integer) ist die autoritative ID der Einlösung — für Abgleich und Refunds persistieren.

5. Gutschein aufbuchen

POST https://cloudgateway.apro.at/voucherservice/api/v1/vouchers/{voucherId}/booking

Bucht amount Minor Units zurück auf den Gutschein — etwa beim Aufladen einer Mehrweg-Karte oder bei Rückbuchungen.

Request-Body

Feld

Typ

Pflicht

Beschreibung

amount

integer

✅

Gutzubuchender Betrag in Minor Units. gift.maxAmount beachten.

clientReference

string

—

Opake Trace-Zeichenkette.

clientType

string

✅

Kennung des aufrufenden Systems. Wird von APRO vergeben — siehe clientType.

clientContext

string (JSON)

—

Zusatzinfo.

comment

string

—

Freitext-Notiz.

Beispielanfrage

POST /voucherservice/api/v1/vouchers/{id}/booking

{
  "amount": 1000,
  "clientReference": "{ \"waiterId\": 10 }",
  "clientType": "<von-apro-vergeben>",
  "clientContext": "{\"outletId\":\"231231\"}",
  "comment": "Aufladung nach Guthabenzukauf"
}

Download: voucher-booking-request.json

Beispielantwort

200 OK

{
  "data": {
    "newAmount": 4830,
    "success": true,
    "errors": [],
    "transactionId": 21143,
    "voucher": {
      "gift": { "amount": 5000, "balance": 4830, "maxAmount": null },
      "id": "f5f4d577-3764-4c25-12f8-08db73dad741",
      "voucherCode": "560620085",
      "categoryName": "OnlineGutschein",
      "enabled": true,
      "discriminator": "GiftVoucherDto"
    }
  }
}

Download: voucher-booking-response.json

clientType

clientType identifiziert das aufrufende System, damit der Gutschein-Ledger weiß, welche Integration eine Transaktion erzeugt hat. APRO vergibt für jede Integration beim Onboarding einen eigenen clientType-Wert — der konkrete String ist nicht frei wählbar und wird zusammen mit dem API-Key übermittelt. Immer den von APRO bereitgestellten Wert verwenden, nie selbst erraten.

Fehlerbehandlung

  • HTTP 4xx bei Validierungsfehlern (ungültige voucherId, malformed Body, fehlende Auth). Siehe Authentifizierung → Fehlerantworten.
  • HTTP 200 mit success: false bei fachlichen Regeln (zu wenig Guthaben, abgelaufen …). errors-Array inspizieren.
  • Niemals clientseitig retryen ohne vorherige Validierung. Timeout auf einen Einlösen-Call? Zuerst /validity aufrufen — die Operation kann serverseitig bereits angewendet sein.

Web-Widgets

Gutschein-Widget einbinden

Das Gutschein-Widget ist eine <giftcard-widget>-Webkomponente, die den APRO-Gutscheinshop für eine Location in einem iframe lädt. Zwei Zeilen genügen — Kundinnen und Kunden kaufen und personalisieren Gutscheine, ohne die Host-Seite zu verlassen.

  • Skript: https://smorderfrontend.azureedge.net/embed/widget.js
  • Tag: <giftcard-widget location-id="…">
  • Drei Modi: Inline-Liste (Gutscheinliste füllt den Container), Inline-Button (Button im Seitenfluss öffnet ein Panel) oder schwebender Button (fixierter FAB öffnet ein Panel)

Schnellstart

Minimale Einbindung — Skript einmal laden, danach das Tag dort einsetzen, wo der Shop erscheinen soll:

<script src="https://smorderfrontend.azureedge.net/embed/widget.js"></script>
<giftcard-widget location-id="535"></giftcard-widget>

Mehr braucht es nicht. Das Widget lädt die Gutscheinkategorien der Location, rendert den Shop und führt durch den gesamten Kaufvorgang. Die Host-Seite muss lediglich Platz für das Layout bereitstellen.

Beispiel — Inline-Button

Gerendert mit presentation="inline-button". Der Trigger steht im Seitenfluss genau dort, wo das <giftcard-widget>-Tag platziert ist; ein Klick öffnet den Gutscheinshop in einem Modal-Panel.

<script src="https://smorderfrontend.azureedge.net/embed/widget.js"></script>
<giftcard-widget
  location-id="535"
  locale="de"
  presentation="inline-button"
  expand-icon="redeem"
  expand-label="Gutscheine"
  headline="Gutschein kaufen"
  max-height="700"
></giftcard-widget>

<div style="padding:1rem; margin:1.25rem 0;">
<giftcard-widget location-id="535" locale="de" presentation="inline-button" expand-icon="redeem" expand-label="Gutscheine" headline="Gutschein kaufen" max-height="700" ></giftcard-widget>
</div>

Beispiel — Inline-Liste

Dasselbe Widget ohne presentation — die volle Gutscheinliste fügt sich in den Seitenfluss ein, begrenzt durch max-height.

<script src="https://smorderfrontend.azureedge.net/embed/widget.js"></script>
<giftcard-widget
  location-id="535"
  locale="de"
  max-height="700"
></giftcard-widget>

<style>{giftcard-widget:not([presentation]) { display: block; min-height: 320px; }}</style>

<div style="border:1px dashed var(--sl-color-gray-5); border-radius:10px; padding:1rem; margin:1.25rem 0; min-height:740px;">
<giftcard-widget location-id="535" locale="de" max-height="700" style="display:block; height:700px;" ></giftcard-widget>
</div>

Interaktiver Builder

Attribute unten anpassen — Markup und Vorschau aktualisieren sich sofort. Den fertigen Schnipsel direkt ins CMS oder Template kopieren.

Inline-Button-Modus

Empfohlener Standard. Mit presentation="inline-button" wird der Trigger genau dort gerendert, wo das <giftcard-widget>-Tag platziert ist. Ein Klick öffnet den Gutscheinshop in einem Modal-Panel. Das Layout der Host-Seite bleibt unverändert, bis der Kunde aktiv wird.

<giftcard-widget
  location-id="535"
  locale="de"
  presentation="inline-button"
  expand-icon="redeem"
  expand-label="Gutscheine"
  headline="Gutschein kaufen"
  max-height="700"
></giftcard-widget>

Schwebender-Button-Modus

Mit presentation="expandable" wechselt das Widget in den fixierten FAB-Modus: ein runder Pill-Button in einer von sechs Ecken öffnet ein verankertes Panel mit dem Shop. Praktisch, wenn dauerhaft ein Einstiegspunkt sichtbar sein soll, der dem Besucher beim Scrollen folgt.

<giftcard-widget
  location-id="535"
  locale="de"
  presentation="expandable"
  position="right-bottom"
  max-height="700"
  max-width="420"
  expand-label="Gutscheine"
  expand-icon="card_giftcard"
  headline="Gutschein kaufen"
></giftcard-widget>

position steuert sowohl die FAB-Position als auch die Kante, an der das Panel andockt. Erlaubte Werte:

  • right-bottom (Standard), left-bottom, center-bottom
  • right-top, left-top, center-top

expand-icon ist ein Material-Icons-Ligaturname (z. B. redeem, card_giftcard, celebration). Wird über @fontsource/material-icons im Embed gebündelt — Bild-URLs werden nicht unterstützt.

expand-theme schaltet zwischen dark (Standard) und light für die Optik. Die Modal-Titelleiste folgt demselben Theme — dunkel im Standard, hell bei expand-theme="light".

Inline-Listen-Modus

presentation weglassen (oder leer setzen), um die volle Gutschein­liste in den Seitenfluss zu rendern. Das Widget übernimmt die Breite des Elternelements und wächst bis zur natürlichen iframe-Höhe, begrenzt durch max-height. Geeignet für eine dedizierte „Gutscheine"-Landingseite.

<giftcard-widget
  location-id="535"
  locale="de"
  max-height="700"
></giftcard-widget>

Attribute

Attribut

Pflicht

Standard

Beschreibung

location-id

✅

—

Numerische APRO-Location-ID. Ohne Wert mountet das Widget nicht.

locale

—

de

UI-Sprache im iframe: de, en, it. Setzt auch die Standard-Beschriftung des Buttons.

presentation

—

`` (Inline-Liste)

inline-button rendert einen Trigger im Seitenfluss, expandable rendert einen fixierten FAB. Leer oder unbekannt = volle Inline-Liste.

max-height

—

—

Pixel-Limit. Inline-Liste: iframe-Höhe. Panel-Modi: Panel-Höhe (zusätzlich an Viewport gekoppelt).

max-width

—

—

Pixel-Limit für die Panel-Breite. Nur Panel-Modi.

position

—

right-bottom

Einer von sechs Ecken-Schlüsseln; bestimmt den Panel-Anker (und FAB-Position bei presentation="expandable"). Nur Panel-Modi.

expand-label

—

locale-Standard

Beschriftung auf dem Trigger-Button. Leer = automatisch nach locale. Nur Panel-Modi.

expand-theme

—

dark

dark oder light. Gilt für Trigger und Modal-Titelleiste. Nur Panel-Modi.

expand-icon

—

—

Material-Icons-Ligature links vom Text. Nur Panel-Modi.

headline

—

—

Titelleiste über dem iframe im Panel; wird auch als ?headline=… an das iframe weitergegeben. Nur Panel-Modi.

page-background

—

—

Beliebige CSS-Farbe (#ffffff, rgb(...), transparent, …). Wird auf den iframe-Body angewendet, damit sich der Shop optisch in die Host-Seite einfügt; tönt zusätzlich Panel und Inline-Wrapper, bis das iframe geladen ist.

breakoutPageOnMobile

—

false

Bei true öffnet sich der individuelle Gutschein-Editor auf Smartphones in einem neuen Tab statt im iframe.

Skript einbinden

widget.js einmal pro Seite laden — beliebig im <head> oder vor </body>. Das Skript registriert das <giftcard-widget>-Custom-Element global; nachfolgende Tags aktivieren sich automatisch.

<script src="https://smorderfrontend.azureedge.net/embed/widget.js"></script>

Mehrere Widgets auf einer Seite sind unterstützt (z. B. ein Inline-Shop plus ein ausklappbares FAB an anderer Stelle). Jedes iframe ist isoliert.

Datenexport

SQL Views

Wenn direkter SQL-Zugriff auf die Kassa gefragt ist — meist weil APRO On-Premise neben bestehenden Systemen läuft — stellt die Kassendatenbank einen stabilen Satz lesender Views bereit. Sie sind gezielt auf die beiden häufigsten Konsumenten zugeschnitten:

  • On-Premise-Integrationen — Buchhaltungsbrücken, Küchenmonitore, Warenwirtschaft, Loyalty-Engines — alles, was im selben Netz läuft und einen schnellen Datenfluss ohne Umweg über REST braucht.
  • Bestehende BI-Workflows — Power BI, Tableau, Qlik, Metabase, Excel, SQL Server Analysis Services — jedes Tool, das bereits SQL-Server-Connections versteht. Connector auf die Views zeigen und du hast Belege, Positionen, Zahlungen und Artikelstamm in der Form, die ein Data Warehouse erwartet.

Die Views sind ausschließlich lesend, über das Standard-SQL-Server- Protokoll abfragbar und greifen nicht auf operative Tabellen zu, die unter Last mutieren — sie sind sicher für Dashboards und geplante ETL-Jobs.

Tipp: SQL Views oder Umsatz API?
Die SQL-Views und die Umsatz API liefern dieselben
Daten — wähle die passende Laufzeit:

- SQL Views (diese Seite) — flach, relational, On-Premise.
Perfekt für ETL, Power-BI-/Tableau-/Qlik-/Metabase-Konnektoren und
jeden Workflow, der SQL-Bulk-Zugriff will.
- Umsatz API — über den Cloud-Endpunkt my.apro.at, liefert ein
hierarchisches JSON-Dokument mit Positionen, Zahlungen und
Steuersätzen bereits pro Beleg verschachtelt. Besser für Realtime-
System-zu-System-Integrationen, SaaS-Clients und alles ausserhalb
des lokalen Netzes.
Hinweis
Der lesende SQL-User wird pro Deployment bereitgestellt. Freischaltung,
Servername, Datenbank und Zugangsdaten bekommst du über deine
APRO-Ansprechperson.

Übersicht

View

Inhalt

vw_Receipts

Eine Zeile pro Geschäftsvorfall.

vw_Receipt_Items

Positionen. Join über ReceiptId.

vw_Payments

Zahlungen. Join über ReceiptId.

vw_Transactions

Leichter Transaktions-Index.

vw_TransactionTypes

Lookup der Transaktionstypen.

vw_Bi_Export

Denormalisiertes Beleg × Position + Zahlungsübersicht — Startpunkt für Power BI.

vw_products_and_categories

Artikelstamm mit Sparte.

1. vw_Receipts

Eine Zeile pro Geschäftsvorfall. Die Spalte TransactionType sagt, was die Zeile repräsentiert; Beträge sind vorzeichenbehaftet — bei Stornos (TransactionType = 4) sind sie negiert, damit SUM(TotalAmount) direkt netto rechnet.

Spalte

Typ

Beschreibung

Id

GUID

Eindeutige ID des Geschäftsvorfalls.

TransactionId

GUID

Transaktions-ID (Join-Schlüssel für vw_Receipt_Items, vw_Payments).

TransactionType

enum

Siehe Transaktionstypen.

TransactionTypeName

string

Lesbarer Transaktionstyp.

ReceiptNr

string

Beleg-/Rechnungsnummer.

TotalAmount

decimal

Bruttosumme (signed — negativ bei Stornos).

TipAmount

decimal

Trinkgeld (signed).

ReceiptDiscountAmount

decimal

Rabatt auf Gesamtbeleg (signed).

BookDate

datetime

Buchungszeitpunkt.

BusinessDay

date

Virtueller Geschäftstag.

ParentId

GUID | null

Vorgänger-Beleg (z. B. bei Rückholung Verweis auf Original).

PaymentGroupId

integer | null

Zahlungsgruppen-ID (z. B. Umsatzwirksam).

PaymentGroupName

string | null

Name der Zahlungsgruppe — nützlich als Umsatzfilter.

BusinessId

integer

ID des durchführenden Unternehmens.

BusinessName

string

Firmenname.

TableId

integer

Tisch-ID.

TableNr

integer

Tischnummer.

TableName

string

Tischbezeichnung.

AreaId

integer

Bereichs-ID.

AreaNr

integer

Bereichsnummer.

AreaName

string

Bereichsname (Restaurant, Bar, Terrasse …).

StaffId

integer

Mitarbeiter-ID.

StaffNr

integer

Personalnummer.

StaffName

string

Mitarbeitername.

CustomerId

integer

Kunden-ID.

CustomerNr

integer

Kundennummer.

CustomerName

string

Kundenname.

DeviceId

integer

Ausführendes Gerät.

Transaktionstypen

Die View enthält nur Transaktionen mit TransactionType in (1, 2, 3, 4, 5, 14, 24, 25). Das autoritative Mapping liegt in vw_TransactionTypes und ist auch pro Zeile als TransactionTypeName beigefügt.

TransactionType

Bedeutung

1

Bestellung — Eingabe am Tisch oder SB.

2

Verschiebung — Tischwechsel.

3

Abrechnung — bezahlter Beleg.

4

Storno — Umkehr einer Bestellung (Beträge negiert).

5

Rechnung zurückholen — Umkehr einer Abrechnung.

14

Ausgangsrechnung erstellt.

24

Ausgangsrechnung — zusätzlich (siehe vw_TransactionTypes).

25

Ausgangsrechnung bezahlt.

2. vw_Receipt_Items

Positionen. Join mit vw_Receipts über ReceiptId / TransactionId. Werte sind wie beim Beleg vorzeichenbehaftet.

Spalte

Typ

Beschreibung

TransactionId

GUID

Transaktions-ID.

ReceiptId

GUID

Beleg-ID (Join auf vw_Receipts.Id).

ReceiptItemId

GUID

Eindeutige Positions-ID.

BookedItemId

GUID

Unterliegende Bonierungs-ID (gemeinsam zwischen Buchung und Storno).

BookedItemDate

datetime

Bonierungszeitpunkt.

ProductCategoryId

integer

Sparten-ID.

ProductCategoryNr

integer

Sparten-Nummer.

ProductCategoryName

string

Sparten-Bezeichnung.

ProductCategoryAccountingNr

string

Buchungskonto-Nummer der Sparte.

ProductId

integer

Artikel-ID.

ProductNr

integer

Artikelnummer.

ProductName

string

Artikelname.

Quantity

decimal

Stückzahl (signed bei Stornos / Rückholungen).

OriginalPrice

decimal

Ursprünglicher Einzelpreis zum Ausführungszeitpunkt (signed bei Stornos).

EffectivePrice

decimal

Tatsächlicher Einzelpreis nach Rabatt (signed bei Stornos).

OriginalAmount

decimal

Stück × Originalpreis (signed).

EffectiveAmount

decimal

Stück × Originalpreis × Rabattfaktor (signed).

DiscountAmount

decimal

Rabattbetrag in Geld (signed bei Stornos / Rückholungen).

DiscountPercentage

decimal

Rabatt in % des Originalpreises.

DiscountType

string

RABATT OFF für klassische Rabatte, sonst die Kampagnenbezeichnung (z. B. Happy Hour). Leer, wenn kein Rabatt.

TaxId

integer

Steuersatz-ID.

TaxName

string

Steuersatz-Name.

TaxAmount

decimal

Steuersatz als Dezimalwert.

TransactionTypeId

enum

Selbe Codes wie vw_Receipts.TransactionType.

3. vw_Payments

Zahlungen zu Belegen. Enthalten sind nur Abrechnungen und ihre Rückholungen (TransactionType in (3, 4, 5, 14, 24, 25)).

Spalte

Typ

Beschreibung

TransactionId

GUID

Transaktions-ID.

ReceiptId

GUID

Beleg-ID.

PaymentId

GUID

Eindeutige Zahlungs-ID.

TotalAmount

decimal

Gezahlter Betrag (signed).

PaymentTypeId

integer

Zahlungsmittel-ID.

PaymentTypeNr

integer

Zahlungsmittel-Nummer.

PaymentTypeName

string

Zahlungsmittel (z. B. Bar, Kreditkarte, Gutschein).

DateTime

datetime

Zahlungszeitpunkt.

PredecessorReceiptId

GUID | null

Bei Rückholung der Original-Beleg, den diese Zahlung aufhebt.

Details

string | null

Freitext-Metadaten zur Zahlung (z. B. maskierte PAN, Gutscheincode).

4. vw_Transactions

Leichter Index aller Transaktionen. Nützlich für Wasserzeichen-ETL (nur neue Zeilen seit dem letzten Checkpoint ziehen).

Spalte

Typ

Beschreibung

TransactionId

GUID

Transaktions-ID.

TransactionType

enum

Transaktionstyp.

TransactionTypeName

string

Lesbarer Name.

BookDate

datetime

Zeitpunkt der Transaktion.

DeviceId

integer

Ausführendes Gerät.

5. vw_TransactionTypes

Lookup aller Transaktionstypen, die die Kassa erzeugt.

Spalte

Typ

Beschreibung

Id

integer

Transaktionstyp-ID.

Bezeichnung

string

Lesbare Bezeichnung (deutsch).

6. vw_Bi_Export

Die One-Stop-View für BI-Tools. Denormalisierter Join aller umsatzrelevanten Belege mit all ihren Positionen, plus einer komma-separierten Zusammenfassung der verwendeten Zahlungsmittel. Power BI oder Tableau auf diese View zeigen — und du hast alles für Umsatz-, Marge-, Mix- und Zahlungsmittel-Dashboards, ohne Joins schreiben zu müssen.

Spalten sind die Vereinigung von vw_Receipts und vw_Receipt_Items (ohne TransactionTypeId, das nur auf der Belegseite vorkommt), plus:

Spalte

Typ

Beschreibung

Payments

string

Komma-separierte Liste der PaymentTypeName-Werte (z. B. "Bar,Kreditkarte"). NULL auf Nicht-Abrechnungszeilen.

Power-BI-Beispiel
  1. Daten abrufen → SQL-Server-Datenbank — Server + Datenbank wie bereitgestellt.
  2. Import- oder DirectQuery-Modus, lesender User.
  3. dbo.vw_Bi_Export als Fakt-Tabelle wählen.
  4. Optional dbo.vw_Transactions, dbo.vw_Payments, dbo.vw_products_and_categories als Dimensions-Tabellen hinzufügen und über TransactionId / ProductId verknüpfen.

Der Import-Modus reicht in der Regel — die Views sind bereits auf Transaktionsebene aggregiert und aktualisieren sich bei einem typischen Betrieb in Sekunden.

7. vw_products_and_categories

Artikelstamm mit Sparte. Eignet sich als Dimension neben vw_Bi_Export, wenn BI-Artikelnamen stabil bleiben sollen, auch wenn historische Zeilen vor einer Umbenennung liegen.

Spalte

Typ

Beschreibung

ProductId

integer

Artikel-ID.

ProductNr

integer

Artikelnummer.

ProductName

string

Artikelname.

ProductCategoryId

integer

Sparten-ID.

ProductCategoryNr

integer

Sparten-Nummer.

ProductCategoryName

string

Sparten-Name.

Price

decimal

Aktueller Listenpreis (Preis1).

Nur Artikel mit Geloescht = 0 (nicht soft-gelöscht) sind enthalten.

Beispiel-Queries

Umsatz pro Geschäftstag, nach Steuersatz — für einen Monats-KPI:

SELECT
  r.BusinessDay,
  ri.TaxName,
  SUM(ri.EffectiveAmount) AS netto_umsatz
FROM vw_Receipts AS r
JOIN vw_Receipt_Items AS ri ON ri.ReceiptId = r.Id
WHERE r.TransactionType IN (3, 4, 5)   -- Abrechnungen + Stornos rechnen netto
  AND r.BusinessDay BETWEEN @from AND @to
GROUP BY r.BusinessDay, ri.TaxName
ORDER BY r.BusinessDay, ri.TaxName;

Top-10-Artikel im aktuellen Monat — direkt über die BI-View:

SELECT TOP 10
  ProductName,
  SUM(Quantity)        AS stueck,
  SUM(EffectiveAmount) AS netto_umsatz
FROM vw_Bi_Export
WHERE BusinessDay >= DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)
  AND TransactionType IN (3, 4, 5)
GROUP BY ProductName
ORDER BY netto_umsatz DESC;

Bar vs. Karte für einen einzelnen Geschäftstag:

SELECT
  PaymentTypeName,
  SUM(TotalAmount) AS total
FROM vw_Payments
WHERE CAST(DateTime AS date) = @businessDay
GROUP BY PaymentTypeName;