Skip to content

Configuration Endpoints

Hyvä POS is in closed beta

Hyvä POS is currently in a closed beta (pilot phase) with a small group of merchants. It is not yet generally available: the App Store release follows the pilot, and features and configuration may still change, possibly in backwards-incompatible ways, before the general release. Want to take part? Sign up at hyva.io/pos.

These endpoints carry admin settings to the register. The device reads the global config and the unified per-terminal payload. Configuration flows one way: no route lets a terminal write location settings back to Magento.

Method Route ACL Purpose
GET /V1/pos/config Hyva_Pos::locations Global POS configuration, defaults, enforcement flags
GET /V1/pos/terminals/:terminalId/config Hyva_Pos::locations Unified per-terminal bootstrap payload
GET /V1/pos/postcode-patterns Hyva_Pos::locations Per-country postcode validation patterns
GET /V1/pos/order-statuses Hyva_Pos::locations Registered order statuses for picker UIs

Global Config (GET /V1/pos/config)

Returns everything under Stores > Configuration > Hyvä POS as one object, plus feature-availability flags derived from the installed modules and provider pools. Called by the device on every settings sync, including before a PRO module is detected; it is how the app discovers what the store supports.

The response body is a JSON-encoded string (the object serialized twice). Parse the body, then parse the resulting string. Abridged example after decoding:

{
  "module_version": "1.3.0",
  "enabled": true,
  "require_staff_login": true,
  "share_held_carts": true,
  "require_terminal_registration": true,
  "default_order_status": "",
  "default_payment_methods": "cash,card",
  "default_allow_store_credit": 1,
  "default_low_stock_threshold": 10,
  "default_use_msi": true,
  "default_receipt_header": "Thank you for shopping with us",
  "default_receipt_footer": "",
  "default_receipt_show_staff_name": true,
  "default_receipt_show_order_number": true,
  "default_layaway_deposit_min_percent": 25,
  "default_layaway_expiry_days": 30,
  "default_layaway_cancellation_fee_percent": 0,
  "default_pickup_queue_filter": "all",
  "card_payments": {
    "provider": "mollie",
    "mollie": {
      "enabled": true,
      "organisation_id": "org_12345",
      "api_key": "live_...",
      "test_api_key": "test_...",
      "test_mode": false
    },
    "providers": {
      "paynl": {
        "enabled": false,
        "token_code": "AT-1234-4321",
        "api_token": "...",
        "service_id": "SL-1234-4321",
        "test_mode": false,
        "service_secret": "..."
      }
    }
  },
  "walk_in_customer": {
    "first_name": "POS",
    "last_name": "Customer",
    "email": "store@example.com",
    "phone": "+31 20 123 4567",
    "street": "Main Street 1",
    "city": "Amsterdam",
    "region_id": 0,
    "postcode": "1234AB",
    "country_id": "NL"
  },
  "telemetry": { "enabled": false, "force_enabled": false, "upload_hour": 3 },
  "tax": {
    "price_includes_tax": true,
    "apply_after_discount": true,
    "discount_tax": true,
    "display_type": 2,
    "cart_display_price": 2,
    "cart_display_subtotal": 2,
    "sales_display_zero_tax": false,
    "cart_display_zero_tax": false,
    "calculation_algorithm": "TOTAL_BASE_CALCULATION",
    "reverse_charge_enabled": false,
    "tax_exempt_enabled": false,
    "weee": {
      "enabled": true,
      "apply_vat": false,
      "include_in_subtotal": false,
      "display_sales": 1,
      "display_product": 1,
      "display_list": 1,
      "attributes": [{ "code": "recycling_fee", "label": "Recycling fee" }]
    }
  },
  "enforcement": { "receipt_header": "1", "payment_methods": "0", "tax_settings": "1" },
  "payments": {
    "pay_by_invoice_method": "checkmo",
    "pay_by_invoice_invoicing_mode": "open",
    "credit_limits_enabled": true,
    "providers": { "mollie": { "magento_method": "mollie_methods_pointofsale" } },
    "available_terminal_payment_methods": [
      { "code": "checkmo", "label": "Check / Money order" },
      { "code": "mollie_methods_pointofsale", "label": "Point of sale" }
    ],
    "builtin_refunds_enabled": true
  },
  "credits": {
    "store_credit_includes_tax": false,
    "reward_points_includes_tax": false,
    "gift_card_includes_tax": false,
    "refund_to_store_credit": false
  },
  "license": { "connected": true },
  "has_credit_providers": true,
  "has_rma_providers": true,
  "has_wishlist": true,
  "has_customer_balance": false,
  "has_gift_card": false,
  "has_reward_points": false,
  "has_rma_module": false,
  "has_msi": true,
  "shipping_label_providers": [],
  "credit_providers": [
    { "key": "store_credit", "code": "pos_builtin", "enabled": true, "label": "Store Credit" }
  ],
  "rma_providers": [
    { "key": "rma", "code": "pos_builtin", "enabled": true, "label": "Built-in Returns" }
  ]
}

The default_* keys are website- or store-scope fallbacks; a location row without an explicit value inherits them.

has_msi says whether the store has Magento Inventory (Magento_InventoryApi enabled); without it the register skips the /V1/inventory/* routes. default_use_msi is the Use Multi-Source Inventory default (hyva_pos_terminal/stock/use_msi, on by default), which a location's use_msi overrides; it is false whatever the setting says when has_msi is false.

credits.refund_to_store_credit (module 1.3.0) says whether the store refunds to store credit by default, as the active store-credit provider answers it (MemoCreditIssuerInterface::refundsToStoreCreditByDefault()). It is false without a provider, for a provider that books no refund to store credit, and when the read fails. The register preselects the Store Credit channel in its refund dialog from it.

sales_display_zero_tax and cart_display_zero_tax are Magento's Display Zero Tax Subtotal switches for sales documents and for the cart (module 1.2.0 and later). With the sales switch on, a receipt whose tax comes to zero still prints its VAT rows at 0.00; with the cart switch on, the customer display does the same. Both default to off, as in Magento.

tax.weee relays Magento's Fixed Product Tax settings (Stores > Configuration > Sales > Tax > Fixed Product Taxes) so the register charges recycling and disposal fees the way the storefront does. The fee amounts travel on the product, in the custom_attributes rows of the FPT attribute on GET /V1/products/{sku} (the product list does not include them, so the register reads them per product); this block says whether the store charges them, whether they are taxed, whether they count towards the subtotal, and what each one is called. enabled is off when the setting is off or Magento_Weee is disabled, in which case attributes is empty. The three display keys carry Magento's own values: 0 including FPT, 1 including FPT and its description, 2 excluding FPT but showing the description and the final price, 3 excluding FPT; display_sales covers the cart and order surfaces, display_product the product view, display_list product lists, and Magento's default is 1. Every value is read at store scope, and none of them is lockable or overridable per location: they describe the store's tax setup, not the terminal's. The card_payments.mollie block contains decrypted API keys for the device, so scope integration tokens accordingly.

Customer Validators Are Server-Side Only

hyva_pos_terminal/customers/validators (website scope, Stores > Configuration > Hyvä POS > Terminal Defaults > Customers) selects which customer validators run on POST /V1/pos/customer-validate and POST /V1/pos/customers. It is applied by Hyva\Pos\Model\Customer\CustomerValidatorPool and is deliberately absent from this payload: the device sends a draft customer and reads back error rows, it does not decide which checks produce them.

The stored value is the comma-separated list of pool registration keys the multiselect saved. required_fields and email_uniqueness always run and are not offered as options. An empty value, which is the default, runs every registered validator, so a validator bridge keeps working on a store that never opens the setting. A selected key that is no longer registered is ignored, and registration order (di.xml) decides execution order, not the order the options were ticked. The pool reads the setting against the website_id on the validation request, falling back to the default scope when that is 0.

Unified Terminal Config (GET /V1/pos/terminals/:terminalId/config)

The bootstrap payload the device pulls at activation, at launch, and whenever the heartbeat's config_hash changes. One call replaces separate fetches of config, location, staff, and roles. Also a JSON-encoded string; parse twice. Envelope keys:

Key Content
terminal The terminal row, with license fields injected: license_id, license_token, license_status, license_type, license_expires_at, fiskaly_tss_id, fiskaly_api_key
license The full linked license record, or null
config The same object as GET /V1/pos/config
location The terminal's location row, with the address already resolved (when address_use_storeview is on, store-view values are swapped in server-side)
resolved_config The effective flat config: terminal overrides layered over location values over global defaults. Read this instead of re-implementing precedence
staff Active staff assigned to the location (or with all_locations). The bcrypt passcode hash is stripped; passcode_updated_at is included
roles The roles referenced by those staff rows

Errors: 404 with Terminal with id "%1" does not exist. for an unknown terminal id. A deleted location yields "location": null rather than an error.

Enforcement Flags and How Locks Work

Each flag lives at hyva_pos_lock/enforcement/<field> (website scope, Stores > Configuration > Hyvä POS > Lock Settings) and is delivered in the enforcement block as string "1" (enforced: the device renders the setting read-only and shows it as managed) or "0" (the device may customize it per location). Defaults are "0".

Flag Locks
receipt_header / receipt_footer Receipt header and footer text
receipt_show_staff_name / receipt_show_order_number Cashier-name and order-number visibility on receipts
receipt_show_receipt_number / receipt_show_sale_reference Receipt-number and sale-reference visibility on receipts
receipt_qr Receipt-bottom QR text and URL
auto_print What prints on checkout success: the customer receipt and the customer and merchant terminal slips
payment_methods The set of available payment methods
card_payment_provider The card payment Mode: Manual or a terminal provider
allow_store_credit Whether store credit is offered in the Discounts panel
mollie_magento_method Which Magento payment method backs Mollie-routed card sales (per-location overrides ignored)
mollie_config Mollie terminal settings (keys, organisation id, test mode)
paynl_config PAY. terminal settings (token code, API token, sales location, its secret, test mode)
paynl_magento_method The Magento payment method PAY. terminal sales book under; with the lock on, a location's override is ignored
pay_by_invoice_method / pay_by_invoice_invoicing_mode Pay-by-invoice payment method and invoice behavior
refund_validity The online-refund validity window
cash_rounding_increment The cash rounding step
require_open_session Whether a register session must be open before a tender is offered
store_details Store address, phone, and VAT number
tax_settings Tax calculation and display settings
low_stock_threshold The low-stock warning threshold
use_msi Whether registers read and write stock per source through Magento Inventory
held_carts The "Share Held Carts" toggle
custom_sale_sku The SKU free-entry register lines are booked against
categories Which top-level categories appear on the register landing page
walk_in_customer Walk-in customer default name / email / phone / address
pickup_queue_filter The default pickup-queue filter
customer_prices When customer prices are fetched
promo_attribute_lookup Whether missing promotion attributes are fetched from Magento

Postcode Patterns (GET /V1/pos/postcode-patterns)

Relays Magento's etc/postcodes.xml so the device can validate addresses client-side without a parallel hardcoded list. Returns a JSON-encoded string keyed by ISO 3166-1 alpha-2 country code; each country maps to an array of {pattern, example} rows, and any matching row is a pass (the US, for example, has both 5-digit and ZIP+4 rows):

{
  "NL": [{ "pattern": "^[0-9]{4} ?[a-zA-Z]{2}$", "example": "1234AB" }],
  "US": [
    { "pattern": "^[0-9]{5}$", "example": "12345" },
    { "pattern": "^[0-9]{5}-[0-9]{4}$", "example": "12345-6789" }
  ]
}

Order Statuses (GET /V1/pos/order-statuses)

Every registered row from sales_order_status, including custom merchant statuses. Feeds the location-config "post-pickup status" dropdown (pickup_completed_status_code).

[
  { "status": "processing", "label": "Processing" },
  { "status": "complete", "label": "Complete" },
  { "status": "picked_up", "label": "Picked Up In Store" }
]