Integrations & Embed

This guide is for technical admins and developers who need to get retailer and product data into Stockisto and the locator experience out of it. It covers the Prodibas / VVS Info data connector, CSV/Excel import, the embeddable product-page widget (with its real data attributes and a Google Tag Manager install), and the public locator endpoints the widget calls.


How data flows

Stockisto has two write paths into the retailer/product graph and one read path out to your website:

  • Inbound automated — the connector platform pulls supplier feeds (today: Prodibas / VVS Info) on a CRON schedule.
  • Inbound manual — CSV/XLSX import and web-scrape assist, both routed through a human review queue. See the Data Import guide.
  • Outbound — the embeddable widget reads from the public, anonymous locator API and renders retailer results on your product pages.

Two different import paths

The connector ImportPolicy (AutoApply vs Review) is a system-level safeguard for automated feeds. It is separate from the manual CSV/scrape review queue described in the Data Import guide. Don't confuse the two.


Data integration: Prodibas / VVS Info connector

The Prodibas connector (ConnectorTypeId = "prodibas") ingests the VVS Info product feed — manufacturers, brands, and products keyed by RSK number — into the Stockisto market graph.

How it runs

A background service (ConnectorSyncWorker) checks every minute whether any enabled connector instance is due to fire based on its CRON schedule, then dispatches to the connector. The Prodibas instance is seeded automatically on startup but is disabled by default and must be turned on once VVS Info credentials are in place.

  • Default schedule: 0 2 * * * (nightly at 02:00 UTC)
  • Default ImportPolicy: Review
  • Enabled: false until an admin enables it

Full vs incremental sync

The connector decides its mode from the last successful sync watermark:

  • Full sync — no prior successful run; all products are fetched from VVS Info.
  • Incremental sync — a prior run exists; only products modified after the last successful sync timestamp are fetched (LastChangedOptionSearchGreaterThanOnly).

Each run is a queued search job: the connector submits a search (batch of ~500), polls for completion every 2 seconds with a hard 5-minute ceiling, then paginates the results and ingests them.

ImportPolicy: AutoApply vs Review

ImportPolicy is the connector's defence-in-depth gate. It controls whether sync results are written to the live market graph:

PolicyBehaviour
AutoApplyFetched products upsert directly into the live market graph (manufacturers → brands → products).
ReviewFetch, parse, validation, dedup, and resilience logic all run — but no live-graph writes are performed.

Under Review, the connector still counts what would have been imported (staged products / brands) and reports those counts in the run log, so a human can decide whether to apply them later. A product is only "stageable" if it could resolve a brand under AutoApply (it has a BrandName plus manufacturer info).

Review is a real no-write gate

Under ImportPolicy.Review the connector performs zero writes to the live market graph — it reports counts only. There is currently no in-app approve/reject UI for staged connector items; switching the instance to AutoApply is what makes the data go live.

Data quality & provenance

Every entity written by the Prodibas connector is tagged with provenance so its origin and trust level are auditable:

  • SourceSystem = "prodibas"
  • Confidence = Official (1.0 — data comes from the official VVS Info feed)
  • FetchedAt = the UTC time of the upsert
  • ExternalId = normalised RSK number (products) or VVS Info manufacturer ID (manufacturers)

RSK numbers are normalised and validated before ingest; invalid RSKs are skipped, and a duplicate RSK seen twice within one sync run is logged and skipped (never merged). On update, only FetchedAt is touched so that updated rows don't re-appear in the next incremental watermark query.

Configuration & credentials

The VVS Info API clients are configured from app configuration / Key Vault — never hard-coded:

  • VvsInfo:BaseUrl — product API (test.webapi.prodibas.se in dev, webapi.vvsinfo.se in prod)
  • VvsInfo:LegacyBaseUrl — legacy manufacturer API (ws.vvsinfo.se)
  • VvsInfo:CompanyIdentifier, VvsInfo:Industry, VvsInfo:Key — sent as request headers

Calls are wrapped in a standard resilience pipeline (3 retries, exponential backoff with jitter, circuit breaker) and capped at 3 concurrent in-flight requests per instance via a shared semaphore.

Adding a new connector

