
APIs
7 Seiten
Stand: 6. November 2026
Inhalt
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 |
|
|
Nur Key |
| 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 |
|---|---|---|---|
| ISO 8601 Timestamp | ✅ | Nur Belege nach diesem Zeitpunkt. |
| ISO 8601 Timestamp | ✅ | Nur Belege vor diesem Zeitpunkt. |
| integer | — | Anzahl zurückgelieferter Belege begrenzen. |
| GUID | — | Cursor — Transaktionen nach dieser ID liefern. Für Paging zusammen mit |
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 |
|---|---|---|
| GUID | Eindeutige Transaktions-ID. |
| string | Siehe Transaktionstypen. |
| string |
|
| decimal | Bruttogesamtbetrag. |
| decimal | Im |
| decimal | Abgezogener Rabatt. |
| ISO 8601 | Zeitpunkt der Transaktion. |
| Unternehmen / Mandant. | |
| Der APRO-Standort, für den abgefragt wurde. | |
| Bediener / Kellner. | |
| Tisch oder Bestellziel (z. B. | |
| Bereich / Zone des Tisches (z. B. Garten, Bar). | |
| Customer · nullable | Rechnungsempfänger. Auf diesem Endpunkt immer |
| Gruppierung, in die die Zahlungsarten zusammenlaufen. | |
| [Payment] | Zahlungen zum Beleg. |
| [Item] | Belegpositionen. |
| integer | Gästeanzahl, summiert über die Positionen des Belegs. |
Item
Feld | Typ | Beschreibung |
|---|---|---|
| string | Eindeutige Artikel-ID. |
| integer | Artikelnummer (in APRO). |
| string | Artikelname. |
| string · nullable | Stabiler Identifier je Position. Zum Deduplizieren bzw. Abgleichen über wiederholte Abfragen hinweg. |
| string · nullable | Referenz aus einem Fremdsystem. |
| ISO 8601 | Buchungszeitpunkt. |
| decimal | Bruttopreis. |
| decimal | Nettopreis. |
| decimal | Einkaufspreis der Position, für Deckungsbeitrags-Auswertungen. |
| decimal | Stückzahl. |
| integer · nullable | Gang bei mehrgängigem Service. |
| Angewendeter Steuersatz. | |
| Sparte. | |
| [Side dish] · nullable | Unterartikel / Beilagen zu dieser Position. |
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 |
|---|---|---|
| string | Eindeutige Unterartikel-ID. |
| integer | Artikelnummer des Unterartikels. |
| string | Bezeichnung des Unterartikels. |
| string | Stabiler Identifier je Unterartikel. Zugleich der Schlüssel, über den dedupliziert wird. |
| decimal | Bruttopreis des Unterartikels. |
| decimal | Nettopreis des Unterartikels. |
| decimal | Stückzahl des Unterartikels. |
Achtung: Unterartikel nicht zu den Summen addieren
Ein Unterartikel mit Aufpreis ist **bereits imgrossAmount/netAmountder übergeordneten Position** und imtotalAmountdes
Belegs enthalten. WersideDishes[].grossAmountzusä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 |
|---|---|---|
| string | Eindeutige ID des Steuersatzes. |
| decimal | Höhe, z. B. |
| string | Bezeichnung, z. B. |
Category
Feld | Typ | Beschreibung |
|---|---|---|
| string | Eindeutige Sparten-ID. |
| integer | Sparten-Nummer. |
| string | Bezeichnung. |
Payment
Feld | Typ | Beschreibung |
|---|---|---|
| string | Eindeutige Zahlungs-ID. Zahlungen werden über diesen Wert dedupliziert. |
| decimal | Gezahlter Betrag. |
| Verwendetes Zahlungsmittel. | |
| string · nullable | Fibu-Kontonummer, auf die die Zahlung gebucht wird. Für die Zuordnung auf Sachkonten im Export. |
Payment type
Feld | Typ | Beschreibung |
|---|---|---|
| integer · nullable | Eindeutige Zahlungsmittel-ID. |
| integer · nullable | Zahlungsmittel-Nummer. Der Schlüssel heißt |
| string | Bezeichnung, z. B. |
Business
Feld | Typ | Beschreibung |
|---|---|---|
| string | Mandanten-ID aus der Kassendatenbank. |
| string | Firmenname. |
Staff
Feld | Typ | Beschreibung |
|---|---|---|
| string | Eindeutige Mitarbeiter-ID. |
| integer | Personalnummer. |
| string | Mitarbeitername. |
Table
Feld | Typ | Beschreibung |
|---|---|---|
| string | Eindeutige Tisch-ID. |
| integer | Tischnummer. |
| string | Tischbezeichnung, z. B. |
Location
Feld | Typ | Beschreibung |
|---|---|---|
| string | APRO-Standort-ID — die |
| string · nullable | Standortname. |
Area
Der Bereich bzw. die Zone, in der ein Tisch steht.
Feld | Typ | Beschreibung |
|---|---|---|
| string | Eindeutige Bereichs-ID. |
| integer | Bereichsnummer. |
| string | Bezeichnung, z. B. |
Payment group
Die Gruppierung, in der die Zahlungsarten zusammenlaufen — für Kassabuch- und Abschluss-Auswertungen.
Feld | Typ | Beschreibung |
|---|---|---|
| integer | Zahlungsgruppen-ID. |
| string | Bezeichnung der Gruppe. |
Customer
Feld | Typ | Beschreibung |
|---|---|---|
| string | Eindeutige Kunden-ID. |
| integer | Kundennummer. |
| string | Kundenname. |
Hinweis: Aktuell immer null
Der Receipts-Endpunkt befülltcustomernicht — 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 |
|---|---|
| Artikel auf offenen Tisch boniert. |
| Tischwechsel. |
| Abschluss — bezahlter Beleg. |
| Stornierung einer Buchung. |
| Rücknahme einer Abrechnung. |
| Debit-Buchung. |
| Ausgangsrechnungs-Lifecycle. |
| Tagesabschluss / Vorschau. |
| Kellnerübergabe / Abschlag. |
| Gesetzliche Fiskalbelege (RKSV). |
| Tisch-Lifecycle. |
| Bestellmanager-Events. |
| Warenkorb-Lifecycle. |
| Datenexport-Markierungen. |
| 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
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:
LocationID— numerische IDLocationShortCode— Kurz-Identifier (z. B.vienna-main)SpotID— konkreter Tisch/Zimmer/Abholpunkt (Standort wird abgeleitet)QRCode— gescannter APRO-QR-Code (Standort und Spot werden abgeleitet)
Fehlen alle, wird die Anfrage abgelehnt.
Request-Body
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| string | — | Idempotenz-Schlüssel. Kommt derselbe |
| int? | ◻︎ | Siehe Routing oben. |
| string | ◻︎ | Siehe Routing oben. |
| int? | — | ID des Bestellziels (Tisch, Zimmer, Abhol-, Lieferstandort — alles Spots, nur unterschiedliche Typen). Entspricht |
| int? | — | Nummer des Spots, wenn die ID unbekannt ist. Aufgelöst über |
| string | — | Fallback, wenn weder |
| int? | — | Nummer des Zimmer-Spots für Hotel-/Zimmerabrechnungs-Flows. Aufgelöst gegen einen |
| int? | — | Für zukünftige Erweiterungen reserviert. Wird aktuell akzeptiert, aber ignoriert — für ein Zimmer den |
| ✅ | Wer bestellt — | |
| — | Lieferadresse. Pflicht bei Zustellung. | |
| bool | ✅ |
|
| string | — | Freitext-Notiz des Gastes (Allergene, Sonderwünsche). |
| string | — | Siehe Routing. |
| [Entry] | ✅ | Bestellte Artikel. |
| long? | — | Vorbestellzeitpunkt, Unix-Zeit in Millisekunden UTC. |
| string | — | Abhol-/Lieferreferenz (z. B. |
| decimal? | — | Trinkgeldhöhe. |
| bool | — |
|
| decimal? | ✅ | Summe aller Artikel + Tip. Wird serverseitig gegengeprüft. |
Entry
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| string | ✅ | Produktnummer in APRO. |
| string | — | Anzeigename — rein informativ; APRO vertraut auf |
| long? | — | Vorbestellzeit pro Artikel. |
| double | ✅ | Bestellmenge. |
| double? | — | Übersteuert den Systempreis dieser Position (Rabatte, Kampagnen). |
| string | — | Artikel-Notiz (z. B. |
| [Addon] | — | Zusatzoptionen. |
Addon
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| string | ✅ | Produktnummer des Addons. |
| string | — | Anzeigename. |
| double | ✅ | Menge. |
| double? | — | Übersteuert den Systempreis. |
UserInfo
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| string | — | |
| string | — | |
| string | ✅ | Kontakt-E-Mail für Belege und Status-Updates. |
| string | — | |
| string | — | IETF-Locale, z. B. |
| string | — | Kurzname für Besteller/Abholer. |
DeliveryInfo
Feld | Typ | Beschreibung |
|---|---|---|
| string | Bezeichnung der Lieferadresse. |
| string | Firmenname. |
| string | Ort. |
| string | PLZ. |
| string | Straße. |
| string | Land. |
| string | Stockwerk. |
| string | Stiege. |
| string | Türe. |
| 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 CollectionapiKeyundlocationShortCodesetzen, dann Send drücken.
Achtung: Filter verwenden eine Bracket-Syntax
Alle Filterfelder — inklusive des PflichtfeldeslocationId/locationShortCode— müssen alsfilter['<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 |
|---|---|---|
|
| Paginierte Liste von Product |
|
| Paginierte Liste von ProductCategory |
|
| Paginierte Liste von Spot |
Gemeinsame Konventionen
Standort-Scope
Jede Anfrage muss genau einen Standort adressieren. Geben Sie einen der folgenden Werte mit:
locationId— numerische IDlocationShortCode— 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 |
|---|---|---|
|
| Anzahl der zu überspringenden Datensätze. |
|
| Seitengröße. Limit 500. |
Jede Antwort steckt in einem Paged-Envelope:
{
"page": { "offset": 0, "limit": 50, "total": 1234 },
"data": [ /* ... */ ]
}offsetundlimitspiegeln die Anfrage.totalist die Gesamtanzahl aller passenden Datensätze über alle Seiten — Grundlage fürskip/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=100Hinweise:
- Der Klammer-Key ist case-insensitive —
filter['LocationShortCode']undfilter['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/productQuery-Parameter
Parameter | Typ | Beschreibung |
|---|---|---|
| int? | Pflicht (oder |
| string | Pflicht (oder |
| int? | Auf eine Kassa einschränken. Muss zum Standort gehören. |
| string | Volltextsuche über Name, externer Name, productNr, GTIN und externe Artikel-ID. |
| int[] | Filter nach APRO-Artikel-ID. |
| string[] | Filter nach |
| string[] | Filter nach GTIN (Barcode). |
| string[] | Filter nach externer Artikel-ID (Drittsystem-Referenz). |
| bool? | Filter nach Sichtbarkeit. |
| bool? | Filter nach Sperr-Flag. |
Product
Feld | Typ | Beschreibung |
|---|---|---|
| int | APRO-Cloud-Artikel-ID. |
| string | Identifier, mit dem die Bestell-Pipeline diesen Artikel auflöst. Diesen Wert direkt als |
| string | Barcode / GTIN. |
| string | POS-Artikel-ID — die im lokalen Kassensystem vergebene ID. Informativ; nicht der Wert, gegen den die External-Order- |
| string | Anzeigename. |
| string | Name für externe Oberflächen — Fallback auf |
| string | Kurz-Untertitel. |
| string | Ausführliche Beschreibung. |
| Preis- und Steuerinfo. | |
| Kassa / Eltern-Artikel / Produktionsplatz. | |
| Bild-URLs. | |
| Sichtbarkeit, Sperre, Review-Status. | |
| Menge, Kalorien, Allergene, Altersgrenze. | |
| string[] | Externe IDs der Warengruppen, denen der Artikel angehört. |
| Gepflegte Übersetzungen der Artikeltexte und des Bildes, ein Eintrag je Sprache. Leer, wenn keine vorhanden sind. |
Pricing
Feld | Typ | Beschreibung |
|---|---|---|
| double? | Standardpreis. |
| double? | Referenz-/Streichpreis. |
| int? | APRO-Steuersatz-ID. |
| bool | Ob Clients den Preis überschreiben dürfen. |
ProductCashbox
Feld | Typ | Beschreibung |
|---|---|---|
| int | Zugehörige Kassa. |
| int? | Küche / Bar, in der zubereitet wird. |
| int? | Bei Varianten / Template-Ableitungen gesetzt. |
Media
Feld | Typ | Beschreibung |
|---|---|---|
| string | Öffentliche Bild-URL. |
Flags
Feld | Typ | Beschreibung |
|---|---|---|
| bool | Sichtbar in Storefronts. |
| bool | Gegen automatische Sync-Änderungen gesperrt. |
| bool | Wartet auf manuelles Review. |
Nutrition
Feld | Typ | Beschreibung |
|---|---|---|
| double? | Nettomenge. |
| string | Einheit, z. B. |
| double? | Kalorien pro |
| int (Flags) | Bitflag der deklarierten Allergene. |
| int | Mindestkäuferalter ( |
| [Ingredient] | Deklarierte Zutatenliste. Leer-Array, wenn nicht gepflegt. |
| [NutritionalValue] | Strukturierte Nährwerte. Leer-Array, wenn nicht gepflegt. |
Ingredient
Feld | Typ | Beschreibung |
|---|---|---|
| string | Zutaten-Bezeichnung (z. B. |
NutritionalValue
Feld | Typ | Beschreibung |
|---|---|---|
| string | Wert-Bezeichnung (z. B. |
| string | Einheit, freier Text (z. B. |
| string | Numerischer Wert, als String serialisiert, um das Quellformat zu erhalten. Client-seitig parsen, falls nötig. |
| int? | Optionale Referenz auf eine Unterkomponente / Variante ( |
Localization
Feld | Typ | Beschreibung |
|---|---|---|
| string | Sprach- / Culture-Schlüssel der Übersetzung (z. B. |
| string | Übersetzter Anzeigename. |
| string | Übersetzter Untertitel. |
| string | Übersetzte ausführliche Beschreibung. |
| 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-categoryWarengruppen 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 |
|---|---|---|
| int? | Pflicht (oder |
| string | Pflicht (oder |
| int? | Auf eine Kassa einschränken. Muss zum Standort gehören. |
| string | Volltextsuche über Name, externer Name und externe ID. |
| int[] | Filter nach APRO-Warengruppen-ID. |
| string[] | Filter nach externer ID. |
ProductCategory
Feld | Typ | Beschreibung |
|---|---|---|
| int | APRO-Cloud-Warengruppen-ID. |
| string | POS-Warengruppen-ID — die im lokalen Kassensystem vergebene ID. |
| string | Anzeigename. |
| string | Name für externe Oberflächen. |
| Kassa / Lieferant. | |
| Steuersatz. | |
| Gepflegte Übersetzungen der Warengruppentexte und des Bildes, ein Eintrag je Sprache. Leer, wenn keine vorhanden sind. |
CategoryCashbox
Feld | Typ | Beschreibung |
|---|---|---|
| int? | Zugehörige Kassa. |
| int? | Standard-Lieferant für Artikel der Warengruppe. |
CategoryTax
Feld | Typ | Beschreibung |
|---|---|---|
| int? | Standard-Steuersatz für Artikel der Warengruppe. |
| double | USt-Prozentsatz (z. B. |
CategoryLocalization
Feld | Typ | Beschreibung |
|---|---|---|
| string | Sprach- / Culture-Schlüssel der Übersetzung (z. B. |
| string | Übersetzter Anzeigename. |
| string | Übersetzte Beschreibung. |
| 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/spotSpots sind die physischen / logischen Ziele, an die ein Gast bestellen kann: Tische, Zimmer, Abholtheken, Lieferzonen, Drive-in.
Query-Parameter
Parameter | Typ | Beschreibung |
|---|---|---|
| int? | Pflicht (oder |
| string | Pflicht (oder |
| int? | Auf einen Bereich / ein Zimmer einschränken. |
| string | Volltextsuche über Name, Alternativname, externe Spot-ID und QR-Code. |
| int[] | Filter nach APRO-Spot-ID. |
| string[] | Filter nach externer Spot-ID. |
| enum |
|
| bool? | Filter nach App-Sichtbarkeit. |
| bool? | Filter nach Tischbestell-Fähigkeit. |
Spot
Feld | Typ | Beschreibung |
|---|---|---|
| int | APRO-Cloud-Spot-ID. |
| string | POS-Spot-ID — die im lokalen Kassensystem vergebene ID. |
| int? | Numerisches Label (z. B. Tischnummer). |
| string | Anzeigename. |
| string | Sekundärer Name für manche Oberflächen. |
| string | Freitext-Beschreibung. |
| string | QR-Code-Payload des Spots. |
| enum | Spot-Typ. |
| int? | Sitzplätze (Tisch) / Maximalgäste (Zimmer). |
| Zugehöriger Standort. | |
| Zugehöriger Bereich, falls vorhanden. | |
| Geokoordinaten. | |
| Fähigkeits-Flags. |
SpotLocation
Feld | Typ | Beschreibung |
|---|---|---|
| int | Numerische Standort-ID. |
| string | Kurzcode für Cross-API-Joins. |
SpotArea
Feld | Typ | Beschreibung |
|---|---|---|
| int | APRO-Bereichs-ID. |
| string | Anzeigename des Bereichs. |
| string | Externe Referenz des Bereichs. |
SpotGeo
Feld | Typ | Beschreibung |
|---|---|---|
| double? | WGS84-Breite. |
| double? | WGS84-Länge. |
SpotFlags
Feld | Typ | Beschreibung |
|---|---|---|
| bool | Sichtbar in Storefronts. |
| bool | Akzeptiert Tischbestellungen. |
| bool | Akzeptiert Bargeld. |
| bool | Default-Bestellmodus ist Takeaway. |
| 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. WennproductNrnicht auflöst, greift im Bestell-Pipeline ein Fallback über denname-Wert des Entries gegenproduct.name.product.productGroupExternalIds[i]↔productCategory.externalIdproduct.cashbox.cashboxId/productCategory.cashbox.cashboxId↔cashboxId-Filter auf beiden Endpunktenspot.location.locationShortCode↔locationShortCode-Filter überallspot.id↔SpotIDder External-Order-API — dieiddes Spots direkt zurückgeben, um eine Bestellung an denselben Spot zurückzurouten, von dem sie gelesen wurde.spot.qrCodeentspricht demQRCode-Feld der External-Order-API.
Fehler
Status | Bedeutung |
|---|---|
| Weder |
|
|
| Key nicht für den Standort autorisiert. |
| 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- Auth: nein
- Beispiel: my.apro.at/api/v1/locations/7236/menus
Die Antwort ist ein Baum. Ebenen von außen nach innen:
- Menu — oberste Gruppierung (z. B. Speisen, Getränke, Angebote).
- Category — Abschnitt innerhalb eines Menüs (z. B. Hauptspeisen).
- MenuEntry — konkreter verkaufbarer Artikel.
- MenuEntryFeature — Beilagen-/Modifier-Gruppe zu einem Artikel.
- MenuEntryFeatureValue — einzelne Option innerhalb einer Gruppe.
- 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 |
|---|---|---|
| string | Anzeigename. |
| enum | Optischer Badge — siehe Badges. |
| string | URL zum Titelbild. |
| string | Beschreibungstext. |
| Category[] | Kategorien in diesem Menü. |
| Hours[] | Zeiten, in denen Bestellungen möglich sind. |
| Hours[] | Zeiten, für die Vorbestellungen möglich sind. |
| Hours[] | Zeiten, in denen Zustellung möglich ist. |
| string | Zu verwendende Preisebene (z. B. Hauspreis, Happy Hour). |
Mutationen (Auth erforderlich):
Aktion | Methode | Pfad |
|---|---|---|
Anlegen |
|
|
Aktualisieren |
|
|
Löschen |
|
|
2. Category
Unterpunkt eines Menüs. Beispiele: Alkoholfreie Getränke, Bier, Hauptspeisen, Nachspeisen.
Feld | Typ | Beschreibung |
|---|---|---|
| string | Anzeigename. |
| enum | Siehe Badges. |
| string | URL zum Titelbild. |
| string | Beschreibungstext. |
| MenuEntry[] | Einträge in dieser Kategorie. |
| Hours[] | Bestellzeiten. |
| Hours[] | Vorbestellzeiten. |
| Hours[] | Lieferzeiten. |
Mutationen (Auth erforderlich):
Aktion | Methode | Pfad |
|---|---|---|
Anlegen |
|
|
Aktualisieren |
|
|
Löschen |
|
|
3. MenuEntry
Konkret verkaufbarer Artikel. Beispiele: Wiener Schnitzel, Kaiser Bier.
Feld | Typ | Beschreibung |
|---|---|---|
| integer | Eindeutige ID; wird mit der Bestellung zurückgeschickt. |
| string | Artikelname. |
| enum | Siehe Badges. |
| string | URL zum Artikelbild. |
| string | Kurzbeschreibung. |
| string | Beschreibungstext. |
| MenuEntryFeature[] | Beilagen-Gruppen (Größen, Beilagen …). |
| money | Verkaufspreis. |
Mutationen (Auth erforderlich):
Aktion | Methode | Pfad |
|---|---|---|
Anlegen |
|
|
Aktualisieren |
|
|
Löschen |
|
|
4. MenuEntryFeature
Beilagen-/Modifier-Gruppe eines MenuEntry. Beispiele: Gebindegröße, Beilage zur Hauptspeise.
Feld | Typ | Beschreibung |
|---|---|---|
| string | Bezeichnung der Beilage. |
| string | Beschreibungstext. |
| string | Gruppierungsschlüssel für seitenweise Anzeige. |
| boolean |
|
| number | Maximalanzahl auswählbarer Optionen. |
| number | Mindestanzahl auswählbarer Optionen. |
| number | Anzahl der Optionen ohne Aufpreis. |
| MenuEntryFeatureValue[] | Verfügbare Optionen. |
Mutationen (Auth erforderlich):
Aktion | Methode | Pfad |
|---|---|---|
Anlegen |
|
|
Aktualisieren |
|
|
Löschen |
|
|
5. MenuEntryFeatureValue
Einzelne Ausprägung einer Beilage. Beispiele: Bier 0,3 l, Bier 0,5 l, Pommes, Kartoffeln.
Feld | Typ | Beschreibung |
|---|---|---|
| string | Bezeichnung. |
| string | Beschreibungstext. |
| string | URL zum Optionsbild. |
| number | Mindeststückzahl bei Auswahl. |
| number | Maximale Stückzahl. |
| number | Stückzahl ohne Aufpreis. |
| number | Preisanpassung pro Stück. |
Mutationen (Auth erforderlich):
Aktion | Methode | Pfad |
|---|---|---|
Anlegen |
|
|
Aktualisieren |
|
|
Löschen |
|
|
6. Hours
Gültigkeitsfenster an Menüs oder Kategorien. Sie steuern, wann bestellt, vorbestellt oder geliefert werden darf.
Feld | Typ | Beschreibung |
|---|---|---|
| enum bitmask | Bitmaske der Wochentage — siehe unten. |
| date | Absolutes Startdatum. |
| date | Absolutes Enddatum. |
| daytime | Startzeit an gültigen Tagen. |
| daytime | Endzeit an gültigen Tagen. |
| boolean | Fenster ausschließen statt einschließen. |
Wochentags-Bitmaske:
Tag | Bit | Wert |
|---|---|---|
Montag |
|
|
Dienstag |
|
|
Mittwoch |
|
|
Donnerstag |
|
|
Freitag |
|
|
Samstag |
|
|
Sonntag |
|
|
Beispiel: Mo–Fr ergibt 2 + 4 + 8 + 16 + 32 = 62.
Badges
Optische Hervorhebungen für Menüs, Kategorien und Einträge:
Bezeichnung | Wert |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
- Host:
cloudgateway.apro.at(Pfadpräfix/hotelservice/) - Auth: API-Key im
x-api-key - Content type:
application/json - Ergänzende Referenz: api-docs.apro.at → hotelservice ↗
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/reservationsRequest-Body
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| Reservation[] | ✅ | Eine oder mehrere Reservierungen. |
Reservation
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| string | ✅ | Reservierungs-ID im PMS. |
| string | ✅ | PMS-Mandant / Hotel. |
| string | ✅ | Gast-ID im PMS. |
| string | ✅ | Zugewiesene Zimmernummer. |
| string | — | Anrede ( |
| string | — | |
| string | — | |
| ISO 8601 | ✅ | Check-in-Zeitpunkt. |
| ISO 8601 | ✅ | Check-out-Zeitpunkt. |
| integer | — | Personenzahl — für Covers/Buffet-Regeln. |
| Identifier[] | — | Keycard-UIDs, NFC-Tokens, Zimmer-Tablets — alle Kennungen, mit denen das POS eine Zimmerbuchung erkennen soll. |
Identifier
Feld | Typ | Beschreibung |
|---|---|---|
| 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.checkedOutReservationsCountzeigt 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=trueQuery-Parameter
Name | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| string | ✅ | PMS-Mandant / Hotel. |
| bool | — |
|
Antwortstruktur
Jedes Element hat ein chargeType, das bestimmt welche Felder befüllt sind:
| Bedeutung |
|
|---|---|---|
| Offene Buchung aufs Zimmer (Gast hat noch nicht gezahlt — POS bucht aufs Zimmer-Folio). | nein |
| Geschlossener Beleg (Gast hat an der Kassa bezahlt; PMS kann ignorieren oder abgleichen). | ja |
Top-Level:
Feld | Typ | Beschreibung |
|---|---|---|
| integer | Angewandte Seitengröße. |
| integer | Offset im Backlog. |
| integer | Noch nicht bestätigte Einträge insgesamt. |
| Item[] | Charges und Receipts. |
Item
Feld | Typ | Beschreibung |
|---|
| `"Charge" | "Receipt"` | Siehe Tabelle oben. |
| ISO 8601 | Buchungszeitpunkt im POS. |
| integer | POS-Belegnummer. |
| integer | Bediener-ID. |
| string | Bedienername. |
| integer | Outlet-ID ( |
| integer | Tischnummer. |
| string | Tischbezeichnung. |
| decimal | Bruttosumme der Zeile. |
| decimal | Enthaltenes Trinkgeld. |
| GUID | APRO-Transaktions-ID — genau dieser Wert wandert als |
| string | null | Reservierungs-ID (bei |
| string | null | Gast-ID. |
| string | null | Zimmernummer. |
| LineItem[] | Einzelpositionen. |
| Payment[] | Nur bei |
LineItem
Feld | Typ | Beschreibung |
|---|---|---|
| integer | Artikelnummer. |
| string | Artikelname. |
| integer | Sparten-Nummer. |
| string | Sparten-Bezeichnung. |
| decimal | Stückzahl. |
| decimal | Brutto-Einzelpreis. |
| 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 wiereservationId,guestIdoderroomNumberkönnen
links mit Leerzeichen aufgefüllt sein, wenn die Kassa diese Felder
fixer Breite speichert (" 15631"). Im PMS vor dem Matchingtrim()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-chargesRequest-Body
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| string | ✅ | PMS-Mandant / Hotel. |
| GUID[] | ✅ |
|
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=Abc123Alle 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/reservationsist idempotent über{ hotelId, Id, guestId }. Gleicher Payload → gleicher Zustand.POST /pms/confirm-chargesist idempotent übertransactionIds. 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.
- Host:
cloudgateway.apro.at(Pfadpräfix/voucherservice/) - Auth: API-Key im
x-api-key - Content type:
application/json - Ergänzende Referenz: api-docs.apro.at → voucherservice ↗
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 |
|---|---|---|
| GUID | Interne APRO-Gutschein-ID (URL-Parameter). |
| string | Lesbarer Code auf der Karte/dem Beleg. |
| integer | Ursprünglicher Nennwert (Minor Units). |
| integer | Verfügbares Guthaben (Minor Units). |
| integer | null | Aufladelimit; |
| string | null | Optionale Zweit-ID. |
| ISO 8601 | null | Vor diesem Zeitpunkt nicht gültig. |
| ISO 8601 | null | Nach diesem Zeitpunkt nicht gültig. |
| bool |
|
| string | null | Freitext-Notiz. |
| integer | Gutscheinkategorie (Ausgabekanal). |
| string | Kategoriename. |
| integer | null | Einlöse-Limit auf Kategorie-Ebene. |
| string | null | Verweis auf externen Payment-Provider. |
| ISO 8601 | Ausgabezeitpunkt. |
| string |
|
1. Gutscheine auflisten
GET https://cloudgateway.apro.at/voucherservice/api/v1/vouchersLiefert 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=560620085Query-Parameter
Name | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| 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 |
|---|---|
|
|
|
|
| Vor |
|
|
|
|
|
|
3. Gutschein ausstellen
POST https://cloudgateway.apro.at/voucherservice/api/v1/vouchers/giftsLegt 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 |
|---|---|---|---|
| integer | ✅ | Gutscheinkategorie. |
| string | ✅ | Aufgedruckter Code. Muss eindeutig sein. |
| string | — | Freitext-Notiz. |
| integer | ✅ | Nennwert in Minor Units. |
| integer | — | Aufladelimit. |
| string | ✅ | Kennung des aufrufenden Systems. Wird von APRO für deine Integration vergeben — siehe clientType. |
| string (JSON) | — | Serialisierte JSON-Zusatzinfo (Outlet etc.). |
| ISO 8601 | — | Gültig ab. |
| 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}/redemptionZieht 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 |
|---|---|---|---|
| integer | ✅ | Abzubuchender Betrag in Minor Units. |
| string | — | Opake Trace-Zeichenkette (z. B. |
| string (JSON) | — | Zusatzinfo. |
| string | — | Freitext-Notiz. |
| 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}/bookingBucht amount Minor Units zurück auf den Gutschein — etwa beim Aufladen einer Mehrweg-Karte oder bei Rückbuchungen.
Request-Body
Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| integer | ✅ | Gutzubuchender Betrag in Minor Units. |
| string | — | Opake Trace-Zeichenkette. |
| string | ✅ | Kennung des aufrufenden Systems. Wird von APRO vergeben — siehe clientType. |
| string (JSON) | — | Zusatzinfo. |
| 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: falsebei 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.