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

# Security

> JSON-LD escaping and the API key model.

## JSON-LD escaping

Embedding JSON inside a `<script>` tag has a classic injection hazard: if any data field contains `</script>`, the browser terminates the script block there and executes whatever follows as HTML. Product titles, vendor names, and FAQ answers are all merchant- or user-influenced data.

Every component and meta helper shipped in the SDK serializes through `serializeJsonLd`, which escapes every `<` to its JSON unicode form (`\u003c`). The output is still valid JSON — parsers read the escape back as `<` — but it can never close the script tag. This was verified with a hostile payload through a real app: a product name containing

```
</script><script>alert(1)</script>
```

renders inertly inside the JSON-LD block; no script breakout, and the structured data remains valid.

**If you write your own script tags** (custom integrations using the core fetch helpers), never `JSON.stringify` straight into the tag:

```ts theme={null}
import { fetchProductBundle, serializeJsonLd } from "@agentshop/seo";

const seo = await fetchProductBundle(handle);

// ❌ vulnerable to script breakout
const bad = `<script type="application/ld+json">${JSON.stringify(seo?.jsonLd)}</script>`;

// ✅ breakout-safe, still valid JSON
const good = `<script type="application/ld+json">${serializeJsonLd(seo?.jsonLd)}</script>`;
```

(Remix and Hydrogen users don't need this — the `"script:ld+json"` meta descriptor is serialized safely by the framework.)

## The API key model

The SDK authenticates with your **store API key** (`ask_…`), sent as `Authorization: Bearer`. Unlike a public tag, this key is a **secret** — treat it like a password:

* **Server-side only.** Use it in environment variables read on the server: `generateMetadata`, async server components, Remix/Hydrogen loaders, and route handlers all run on the server, so the shipped SDK never exposes it. Never put it in a `NEXT_PUBLIC_*` (or any client-exposed) variable, a client component, or a committed file.
* **Never in the browser or a client bundle.** Anyone with the key can read your store's SEO bundles. If it leaks (a public repo, a client bundle), rotate it immediately.
* **One key per store.** A key resolves to exactly one connected store; a merchant with several stores has one key each.

This is also why the endpoints send `Cache-Control: private` — a per-store secret can't be stored in a shared or CDN cache.

### Rotating a key

Rotate a key any time in the dashboard under **Settings → API Keys** (this is also where you generate it the first time). After a rotation the **previous key keeps working for 24 hours**, so you can roll the new key out to your deployments without downtime. Update your `AGENTSHOP_API_KEY` env var and redeploy within that window.
