developerUpdated 2026-05-19

Embed Agent Chat

What this covers

This article explains how to embed Tessallite's conversational agent inside a third-party web application. The embedded chat lets end users ask questions about semantic models without leaving the host application. It builds on the Embed API, which handles token minting and scope control.

Embedding uses a lightweight iframe that loads the Tessallite conversational client at /embed/chat. The host application mints a scoped embed token server-side, passes it to the iframe via a URL parameter, and handles token refresh when the token nears expiry.

Architecture

The embed flow involves three participants:

  1. ISV backend — authenticates to Tessallite as a tenant admin, mints an embed token via POST /api/v1/auth/embed-token, and passes it to the browser.
  2. Host page — renders an iframe pointing at the Tessallite conversational client's /embed/chat route with the token as a URL parameter.
  3. Embedded chat — authenticates using the embed token, loads the scoped project, and renders a full-screen chat interface with no login page, navigation bar, or sidebar.

The embedded chat communicates with the host page using postMessage for token refresh. No Tessallite credentials are exposed to the end user.

Embedding with an iframe

The simplest integration is a single iframe element:

<iframe
  id="tessallite-chat"
  src="https://chat.tessallite.example.com/embed/chat?token=eyJhbGci..."
  style="width: 100%; height: 600px; border: none; border-radius: 8px;"
  allow="clipboard-write"
></iframe>

The ?token=... parameter is the embed JWT returned by the embed token endpoint. The iframe renders the chat interface immediately — no login page is shown.

Sizing

The embedded chat is responsive and fills its container. Set the iframe dimensions to fit the host layout. A minimum height of 400px is recommended; 600-800px provides a comfortable experience.

Sandbox attributes

If your content security policy requires sandbox on iframes, include at minimum:

<iframe sandbox="allow-scripts allow-same-origin allow-forms" ...></iframe>

allow-same-origin is required for the embed token to be sent with API requests. allow-scripts is required for the chat to function.

Token refresh

Embed tokens expire after the configured expiry_minutes (default 3 hours). When the token is within 10 minutes of expiry, the embedded chat posts a message to the parent frame:

{ "type": "tessallite:token-expiring" }

The host page should listen for this message, mint a fresh token from its backend, and send it back:

window.addEventListener("message", async (event) => {
  if (event.data?.type === "tessallite:token-expiring") {
    const freshToken = await fetchFreshEmbedToken();
    const iframe = document.getElementById("tessallite-chat");
    iframe.contentWindow.postMessage(
      { type: "tessallite:token", token: freshToken },
      "https://chat.tessallite.example.com"
    );
  }
});

The embedded chat picks up the new token and continues without interruption.

Refresh best practices

Scope and capabilities

The embed token controls what the chat can do. The capabilities field in the token request restricts available features:

CapabilityEffect in embedded chat
chatRequired. Without it the embed route shows an error.
queryAllows the agent to execute queries. If excluded, the agent can still respond using cached context but cannot run new queries.
exploreAllows the agent to browse model metadata for context.

The persona_id field locks the session to a specific persona's row-level security filters and column restrictions. The model_ids field restricts which models the agent can query.

Theming

The conversational client supports light and dark mode. By default it follows the user's system preference. You can override by appending &theme=light or &theme=dark to the embed URL:

/embed/chat?token=eyJ...&theme=dark

For deeper customisation (brand colours, typography), deploy the conversational client with a custom theme configuration file. See the conversational client's tessallite-theme.json for the available theme tokens.

Security considerations

Complete example

Server-side (Python)

import requests

TESSALLITE_URL = "http://localhost:3000"

# 1. Authenticate as tenant admin
login = requests.post(f"{TESSALLITE_URL}/api/v1/auth/login", json={
    "tenant_id": "acme-demo",
    "email": "admin@acme-demo.com",
    "password": "acme-demo",
})
cookies = login.cookies

# 2. Mint an embed token scoped to chat only
resp = requests.post(
    f"{TESSALLITE_URL}/api/v1/auth/embed-token",
    json={
        "tenant_id": "acme-demo",
        "user_identity": "viewer@customer.com",
        "capabilities": ["chat", "query"],
        "expiry_minutes": 60,
    },
    cookies=cookies,
)
embed_token = resp.json()["token"]

# 3. Return embed_token to the browser via your page template

Client-side (HTML)

<!DOCTYPE html>
<html>
<head><title>Embedded Tessallite Chat</title></head>
<body>
  <h1>Ask our data</h1>
  <iframe
    id="tessallite-chat"
    src="http://localhost:3001/embed/chat?token=TOKEN_FROM_SERVER"
    style="width: 100%; height: 700px; border: 1px solid #ddd; border-radius: 8px;"
  ></iframe>

  <script>
    window.addEventListener("message", async (event) => {
      if (event.data?.type === "tessallite:token-expiring") {
        const resp = await fetch("/api/refresh-embed-token");
        const { token } = await resp.json();
        document.getElementById("tessallite-chat")
          .contentWindow.postMessage(
            { type: "tessallite:token", token },
            "http://localhost:3001"
          );
      }
    });
  </script>
</body>
</html>

Troubleshooting

Related