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. |
