External Catalog API
The External Catalog API exposes a slim, read-only view of the master data inside an APRO location — the products, product categories and spots (tables, rooms, pickup points) — for use in white-label apps, kiosks, external menus and partner integrations.
The three endpoints share the same conventions: scoping by location, nested DTO shape, filtering via query parameters, and pagination.
- Host:
my.apro.at - Auth: API key in
x-api-key - Content type:
application/json
Endpoints
| Method | URL | Returns |
|---|---|---|
GET | /api/v1/external/product | Paged list of Product |
GET | /api/v1/external/product-category | Paged list of ProductCategory |
GET | /api/v1/external/spot | Paged list of Spot |
Common conventions
Scoping to a location
Every request must address one concrete location. Provide one of:
locationId— numeric IDlocationShortCode— short string identifier (e.g.vienna-main)
If neither resolves to an existing location, the request is rejected
with 400 Bad Request.
Pagination
| Query param | Default | Description |
|---|---|---|
skip | 0 | Number of records to skip. |
take | 50 | Page size. Capped at 500. |
Every response is wrapped in a paged envelope:
{ "page": { "offset": 0, "limit": 50, "total": 1234 }, "data": [ /* ... */ ]}offsetandlimitecho the request.totalis the total number of records matching the filter, across all pages — use it to driveskip/takeloops.
Filtering
Filters are passed as bracketed query parameters under the
filter key — filter['<name>']=<value>. Plain ?<name>=<value> is
ignored. Pagination (skip, take) stays as plain query params.
List-valued filters (productIds, gtins, externalSpotIds, …) are
comma-separated. Escape literal commas with \,.
GET /api/v1/external/product?filter['locationShortCode']=vienna-main &filter['productNrs']=A-100,A-200 &filter['isVisible']=true &filter['search']=schnitzel &skip=0&take=100Notes:
- The bracket key is case-insensitive —
filter['LocationShortCode']andfilter['locationShortCode']are equivalent. filter["…"](double quotes, URL-encoded as%22) works too.- Unknown filter parameters are ignored.
Products
GET https://my.apro.at/api/v1/external/productQuery parameters
| Param | Type | Description |
|---|---|---|
locationId | int? | Required (or locationShortCode). |
locationShortCode | string | Required (or locationId). |
cashboxId | int? | Restrict to one cashbox. Must belong to the location. |
search | string | Free-text match over name, external name, productNr, GTIN and external product ID. |
productIds | int[] | Filter by APRO product ID. |
productNrs | string[] | Filter by productNr (the value passed back in External Order entries). |
gtins | string[] | Filter by GTIN (barcode). |
externalProductIds | string[] | Filter by external product ID (third-party reference). |
isVisible | bool? | Filter by visibility flag. |
isLocked | bool? | Filter by locked flag. |
Product
| Field | Type | Description |
|---|---|---|
id | int | APRO Cloud product ID. |
productNr | string | Identifier the order pipeline uses to resolve this product. Pass this value straight back as the entry’s productNr when placing an order via the External Order API. Backed by the POS article number. |
gtin | string | Barcode / GTIN. |
externalProductId | string | POS product ID — the ID assigned by the local POS system. Informational; not what External Order’s productNr resolves against. |
name | string | Display name. |
externalName | string | Name for external surfaces — fallback to name if empty. |
subtitle | string | Short subtitle. |
description | string | Long-form description. |
pricing | Pricing | Price and tax info. |
cashbox | ProductCashbox | Cashbox / parent / production-place wiring. |
media | Media | Image URLs. |
flags | Flags | Visibility, locking, review state. |
nutrition | Nutrition | Amount, calories, allergens, age limit. |
productGroupExternalIds | string[] | External IDs of the product categories this product belongs to. |
localizations | Localization[] | Maintained translations of the product’s texts and image, one entry per locale. Empty when none exist. |
Pricing
| Field | Type | Description |
|---|---|---|
defaultPrice | double? | Default price. |
referencePrice | double? | Reference / strike-through price. |
taxRateId | int? | APRO tax rate ID. |
allowClientPrice | bool | Whether clients may override the price. |
ProductCashbox
| Field | Type | Description |
|---|---|---|
cashboxId | int | Owning cashbox. |
productionPlaceId | int? | Kitchen / bar where the product is prepared. |
parentProductId | int? | Set on variants / template-derived products. |
Media
| Field | Type | Description |
|---|---|---|
imageUrl | string | Public image URL. |
Flags
| Field | Type | Description |
|---|---|---|
isVisible | bool | Visible in storefronts. |
isLocked | bool | Locked from automatic sync changes. |
queuedForReview | bool | Pending manual review. |
Nutrition
| Field | Type | Description |
|---|---|---|
amount | double? | Net amount. |
amountUnit | string | Unit, e.g. g, ml. |
calories | double? | Calories per amount. |
allergens | int (flags) | Bit-flag of declared allergens. |
minimumAge | int | Minimum buyer age (0 if none). |
ingredients | Ingredient[] | Declared ingredients list. Empty array when not maintained. |
nutritionalValues | NutritionalValue[] | Structured nutritional values. Empty array when not maintained. |
Ingredient
| Field | Type | Description |
|---|---|---|
name | string | Ingredient name (e.g. Lamm, Weizenmehl). |
NutritionalValue
| Field | Type | Description |
|---|---|---|
name | string | Value label (e.g. Gewicht, Kalorien, Fett). |
unit | string | Unit, free-form (e.g. g, ml, kcal). |
value | string | Numeric value, serialized as a string to preserve the source format. Parse client-side if needed. |
belongsTo | int? | Optional reference to a sub-component / variant (null when the value applies to the whole product). |
Localization
| Field | Type | Description |
|---|---|---|
locale | string | Locale / culture key of the translation (e.g. en, it, de-DE). |
name | string | Translated display name. |
subtitle | string | Translated subtitle. |
description | string | Translated long-form description. |
imageUrl | string | Locale-specific image, when one is maintained. |
The top-level name, subtitle, description and media.imageUrl
always carry the default (German) values — localizations never
replaces them, it only adds the translations that exist. Entries without
a locale key are omitted, and individual fields inside an entry may be
null when that particular text was not translated. Resolve a display
string by looking for the caller’s locale in localizations and falling
back to the top-level field.
Example
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 } ] }, { "id": 9002, "productNr": "A-110", "gtin": null, "externalProductId": null, "name": "Tafelspitz", "externalName": null, "subtitle": null, "description": null, "pricing": { "defaultPrice": 24.5, "referencePrice": null, "taxRateId": 3, "allowClientPrice": false }, "cashbox": { "cashboxId": 17, "productionPlaceId": 2, "parentProductId": null }, "media": { "imageUrl": null }, "flags": { "isVisible": true, "isLocked": false, "queuedForReview": false }, "nutrition": { "amount": null, "amountUnit": null, "calories": null, "allergens": 0, "minimumAge": 0, "ingredients": [], "nutritionalValues": [] }, "productGroupExternalIds": ["mains"], "localizations": [] } ]}Download: external-products-response.json
Product categories
GET https://my.apro.at/api/v1/external/product-categoryCategories correspond to APRO product groups — the cashbox-scoped
grouping that drives reports, tax rates and printing order. The
productGroupExternalIds field on a product references the same
externalId returned here, so you can join the two responses without
extra lookups.
Query parameters
| Param | Type | Description |
|---|---|---|
locationId | int? | Required (or locationShortCode). |
locationShortCode | string | Required (or locationId). |
cashboxId | int? | Restrict to one cashbox. Must belong to the location. |
search | string | Free-text match over name, external name and external ID. |
categoryIds | int[] | Filter by APRO category ID. |
externalIds | string[] | Filter by external ID. |
ProductCategory
| Field | Type | Description |
|---|---|---|
id | int | APRO Cloud category ID. |
externalId | string | POS category ID — the ID assigned by the local POS system. |
name | string | Display name. |
externalName | string | Name for external surfaces. |
cashbox | CategoryCashbox | Cashbox / supplier wiring. |
tax | CategoryTax | Tax wiring. |
localizations | CategoryLocalization[] | Maintained translations of the category’s texts and image, one entry per locale. Empty when none exist. |
CategoryCashbox
| Field | Type | Description |
|---|---|---|
cashboxId | int? | Owning cashbox. |
supplierId | int? | Default supplier for products in this category. |
CategoryTax
| Field | Type | Description |
|---|---|---|
taxRateId | int? | Default tax rate for products in this category. |
vat | double | VAT percentage (e.g. 20 for 20 %). |
CategoryLocalization
| Field | Type | Description |
|---|---|---|
locale | string | Locale / culture key of the translation (e.g. en, it, de-DE). |
name | string | Translated display name. |
description | string | Translated description. |
imageUrl | string | Locale-specific image, when one is maintained. |
Same rules as on products: the top-level name stays the default
(German) value and localizations only adds what exists.
Example
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 are the physical / logical destinations a guest can order to: tables, rooms, pickup counters, delivery zones, drive-in lanes.
Query parameters
| Param | Type | Description |
|---|---|---|
locationId | int? | Required (or locationShortCode). |
locationShortCode | string | Required (or locationId). |
areaId | int? | Restrict to one area / room. |
search | string | Free-text match over name, alternative name, external spot ID and QR code. |
spotIds | int[] | Filter by APRO spot ID. |
externalSpotIds | string[] | Filter by external spot ID. |
type | enum | Spot / Table / Room / Pickup / Delivery / … |
visibleInApp | bool? | Filter on app visibility. |
supportsTableOrder | bool? | Filter on table-order capability. |
Spot
| Field | Type | Description |
|---|---|---|
id | int | APRO Cloud spot ID. |
externalSpotId | string | POS spot ID — the ID assigned by the local POS system. |
number | int? | Numeric label (e.g. table number). |
name | string | Display name. |
alternativeName | string | Secondary name used by some surfaces. |
description | string | Free-form description. |
qrCode | string | QR-code payload bound to the spot. |
type | enum | Spot type. |
capacity | int? | Number of seats (tables) / max guests (rooms). |
location | SpotLocation | Owning location. |
area | SpotArea | Owning area, if any. |
geo | SpotGeo | Geo coordinates, if any. Omitted when both are null. |
flags | SpotFlags | Capability flags. |
SpotLocation
| Field | Type | Description |
|---|---|---|
locationId | int | Numeric location ID. |
locationShortCode | string | Short code for cross-API joins. |
SpotArea
| Field | Type | Description |
|---|---|---|
areaId | int | APRO area ID. |
name | string | Area display name. |
externalAreaId | string | External reference for the area. |
SpotGeo
| Field | Type | Description |
|---|---|---|
latitude | double? | WGS84 latitude. |
longitude | double? | WGS84 longitude. |
SpotFlags
| Field | Type | Description |
|---|---|---|
visibleInApp | bool | Visible in storefronts. |
supportsTableOrder | bool | Accepts in-house orders. |
supportsCashPayment | bool | Accepts cash. |
defaultToTakeAway | bool | Default order mode is takeaway. |
displayAsButton | bool | Render as a button instead of a list item. |
Example
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 } }, { "id": 1502, "externalSpotId": "table-02", "number": 2, "name": "Tisch 2", "alternativeName": null, "description": null, "qrCode": "QR-02", "type": "Table", "capacity": 2, "location": { "locationId": 7236, "locationShortCode": "vienna-main" }, "area": { "areaId": 11, "name": "Gastgarten", "externalAreaId": "garden" }, "geo": { "latitude": 48.2082, "longitude": 16.3738 }, "flags": { "visibleInApp": true, "supportsTableOrder": true, "supportsCashPayment": true, "defaultToTakeAway": false, "displayAsButton": false } } ]}Download: external-spots-response.json
Cross-API joins
These three endpoints are designed to be joined client-side:
product.productNr↔productNr(entry / addon) on the External Order API — case-insensitive, matched only against products on the location’s primary cashbox. WhenproductNrdoesn’t resolve, the order pipeline falls back to matching the entry’snameagainstproduct.name.product.productGroupExternalIds[i]↔productCategory.externalIdproduct.cashbox.cashboxId/productCategory.cashbox.cashboxId↔cashboxIdfilter on either endpointspot.location.locationShortCode↔locationShortCodefilter everywherespot.id↔SpotIDon the External Order API — pass the spot’sidstraight back to route an order to the same spot it was read from.spot.qrCodemaps to the External Order API’sQRCodefield.
Errors
| Status | Meaning |
|---|---|
400 Bad Request | Neither locationId nor locationShortCode resolved to a location. |
401 Unauthorized | Missing or invalid x-api-key. |
403 Forbidden | Key not authorised for the requested location. |
429 Too Many Requests | Rate-limited — back off and retry with jitter. |