> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neuronsearchlab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The embed

> One script tag that renders recommendations and collects the behaviour needed to improve them.

## Why this exists

The SDK path asks you to render recommendations yourself and then send events back. That is two integrations, a backend to hold your credentials, and a mapping between your product ids and ours.

The embed collapses all of it. The widget that draws the recommendations is the widget that reports which ones were seen and clicked, so behavioural data arrives as a by-product of showing recommendations rather than as a prerequisite for getting any.

```html theme={null}
<script src="https://cdn.neuronsearchlab.com/embed/1.0.0/nsl.min.js"
        data-nsl-key="nsl_pk_YOUR_KEY" async></script>

<div data-nsl-rec="related" data-nsl-item-url="auto" data-nsl-limit="6"></div>
```

That is the whole integration.

***

## Get a publishable key

<Steps>
  <Step title="Open Security">
    In the console, go to **Security** and find **Embed key**.
  </Step>

  <Step title="List your origins">
    Add every origin that will render the widget, including staging. A key with no origins does nothing.
  </Step>

  <Step title="Copy the snippet">
    The console gives you the script tag with your key already in it.
  </Step>
</Steps>

<Note>
  A publishable key (`nsl_pk_...`) is **designed to be readable** by anyone who views your page source. It can only read recommendations and report impressions - it can never administer your workspace, read your catalogue, or create credentials. This is not the same kind of secret as your Client Secret, and it is the only NeuronSearchLab credential that belongs in a browser.
</Note>

<Warning>
  **Never put a Client Secret or an `nsl_` secret key in browser code.** Those grant full access to your workspace. Publishable keys exist precisely so you never have to.
</Warning>

***

## Placeholders

| Attribute           | Values            | Notes                                                             |
| ------------------- | ----------------- | ----------------------------------------------------------------- |
| `data-nsl-rec`      | `related`, `feed` | Required. Anything else is ignored.                               |
| `data-nsl-item-url` | `auto` or a URL   | `related` only. `auto` reads the page's `<link rel="canonical">`. |
| `data-nsl-limit`    | 1-24              | Defaults to 6.                                                    |

`auto` is why no integration code is needed. Your catalogue is keyed on canonical URLs, so the page already knows which item it is - you never map your ids onto ours.

Placeholders added after load are picked up automatically, so single-page apps work without re-initialising anything.

***

## Styling

The strip renders in a shadow root, so your stylesheet cannot break it and it cannot leak styles into your page. CSS custom properties cross that boundary and are the supported way to restyle it:

```css theme={null}
[data-nsl-rec] {
  --nsl-embed-font: "Inter", sans-serif;
  --nsl-embed-fg: #111;
  --nsl-embed-gap: 20px;
  --nsl-embed-radius: 12px;
  --nsl-embed-card-width: 200px;
  --nsl-embed-aspect: 3 / 4;
}
```

***

## Which URL to load

Subresource integrity and automatic updates cannot coexist on one URL, so both are offered rather than pretending otherwise.

| URL                           | Updates             | `integrity`           |
| ----------------------------- | ------------------- | --------------------- |
| `/embed/<version>/nsl.min.js` | When you choose     | Yes - **recommended** |
| `/embed/v1.js`                | Automatic within v1 | No                    |

Per-version integrity hashes are published with each release.

***

## What it sends

* A recommendation request with the page's canonical URL and a random first-party visitor id. Your key travels as a header, never in the URL.
* **Impressions**, once a card has been at least 50% visible for two seconds. Scrolling past quickly is not an impression, and scrolling back does not double-count.
* **Clicks**, delivered with `sendBeacon` so they survive the navigation.

The widget never intercepts or delays a click. Your navigation happens exactly as it would without it.

No cookies, no third-party storage, no personal data. Visitor and session ids are random values in first-party `localStorage` and `sessionStorage`. Where storage is unavailable - private browsing, blocked site data - the widget still works and simply does not remember the visitor between pages.

***

## Events it will not send

The embed reports `view` and `click` only.

`purchase` carries the highest weight in training and cannot be verified from a browser, so the API rejects it from a publishable key. Send conversions from your server with the [Events API](/api-reference/endpoint/create-events), or import them with a [backfill](/guides/backfill).

Every event is also checked against the recommendations we actually served, so a key copied from your page source cannot be used to invent engagement for arbitrary items.

***

## Quota

Embed traffic is metered separately from API requests, because a recommendation strip makes one request per pageview.

When the allowance runs out the widget **keeps working** and serves cached results, with `X-NSL-Quota: exceeded` on the response. It does not start failing on your live site.

***

## Failure behaviour

Every failure path renders nothing and leaves the page untouched. The script sits in your critical render path, so it never throws, never blocks, and never shows an error to your visitors. It is under 4 kB gzipped.
