ArticleIntermediate7 minUpdated 2026-10-09

Embedding a chat on a website

The "Embed into webpage" dialog, an iframe with shared_id and a beta token, public /chatbots endpoints, the SSE format and measures to protect the public token.

You can show an assistant on an external site or in a corporate portal in two ways: with a ready-made iframe, or with your own interface on top of the public endpoints. Both use a beta token, not the main API key.

The "Embed into webpage" dialog

  1. Create an API key on the "API" page in settings: embedding is unavailable without it. A beta token for public access is created together with the key.
  2. Open the assistant (or an agent on the canvas) and choose "Embed into webpage".
  3. Copy the generated iframe code and paste it into your page.

The address inside the iframe looks like this:

html
<iframe
  src="https://<your-host>/chat/share?shared_id=<dialog_id>&from=chat&auth=<beta>"
  style="width: 100%; height: 100%; min-height: 600px"
  frameborder="0">
</iframe>

Parameters: shared_id is the assistant identifier (for an agent, the address /agent/share and from=agent are used), and auth holds the beta token. The widget draws the assistant's greeting, the conversation history and answers with sources.

Public endpoints

If you need your own interface, call the public methods directly. Authorization is the same: Authorization: Bearer <beta>.

Assistant information. GET /api/v1/chatbots/<dialog_id>/info returns the name, avatar and greeting (prologue). For an agent, GET /api/v1/agentbots/<agent_id>/inputs additionally returns the form of input parameters.

Assistant answer. POST /api/v1/chatbots/<dialog_id>/completions with the body:

json
{"question": "What is the delivery time?", "session_id": "<optional>", "stream": true, "quote": false}

The first call without session_id does not answer the question but creates a conversation: it returns the greeting and a new session_id. Save it on the client and pass it in the following calls.

text
data: {"code": 0, "message": "", "data": {"answer": "Hello! ...", "reference": {}, "id": null, "session_id": "a1b2..."}}

data: {"code": 0, "message": "", "data": true}

Subsequent calls with session_id stream the answer. The answer field of each event holds the accumulated text, reference holds document chunks and per-document aggregates, and the final event holds "data": true:

text
data: {"code": 0, "message": "", "data": {"answer": "Delivery takes", "reference": {...}, "session_id": "a1b2..."}}

data: {"code": 0, "message": "", "data": {"answer": "Delivery takes 3 business days.", "reference": {...}, "session_id": "a1b2..."}}

data: {"code": 0, "message": "", "data": true}

On an error, an event arrives with "code": 500 and the error text in message. With "stream": false, the method returns a single JSON with the full answer. The quote parameter turns on inserting source links into the answer text. For agents the equivalent method is POST /api/v1/agentbots/<agent_id>/completions.

A minimal JavaScript client:

js
const res = await fetch(`${base}/api/v1/chatbots/${dialogId}/completions`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${beta}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ question, session_id: sessionId, stream: true }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  for (const line of buffer.split('\n\n')) {
    if (!line.startsWith('data:')) continue;
    const event = JSON.parse(line.slice(5));
    if (event.data === true) return;
    render(event.data.answer, event.data.reference);
    sessionId = event.data.session_id;
  }
  buffer = buffer.slice(buffer.lastIndexOf('\n\n') + 2);
}

Security

Warning

The beta token ends up in the page HTML and is considered public. It lets anyone ask the assistant questions on behalf of the organization, but not manage data. Do not put the main API key on the page.

  • Rotation. The beta token is tied to the API key: delete the key and create a new one so that the old widget stops working. Update the iframe on the site.
  • Rate limiting. Limit the number of requests to /api/v1/chatbots/* on the reverse proxy (by IP and by session) and set a token spend limit for the organization in the admin console: without this, a public widget can exhaust the model budget.
  • Origin restriction. Allow embedding only from your own domains (the Content-Security-Policy and X-Frame-Options headers on the ingress service) and check Referer on the proxy.
  • Assistant content. Connect to a public assistant only the knowledge bases meant for external readers, and set the answer text for when no information is available. The answer must not reveal internal documents.
  • Log. Requests through the widget are recorded in the log with the session identifier; if you see anomalies, revoke the token.

What next

Ask Sema

Was this helpful?