Skip to main content
Status Sign in

Two-layer data model

PowersportOS data lives in two clearly separated layers: a central catalog curated by us, and a tenant layer where each customer keeps their own commercial data. The separation is the architectural foundation that makes a multi-tenant catalog platform work without duplication or lock-in.

The central catalog

The central catalog is the shared reference data: every make, model, vehicle (a model plus submodel plus year), and part that exists in the powersport space, together with the fitments that link parts to vehicles. It's curated by Umbr AB plus by data-curating tenants (data providers, manufacturers, and distributors) who have been granted write access per-brand via the brand-permission model. Most tenants read from it; they cannot write to it.

Everything in this layer is shared across every tenant on the platform. When the central catalog gets updated, every retailer's storefront sees the corrected data on their next page load. No retailer maintains their own copy. No retailer needs to.

Make
Can-Am, Polaris, Yamaha, KTM, Husqvarna, Honda, Ski-Doo, etc. The brand of the vehicle, not the brand of the part.
Model
A specific product line within a make, Outlander 1000, Sportsman 850, MXZ 600.
Vehicle
A specific (model, submodel, year) row. Outlander 1000 G3 X-MR 2024 is a vehicle. An empty submodel is a wildcard meaning 'all submodels of that model+year'.
Part
A specific SKU, a brake pad, a filter, a fork seal, with brand, part number, optional OEM cross-references, and storefront content (description, images, dimensions, EAN).
Fitment
A many-to-many link between a part and the vehicles it fits. Includes optional position (LH/RH/front/rear) and notes ('fits with electric start').

The data-curation layer (delegated central writes)

A subset of tenants get write access to the central catalog, scoped per brand. This is how brands and importers maintain their own product data on PowersportOS without the platform team being a bottleneck. The mechanism:

TenantBrandPermission
One row per (tenant, brand) pair. Role of READ, WRITE, or OWNER controls how far the permission goes. Granted by PowersportOS staff in the admin portal; revocable at any time.
Tenant types involved
DATA_PROVIDER, MANUFACTURER, DISTRIBUTOR. Other tenant types (STANDARD, RESELLER, HYBRID, RETAIL) consume the central catalog but don't curate it.
What can be edited
Part rows under permitted brands (create, edit, set status, point successor SKUs via replacedById) and Brand metadata (logo, description, country, website, for OWNER role only).
What can't
Hard-delete a Part that retailers have already activated. Lifecycle status + replacedById is the right path; cascade-delete would destroy retailer activations.
Audit
Every write is captured by the platform audit log with tenant + user + path + timestamp. PowersportOS staff can see the full history of who changed what.

The portal surfaces this layer as Managed brands and Managed parts for tenants who have any brand permissions. CSV upload and direct API are both supported. See Data ingest for the practical workflows.

The tenant layer

Each tenant, every retailer, manufacturer, distributor on the platform, has their own private data space. This is where commercial information lives, the stuff that belongs to the customer alone:

TenantCatalogItem
The activated subset of the central catalog. A retailer subscribes to a brand (or hand-picks parts) and each activated part shows up as a TenantCatalogItem with the retailer's price and stock.
TenantPart
Proprietary parts the tenant sells that aren't in the central catalog. Their own house-brand SKUs, custom-built items, or specialty parts not yet on the platform.
TenantVehicle
Custom vehicles a tenant adds, the same shape as the central Vehicle but scoped to one tenant. Used for niche or custom builds the central catalog doesn't cover.
Dealer
For multi-location retailers, distributors, or manufacturers showing their dealer network: physical locations with addresses, geocoded lat/long, and per-location stock.

How the two layers join at render time

When an end customer hits a product page on a retailer's storefront, the retailer's Shopify theme (or whatever frontend they run) makes a single API call that joins both layers:

  • The product-page rendering pulls catalog content (description, images, fitments, dimensions) from the central catalog
  • The same call pulls the retailer's price and stock from the tenant layer
  • If the part is sold by multiple retailers and the manufacturer has opted into reseller stock sharing, the "find this part at a reseller near you" widget queries across multiple tenant layers (with explicit opt-in from each)

The join happens server-side at request time. The retailer never sees a stale copy of the catalog; the customer always sees the same descriptive content regardless of which retailer they buy from.

Why the separation matters

Three concrete reasons the two-layer model is non-negotiable:

  1. No data duplication. A product description maintained centrally doesn't get retyped into 50 retailer Shopify stores. A fitment correction propagates to every storefront on the next page load.
  2. No ownership ambiguity. Catalog content is platform-curated. Tenant data is tenant-owned. There's no fuzzy middle ground where data lives across tenants without clear provenance.
  3. No platform lock-in for commercial data. A tenant leaving the platform takes their tenant data with them, full export, full deletion, no claims by us. The central catalog stays with us because it was always ours to begin with.