Settings registry

A typed, hierarchical configuration system. Modules register their settings as code; tenants override at any of three scope levels; the resolver walks specific → general.

Resolution order

channel scope     →  most specific (e.g. POS terminal #5)
market scope      →  one regional surface (CZ vs DE)
account scope     →  whole e-shop default
registered default →  baked into the module

A GET .../settings/resolve.json?key=...&market_id=... returns {value, source_scope, source_id, definition} so the caller knows where the value came from.

Built-in commerce settings

26 keys ship out of the box across these categories:

CategoryExamples
ordernumber_format, number_prefix, starting_number, auto_archive_after_days
taxcalculation_mode, b2b_reverse_charge, vies_cache_days
checkoutguest_allowed, captcha_provider, session_ttl_minutes, reserve_at
inventoryreservation_ttl_minutes, allow_overselling, low_stock_threshold
shippingallow_pickup_points, free_shipping_threshold_cents
emailtransactional_from_address, transactional_from_name, support_email, locale_default, api_key
brandprimary_color, logo_url, favicon_url
customerwelcome_email_enabled, abandoned_cart_email_enabled, abandoned_cart_delay_minutes

Inspect the registry

curl "$API/settings/registry.json?category=tax" \
  -H "Authorization: Bearer $NEVIOS_KEY"
{
  "settings_registry": [
    {
      "key": "commerce.tax.calculation_mode",
      "type": "string",
      "default": "vat_inclusive",
      "description": "...",
      "category": "tax",
      "scopes": ["account","market"],
      "allowed_values": ["vat_inclusive","vat_exclusive"],
      "min_value": null,
      "max_value": null,
      "sensitive": false
    },
    ...
  ]
}

The dashboard renders settings pages dynamically by reading this.

Set a value

# Account-wide override
curl -X PUT "$API/settings/commerce.tax.calculation_mode.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{"scope_type":"account","value":"vat_exclusive"}'
# Per-market override (more specific)
curl -X PUT "$API/settings/commerce.tax.calculation_mode.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{"scope_type":"market","scope_id":308...,"value":"vat_inclusive"}'

The service validates against the registry — wrong type, out-of-range, or invalid enum value returns 422 with details.

Resolve

# Account-only
curl "$API/settings/resolve.json?key=commerce.tax.calculation_mode" \
  -H "Authorization: Bearer $NEVIOS_KEY"

# With market context (most specific wins)
curl "$API/settings/resolve.json?key=commerce.tax.calculation_mode&market_id=308..." \
  -H "Authorization: Bearer $NEVIOS_KEY"

# Bulk
curl "$API/settings/resolve.json?category=email" \
  -H "Authorization: Bearer $NEVIOS_KEY"

Delete an override

Reverts to the next-up scope (or default).

curl -X DELETE "$API/settings/commerce.tax.calculation_mode.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" \
  -d 'scope_type=account'

Type system

The registry supports 7 canonical types: string | int | bool | float | json | list. Each definition can add:

  • allowed_values — enum constraint
  • min_value / max_value — numeric bounds
  • schema — Pydantic model for json types
  • sensitive — never log values; <sensitive> in registry response

commerce.email.api_key is sensitive — list/registry responses show "default": "<sensitive>", event payloads redact the value, the dashboard masks it in the UI.

Endpoints

GET    /admin/2026-01/settings/registry.json
GET    /admin/2026-01/{handle}/settings.json
GET    /admin/2026-01/{handle}/settings/resolve.json
PUT    /admin/2026-01/{handle}/settings/{key}.json
DELETE /admin/2026-01/{handle}/settings/{key}.json

Next: Themes.