01Save your widget
Open Locator → Widgets, save a Where to buy widget and copy its Install snippet. Choose the inline view for a panel inside the product page. Keep the embed id and API origin from your saved widget.
02Add the script to your product page
Paste the script where the widget belongs. Your page template must print the current product code into data-sku. Replace the example embed id with your saved id.
<script
src="https://cdn.test.stockisto.com/widget.js"
data-api-base="https://api.test.stockisto.com"
data-embed="we_0123456789abcdef0123456789abcdef"
data-sku="2910784"
async
></script>
Use the WordPress, Shopify or Google Tag Manager guide for those builders.
03Set the page attributes
Page attributes override saved settings. Omitted values use the saved configuration. An empty data-sku suppresses product-dependent rendering: Where to buy searches the brand without online shops or reservations; store availability renders nothing. An unknown code shows a not-found state.
| Attribute | What to set |
|---|---|
data-embed | The saved widget’s public we_ id. It supplies the owner, type and saved settings. |
data-api-base | The generated API origin. Keep TEST and production separate. |
data-sku | The current product’s code, escaped for an HTML attribute. |
data-sku-type | Optional code scheme: rsk, gtin (or ean), sku, mpn, enummer, finfo, nrf, nobb, efo, vvs, elnummer_dk, tun, lvi, sahkonumero or retailer_sku. |
data-view | Optional Where to buy view: full, inline, button, pill or corner. |
data-analytics-consent | Only the literal true enables analytics. Set it only after the visitor consents. |
04Check it works
- Open the published product page and scroll the widget into view.
- In the browser Network panel, check the anonymous lookup at
/api/v1/locator/widget-embeds/we_.... It must return your saved widget. - Test another product with a different code. Check the product, shop links and directions.
- Add each site origin under On your site, Embed script → Allowed origins, then open the page again. Analytics and lead attribution require a listed origin.
- Read the last reported render outcome under Install Health on that page.
05If the widget does not appear
A failed named-embed lookup cannot use cached results. Copy the id and API origin again. For an unknown product, check its code and code type. Inspect data-stockisto-error on the script for a boot failure. Keep legacy attribute snippets already installed; omitted settings use the owner’s default embed, with attribute behaviour as fallback if that lookup fails.
Content Security Policy
Allow the generated CDN origin in script-src and API origin in connect-src. For store locator and installer finder frames, allow the Find origin in frame-src. On TEST these are https://cdn.test.stockisto.com, https://api.test.stockisto.com and https://find.test.stockisto.com. Production uses the generated origins without .test. A blocked API request is a failed load, never proof of an empty shop list.
What Install Health observed
| Reported outcome | Observation and next step |
|---|---|
| Rendered on your site | The widget reported a render. Open the page and check its content. |
| Loads; framed view not observable | The widget mounted a frame; it cannot observe inside it. Check the framed page in your browser. |
| Loads, but refused | The widget reported a refusal. Check publication and whether the plan includes this widget type. |
| Loads, showed nothing | The widget reported no visible content. Check the product key and saved widget settings. |
| Loads, failed to render | The widget reported a render failure. Inspect the browser Network panel and CSP errors before changing the snippet. |
Widget and API reference
Configuration attributes
Identity and placement
For new installs use data-embed, the we_ id copied from your saved widget, plus data-api-base.
The following attributes describe page overrides and legacy installs. Saved settings fill omitted values;
an explicit attribute takes precedence. A named embed does not need a brand slug on the page.
| Attribute | Required | Default | What it does |
|---|---|---|---|
data-stockisto-slug | no | Your public brand slug for a legacy install; a named embed supplies it. The widget resolves it through GET /api/v1/locator/brands/{slug}. data-supplier is an accepted alias. | |
data-supplier-id | no | Your supplier tenant GUID, as an alternative to the slug. A non-GUID value is ignored. A GUID embed skips the brand fetch, so it gets no saved theme. | |
data-api-base | no | prod API | The API origin. Only https://*.stockisto.com origins are accepted; anything else falls back to production. |
data-widget-type | no | where-to-buy | One of the eight widgets below. An explicit empty, unknown or retired value renders nothing, so check your spelling. |
data-widget-id | no | random | A stable id when one page carries several widgets. |
Products
| Attribute | What it does |
|---|---|
data-sku | One public product code, up to 100 characters. Scopes Where to buy, and required by store-availability and stock-alert. data-product is an alias. An unknown code renders a not-found state, never brand-wide rows. |
data-sku-type | Declares the scheme of data-sku: rsk, gtin (ean accepted), sku, mpn, vvs, nrf, lvi, nobb, finfo, enummer, efo, elnummer_dk, sahkonumero, tun or retailer_sku. |
data-skus | A comma-separated list of up to 6 codes, forwarded into the store locator's framed search. |
data-skus-type | The scheme for every code in data-skus. One entry that fails the scheme refuses the whole list. |
Without a declared scheme the code is tried as rsk, then sku, then gtin. A declared scheme is
only ever looked up as that scheme. Classification and certificate codes such as etim, bk04 or
va are refused: they are not product keys.
Location and list
| Attribute | Default | What it does |
|---|---|---|
data-lat / data-lng | A fixed search centre in decimal degrees. When set, the widget never asks the browser for location. Results are then always centred on that point, not on the visitor. | |
data-postcode | A label shown with an explicit centre. | |
data-radius | 50 | Search radius in km. The API accepts 1 to 500. |
data-filter | Where to buy: confirmed shows only rows with a confirmed in-stock signal; showroom only showroom rows. Absent shows every row the search returned. | |
data-rank | server | distance or price. Absent means the server's own ranking. Price ranking refuses when prices are hidden. |
data-per-view | 3 | Rows shown before the "see all" footer, 1 to 12 (Where to buy's inline view). |
data-max-height | Caps the scrolling store list, 240 to 1200 px. | |
data-channel | online opens the webshop view first. | |
data-prices | retailer | off hides published retailer prices; online shows them on webshop rows only. msrp is refused with a console warning, because no suggested retail price exists on the wire. |
data-guided | false | Where to buy: ask two guided questions before showing rows (requires a resolvable SKU). Excluded by a locator-target pill. |
data-sold-out | false | Where to buy: a "sold out online" banner when nothing in the response is attested in stock. |
data-reserve | false | Where to buy: a per-row Reserve action that posts a reservation. |
data-pill-target | locator | Where to buy pill view: locator links to the hosted brand page, overlay opens the store list overlay. |
data-corner | bottom-right | Where to buy corner view: bottom-right, bottom-left, top-right or top-left. |
data-show-hours | true | Show opening hours (store card visit part; the shared store-list expansion). |
data-show-services | false | Show the showroom or service chip when known. |
data-directions | true | Show the Directions link/button. |
data-fields | Store card only: a comma list of hours, phone, directions. The address always shows. | |
data-exceptions | on | Store card only: off hides dated holiday hours. |
data-view | Where to buy: full, inline, button, pill or corner. Store locator: list opens the directory instead of the map. | |
data-map-height | 480 | Frame height for store-locator / installer-finder, 320 to 800 px. Defaults: 480 for the map leg, 560 for the list leg, 640 for installer-finder. |
data-service | installer-finder: preselect a service type the brand publishes. | |
data-audience | Where to buy: trade sends authorizedOnly=true server-side and keeps wholesalers. | |
data-quantity | Where to buy: a positive integer quantity filter (requires a resolvable SKU). | |
data-brands | Store card: up to 12 brand slugs to narrow the brands shown. It can never add a brand the store does not carry. | |
data-lang | page | UI language: sv, nb, da or fi. Falls back to <html lang>, then the browser, then English. data-locale is an alias. |
data-analytics-consent | false | Only the literal "true" sends analytics. See below. |
data-jsonld | on | off disables the structured-data block. |
Keys for retailer and installer widgets
| Attribute | Used by |
|---|---|
data-store-id | store-card, stock-alert: a store location GUID |
data-retailer | The retailer's public slug. Required by the three retailer widgets. |
data-installer | installer-card: the installer company GUID |
A non-GUID id is dropped and the widget hides itself rather than looping on a 404.
Styling
Every styling attribute is optional. Absent means the Heritage default look.
| Attribute | Values |
|---|---|
data-theme | A hex accent (#0057A8), a preset name, or the id of a theme you saved in Supplier Admin |
data-theme-preset | light, heritage, dark, blue, copper, fjord, oak, signal, graphite |
data-theme-accent2 | Secondary accent, hex |
data-theme-surface | Card surface colour, hex |
data-theme-ink | Text colour, hex |
data-radius-scale | sm, md, lg |
data-density | spacious, cozy, compact, dense |
data-shadow | soft, flat, none |
data-font | grotesk, sans, serif, mono, system. The widget never loads a web font. |
data-width | full, or a pixel value from 200 to 2000 |
Anything off these lists is dropped. A hex accent with poor contrast against the card is replaced by the default.
Consider a default search centre
Without data-lat/data-lng the widget asks the visitor before it uses
browser location, and offers a postcode field instead. If most of your
shoppers are in one market, a fixed centre (Stockholm: data-lat="59.3293" data-lng="18.0686") with a generous data-radius shows them stores at
once. The trade-off: a fixed centre disables the location prompt entirely.
Widget list
Exactly eight widgets ship. Select one with data-widget-type; an omitted value defaults to
where-to-buy, and an explicit empty, unknown or retired value renders nothing. Save brand widgets
under Locator → Widgets. Retailer and installer accounts have their
own profile widget editors.
On a supplier's product pages
where-to-buy(default): nearby authorized retailers. Runs brand-wide, or scoped to one product withdata-sku. Five views (data-view):full(the whole list, default),inline(a compact panel in the page's own flow),button(opens an overlay),pill(a compact count pill, "Stocked by N stores near you", degrading to a count-less "Find where to buy" CTA with zero nearby stores, never "0 stores"),corner(a pinned corner pill that opens the list over the page).data-guided,data-sold-outanddata-reservelayer optional guided questions, a sold-out-online banner and a reserve action over any view.guided-handoff: a three-step flow. Pick a retailer, optionally pick an approved installer, then give explicit consent before an installer lead is sent.
On a supplier's brand pages
store-locator: the whole network as a map, or as a server-rendered directory withdata-view="list". Visitors can switch inside the frame.installer-finder: your approved installers with coverage areas, service filters and an in-frame callback request. It never asks for browser location.
On a retailer's own site
All three need data-retailer.
store-availability: the retailer's own branches near the shopper with the stock signal fordata-sku, and the retailer's published price where the server releases one. Needsdata-stockisto-slug,data-retaileranddata-sku.store-card: one store's address, opening hours, the brands it carries and its aggregate rating, stacked in that order. Each part hides on its own (data-show-visit/-brands/-rating); with every part off, empty or failed the card hides. Needsdata-store-idanddata-retailer.stock-alert: a back-in-stock email form for one product at one store. Needsdata-stockisto-slug,data-store-idanddata-sku. Sign-up is double opt-in.
On an installer's own site
installer-card: the installer's registry credential and service-area coverage check, stacked in that order; each section hides on its own (data-show-credential/-coverage). Needsdata-installerand the supplier'sdata-stockisto-slug. The public wire carries a bare certified flag with no supplier, category or date, so it cannot say which brand approved what.
Display signals are server-computed
The In stock / May carry / Contact retailer label comes from the API's
displaySignal field and is never re-derived in the browser. The freshness
line ("confirmed 3 days ago") is likewise the server's verdict. When the API
marks a row isSponsored, the widget always shows a "Sponsored" label.
Content Security Policy
Allow https://cdn.test.stockisto.com in script-src and https://api.test.stockisto.com in connect-src on TEST.
For a production snippet, allow https://cdn.stockisto.com and https://api.stockisto.com respectively.
Use the origins from your generated snippet. The API origin must be allowed for the embed resolve.
The map and installer-finder frames also need frame-src https://find.test.stockisto.com on TEST.
A refused connect-src prevents a named embed from resolving. It shows a load-failed state with Retry;
it never renders cached results. A deleted or unknown id renders nothing.
Public locator endpoints
The widget reads from anonymous endpoints under https://api.test.stockisto.com/api/v1/locator. No
token or cookie is needed, and any origin may call them. You can call them yourself for a custom
integration.
GET /api/v1/locator/search
The widget's main read. It needs the supplier GUID and a search centre:
GET /api/v1/locator/search?supplierId=SUPPLIER_GUID&latitude=59.33&longitude=18.06&radiusKm=50&pageSize=12
| Parameter | Rule |
|---|---|
supplierId | Required. Resolve a brand slug to it with GET /api/v1/locator/brands/{slug} first. |
latitude / longitude | Required unless browse=true. Dot-decimal only. lat and lng are accepted aliases. |
radiusKm | 1 to 500, default 25. radius is an alias. |
browse=true | Returns the whole active network without a centre. |
skuCode | Scope to one public product code. skuCodes takes up to 50 codes for one batched search. |
inStockOnly, showroomOnly, authorizedOnly | Boolean filters. authorizedOnly keeps only tiered-relationship stockists. |
page, pageSize | Zero-based page; pageSize defaults to 50. |
Each retailer row carries id, retailerId, slug, name, address, city, postalCode,
country, latitude, longitude, distanceKm, isShowroom, phone, email, website,
openingHours, displaySignal, rankingScore, isSponsored, isSample and a stockStatus
object (freshness, lastVerifiedAt, confidenceTier). The envelope adds totalCount and
hasMore. displaySignal is InStock, MayCarry or ContactRetailer. Results are cached for
five minutes per supplier.
Coordinates are always dot-decimal
Write latitude=59.33, never 59,33. A search without a usable centre
returns HTTP 400 rather than silently searching at (0,0).
Other routes the widget uses
| Route | Purpose |
|---|---|
GET /api/v1/locator/brands/{slug} | Brand theme and supplier id for a slug. Cached ten minutes. |
GET /api/v1/locator/retailers/{slug}/stores | A retailer's own stores. A centre is optional here. |
GET /api/v1/locator/find/stores/{id} | One store's detail: address, structured hours, brands carried. |
GET /api/v1/locator/find/stores/{id}/reviews | The store's rating aggregate. |
GET /api/v1/locator/find/installers/{id} | One installer's coverage areas and registry certification. |
GET /api/v1/locator/{supplierSlug}/installers?take=5 | Approved installers for the guided handoff. |
POST /api/v1/installers/companies/{id}/leads | Sends the guided-handoff lead once the visitor consents. |
POST /api/v1/locator/stock-alerts | Back-in-stock sign-up. 202 means a confirmation email was sent, not a live alert. |
POST /api/v1/locator/reservations | A reservation request from data-reserve. |
GET /api/v1/locator/geocode?q=… | Postcode and address geocoding, Nordic countries only. 20 requests per minute per IP. |
GET /api/v1/locator/resolve-host?host=… | Maps a verified custom domain to { supplierId, slug }. No DNS lookup happens here. |
Status codes to handle
| Status | Meaning |
|---|---|
400 | Missing or invalid parameters, for example no latitude. |
403 | The locator is set to Private, or the widget type is above the supplier's plan. |
429 | Rate limit hit. Honor Retry-After. |
503 | The supplier tenant is suspended. |
Rate limits
/locator/search allows 100 requests per minute per tenant and 20 per
minute per anonymous IP. /locator/brands/{slug} allows 500 per minute.
Store and installer detail reads allow 80 per minute per anonymous IP.
Analytics events
With data-analytics-consent="true" the widget posts events to
POST https://api.test.stockisto.com/api/v1/analytics/events. It uses fetch with keepalive
and no credentials, so no cookie ever travels with an event.
The body carries schemaVersion: 1, sessionId, correlationId, eventType and a payload.
The server resolves your tenant from the brand slug and ignores any tenant id in the body. It
answers 202 Accepted without waiting for the write. Anonymous callers may only send known event
types; an unknown type returns 400.
Events are credited to your account only when the page's origin is on your Allowed origins list
(Supplier Admin → Locator → On your site → Embed script). Add every domain that hosts the widget, one per line,
such as https://www.yourbrand.com. The same list gates lead attribution for the guided handoff.
Consent gating
With the default data-analytics-consent="false" the widget sends no
analytics at all and writes nothing to session storage. Set it to "true"
only after your consent tool has recorded a lawful basis.