It’s an iframe, not a library
EPIS.mountChat('#container') doesn’t inject EPIS’s UI into your DOM. It creates an <iframe src="https://embed.pastpaperlm.com/..."> inside your container. Everything — the chat UI, the auth screen, the theming engine — runs on embed.pastpaperlm.com, in a separate browsing context that your JavaScript cannot reach and that cannot reach yours.
This is the same trust model Stripe Elements, Intercom, and Calendly’s embed widgets use, and for the same reason: it means a bug in your app can never leak into EPIS’s session data, and a bug in EPIS can never touch your page. The tradeoff is that anything crossing that boundary — theming, events, mounting — has to go through the SDK’s explicit API (init, mountChat, updateTheme, .on()) instead of direct DOM/JS access. You can’t document.querySelector() into the chat UI. That’s deliberate, not a missing feature.
One direct consequence: the iframe’s ability to load into your page at all is governed by the Content-Security-Policy: frame-ancestors header embed.pastpaperlm.com sends, which browsers check against your page’s origin — not your page’s content. A page with an opaque origin (file://) fails this check regardless of anything else being correct. See Local Development.
Auth is token-based, not cookie-based
Because the iframe’s origin (embed.pastpaperlm.com) is different from your page’s origin, it’s a third-party context from the browser’s point of view. Safari’s ITP, Firefox’s ETP, and Chrome’s third-party cookie phase-out all specifically target this exact pattern — a cross-origin iframe trying to persist a session. Cookie-based auth would break intermittently, differently, per browser, per user, over time as browsers tighten these rules further.
So EpisBox doesn’t use cookies for the embedded session at all. After the auth popup completes, the access token is written to localStorage scoped to embed.pastpaperlm.com’s own origin — invisible to your page, and unaffected by third-party cookie restrictions because it was never a cookie. Every request the iframe makes to PastpaperLM’s API carries this token as an Authorization: Bearer header instead of relying on cookies being sent automatically.
This is why “logged in on pastpaperlm.com in another tab” doesn’t automatically log a user into your embed, and why nothing you do with your own cookies or localStorage can affect it either way — the two are on entirely separate origins by design. See Security for the full flow.
Theming is token-based, not CSS-based
You can’t override EPIS’s styles with your own CSS — iframe isolation blocks that (see above), and even if it didn’t, arbitrary CSS injection into someone else’s UI is exactly the kind of coupling that breaks on every unannounced redesign. Instead,theme in EPIS.init() (and EPIS.updateTheme() for later changes) sets a fixed set of design tokens — primary, background, radius, font, and so on — that EPIS’s own components consume internally. See Theming Overview for the full token set, and the AI theming skill if you’d rather describe your brand in English than pick hex values by hand.
What clientId actually does today
clientId identifies your integration to PastpaperLM for support and analytics — it is not currently a security boundary. Any non-empty string works, from any domain, without registration. This is a deliberate, honestly-documented gap, not an oversight — see Security for the current state and what’s planned.
Where to go next
Quickstart
Now that the model makes sense, wire it up in three steps.
Security
The full auth and token flow, end to end.
Theming
Every token, every preset, and how to sync theme changes live.
Troubleshooting
Fixes for the issues that fall directly out of the model above.