Web SDK Docs

Installation and integration of the Inops Web SDK. This page is intentionally technical and implementation-focused.

Do these 3 steps to get a working search widget.
1) Add this script
Paste into your theme/site (preferably in the head).
CDN script (pinned)
Pin the version. For SRI/CSP hardening, see Advanced / Security.
<script src="https://cdn.inops.io/inops-web-sdk@1.2.0/index.global.js"></script>
2) Add the widget div
Place it where the search UI should appear.
Search widget div
<div data-widget="search" data-id="my-search-widget"></div>
3) Set your Search Key
Copy it from the portal (Shop → Security → SearchKey).
Set data-search-key
<div data-widget="search" data-search-key="YOUR_SEARCH_KEY" data-id="my-search-widget"></div>
SDK installation (Quick Start · 5 minutes)
Test: search, similar_products, campaignId.
Playground settings
Language
Applies to all requests below.
Feature 1 — Search
Use this for your main “search box” experience. The backend will return a streaming summary + ranked products.
Query
Tip: type 3+ characters to search. Single words trigger direct search, multi-word queries use intent-based matching.
JSON payload
Request body for POST /shop/flow/execute (headers omitted).
{
  "language": "en",
  "userInput": {
    "type": "search",
    "value": ""
  }
}
Result JSON
What you can expect back via SSE (summary + products + meta).
{
  "summary": "",
  "products": [],
  "meta": null
}
Feature 2 — Similar products
Use this on product pages to show “alternatives” or “you may also like” results. It takes a single seed productId.
To enable this section, first run Feature 1 so we can reuse a productId from the search results.
Reuse a productId (from your last search)
Preset via VITE_DOCS_DEMO_PRODUCT_ID or paste your own below.
productId (manual)
JSON payload
Request body for POST /shop/flow/execute (headers omitted).
{
  "language": "en",
  "userInput": {
    "type": "similar_products",
    "productId": "YOUR_PRODUCT_ID"
  }
}
Result JSON
What you can expect back via SSE (summary + products + meta).
{
  "summary": "",
  "products": [],
  "meta": null
}
Feature 3 — campaignId landing
Use this to turn marketing traffic into an immediate search experience. Your storefront reads a campaignId from the URL, and the backend resolves it to the configured searchTerm.
campaignId (preset examples)
Configure presets via VITE_DOCS_DEMO_CAMPAIGN_IDS (comma-separated). If the campaign is missing/expired, results are empty.
campaignId (manual)
JSON payload
Request body for POST /shop/flow/execute (headers omitted).
{
  "language": "en",
  "userInput": {
    "type": "campaignId",
    "campaignId": "longboard_dummy_shop_campaign_01"
  }
}
Result JSON
What you can expect back via SSE (summary + products + meta).
{
  "summary": "",
  "products": [],
  "meta": null
}
Paste a Search Key from the portal (Shop → Security → SearchKey). Keys are read‑only and safe to embed in storefront HTML.
Demo shop data

The demo shop uses a surf catalog spanning 9 categories: Surfboards, Wetsuits, Accessories, Lifestyle, Fins, Leashes, Bags, Apparel, and Beginner Gear. The catalog supports bundle search — try multi-product queries to see assembled bundles across categories. All products are listed on the live dummy shop. You can use the same catalog as a dummy Shopify store: import the CSV into Shopify (Products → Import) to get a matching product set for testing.

Download dummy-shop-in-shopify-format.csv — Shopify-ready import file for the demo catalog.

In the playground above, try queries like longboard, wetsuit, leash, beginner soft-top, my boy wants to start surfing, or complete surf kit budget $500.

Live demo (demo shop)
When a shopper clicks a product, fire a similar_products flow with the clicked product's ID. Inops returns semantically similar products from your catalog, which you can render as a recommendations strip or modal.
How it works
  1. Shopper clicks a product card.
  2. Your code fires a similar_products request with the productId.
  3. Inops returns a ranked list of related products from your catalog.
  4. Render the results as "You might also like" or "Related products".
