Embed reference

Everything a developer needs to control how the Olotalk assistant loads and behaves.


Basic snippet

<script
  src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
  data-olotalk-assistant-id="YOUR_ASSISTANT_ID"
  async
></script>

Place this before </body>. async is recommended so the loader stays non-blocking.

One async script
Reads your config
Fetches bundle + CSS
Mounts the assistant
The single script tag is a tiny async loader: it reads your config (data-* attributes merged with any window.OlotalkConfig), pulls the assistant bundle and CSS from the CDN, and mounts automatically on DOMContentLoaded.

data-olotalk-* attributes

All configuration is passed as data- attributes on the script tag.

<script
src=".../@olotalk/assistant-loader@0/dist/loader.iife.js"
1data-olotalk-assistant-id="ast_oltk_..."
2data-olotalk-origin="https://api.example.com"
3data-olotalk-locale="fr"
4data-olotalk-cdn-base="..."
async
></script>
  1. 1
    data-olotalk-assistant-idRequired: which assistant to load.
  2. 2
    data-olotalk-originOptional API origin override for self-hosting; omit it on Olotalk Cloud.
  3. 3
    data-olotalk-localeForce a language. Otherwise auto-detected.
  4. 4
    data-olotalk-cdn-baseWhere to fetch the bundle. Defaults to jsDelivr.
All configuration rides on the one script tag as data-* attributes. Only the assistant ID is required; every other attribute has a sensible default.

data-olotalk-assistant-id (required)

Your assistant’s unique identifier. Find it in the dashboard: open your assistant from Assistants — the ID is shown on its Install step (and in the page URL).

data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"

data-olotalk-origin (optional)

The base URL of the Olotalk API. Omit it for Olotalk Cloud — the loader defaults to https://api.olotalk.com. Set this only in advanced self-managed deployments where the API runs on a different origin.

<!-- Self-managed deployment -->
data-olotalk-origin="https://api.example.com"

data-olotalk-locale

Force a specific locale for the assistant UI. Accepts any well-formed BCP 47 language tag — "en", "fr", "en-CA", "vi", "ja-JP", "sw", etc. UI strings for en/fr/de are bundled; other locales are translated by Olotalk on first request and cached.

When omitted, the assistant auto-detects from (in order): the visitor’s previously-accepted choice (localStorage) → the page’s <html lang>navigator.language. The visitor’s auto-detected language is honoured only when it’s in the assistant’s supportedLocales set — operators declare which languages their site ships in the dashboard, and an unsupported visitor language falls through to the configured default. Visitors who type their first message in a different supported language see an inline Continue in {language}? prompt that swaps the chrome on accept.

data-olotalk-locale="fr"

See the Multilingual section in the loader README for the full priority chain.

This attribute is read once, when the assistant mounts. If your own language switcher changes the page’s language without reloading it, call Olotalk('locale', 'fr') — that swaps the assistant’s strings in place and keeps the open conversation. See Driving the language yourself.

data-olotalk-mode

Which shape to mount. Omit it and you get the corner bubble.

data-olotalk-mode="drawer"

Several shapes, comma-separated, share one conversation — the first is the residence:

data-olotalk-mode="bubble,drawer"

The full list, and what each one is for, is in Shapes. Four of them — drawer, anchored, navfield and askbar — attach to something on your page and need a line of JavaScript besides this attribute; the rest work from the script tag alone.

An id the assistant does not recognise falls back to a working mount and says so in the console, so a typo degrades to a bubble rather than to a blank page.

data-olotalk-reach

Extra ways to summon the shape you mounted, comma-separated. selection opens it beside a passage the visitor highlights, with that passage quoted above an empty composer; commandk opens it on K.

data-olotalk-mode="bubble"
data-olotalk-reach="selection,commandk"

However many ways in you add, the page still has exactly one panel and one conversation — see Combining shapes. An id reach does not recognise is ignored with a console warning.

data-olotalk-open

Turns any element you already have into a door. No JavaScript.

