Skip to content

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

MethodURLReturns
GET/api/v1/external/productPaged list of Product
GET/api/v1/external/product-categoryPaged list of ProductCategory
GET/api/v1/external/spotPaged list of Spot

Common conventions

Scoping to a location

Every request must address one concrete location. Provide one of:

  1. locationId — numeric ID
  2. locationShortCode — short string identifier (e.g. vienna-main)

If neither resolves to an existing location, the request is rejected with 400 Bad Request.

Pagination

Query paramDefaultDescription
skip0Number of records to skip.
take50Page size. Capped at 500.

Every response is wrapped in a paged envelope:

{
"page": { "offset": 0, "limit": 50, "total": 1234 },
"data": [ /* ... */ ]
}
  • offset and limit echo the request.
  • total is the total number of records matching the filter, across all pages — use it to drive skip/take loops.

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=100

Notes:

  • The bracket key is case-insensitivefilter['LocationShortCode'] and filter['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/product

Query parameters

ParamTypeDescription
locationIdint?Required (or locationShortCode).
locationShortCodestringRequired (or locationId).
cashboxIdint?Restrict to one cashbox. Must belong to the location.
searchstringFree-text match over name, external name, productNr, GTIN and external product ID.
productIdsint[]Filter by APRO product ID.
productNrsstring[]Filter by productNr (the value passed back in External Order entries).
gtinsstring[]Filter by GTIN (barcode).
externalProductIdsstring[]Filter by external product ID (third-party reference).
isVisiblebool?Filter by visibility flag.
isLockedbool?Filter by locked flag.

Product

FieldTypeDescription
idintAPRO Cloud product ID.
productNrstringIdentifier 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.
gtinstringBarcode / GTIN.
externalProductIdstringPOS product ID — the ID assigned by the local POS system. Informational; not what External Order’s productNr resolves against.
namestringDisplay name.
externalNamestringName for external surfaces — fallback to name if empty.
subtitlestringShort subtitle.
descriptionstringLong-form description.
pricingPricingPrice and tax info.
cashboxProductCashboxCashbox / parent / production-place wiring.
mediaMediaImage URLs.
flagsFlagsVisibility, locking, review state.
nutritionNutritionAmount, calories, allergens, age limit.
productGroupExternalIdsstring[]External IDs of the product categories this product belongs to.
localizationsLocalization[]Maintained translations of the product’s texts and image, one entry per locale. Empty when none exist.

Pricing

FieldTypeDescription
defaultPricedouble?Default price.
referencePricedouble?Reference / strike-through price.
taxRateIdint?APRO tax rate ID.
allowClientPriceboolWhether clients may override the price.

ProductCashbox

FieldTypeDescription
cashboxIdintOwning cashbox.
productionPlaceIdint?Kitchen / bar where the product is prepared.
parentProductIdint?Set on variants / template-derived products.

Media

FieldTypeDescription
imageUrlstringPublic image URL.

Flags

FieldTypeDescription
isVisibleboolVisible in storefronts.
isLockedboolLocked from automatic sync changes.
queuedForReviewboolPending manual review.

Nutrition

FieldTypeDescription
amountdouble?Net amount.
amountUnitstringUnit, e.g. g, ml.
caloriesdouble?Calories per amount.
allergensint (flags)Bit-flag of declared allergens.
minimumAgeintMinimum buyer age (0 if none).
ingredientsIngredient[]Declared ingredients list. Empty array when not maintained.
nutritionalValuesNutritionalValue[]Structured nutritional values. Empty array when not maintained.

Ingredient

FieldTypeDescription
namestringIngredient name (e.g. Lamm, Weizenmehl).

NutritionalValue

FieldTypeDescription
namestringValue label (e.g. Gewicht, Kalorien, Fett).
unitstringUnit, free-form (e.g. g, ml, kcal).
valuestringNumeric value, serialized as a string to preserve the source format. Parse client-side if needed.
belongsToint?Optional reference to a sub-component / variant (null when the value applies to the whole product).

Localization

FieldTypeDescription
localestringLocale / culture key of the translation (e.g. en, it, de-DE).
namestringTranslated display name.
subtitlestringTranslated subtitle.
descriptionstringTranslated long-form description.
imageUrlstringLocale-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-category

Categories 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

ParamTypeDescription
locationIdint?Required (or locationShortCode).
locationShortCodestringRequired (or locationId).
cashboxIdint?Restrict to one cashbox. Must belong to the location.
searchstringFree-text match over name, external name and external ID.
categoryIdsint[]Filter by APRO category ID.
externalIdsstring[]Filter by external ID.

ProductCategory

FieldTypeDescription
idintAPRO Cloud category ID.
externalIdstringPOS category ID — the ID assigned by the local POS system.
namestringDisplay name.
externalNamestringName for external surfaces.
cashboxCategoryCashboxCashbox / supplier wiring.
taxCategoryTaxTax wiring.
localizationsCategoryLocalization[]Maintained translations of the category’s texts and image, one entry per locale. Empty when none exist.

CategoryCashbox

FieldTypeDescription
cashboxIdint?Owning cashbox.
supplierIdint?Default supplier for products in this category.

CategoryTax

FieldTypeDescription
taxRateIdint?Default tax rate for products in this category.
vatdoubleVAT percentage (e.g. 20 for 20 %).

CategoryLocalization

FieldTypeDescription
localestringLocale / culture key of the translation (e.g. en, it, de-DE).
namestringTranslated display name.
descriptionstringTranslated description.
imageUrlstringLocale-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/spot

Spots are the physical / logical destinations a guest can order to: tables, rooms, pickup counters, delivery zones, drive-in lanes.

Query parameters

ParamTypeDescription
locationIdint?Required (or locationShortCode).
locationShortCodestringRequired (or locationId).
areaIdint?Restrict to one area / room.
searchstringFree-text match over name, alternative name, external spot ID and QR code.
spotIdsint[]Filter by APRO spot ID.
externalSpotIdsstring[]Filter by external spot ID.
typeenumSpot / Table / Room / Pickup / Delivery / …
visibleInAppbool?Filter on app visibility.
supportsTableOrderbool?Filter on table-order capability.

Spot

FieldTypeDescription
idintAPRO Cloud spot ID.
externalSpotIdstringPOS spot ID — the ID assigned by the local POS system.
numberint?Numeric label (e.g. table number).
namestringDisplay name.
alternativeNamestringSecondary name used by some surfaces.
descriptionstringFree-form description.
qrCodestringQR-code payload bound to the spot.
typeenumSpot type.
capacityint?Number of seats (tables) / max guests (rooms).
locationSpotLocationOwning location.
areaSpotAreaOwning area, if any.
geoSpotGeoGeo coordinates, if any. Omitted when both are null.
flagsSpotFlagsCapability flags.

SpotLocation

FieldTypeDescription
locationIdintNumeric location ID.
locationShortCodestringShort code for cross-API joins.

SpotArea

FieldTypeDescription
areaIdintAPRO area ID.
namestringArea display name.
externalAreaIdstringExternal reference for the area.

SpotGeo

FieldTypeDescription
latitudedouble?WGS84 latitude.
longitudedouble?WGS84 longitude.

SpotFlags

FieldTypeDescription
visibleInAppboolVisible in storefronts.
supportsTableOrderboolAccepts in-house orders.
supportsCashPaymentboolAccepts cash.
defaultToTakeAwayboolDefault order mode is takeaway.
displayAsButtonboolRender 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.productNrproductNr (entry / addon) on the External Order API — case-insensitive, matched only against products on the location’s primary cashbox. When productNr doesn’t resolve, the order pipeline falls back to matching the entry’s name against product.name.
  • product.productGroupExternalIds[i]productCategory.externalId
  • product.cashbox.cashboxId / productCategory.cashbox.cashboxIdcashboxId filter on either endpoint
  • spot.location.locationShortCodelocationShortCode filter everywhere
  • spot.idSpotID on the External Order API — pass the spot’s id straight back to route an order to the same spot it was read from. spot.qrCode maps to the External Order API’s QRCode field.

Errors

StatusMeaning
400 Bad RequestNeither locationId nor locationShortCode resolved to a location.
401 UnauthorizedMissing or invalid x-api-key.
403 ForbiddenKey not authorised for the requested location.
429 Too Many RequestsRate-limited — back off and retry with jitter.