Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Advanced: embed a bot on your site

Developer-facing tutorial. This page assumes you write code and run a backend. For the no-code path through the ScaiGrid admin UI, see Build a bot with the admin UI.

The quickstart drops a static token into the page. This tutorial covers the production version: tokens issued per visitor by your backend, custom styling, authenticated visitors, and how to set up a content-security policy that doesn't block the widget.

Architecture#

Your server, not your client, talks to ScaiGrid. The widget receives only a short-lived, visitor-scoped token that has no power beyond chatting with the one bot.

sequenceDiagram participant V as Visitor participant YS as Your server participant SG as ScaiGrid V->>YS: GET /page YS->>SG: POST /embed-token SG-->>YS: { token, ttl } YS-->>V: HTML + token V->>SG: loads widget.js + chats via token

1. Server-side token issuance#

Add an endpoint to your backend that mints a token on demand. Cache nothing — each visitor gets their own.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Flask-style example
from flask import Flask, request, jsonify
import httpx, os

app = Flask(__name__)

@app.route("/chat-token")
def chat_token():
    visitor = current_visitor()    # your auth — may be anonymous
    resp = httpx.post(
        f"{os.environ['SCAIGRID_HOST']}/v1/modules/scaibot/bots/{BOT_ID}/embed-token",
        headers={"Authorization": f"Bearer {os.environ['SCAIGRID_API_KEY']}"},
        json={
            "ttl_seconds": 3600,
            "visitor_id": visitor.id if visitor else None,
            "visitor_email": visitor.email if visitor else None,
            "metadata": {"plan": visitor.plan} if visitor else None,
        },
    )
    return jsonify(resp.json()["data"])
javascript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Express equivalent
app.get("/chat-token", async (req, res) => {
  const visitor = req.user; // your auth
  const upstream = await fetch(
    `${process.env.SCAIGRID_HOST}/v1/modules/scaibot/bots/${BOT_ID}/embed-token`,
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.SCAIGRID_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ttl_seconds: 3600,
        visitor_id: visitor?.id,
        visitor_email: visitor?.email,
      }),
    },
  );
  const { data } = await upstream.json();
  res.json(data);
});

The response includes token and expires_at. Pass the token to the widget; expiry is informational (the widget refreshes itself automatically if you give it your endpoint).

2. Loading the widget with auto-refresh#

Instead of pasting a static token, point the widget at your token endpoint:

html
1
2
3
4
5
6
<script
  src="https://scaigrid.scailabs.ai/static/modules/scaibot/widget.js"
  data-bot-id="bot_abc123"
  data-token-url="/chat-token"
  async>
</script>

The widget fetches /chat-token on load, then again 60 seconds before expiry. If your visitor is anonymous, the widget passes no extra headers; if authenticated, your endpoint reads session cookies as normal.

3. Visitor identification#

Pass identity attributes so conversations attribute to the right user in analytics:

html
1
2
3
4
5
6
7
8
9
<script
  src="..."
  data-bot-id="bot_abc123"
  data-token-url="/chat-token"
  data-user-id="usr_42"
  data-user-email="alice@example.com"
  data-user-name="Alice"
  async>
</script>

The token issuance pattern is better — data-user-* attributes are advisory and could be spoofed by a sufficiently determined visitor. Critical attribution should come from the server-issued token, not from HTML attributes.

4. Customising appearance#

Built-in attributes:

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script
  src="..."
  data-bot-id="..."
  data-token-url="/chat-token"
  data-position="bottom-right"
  data-primary-color="#4F46E5"
  data-launcher-icon="speech-bubble"
  data-open-on-load="false"
  data-locale="en"
  async>
</script>

For custom CSS:

html
1
<link rel="stylesheet" href="/your-scaibot.css">
css
1
2
3
4
5
6
7
8
:root {
  --scaibot-primary: #4F46E5;
  --scaibot-bg: #ffffff;
  --scaibot-text: #1a1a1a;
  --scaibot-font: "Inter", sans-serif;
  --scaibot-border-radius: 12px;
}
.scaibot-launcher { box-shadow: 0 10px 24px rgba(0,0,0,.18); }

Every variable is documented in the widget reference (under Customisation in the admin UI).

5. Embedded-in-page layout#

For a chat panel that lives inside a layout (instead of a floating bubble), mount the widget into a container:

html
1
2
3
4
5
6
7
8
9
<div id="my-chat" style="height: 600px;"></div>
<script
  src="..."
  data-bot-id="..."
  data-token-url="/chat-token"
  data-mount="#my-chat"
  data-mode="inline"
  async>
</script>

6. CSP#

If you serve under a strict content-security policy, add:

css+lasso
1
2
3
4
5
script-src 'self' https://scaigrid.scailabs.ai;
connect-src 'self' https://scaigrid.scailabs.ai;
img-src    'self' https://scaigrid.scailabs.ai data:;
style-src  'self' 'unsafe-inline';      # the widget injects styles
font-src   'self' data:;

The widget streams responses over Server-Sent Events; connect-src must allow the ScaiGrid host.

7. Cleaning up across SPAs#

If your site is a single-page app, the widget persists across route changes by default. To unmount it (e.g. on logout):

javascript
1
window.ScaiBot?.destroy();

To remount with a new token:

javascript
1
window.ScaiBot?.init({ token: newToken, botId: "bot_abc123" });

Common gotchas#

  • 403 on /chat — token expired and your data-token-url is unreachable. Check your endpoint returns 200 with a fresh token.
  • Widget visible but never connects — usually CSP blocking connect-src to ScaiGrid. Open the browser console; the widget logs a clear error.
  • Conversations not attributing to usersdata-user-id was set in HTML but token didn't carry it. Mint tokens with visitor_id from your server.
  • Widget on top of your nav bar — set data-z-index="10" (default is 9999, deliberately high). Or use data-mount with inline mode.
Updated 2026-05-18 15:01:26 View source (.md) rev 17