<button data-olotalk-open>Ask AI</button>
<button data-olotalk-open="drawer">Ask AI</button>
<button data-olotalk-open data-olotalk-prefill="How does billing work?">Pricing help</button>

The value names which mounted shape to open; leave it empty when the page has one panel, which is the usual case. data-olotalk-prefill opens it carrying a question — the shape then sends it or leaves it editable according to its own behaviour (a corner bubble sends; select-to-ask holds it so the visitor can amend). A door naming a shape nothing has mounted warns in the console and does nothing, rather than failing quietly.

A plain door toggles: press it again while the panel is open and it closes. A door carrying data-olotalk-prefill always opens instead — those read as “ask this”, where a second press means “ask it again”, not “never mind”.

This is what makes the slide-in drawer a paste: it has no launcher of its own by design, so its snippet ships this button with it. Move the button into your header, restyle it, or drop the attribute onto a button you already have.

data-olotalk-door="branded"

Opt a door into the assistant’s own look: a quiet chip at rest, with the launcher’s gradient rim turning around it on hover.

<button data-olotalk-open="drawer" data-olotalk-door="branded">Ask AI</button>

Opt-in on purpose. Without this attribute a door inherits your styles and we touch nothing — the point of data-olotalk-open is that it goes on an element you already have. The generated snippet includes the attribute, and the robot mark inside its markup: the mark blinks, which a background image cannot do, so it ships as inline SVG rather than being painted on. Delete the attribute at any time to get your own button back.

data-olotalk-selection-answers

Where a highlight opens the panel. beside (the default) brings it to the words, so the answer arrives where the question was asked. in-place opens it where the assistant lives — worth choosing when your assistant is a fixture visitors have already located and you would rather nothing moved.

data-olotalk-mode="bubble"
data-olotalk-reach="selection"
data-olotalk-selection-answers="in-place"

Only the selection trigger is affected; your own open({ anchor }) calls position themselves as they always did. Any other value is ignored with a console warning and you get beside.

data-olotalk-mount

CSS selector for the container the assistant renders into. Required for the in-flow shapes — inline and expand — which live in your layout rather than over it. Everything else positions itself against the viewport and ignores it.

data-olotalk-mount="#olotalk-assistant"

data-olotalk-sandbox-token

Preview feature. A signed, expiring token minted from the dashboard’s Test in your own app card. Its presence switches the assistant to the un-billed developer sandbox — see Testing in the sandbox for the full model. If the feature isn’t enabled for your workspace, ignore this attribute.

data-olotalk-sandbox-token="<token from the dashboard>"

data-olotalk-cdn-base

Override the CDN base URL where the assistant bundle and CSS are loaded from. Useful for self-hosting.

<!-- Self-hosted on your own origin -->
data-olotalk-cdn-base="https://assets.example.com/olotalk"

When omitted, the loader defaults to jsDelivr with an assistant version pin matching the loader build.


JSON attribute (alternative syntax)

Instead of individual data-olotalk-* attributes, you can pass a single JSON blob:

<script
  src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
  data-olotalk='{"assistantId":"ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy","locale":"fr"}'
  async
></script>

The JSON attribute is merged with any individual data-olotalk-* attributes — individual attributes take precedence.


Global config object (advanced)

For full programmatic control, define window.OlotalkConfig before the loader script:

<script>
  window.OlotalkConfig = {
    assistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
    locale: 'fr',
    theme: 'dark',
    mode: 'inline',           // which shape — 'bubble' is the default
    mount: '#chat-container', // CSS selector — required for 'inline' and 'expand'
    // origin: 'https://api.example.com', // only for self-managed deployments
    // cdnBase: 'https://assets.example.com/olotalk', // only for self-hosted assets
    // version: '1.2.3',                 // pin the assistant bundle version
  };
</script>
<script
  src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
  async
></script>

window.OlotalkConfig is merged with data- attributes. data- attributes win on conflict.

data-olotalk-* attribute
on the script tag
overrides
window.OlotalkConfig
the global config object
overrides
built-in default
what ships

Full config reference

FieldTypeDefaultDescription
assistantIdstringAssistant ID (required)
siteIdstringLegacy alias for assistantId
originstringhttps://api.olotalk.com (cloud build)Olotalk API base URL. Omit for Olotalk Cloud; set only for self-managed deployments.
localestringauto-detectedExplicit locale override (any BCP 47 tag). Auto-detect priority: visitor’s localStorage choice → <html lang>navigator.language, gated by assistant’s supportedLocales.
theme"light" | "dark" | object"light"UI theme
modeshape id"bubble"Which shape to mount — see Shapes
placement"floating" | "embedded"Deprecated — use mode. floating is bubble, embedded is inline. Still resolves, because it is in live snippets
mountstringdocument.bodyCSS selector for the container. Required for inline and expand
cdnBasestringjsDelivrAsset CDN base URL
versionstringmatching loader buildAssistant bundle version pin
cssUrlstringderived from cdnBaseOverride assistant CSS URL

Conversion config

The conversion pipeline enables intent-based CTA proposals and lead capture. When enabled, the assistant classifies each visitor message into a commercial intent category (pricing, demo, integration, support, general) and, when confidence is high enough, proposes a configured action alongside the answer.

Conversion config is server-side only. It lives in the assistant record — not in the client-side embed snippet — so you configure it from the dashboard, not the embed code.

Configure in the dashboard

Open your assistant from the Assistants nav and use the builder:

  • CTA actions, confidence, and the webhook target live on the Base step, under Advanced configuration → Action policy. Pick a preset or go Custom, then set your booking URL, escalation email, webhook URL, and confidence threshold.
  • Tool connections (Calendly booking, HubSpot) live on the Behavior step.

The recommended production confidence range is 0.7–0.8; lower it during testing to make CTAs trigger more easily.

CTA action types

link
Send the visitor to a URL.
lead_form
Collect name, email, company, use case.
escalate_to_human
Hand the conversation to a person.
trigger_webhook
Fire your webhook with the lead.
book_meeting
Open a scheduling flow.
hubspot_create_contact_deal
Create a HubSpot contact + deal.
ActionTriggered byWhat happens
linkCTA button clickOpens href in a new tab
lead_formCTA button clickInline form — email (required) + optional name/company/useCase fields
escalate_to_humanCTA button clickServer-side action — notifies notifyEmail (configured server-side)
trigger_webhookCTA button clickServer-side action — POSTs lead data to the assistant’s webhookUrl
book_meetingCTA button clickOpens an inline Calendly scheduler (server-side connection)
hubspot_create_contact_dealCTA button clickServer-side action — creates a HubSpot contact + deal

escalate_to_human targets (notifyEmail) and trigger_webhook targets (webhookUrl) are stored server-side and never exposed to the browser.

Page patterns and confidence tuning

By default, a CTA fires on any page. Restrict to specific paths with pagePatterns:

{ "pagePatterns": ["/pricing", "/integrations", "/docs"] }

Pattern matching is substring — /pricing matches https://example.com/pricing and https://example.com/pricing/annual.

Omit pagePatterns (or leave it empty) to match all pages.

minIntentConfidence (0–1) controls how clearly the visitor must express intent before the CTA fires. The recommended production range is 0.7–0.8. Lower to 0.5–0.6 during development to make triggering easier.

Lead scoring and webhook dispatch

When a lead is submitted via lead_form, Olotalk scores it based on intent, email domain, company presence, and use-case completeness. Leads that reach score ≥ 60 are marked qualified. If the assistant has a webhookUrl, Olotalk POSTs the lead payload to it — by default for every submitted lead (both new and qualified), not only qualified ones. See Leads & conversions for the full scoring reference and dispatch policy.

Conversion events

Listen to conversion outcomes on the assistant instance. These events are exposed on the @olotalk/assistant SDK instance returned by createAssistant() — the standard loader snippet manages the instance internally and does not expose them, so use the SDK path if you need to subscribe.

import { createAssistant } from '@olotalk/assistant'

