Quick test
Create an API key at Organization Settings → API → Generate Key — it is shown once. Mint yourself a token with it:email is the only required field; name and ttl_seconds are in Step 1.
Take the token from the response and open:
How it works
Your user logs into your app → your frontend asks your backend for a token → your backend calls ClarityQ with your API key → your page loads the iframe, and hands it a fresh token whenever it asks for one.Identity
The email you send is the identity. Same address next visit, same conversation history. It becomes an ordinary ClarityQ user — so if that address already uses ClarityQ, it is that person, with the history and remembered preferences they already have.What the embedded user can do
A brand-new address gets the Business User role. That’s the analyst answering questions with charts and tables: they can ask and follow up, edit their own messages, use filters, export results to CSV, rate answers, turn an answer into a dashboard, and schedule a task — all inside the iframe. Two things are analyst-tier and therefore hidden from a Business User: viewing the generated SQL (the View Code button) and editing it. If the address belongs to someone who already holds an analyst role in ClarityQ, they keep it and see both. The Context Layer is read-only in the embed, and the Builder isn’t part of it at all — no embedded user can edit your catalog, whatever their role. Navigation out of the iframe is removed too, so there’s no route from the embed into the main app.Embedded usage counts toward your organization’s usage limits just like usage in the app, and shows up as Embedded in the Usage dashboard. Limit warnings and blocks apply to embedded users the same way they do in the app.
Step 1 — Mint tokens on your server
Using the same API key, expose one endpoint behind your own login:Step 2 — Render the iframe and answer one message
The embed manages its own lifetime. At about 80% of its token’s lifetime it postsclarityq:token-expiring to your page. You reply with a fresh token and it swaps it in
place: no reload, no timer, nothing lost. An answer that is streaming keeps streaming;
text your user has typed stays typed.
clarityq:token-expired, and the same listener answers it when your endpoint recovers.
The request that failed in between is lost; the conversation is not.
Step 3 — Add a history panel (optional)
The iframe is chat only — one conversation at a time, no list, no new-chat button — so it drops into your product without bringing a second navigation with it. Skip this step and every visit starts a fresh chat. Nothing is lost: ClarityQ stores each conversation either way, and they are all still there under that user’s identity. Your users just have no route back to them. If you want that route, the split is: ClarityQ stores the conversations, you render the list. Two things to wire. 1. List them — List conversations, authenticated with the token you already hold (Authorization: Bearer, not the API key). It returns only
that user’s conversations, scoped server-side. Sort on last_updated; description is the
title — a new conversation starts as New Chat and gets its real title a few seconds into
the first answer, announced by clarityq:title so you never re-fetch for it.
2. Open them, and keep them named — switching is a postMessage into the running iframe: internal navigation, so it is instant and needs no token. A brand-new chat announces its id the same way — catch it or that conversation is orphaned — and its title arrives moments later: