Plugin data and statistics endpoints

This guide describes the plugin data and statistics endpoints exposed by APIPluginController, and the response models an integration should expect.

At a glance

Both endpoints are GET requests scoped to a company plugin registration. They require the registration's shared secret in the SCANFIE-ACP request header. The registration must exist and be enabled, and requests for the same registration are serialized by a lock.

PurposeEndpointSuccessful response
Read products or catalog combos/api/plugin/data/{companyPluginRegistrationId}JSON array of PluginProduct or PluginCatalogCombo
Read sales statistics/api/plugin/statistics/{companyPluginRegistrationId}APIStatisticsResponse containing PluginStatisticsLine records

Replace {companyPluginRegistrationId} with the ID of the configured company plugin registration. Use the same base URL as the Scanfie application.

Authentication and request behavior

Send the registration's shared secret in the SCANFIE-ACP header:

SCANFIE-ACP: <shared-secret>

The endpoint compares the value case-insensitively with the registration's configured shared secret. Do not put the secret in the URL. Requests without a valid secret are rejected with a JSON command-response error.

Both endpoints run the corresponding registered account hook before producing their endpoint data. The hook request contains the company transcribed as PluginCompany, the initialized hook data, and the plugin instance. A non-empty raw request body is also copied to the hook request's plugin-hook-payload field; integrations should normally call these GET endpoints without a body and use query parameters as documented below.

Only enabled registrations are processed. A concurrent request for the same registration is rejected. The controller sets the response content type to application/json; it does not explicitly set endpoint-specific HTTP status codes for these validation errors, so clients should inspect the JSON response rather than relying only on the HTTP status.

The controller does not add a not-found error when the registration ID does not resolve; in that case it may return an empty response. Treat an empty or non-JSON body as an unsuccessful integration response.

Data endpoint

Request

GET /api/plugin/data/{companyPluginRegistrationId}
SCANFIE-ACP: <shared-secret>

Optional query parameter:

NameValuesBehavior
typePRODUCTS, COMBOSSelects the data array. Case-insensitive. If absent, the endpoint uses the data type returned by the data hook. An unrecognized non-empty value currently falls back to PRODUCTS.

The data hook is selected from the registration's DATA hook registry and is expected to be an AbstractAccountHook. Its response should be an AccountPluginDataHookResponse with dataType set to PRODUCTS or COMBOS, unless the caller supplies type. The hook is still executed when type is supplied; the query parameter overrides the hook's returned type for choosing the endpoint output.

If no data type is available, the endpoint returns an API command-response error. If there are no registered data hooks, the current error text is "No statistics hooks found" (the message is shared with the statistics endpoint).

Successful response: products

The endpoint converts the company's active products into a JSON array of PluginProduct. "Active" here means products for the registration's company that are not deleted.

FieldJSON typeNotes
internalIdstringScanfie product ID.
systemArticleId, externalIdstringProduct identifiers used by the integration.
name, internalName, subtitle, descriptionstringDisplay and internal text.
isCombobooleanWhether the product represents a combo.
productGroupExternalIdstringExternal product-group identifier.
productGroupobjectPluginProductGroup: internalId, name, externalId.
eanSkustringEAN/SKU identifier.
price, nettPrice, vatPercentagenumberMonetary values and optional VAT percentage.
vatTypestringUNKNOWN, NONE, ZERO, HIGH, STANDARD, LOW, REDUCED_1, REDUCED_2, or SPECIAL.
soldOut, hidden, dynamicProductbooleanAvailability/display and dynamic-product flags.
turnoverGroupintegerTurnover group number.
turnoverGroupExternalIdstringExternal turnover-group identifier.
turnoverGroupEntityobjectPluginTurnoverGroup, when populated.
syncMarkerstringSynchronization marker.
dtDeleteddate/timeDeleted timestamp; active products normally have no value.
imageUrlstringURL of a non-deleted product image, when present.
base64ImageListarray of stringsBase64 image data, when populated.
productTypestringProduct category such as FOOD, DRINKS, TICKET, or OTHER.
productCounterColorstringCounter display color.
propertiesarray of objectsPluginCatalogProperty: name, externalId, internalId, deliverectTagId.

Example (fields can be null or omitted depending on serialization and source data):

[
  {
    "internalId": "product-id",
    "systemArticleId": "1001",
    "name": "Coffee",
    "externalId": "coffee",
    "price": 3.0,
    "nettPrice": 2.75,
    "vatType": "HIGH",
    "soldOut": false
  }
]

Successful response: combos

The endpoint converts the company's catalog combos into a JSON array of PluginCatalogCombo. The combo model contains:

FieldJSON typeNotes
namestringCombo name.
systemArticleId, externalId, internalIdstringScanfie and integration identifiers.
sequenceintegerDisplay/order sequence.
pricenumberCombined price when configured; otherwise primary-product price, or 0.
descriptionstringCombo description.
primaryProductobjectMain PluginProduct, when present.
subselectionsarray of objectsPluginSubselection values describing selectable components.

Each PluginSubselection can include name, externalId, internalId, catalogProduct, subselectionType, subselectionFunctionalType, shallowSubselection, sequence, required, products, productSelections, properties, minimumSelections, and maximumSelections. Nested products and primaryProduct values use the PluginProduct model above.

Example:

[
  {
    "name": "Lunch combo",
    "systemArticleId": "2001",
    "externalId": "lunch-combo",
    "price": 12.5,
    "primaryProduct": {
      "internalId": "product-id",
      "name": "Sandwich"
    },
    "subselections": []
  }
]

Statistics endpoint

Request

GET /api/plugin/statistics/{companyPluginRegistrationId}?from=2026-09-01&to=2026-09-30
SCANFIE-ACP: <shared-secret>

Required query parameters:

NameMeaning
fromInclusive start boundary for submitted-order statistics.
toInclusive end boundary for submitted-order statistics.

Supported formats include yyyy-MM-dd, dd-MM-yyyy, yyyy-MM-dd HH:mm:ss, dd-MM-yyyy HH:mm:ss, and yyyyMMddHHmm. Prefer full timestamps (yyyy-MM-dd HH:mm:ss) when exact boundary times matter. A date-only to value is parsed at midnight, not adjusted to the end of that day.

The date range must be valid, from must not be after to, and the difference between the boundaries must not exceed 42 days.

The statistics hook is selected from the registration's STATISTICS hook registry and is expected to be an AbstractAccountHook returning AccountPluginStatisticsHookResponse. The response must set groupType:

groupTypeAggregation
TURNOVERGROUPSGroup by turnover group.
PRODUCTGROUPSGroup by product group.

The hook chooses the group type; this endpoint has no query parameter for overriding it. The endpoint calculates and returns statistics after the hook executes.

Successful response model

A successful response is an APIStatisticsResponse:

{
  "lines": [
    {
      "id": "group-id",
      "turnoverGroupNumber": 1,
      "externalId": "group-1",
      "name": "Food",
      "currency": null,
      "quantity": 10,
      "price": 100.0,
      "nettPrice": 90.91,
      "taxPrice": 9.09,
      "taxRate": 10.0,
      "statisticsGroupType": "TURNOVERGROUPS"
    }
  ],
  "errors": []
}

APIStatisticsResponse contains errors (an array of APIError) and lines (a collection of PluginStatisticsLine). The line fields are:

FieldJSON typeMeaning
idstringScanfie group ID; can be null for an unassigned group.
turnoverGroupNumberintegerGroup number, or an internally generated fallback value.
externalIdstringExternal group ID when available.
namestringGroup name or fallback label.
currencystringCurrency field in the model; may be unset.
quantityintegerAggregated confirmed quantity.
pricenumberGross total.
nettPricenumberNet total.
taxPricenumberTax total (price - nettPrice).
taxRatenumberTax percentage when it can be determined.
statisticsGroupTypestringTURNOVERGROUPS or PRODUCTGROUPS.

Statistics include submitted, confirmed order lines in the requested range and exclude aborted, refused, returned, or deleted orders, zero-quantity lines, and registrations excluded from turnover.

Error responses and client handling

Validation and processing errors are serialized as an API command response rather than the successful data array. The common shape includes an errors array; each APIError has errorMessage and severity (info, warning, or error). Error responses can also include command metadata such as messages, responseCode, and responseBody.

Examples of endpoint errors include invalid shared secret, disabled registration, a concurrent request, no matching hooks, a missing hook result type, an invalid date range, and a range exceeding 42 days. For robust integrations:

  1. Send SCANFIE-ACP and check the response body is valid JSON.
  2. For the data endpoint, expect an array on success and check for an errors object/array before deserializing data.
  3. For the statistics endpoint, deserialize APIStatisticsResponse and inspect errors before consuming lines.
  4. Treat an empty body, unexpected shape, or non-empty errors as a failed call; do not interpret an error payload as an empty data set.

Related API types

The endpoint models are defined in the integration plugin API: