Skip to content

Embedding an agent

This content is not available in your language yet.

Put an agent on a website. Visitors chat with it anonymously — no account, no login — inside a panel you style from the Embed tab.

Open the agent, then the Embed tab, and press Create embed. You need a name and at least one allowed origin.

An origin is the scheme, host and (if it is not the default) port of the site the widget will run on — https://help.example.com, not https://help.example.com/support. Add every origin the widget will appear on, including www and non-www if you serve both.

The Install block gives you the whole thing:

<script src="https://your-kovac-host/embed/v1/embed.js"
data-embed-key="kve_…"
defer></script>

Paste it once, before the closing </body> tag. The tag carries only the key: the theme, greeting, starter prompts and corner are all read from the Embed tab at runtime, so restyling never needs your site redeployed.

An embed key starts kve_ and is meant to sit in your page’s HTML where anyone can read it. It is not masked anywhere in the interface, and it is not a secret. What protects it is the origin allowlist and the limits, not secrecy — so treat it like a public site identifier, not like an API key.

Tenant API keys are the opposite: those carry a role and are shown once. Never put one in a web page.

The allowlist stops the agent being mounted on another website. A widget loaded on an origin you did not list renders its own chrome and never receives a session, so it cannot answer anything.

It is not a defence against someone who copies the key out of your page and calls the API directly from a script. Nothing in a browser can be, because the browser is the thing making the claim. That is what the limits are for.

Three ceilings, all counted in runs — one run is one turn the model takes, whatever triggered it:

limit what it stops
Runs / min per visitor one visitor hammering the widget
Runs / day per visitor one visitor’s total daily cost
Runs / day, this embed the whole embed’s daily cost

The Embed tab shows today’s count against the last of these. When it is reached, the widget reports itself unavailable rather than failing on a visitor’s first message.

The Appearance section starts from the platform’s own palette, in a light and a dark half, and mode decides which a visitor sees (auto follows their own system setting). Every one of the twenty colours can be overridden; anything you do not touch tracks the platform default.

The preview beside the form is the real widget, fed your unsaved changes. Messages you send in it are real runs and count against the embed’s daily limit.

Where a colour combination falls below the contrast the text needs, the preview says so and names the ratio. Those warnings never block saving — they are there so you find out before your visitors do.

The header background is the one structured value, so a gradient is expressible without free-form CSS:

{
"header_bg": { "type": "gradient", "from": "#0d2d59", "to": "#1e4d8c", "angle": 135 },
"bg": "#ffffff",
"surface": "#f4f7fb",
"border": "#dbe3ec",
"text": "#19212b",
"text_muted": "#57606b",
"accent": "#123b72",
"header_text": "#ffffff",
"user_bubble": "#123b72",
"user_text": "#ffffff"
}

Regenerate issues a new key and kills the old one immediately — every page still carrying the old tag goes quiet until you paste the new one.

Revoke retires the embed. The row stays visible as a record, and the key stops working everywhere. There is no un-revoke: create a new embed instead.

A workflow embeds the same way. Open the workflow, then its Embed tab, and follow the steps above. The tag, the allowlist, the limits and the theme are the same, and one embed is one workflow.

Replies stream with the name of the agent that wrote them. Under Agents, the switch Show which agent is answering decides whether visitors see that name. With it on, a Label for field appears for each member, so you can show Billing team instead of support-desk-billing. Labels are up to 40 characters, and a blank label shows the agent’s own name. See Workflows.

When the agent answers from a collection, the widget numbers its sources inline and lists them as chips under the answer. A source is only linked if it has an http(s) URI; anything else is still named.

When the agent or workflow has follow-up questions turned on (see Agents), the widget shows the latest answer’s suggested questions under it as stacked buttons, with a small “Suggested questions” label in English or Macedonian, following the widget’s language. A tap sends that question. They are hidden while an answer is being written and while an approval, question or page action is waiting. They use the theme’s Follow-up buttons colours, the same as starter prompts.

The assistant can act on your page, within what you allow. In the Embed tab, On the page lists the actions your page can perform: each has a name, a title the visitor reads, a description for the assistant and the parameters it takes. Your page provides the code. The Install block shows a second snippet with one handler per action; paste it after the tag:

<script>
window.kovacEmbed = window.kovacEmbed || [];
kovacEmbed.push({ actions: {
apply_filter: async ({ brand }, ctx) => {
await ctx.type('#search', brand);
await ctx.click('#apply');
return { results: document.querySelectorAll('.product').length };
},
}});
</script>

A handler receives the validated arguments and ctx: ctx.point, ctx.click, ctx.type and ctx.scrollTo take an element or a CSS selector, move a visible cursor and perform the step. ctx.type fills a field the way a person would (React and Vue forms see it) and never types into a password, file, read-only, disabled or non-text field. ctx.click fires a click event only, without the pointer and mouse down and up events, so a control that listens for those will not react. ctx.signal is aborted when the visitor presses Escape or the action runs too long. Return a JSON value (under 16 KB) for the assistant to read, or nothing.

ctx.navigate(url) takes the visitor to another page of the same site, such as a map or a product page. It waits a moment so your handler’s answer reaches the assistant first, and refuses another site’s address. On a laptop or desktop the chat opens again on the new page; on a phone it stays closed, so the page is in view.

Before an action runs, the visitor sees a card with its title and arguments and chooses Allow or Not now. An action you mark as not needing confirmation runs at once, unless an earlier automatic attempt failed, when the card appears: use that setting only for something the visitor would not need to undo, such as a filter. Handlers run without a user activation, so a pop-up, the clipboard or a file picker is blocked; use ctx.navigate to change pages, and the answer still reaches the assistant. Pressing Escape stops a running action and leaves the chat open. The live preview in the Embed tab answers every action with a stub and lists the calls, so you can try the assistant before your page has the code.

Page control lets the assistant look at the page the visitor is on and act on it, without you writing a handler for each action. Switch it on under On the page in the Embed tab; it is off by default.

With it on, the assistant can read the page’s visible text and controls, point at a part of the page and propose a short plan of clicks, typed values and navigations. The visitor allows each plan on a card that lists every step in plain words, with the values the assistant chose marked as such; nothing runs until they choose Allow, and Escape stops a plan that is running.

What the assistant cannot do: it stays on the same origin as your page (same scheme, host and port: it never follows a link or submits a form to another origin, never opens a new tab, starts a download or follows a link that runs script). It never shows or fills a card-related field (card number, expiry, security code or one-time code, a drop-down included), a password, a file picker, or a disabled or read-only field. It can clear a text field by filling it with an explicitly empty value.

The visible text of the page, and the names of its controls, are sent to the model and appear in the conversation’s traces. Mark anything that must not be sent with data-kovac-private; the assistant then sees neither that region’s text nor its controls:

<section data-kovac-private>
<p>Account number: 1234 5678</p>
</section>

Hidden elements, aria-hidden content and field values that look secret are left out as well, and the page address is shown without its query or hash. The live preview in the Embed tab answers page control from a small sample page and lists a plan instead of running it.

On a window at least 640 pixels wide, visitors get a pin button in the widget’s header. Pinned, the chat becomes a full-height panel on the right edge of the page and stays open when the visitor moves to another page of your site. Closing it keeps the pin, so the next time it opens it is a side panel again; unpinning returns it to its corner. Escape does not close a pinned panel. On a narrower window the chat already fills the screen, and there is no pin.

Two switches in the Appearance section control this:

  • Let visitors pin the chat to the side turns the pin button on or off.
  • Pinned chat pushes the page aside moves your page to the left to make room instead of covering it. Headers and other elements fixed to the window stay where they are, so they may be covered by the panel.

A history button in the header lists the visitor’s earlier conversations with this widget — up to 50, newest first. A visitor can filter the list by title, reopen a conversation or delete one. A new conversation is titled with the start of its first message. History is per browser: it shows only that visitor’s own conversations on this embed.

The search button in the header opens a search field for the conversation that is open. It highlights every match, shows how many there are, and steps forward and back through them. It searches the open conversation only, not the whole history.

Under Content, Widget language sets the language of everything the widget says — buttons, hints, messages and errors. Automatic uses the visitor’s browser language, Macedonian when that is Macedonian and English otherwise; you can also fix it to English or Macedonian. It is separate from the dictation language, which only tells the speech model what to expect.