API call
similar_products request
Pass the clicked product's ID. The response streams products via SSE.
// Fire after a shopper clicks a product card
async function loadSimilarProducts(productId) {
  const response = await fetch('https://api.inops.dev/shop/flow/execute', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Search-Key': 'YOUR_SEARCH_KEY',
      'Accept': 'text/event-stream',
    },
    body: JSON.stringify({
      userInput: { type: 'similar_products', productId },
      language: 'en',
    }),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const text = decoder.decode(value);
    for (const line of text.split('\n')) {
      if (!line.startsWith('data: ')) continue;
      const event = JSON.parse(line.slice(6));
      if (event.type === 'products') {
        renderRecommendations(event.data); // "You might also like"
      }
      if (event.type === 'flow-end') break;
    }
  }
}
Similar products
Once results or a bundle are rendered, use Shopify's /cart/add.js endpoint to add items to the cart. You can add a single product or all items in a bundle in one request.
Single product
Add one product variant to cart
Use the Shopify variant ID from the product data returned by Inops.
// Add a single product variant to the Shopify cart
async function addToCart(variantId, quantity = 1) {
  const response = await fetch('/cart/add.js', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      items: [{ id: variantId, quantity }],
    }),
  });
  const cart = await response.json();
  return cart;
}
Full bundle
Add all bundle items to cart at once
Map each product group in the bundle to a cart item and POST them together.
// Add all items in a bundle-result to the Shopify cart at once
async function addBundleToCart(bundle) {
  // bundle.groups is the array from the bundle-result event
  const items = bundle.groups.map((group) => ({
    id: group.product.variantId,
    quantity: 1,
  }));

  const response = await fetch('/cart/add.js', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ items }),
  });
  const cart = await response.json();
  return cart;
}
Add to cart
The POST /shop/flow/execute endpoint returns a Server-Sent Events (SSE) stream. Each event has a type field. Handle each type to progressively render results.
Event typeDescription
productsInitial search results (unranked). Render immediately for fast first paint.
unranked-productsAlias for the first batch of results before reranking is complete.
ranked-resultsReranked results with per-product reasons. Replace the initial results list.
summary-resultAI-generated summary of the search intent and top results. Display above results.
bundle-resultA product bundle assembled from a multi-product query. Multiple events may arrive (one per bundle variant).
flow-endThe pipeline is complete. No more events will follow for this request.
flow-errorAn error occurred in the pipeline. Check the message field for details.
Generic SSE event handler
A minimal handler that routes all event types.
async function executeFlow(userInput) {
  const response = await fetch('https://api.inops.dev/shop/flow/execute', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Search-Key': 'YOUR_SEARCH_KEY',
      'Accept': 'text/event-stream',
    },
    body: JSON.stringify({ userInput, language: 'en' }),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const text = decoder.decode(value);
    for (const line of text.split('\n')) {
      if (!line.startsWith('data: ')) continue;
      const event = JSON.parse(line.slice(6));
      // event.type is one of: products, unranked-products, ranked-results,
      //   summary-result, bundle-result, flow-end, flow-error
      handleEvent(event);
    }
  }
}

function handleEvent(event) {
  switch (event.type) {
    case 'products':
    case 'unranked-products':   renderResults(event.data);           break;
    case 'ranked-results':      replaceResults(event.data);          break;
    case 'summary-result':      renderSummary(event.data.summary);   break;
    case 'bundle-result':       renderBundle(event.data);            break;
    case 'flow-end':            setLoading(false);                   break;
    case 'flow-error':          showError(event.data.message);       break;
  }
}
SSE events reference
Shopify theme
Add the script in theme.liquid (head). Add the widget div where you want search.
Example (theme.liquid)
{% comment %} Inops Web SDK (head) {% endcomment %}
<script src="https://cdn.inops.io/inops-web-sdk@1.2.0/index.global.js"></script>

{% comment %} Place where you want search {% endcomment %}
<div data-widget="search" data-search-key="YOUR_SEARCH_KEY" data-id="my-search-widget"></div>
WooCommerce
Add the script in your theme’s header (or via a header/footer plugin). Add the widget div to a page/template.
Example (header.php)
<!-- Inops Web SDK (head) -->
<script src="https://cdn.inops.io/inops-web-sdk@1.2.0/index.global.js"></script>

<!-- Place where you want search -->
<div data-widget="search" data-search-key="YOUR_SEARCH_KEY" data-id="my-search-widget"></div>
Custom site
Add the script tag to your HTML and place the widget div where needed.
Example (HTML)
<!-- 1) Add script -->
<script src="https://cdn.inops.io/inops-web-sdk@1.2.0/index.global.js"></script>