const assistant = createAssistant(mountEl, { assistantId, origin })

assistant.on('cta_click', ({ id, action, intent }) => {
  // id: CTA id from server config
  // action: 'link' | 'lead_form' | 'escalate_to_human' | 'trigger_webhook'
  //         | 'book_meeting' | 'hubspot_create_contact_deal'
  // intent: detected commercial intent that triggered the proposal
  console.log('CTA clicked', id, action, intent)
})

assistant.on('lead_submit', ({ leadId, status, score }) => {
  // status: 'new' | 'qualified'
  // score: 0–100 lead quality score
  console.log('Lead captured', leadId, status, score)
})

Embedded mode

By default the assistant renders as the corner bubble (a floating launcher in the bottom-right). To embed it inline inside an existing container:

<div id="chat-container" style="height: 600px;"></div>

<script>
  window.OlotalkConfig = {
    assistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
    mode: 'inline',
    mount: '#chat-container',
  };
</script>
<script
  src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
  async
></script>

The container must have an explicit height.

To make the embedded assistant match the surrounding page — font, colours, corners — set a few CSS variables. See Theming; a worked in-page example is at the bottom of that page.


Framework guides

Using a no-code builder or CMS? WordPress, Shopify, Wix, Squarespace, Webflow, Framer, Ghost, BigCommerce, Google Tag Manager and more each have a step-by-step guide under Install on your platform — with the exact menu path and platform-specific gotchas. The framework snippets below are for code-first apps.

The examples below assume Olotalk Clouddata-olotalk-origin is omitted because the loader defaults to https://api.olotalk.com. In an advanced self-managed deployment, also pass data-olotalk-origin="https://api.example.com" (or set origin on window.OlotalkConfig).

Each guide below mounts the corner bubble — the default, right for most sites. Want it inline in your layout instead? See Choosing a surface for the current options, then Inline in a framework for the two-line change. To match your site’s look, see Theming.

Framework not listed? The integration is a single <script> tag, so the Plain HTML snippet works in any framework that renders markup — Solid, Qwik, Preact, Alpine, Rails, Django, Laravel, and the rest. For a component-native feel, the <olotalk-assistant> custom element works in any template.

React

import { useEffect } from 'react'

export function OlotalkAssistant() {
  useEffect(() => {
    if (document.getElementById('olotalk-loader')) return

    window.OlotalkConfig = {
      assistantId: process.env.NEXT_PUBLIC_OLOTALK_ASSISTANT_ID!,
    }

    const script = document.createElement('script')
    script.id = 'olotalk-loader'
    script.src =
      'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
    script.async = true
    document.body.appendChild(script)

    return () => {
      window.Olotalk?.('destroy')
    }
  }, [])

  return null
}

Add <OlotalkAssistant /> to your root layout (app/layout.tsx for Next.js App Router).

Next.js (Script component)

Requires Next.js 14+. The example below follows Next.js 16 conventions (async server components, no NEXT_PUBLIC_* env vars baked into the bundle).

Use data-* attributes directly on the <Script> tag. Read assistant config from plain (non-NEXT_PUBLIC_) env vars in an async server component so they are evaluated at request time, not baked in at build time.

// app/layout.tsx
import Script from 'next/script'

export const dynamic = 'force-dynamic' // read env vars at request time

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const loaderSrc = process.env.OLOTALK_LOADER_SRC
  return (
    <html>
      <body>
        {children}
        {loaderSrc && (
          <Script
            id="olotalk-loader"
            src={loaderSrc}
            strategy="afterInteractive"
            data-olotalk-assistant-id={process.env.OLOTALK_ASSISTANT_ID}
          />
        )}
      </body>
    </html>
  )
}

Vue / Nuxt

// plugins/olotalk.client.ts
export default defineNuxtPlugin(() => {
  window.OlotalkConfig = {
    assistantId: useRuntimeConfig().public.olotalkAssistantId,
  }

  const script = document.createElement('script')
  script.src =
    'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
  script.async = true
  document.body.appendChild(script)
})

