<!-- Generated from /docs/embed. Do not edit by hand. -->
> Source: https://inspekt.gg/docs/embed · Every option of the live 3D CS2 skin viewer embed: item, float, seed, stickers, charm, scene, view and frame chrome, with bounds and defaults.

# Embed parameters

The smallest working embed:

```javascript
CS2Embed.mount(el, { inspect: inspectLink, key: 'cs2pk_your_key_here' });
```

Only the item (`inspect` or `s`) and `key` are required. Pass each option to `CS2Embed.mount()` or as a query parameter on the `/embed` URL. The server and the frame both enforce the bounds. An out-of-range or malformed value is replaced by its default and named in the `X-CS2-Dropped` response header. Only a query string over 4,096 bytes is refused, with a `400`.

## Required input

Pass either `inspect` or `s`.

- **`inspect`**: an inspect link that carries the item as hex, either copied from an item inside CS2 or built as a "gen" link. The frame decodes the item, its float and pattern, stickers, charm and StatTrak count from it. Use this by default. A Steam Community Market or Steam inventory link ends in `M…A…D…` or `S…A…D…`, carries no item data, and shows the [`bad-inspect` card](https://inspekt.gg/docs/errors#fallback-codes).
- **`s`**: a catalog id, with `wear`, `seed`, `st` and `name` alongside it. Each id is a row of `/assets/skins.json`; see [Catalog ids](https://inspekt.gg/docs/render-api#catalog-ids).

> **If you pass both, `inspect` wins.** The frame ignores `s`, `wear`, `seed` and `st` and uses the link's own float, pattern, stickers, charm and StatTrak count.

## Item and condition

| Param | Type / bounds | Default | Notes |
| --- | --- | --- | --- |
| inspect | Steam inspect link, up to 1,024 characters | n/a | Decoded client-side. Carries its own wear, seed and stickers; see above. |
| s | `/^[a-z0-9][a-z0-9_.-]*$/i`, up to 128 characters | n/a | A catalog id. An unknown id fails to load (see [fallback codes](https://inspekt.gg/docs/errors#fallback-codes)). |
| wear | number, `0`-`1` | kit default | The float. A value outside 0 to 1, or one that is not a number, is replaced by the kit default. A value outside the finish's own range, such as 0.05 on a finish made from 0.10 to 0.70, is drawn at the nearest end of that range. |
| seed | integer, `0`-`1000`, the game's own range | `0` | Pattern seed. Without one, the item shows seed 0, the pattern in Valve's inventory picture. |
| st | `slot:stickerId:wear[:offsetX:offsetY:rotation]`, comma-separated, up to 5, slots `0`-`7` | none | Stickers, with CS2's free placement. Same grammar and bounds as the [Render API](https://inspekt.gg/docs/render-api#stickers). A malformed string is dropped whole, and the item is shown without stickers. An offset that puts a sticker's centre outside the weapon's sticker area is dropped on its own. The sticker then sits at its slot's default position, and `x-cs2-dropped` names the offset, as in `st slot 2 offset`. A seventh field is ignored and named as `st scale`. |
| charm | `defIndex[:offsetX:offsetY:offsetZ][@seed][~stickerId]` | none | One charm, where it hangs, its pattern seed, and the sticker sealed in a Sticker Slab. The grammar, the bounds, the charm list, spots and seeds are in the Render API's [Charms](https://inspekt.gg/docs/render-api#charms) section, and the frame's still and its 3D view draw the same charm. A malformed or out-of-bounds value is dropped whole: the item is shown without a charm, and `x-cs2-dropped` names `charm`. A charm we hold no model for is skipped. |
| stattrak | integer, `0`-`999999` | none | The kill counter. Any value makes the item StatTrak, so `stattrak=0` is a StatTrak item with zero kills. A StatTrak knife shows no counter, because the game etches its count into the blade. |
| name | ≤20 printable characters, no control characters | none | A name tag, trimmed. A value over 20 characters or with a control character is dropped whole. |

## Scene, view and chrome

| Param | Type / bounds | Default | Notes |
| --- | --- | --- | --- |
| scene | `/^(plate\|pano\|bg)_[a-z0-9_]{1,58}$/i` | none (see `bg`) | A backdrop id, such as `plate_train_video`. `plate_` is a video loop of a CS2 map, `pano_` a 360° panorama and `bg_` a plain background. An unknown id falls back to the default and is named in `x-cs2-dropped`. With `scene` and `bg` both unset, the item stands on its rarity plate and the frame can open on a still. A video backdrop adds a few tens of megabytes to your page. See [First paint](#first-paint). |
| bg | `rarity` \| `black` \| `white` \| `charcoal` \| `ember` \| `steel` \| `violet` \| the eight `rarity-*` tier plates | `rarity`, or the backdrop `scene=` named | The background behind the item. The same list as the [Render API](https://inspekt.gg/docs/render-api#backgrounds), except the stills-only `transparent`. `rarity` is the item's own tier plate, resolved the same way as for a still, so the opening still and the 3D view match. `bg` and `scene` share one setting. A plain background brings its own lighting and replaces the backdrop, so `bg` wins if you send both. |
| light | `icon` \| `inspect` | set by `view` | The lighting, the same two with the same defaults as the [Render API](https://inspekt.gg/docs/render-api#lighting), for the opening still and the 3D view alike. It lights a plain background or a rarity plate. A `scene=` map loop or panorama keeps its own light. An unknown value falls back to the default and is named in `x-cs2-dropped`. |
| view | `front` \| `back` \| `top` \| `hero` | `front` | The starting camera framing. The same set as the [Render API](https://inspekt.gg/docs/render-api#views), where the default is `hero`. |
| autorotate | `1`/`true` to enable | off | Off when the visitor has `prefers-reduced-motion` set. Stops at the visitor's first pointer-down and stays off. |
| ui | `minimal` \| `none` | `minimal` | `none` hides the name plate. The corner wordmark shows with either value. See [Frame chrome](#ui-chrome). |
| theme | `dark` \| `light` | `dark` | Styles the name plate and wordmark only. |

## Identity and access

| Param | Type / bounds | Default | Notes |
| --- | --- | --- | --- |
| key | `/^[a-z0-9_-]{4,64}$/i` | required | Your publishable key. It is visible in the iframe's `src` and your page source. The origins you register in the [dashboard](https://inspekt.gg/dashboard) protect it. A short-lived token from your server works here in its place; see [Minting a token](https://inspekt.gg/docs/render-api#minting-a-token). |
| origin | an `http(s)` origin | the page's referrer, if any | Your page's origin, where the frame sends its postMessage events. `CS2Embed.mount()` sets it. Set it yourself on a hand-built iframe; see [postMessage API](https://inspekt.gg/docs/postmessage). |

### Frame chrome

`ui=minimal`, the default, shows a small plate with the item's name, and `ui=none` hides it. The Inspekt wordmark in the bottom-right corner shows on every plan, with either value. Clicking it opens the item in the full studio with the same wear, seed, stickers, charm, StatTrak count and name tag. No plan requires other attribution.

## The loader: `CS2Embed.mount(el, options)`

The loader takes every parameter above as an options object, plus two fields of its own:

| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| title | string | `"3D skin viewer"` | The iframe's accessible `title` attribute. |
| lazy | boolean | `true` | Pass `false` to load eagerly (`loading="eager"`) instead of the browser's native lazy loading. |

```javascript
var viewer = CS2Embed.mount(document.getElementById('viewer'), {
  inspect: 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview 001807209A0228043004388F85D7F30340950512E67D72',
  scene: 'plate_train_video',
  view: 'front',
  autorotate: true,
  ui: 'minimal',
  theme: 'dark',
  key: 'cs2pk_your_key_here',
  title: 'AK-47 | Redline in 3D',
  lazy: false,
});
```

`mount()` returns a handle with the methods `setItem`, `setScene`, `setView`, `setAutorotate`, `setQuality`, `resetView`, `screenshot` and `destroy`, plus `on` and `off` for events. [postMessage API](https://inspekt.gg/docs/postmessage) lists every command and event, and the [sandbox](https://inspekt.gg/sandbox) builds these snippets from your own settings.

`cs2embed.js` is a script with no npm package. In React or Vue, load it once and mount a viewer into an element:

```jsx
import { useEffect, useRef } from 'react';

// Loaded once per page, and shared by every viewer.
let loaderPromise;
function loadCs2Embed() {
  if (window.CS2Embed) return Promise.resolve(window.CS2Embed);
  if (!loaderPromise) {
    loaderPromise = new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://inspekt.gg/cs2embed.js';
      script.onload = () => resolve(window.CS2Embed);
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }
  return loaderPromise;
}

export function SkinViewer({ inspect, scene, autorotate = true }) {
  const hostRef = useRef(null);
  const viewerRef = useRef(null);

  useEffect(() => {
    let cancelled = false;
    loadCs2Embed().then((CS2Embed) => {
      if (cancelled || !hostRef.current) return;
      viewerRef.current = CS2Embed.mount(hostRef.current, {
        inspect, key: 'cs2pk_your_key_here', scene, autorotate,
      });
      viewerRef.current.on('error', (e) => console.error('embed refused:', e.code, e.message));
    });
    return () => {
      cancelled = true;
      viewerRef.current?.destroy();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [inspect, scene, autorotate]);

  return <div ref={hostRef} style={{ width: '100%', height: 400 }} />;
}

// usage: <SkinViewer inspect={item.inspectLink} />
```

```vue
<template>
  <div ref="host" style="width: 100%; height: 400px"></div>
</template>

<script setup>
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';

const props = defineProps({
  inspect: { type: String, required: true },
  scene: { type: String },
});

const host = ref(null);
let viewer = null;
let loaderPromise;

function loadCs2Embed() {
  if (window.CS2Embed) return Promise.resolve(window.CS2Embed);
  if (!loaderPromise) {
    loaderPromise = new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://inspekt.gg/cs2embed.js';
      script.onload = () => resolve(window.CS2Embed);
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }
  return loaderPromise;
}

onMounted(async () => {
  const CS2Embed = await loadCs2Embed();
  viewer = CS2Embed.mount(host.value, {
    inspect: props.inspect,
    key: 'cs2pk_your_key_here',
    scene: props.scene,
    autorotate: true,
  });
  viewer.on('error', (e) => console.error('embed refused:', e.code, e.message));
});

onBeforeUnmount(() => viewer?.destroy());

watch(() => props.inspect, (inspect) => viewer?.setItem({ inspect }));
</script>
```

The loader frames `/embed` on the host it was loaded from and accepts messages only from that host. Load it from `https://inspekt.gg` with a script tag. It then frames `https://inspekt.gg/embed` and sets `CS2Embed.origin` to `https://inspekt.gg`. A copy bundled into your scripts, or served from your own site, frames your site's `/embed` instead. It builds each iframe with `referrerpolicy="strict-origin"`, so the frame sends your page's origin even when the page's own `Referrer-Policy` withholds the referrer.

## Raw iframe

To embed without the loader script, build the iframe yourself. Every parameter above is a query parameter on `GET /embed`:

```html
<iframe
  src="https://inspekt.gg/embed?inspect=steam%3A%2F%2Frungame%2F730%2F76561202255233023%2F%2Bcsgo_econ_action_preview%20001807209A0228043004388F85D7F30340950512E67D72&autorotate=1&key=cs2pk_your_key_here&origin=https%3A%2F%2Fyoursite.example"
  style="width:640px;height:400px;border:0"
  loading="lazy"
  allow="autoplay; fullscreen"
  referrerpolicy="strict-origin"
  title="3D skin viewer"
></iframe>
```

> Set `origin` on a hand-built iframe. Without it, the frame uses your page's `document.referrer`, which some browsers withhold. With neither, the frame addresses its postMessage events to `*`.

## First paint

The frame opens on a still of your item, the same picture [GET /api/still](https://inspekt.gg/docs/render-api) serves for that camera, background and size, and switches to the live 3D view once that view has drawn its first frame. The still is part of the embed load and is not counted as a render.

`loaded` fires when the item is visible, usually as the still. `interactive` fires when the 3D view has drawn and accepts `setView`, `screenshot` and the other commands. `autorotate` starts once the 3D view has drawn.

The frame opens on a loading card instead of the still when:

1. You set a `scene` backdrop. Stills are rendered on the default plates only.
2. The frame's size needs a still outside the Render API's 160 to 1600 px bounds, such as a box smaller than 160 px or wider than 10:1.
3. The still is not ready before the 3D view draws, for example during an outage.

## Access control

Our server checks your page's origin against the key's registered origins, and the visitor's browser checks it again through a `frame-ancestors` header, so a key copied onto another site gets the refusal card with `origin_not_allowed`. See [Keys and origins](https://inspekt.gg/docs/render-api#keys-and-origins).

## Embed loads

An embed load counts once per viewer, per item, per 24 hours, and each served `/embed` response says `1` or `0` in `X-CS2-Counted`. [Limits & SLA](https://inspekt.gg/docs/limits#embed-loads) has the full rule.