The connector platform is plugin-based. To add a supplier feed: implement the IConnector interface, register it with keyed DI under a lowercase hyphen-separated id (e.g. services.AddKeyedScoped<IConnector, YourConnector>("my-supplier")), and seed a matching ConnectorInstance. The manufacturer (legacy) VVS Info service is BETA — confirm with VVS Info before using it in production.


CSV / Excel import

For retailer data you maintain yourself, use the file import flow. Stockisto supports CSV and XLSX, and both file upload and web-scrape assist route through the same human review queue before anything is saved.

Full field reference, geocoding behaviour, deduplication rules, and the review workflow are documented in the dedicated guide:

  • Data Import guide — CSV template, field definitions, geocoding, dedup, review queue, and common errors

In short: download the template, fill in retailer rows (name/address/city/postal_code/country required), upload, then approve/edit/reject each row in Import → Review queue before applying to your retailer network.


The embeddable widget

The widget is a single, dependency-free IIFE bundle (widget.min.js, built with esbuild, kept under 30 KB gzipped). Drop one <script> tag onto a product page and it injects a self-contained locator block right after itself.

What the script does

  • Finds its own <script> tag (via document.currentScript, falling back to script[data-stockisto-widget="true"]).
  • Reads configuration from the tag's data-* attributes.
  • Injects a <div class="stockisto-widget-root"> container (max-width 480px) immediately after the script.
  • Uses an Intersection Observer so retailer data is only fetched when the widget scrolls into view — it never blocks your page load.
  • Fails closed: if required attributes are missing, a request errors, or there are no results, it renders nothing (or a non-breaking empty state) rather than breaking the host page.

Embed snippet

<script
    src="https://cdn.stockisto.com/widget.min.js"
    data-stockisto-widget="true"
    data-supplier-id="YOUR_SUPPLIER_SLUG"
    data-sku="PRODUCT_SKU"
    data-widget-type="where-to-buy"
    data-audience-mode="consumer"
    data-theme="#0057A8"
    data-analytics-consent="false"
    async
></script>

The script tag itself is the mount point — the widget injects its container right after this tag, so place the snippet exactly where you want the locator to appear on the page.

Configuration attributes

AttributeRequiredDefaultDescription
data-stockisto-widgetMust be "true" so the script can find itself for async/defer loads.
data-supplier-idYour supplier slug. Used as the locator supplierId and analytics tenant.
data-skuThe product SKU to find retailers for.
data-widget-typewhere-to-buyOne of where-to-buy, in-stock, showroom, guided-handoff.
data-audience-modeconsumerconsumer or pro. pro always uses the B2B distributor template.
data-theme#0057A8Brand colour (CSS hex) applied to headings and CTAs.
data-analytics-consentfalseSet "true" to enable analytics events (consent gating — see below).
data-widget-idauto (crypto.randomUUID())Unique id for pages with multiple widgets; auto-generated if absent.
data-api-basehttps://api.stockisto.comOverride the API origin (e.g. for staging).

Required attributes

If data-supplier-id or data-sku is missing, the widget logs a single [Stockisto Widget] warning to the console and renders nothing. Both are mandatory.

Widget types

The data-widget-type value selects which template renders (when data-audience-mode is consumer):

  • where-to-buy (default) — full nearby-retailer list with in-stock / may-carry / contact signal badges and a Visit CTA.
  • in-stock — same list, filtered to retailers whose server-computed signal is InStock.
  • showroom — surfaces retailers with a physical showroom.
  • guided-handoff — a 3-step consumer flow: pick a retailer → optionally pick a certified installer → enter email + consent → submit. On submit it can post an installer lead and then redirects to the chosen retailer's website.

Setting data-audience-mode="pro" overrides the type and always renders the Preferred Distributors B2B template (trust badges, "Request quote" CTAs), regardless of data-widget-type.

Display signals are server-computed

The In stock / May carry / Contact retailer badge comes from the API's displaySignal field and is never re-derived in the browser. Likewise the isSponsored flag is server-set — when true, the widget always shows a "Sponsored" label and no config can suppress it.


Installing via Google Tag Manager

You can ship the snippet site-wide through GTM instead of editing your page templates. The full walkthrough lives in the GTM Install guide; the essentials:

  1. Tags → New → Custom HTML, and paste the embed <script> from above.
  2. Check ✅ Support document.write.
  3. Triggering → All Pages (or limit to your product pages).
  4. Preview, confirm the widget renders and [Stockisto] console logs appear, then Submit / Publish.

Content-Security-Policy

If your site sends a CSP header, add cdn.stockisto.com to script-src and api.stockisto.com to connect-src, otherwise the bundle and its locator fetch will be blocked.

After publishing, verify from your Stockisto dashboard → Install tab → Check Installation.


Public locator endpoints

The widget reads from the public locator API. These endpoints are anonymous (no JWT, no Bearer token, no cookies) and are the same ones you can call directly for custom integrations. The base origin is https://api.stockisto.com.

GET /api/v1/locator

The widget's primary read. The widget calls it with supplierId, sku, and maxResults:

GET https://api.stockisto.com/api/v1/locator?supplierId=YOUR_SUPPLIER_SLUG&sku=PRODUCT_SKU&maxResults=5

Response shape (per retailer):

{
    "retailers": [
        {
            "retailerId": "…",
            "name": "Nordic Bath AB",
            "address": "Drottninggatan 12",
            "city": "Stockholm",
            "distanceKm": 2.4,
            "displaySignal": "InStock",
            "rankingScore": 0.87,
            "showroomStatus": true,
            "phone": "+46 8 123 456",
            "websiteUrl": "https://nordicbath.se",
            "isSponsored": false
        }
    ]
}

displaySignal is one of InStock, MayCarry, or ContactRetailer.

GET /api/v1/locator/search

The geo-search endpoint behind the consumer locator pages. It returns filtered, scored, and sorted retailers within a radius and requires a search centre:

  • supplierId — required
  • latitude / lat — required, between -90 and 90
  • longitude / lng — required, between -180 and 180
  • radiusKm / radius — optional, between 1 and 500 (defaults to 25 km)

Coordinates are always dot-decimal

Latitude/longitude in the query string must use a . decimal separator (e.g. latitude=59.33). A search without a usable centre returns HTTP 400 rather than silently defaulting to (0,0).

GET /api/v1/locator/brands/{slug}

Resolves a supplier's brand theme (colours, logo, hero copy, locator config) by slug. Used by the locator front-end and as embed config. Higher rate limit than search (500 req/min per tenant).

Other locator routes

  • GET /api/v1/locator/{supplierId}/installers?take=5 — certified installers for the guided-handoff flow.
  • POST /api/v1/installers/companies/{installerId}/leads — submits a consumer lead to an installer (used by guided-handoff when consent is given).
  • GET /api/v1/locator/resolve-host?host=… — resolves a custom-domain Host header to { supplierId, slug } (no DNS lookup — DNS verification is a separate background job).
  • GET /api/v1/locator/geocode?q=… — server-side geocoding proxy (Nominatim in dev, Azure Maps in prod), restricted to Nordic country codes.

Status codes you should handle

StatusMeaning
403The supplier's locator is Private — all data endpoints refuse to serve it.
503The supplier tenant is suspended.
400Invalid/missing search parameters (e.g. no latitude).

Rate limits

/locator/search is limited to 100 req/min per tenant (20/min per anonymous IP); /locator/brands/{slug} to 500 req/min; other endpoints inherit the global 500 req/min per tenant. Search responses are cached ~5 minutes per supplier and brand themes ~10 minutes.


Analytics events

When data-analytics-consent="true", the widget fires fire-and-forget events to the ingest endpoint (preferring navigator.sendBeacon, falling back to fetch with keepalive):

POST https://api.stockisto.com/api/v1/analytics/events

The body must set schemaVersion: 1 and include sessionId, correlationId, tenantId (your supplier slug), eventType, and a payload. The endpoint is anonymous and returns 202 Accepted without waiting for the write; unknown event types are accepted and logged.

Consent gating

With data-analytics-consent="false" (the default), the widget fires no analytics events at all. Set it to "true" only after you have a lawful basis / consent to do so.


What's next?