APRO Dokumentation · Stand 6. November 2026

External Catalog API

Zuletzt aktualisiert: 5. November 2026ca. 8 Min. Lesezeit

PDF erzeugen

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
Tip: Try it in Postman
Download the ready-to-run collection:
apro-external-catalog.postman_collection.json.
Import it into Postman, set apiKey and locationShortCode in the
collection's Variables tab, hit Send.
Caution: Filters use a bracketed query syntax
All filter fields — including the required locationId /
locationShortCode — must be passed as filter['<name>']=<value>,
not as plain query parameters. Plain ?locationShortCode=… is
silently ignored and the request fails with "location not resolved".
See Filtering for full rules.

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:

  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 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": [ /* ... */ ]
}
  • 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-insensitive — filter['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

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

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/spot

Spots 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. 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.cashboxId ↔ cashboxId filter on either endpoint
  • spot.location.locationShortCode ↔ locationShortCode filter everywhere
  • spot.id ↔ SpotID 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

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.