Astro

Add the loader to your shared layout. Astro bundles <script> tags by default, so mark it is:inline to keep the external loader and its data- attributes intact. Frontmatter expressions still evaluate at build/SSR time.

---
// src/layouts/Layout.astro
const assistantId = import.meta.env.PUBLIC_OLOTALK_ASSISTANT_ID
---
<!-- ...your layout... -->
<slot />

<script
  is:inline
  src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
  data-olotalk-assistant-id={assistantId}
  async
></script>

Set PUBLIC_OLOTALK_ASSISTANT_ID in your .env (the PUBLIC_ prefix exposes it to the browser). For inline placement instead of the corner bubble, see Inline in a framework below.

Plain HTML / static sites

<!DOCTYPE html>
<html>
  <body>
    <!-- your page content -->

    <script
      src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
      data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
      async
    ></script>
  </body>
</html>

Remix

Add the loader in app/root.tsx after the <Scripts /> component. Use defer instead of async — Remix’s streaming renderer renders <Scripts /> last, so defer is more compatible.

// app/root.tsx
import { Scripts } from '@remix-run/react'

export default function App() {
  return (
    <html>
      <body>
        <Outlet />
        <Scripts />
        <script
          src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
          data-olotalk-assistant-id={process.env.OLOTALK_ASSISTANT_ID}
          defer
        />
      </body>
    </html>
  )
}

Environment variables in Remix are server-only by default. If you need them in a client component, expose them via a loader and pass them as props, or use .env with VITE_ prefix for Vite-based Remix projects.

SvelteKit

Option A — src/app.html (simplest, no env vars)

<!-- src/app.html -->
<body>
  %sveltekit.body%

  <script
    src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
    data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
    async
  ></script>
</body>

Option B — +layout.svelte with env var injection

<!-- src/routes/+layout.svelte -->
<script>
  import { onMount } from 'svelte'
  export let data

  onMount(() => {
    if (document.getElementById('olotalk-loader')) return

    window.OlotalkConfig = {
      assistantId: data.olotalkAssistantId,
    }

    const script = document.createElement('script')
    script.id = 'olotalk-loader'
    script.src =
      'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
    script.async = true
    document.body.appendChild(script)
  })
</script>

<slot />
// src/routes/+layout.server.ts
import { env } from '$env/static/private'

export function load() {
  return {
    olotalkAssistantId: env.OLOTALK_ASSISTANT_ID,
  }
}

Angular

Option A — src/index.html (simplest)

<!-- src/index.html -->
<body>
  <app-root></app-root>

  <script
    src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
    data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
    async
  ></script>
</body>

Option B — AppComponent with environment.ts

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core'
import { environment } from '../environments/environment'

@Component({ selector: 'app-root', templateUrl: './app.component.html' })
export class AppComponent implements OnInit {
  ngOnInit() {
    if (document.getElementById('olotalk-loader')) return

    ;(window as any).OlotalkConfig = {
      assistantId: environment.olotalkAssistantId,
    }

    const script = document.createElement('script')
    script.id = 'olotalk-loader'
    script.src =
      'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
    script.async = true
    document.body.appendChild(script)
  }
}
// src/environments/environment.ts
export const environment = {
  production: false,
  olotalkAssistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
}

Inline in a framework

Every guide above mounts the corner bubble. To place the assistant inline — a block inside your own layout instead of a corner launcher — you have two options that work in any framework.

Simplest — the custom element. <olotalk-assistant> renders wherever you put it, so mode="inline" makes it inline in any HTML or component template. Register it once at startup, then drop the tag in a sized container:

// once, at app startup (main.ts / entry file)
import { defineOlotalkAssistantElement } from '@olotalk/assistant'
defineOlotalkAssistantElement()
<div style="height: 600px">
  <olotalk-assistant assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy" mode="inline"></olotalk-assistant>
</div>

Imperative — mount into a ref. When you’d rather control mounting yourself, call createAssistant(element, …) with the container. Shown in React; Vue (ref + onMounted), Svelte (bind:this + onMount) and Angular (@ViewChild + ngAfterViewInit) follow the identical pattern — a container ref plus a mount-on-init, destroy-on-unmount lifecycle.

import { useEffect, useRef } from 'react'
import { createAssistant } from '@olotalk/assistant'

export function InlineAssistant() {
  const ref = useRef<HTMLDivElement>(null)
  useEffect(() => {
    if (!ref.current) return
    const assistant = createAssistant(ref.current, {
      assistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
      mode: 'inline',
    })
    return () => { void assistant.destroy() }
  }, [])

  return <div ref={ref} style={{ height: 600 }} />
}

The container needs an explicit height — the assistant fills it. Both paths are documented in full under Assistant JS API. To make an inline assistant match the surrounding page, set a few CSS variables — see Theming.


Self-hosting

To host the assistant bundle on your own infrastructure:

  1. Install the npm packages on a machine with Node.js:
    npm install @olotalk/assistant-loader @olotalk/assistant
    
  2. Copy the dist files to your static asset server:
    cp node_modules/@olotalk/assistant/dist/olotalk-assistant.js /your/assets/
    cp node_modules/@olotalk/assistant/dist/olotalk-assistant.iife.js /your/assets/
    cp node_modules/@olotalk/assistant/dist/olotalk-assistant.css /your/assets/
    cp node_modules/@olotalk/assistant-loader/dist/loader.iife.js /your/assets/
    cp node_modules/@olotalk/assistant/dist/version.json /your/assets/
    
  3. Set data-olotalk-cdn-base to the URL where your assets are served. Leave data-olotalk-origin off so the loader keeps calling Olotalk Cloud’s API (only set it in an advanced self-managed deployment where the API runs on your own origin):
    <script
      src="https://assets.example.com/olotalk/loader.iife.js"
      data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
      data-olotalk-cdn-base="https://assets.example.com/olotalk"
      async
    ></script>
    

The loader requests {cdnBase}/olotalk-assistant.js on module-capable browsers, falls back to {cdnBase}/olotalk-assistant.iife.js when needed, and loads CSS from {cdnBase}/olotalk-assistant.css. It also requests {cdnBase}/version.json for cache-busting — omit that file and everything still works, but you’ll see a 404 in the console and visitors may hold a cached bundle longer.


Ingesting your content

Olotalk builds your assistant’s knowledge base by crawling the pages you point it at — all from the dashboard, no API calls or scripts required.

Open your assistant from the Assistants nav and go to the Source step. Two things stay independent here:

  • Source URLs — what content gets indexed into the knowledge base
  • Install domains — where the launcher is allowed to run (set on the Install step)

Add the URLs you want indexed, or enter your site root and let Olotalk discover pages from your sitemap.xml and robots.txt (it filters out disallowed paths automatically). Start the crawl, and the Ingestion status, coverage & languages panel on the same step tracks progress. When you update your content, re-crawl from the same panel — Olotalk remembers the discovered URL set, so you don’t have to re-enter it.


Security

Install domain allowlist — Olotalk validates every request against the assistant’s configured install domains. Add the hostname where your snippet runs on the assistant builder’s Install step, under Install domains (open your assistant from the Assistants nav). Requests from unlisted domains receive 403.

Rate limiting — the config endpoint is limited to 60 requests per IP per minute. The ask endpoint inherits the per-assistant rateLimitPer5Min setting (default: 20).

Request tracing — every response includes an x-request-id header. If you pass your own value in the request, Olotalk echoes it back unchanged; otherwise a UUID is generated. Include this header when reporting issues — it correlates your request to the server logs.

Content Security Policy — if your site uses a strict CSP, add:

script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' https://cdn.jsdelivr.net;
connect-src 'self' https://api.olotalk.com https://cdn.jsdelivr.net;

Adjust cdn.jsdelivr.net if self-hosting. If you use the book_meeting CTA, also allow the scheduler iframe with frame-src https://calendly.com; (use the host your booking links point to).