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:
falseuntil 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:
| Policy | Behaviour |
|---|---|
| AutoApply | Fetched products upsert directly into the live market graph (manufacturers → brands → products). |
| Review | Fetch, 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 upsertExternalId =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.sein dev,webapi.vvsinfo.sein 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 (viadocument.currentScript, falling back toscript[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
| Attribute | Required | Default | Description |
|---|---|---|---|
data-stockisto-widget | ✅ | — | Must be "true" so the script can find itself for async/defer loads. |
data-supplier-id | ✅ | — | Your supplier slug. Used as the locator supplierId and analytics tenant. |
data-sku | ✅ | — | The product SKU to find retailers for. |
data-widget-type | where-to-buy | One of where-to-buy, in-stock, showroom, guided-handoff. | |
data-audience-mode | consumer | consumer or pro. pro always uses the B2B distributor template. | |
data-theme | #0057A8 | Brand colour (CSS hex) applied to headings and CTAs. | |
data-analytics-consent | false | Set "true" to enable analytics events (consent gating — see below). | |
data-widget-id | auto (crypto.randomUUID()) | Unique id for pages with multiple widgets; auto-generated if absent. | |
data-api-base | https://api.stockisto.com | Override 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 isInStock.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:
- Tags → New → Custom HTML, and paste the embed
<script>from above. - Check ✅ Support document.write.
- Triggering → All Pages (or limit to your product pages).
- 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— requiredlatitude/lat— required, between -90 and 90longitude/lng— required, between -180 and 180radiusKm/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
| Status | Meaning |
|---|---|
403 | The supplier's locator is Private — all data endpoints refuse to serve it. |
503 | The supplier tenant is suspended. |
400 | Invalid/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?
- Data Import guide — field reference, geocoding, and the review queue
- GTM Install guide — full Tag Manager walkthrough and troubleshooting
- Getting Started — trial, onboarding, and publishing your locator
- Analytics + ROI guide — what the events power in your dashboard