<!-- 2) Add widget div -->
<div data-widget="search" data-search-key="YOUR_SEARCH_KEY" data-id="my-search-widget"></div>
Common setups
Subresource Integrity (SRI) + version pinning
Pin an exact version and add SRI so the browser rejects tampered assets.
CDN script with SRI
<script
  src="https://cdn.inops.io/inops-web-sdk@1.2.0/index.global.js"
  integrity="sha384-PASTE_SRI_HASH_1_0_0"
  crossorigin="anonymous"
  referrerpolicy="no-referrer"></script>
Content Security Policy (CSP)
Restrict scripts and API calls to only the domains you use.
Example CSP
Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.inops.io;
  connect-src 'self' https://api.inops.dev;
  img-src 'self' data:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  frame-ancestors 'none';
Search Key safety
Search Keys are designed for public read‑only flows. Restrict allowed origins when possible and rotate keys regularly.
Advanced / Security
Inops can cache search results for specific search terms. This is controlled via a per-term TTL (time-to-live). When a term is cached, the response is typically ~10–20ms. When a term is not cached (or the TTL has expired), the request automatically runs through the full flow (embedding + retrieval + ranking + summary), which is slower but keeps results fresh.
Cached search term
  • Fast response (~10–20ms)
  • Great for top queries and campaign landing pages
  • Controlled by TTL per search term
Uncached / expired term
  • Runs the full AI flow automatically
  • Higher latency, but recomputes relevance
  • Useful for long-tail queries and freshness
Configure this in the portal under Shop → Tuning → Search term TTL. You can also manually refresh cache for a term from the same screen.
Caching & TTL (search performance)
Capabilities
  • AI search results that match shopper intent.
  • Similar products to keep shoppers exploring.
  • Track baskets & purchases to see what converts.
  • Control ranking with boosts for products and brands (optional expiry).
For developers
  • Embed a search widget with a Search Key.
  • Call search and similar products via one endpoint.
  • Send basket/purchase events to improve ranking and insights.
Follow-up flows (similar products, basket, purchase)
Inops uses one endpoint: POST /shop/flow/execute. You change behavior by setting userInput.type.
1) Initial search
Use X-Search-Key so you don't leak keys in URLs.
// 1) Initial search (server-side or client-side)
await fetch('https://api.inops.dev/shop/flow/execute', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Search-Key': 'YOUR_SEARCH_KEY',
  },
  body: JSON.stringify({
    userInput: { type: 'search', value: 'kid longboard beginner' },
    // shopConfigId is optional when using Search Key auth
    language: 'en',
  }),
});
2) Similar products (after clicking a product)
Send the clicked productId and render the returned products as recommendations.
// 2) Similar products (after a shopper clicks a product)
await fetch('https://api.inops.dev/shop/flow/execute', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Search-Key': 'YOUR_SEARCH_KEY',
  },
  body: JSON.stringify({
    userInput: { type: 'similar_products', productId: 'YOUR_PRODUCT_ID' },
    language: 'en',
  }),
});
3) Basket event (optional)
Helps improve ranking and analytics.
// 3) Basket event (optional)
await fetch('https://api.inops.dev/shop/flow/execute', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Search-Key': 'YOUR_SEARCH_KEY',
  },
  body: JSON.stringify({
    userInput: { type: 'basket', productId: 'YOUR_PRODUCT_ID' },
    language: 'en',
  }),
});
4) Purchase event (optional)
Links search → checkout for accurate attribution and analytics.
// 4) Purchase event (optional)
await fetch('https://api.inops.dev/shop/flow/execute', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Search-Key': 'YOUR_SEARCH_KEY',
  },
  body: JSON.stringify({
    userInput: { type: 'purchase', productId: 'YOUR_PRODUCT_ID', orderId: 'ORDER_123' },
    language: 'en',
  }),
});
Show API endpoints
POST /shop/flow/execute — search, similar products, basket, purchase
GET /insights/shop-config/:shopConfigId/* — insights
GET /tuning/shop-config/:shopConfigId/* — tuning (boosts, TTL)
What this enables
If the widget doesn’t show results, open DevTools → Network and find the request to /shop/flow/execute.
  • 401/403: the Search Key is missing/invalid or blocked by origin restrictions.
  • No requests at all: the script isn’t loading or the widget div is missing.
  • CORS errors: your origin isn’t allowed (ask us to add it, or update allowlists).
Want to sanity-check quickly? Use the above.
Troubleshooting & Debugging