For developers

Install on a site

Where the two lines go and how to check that the chat is live.

On this page

The chat is a web component. You add two lines to the layout all your pages share. Everything else comes from the Tolkly server.

The two lines

HTML
<script type="module" src="https://api.tolkly.io/widget.js"></script>
<tolkly-chat project="your-project"></tolkly-chat>

Replace your-project with the project's id. In the app, open the project's «Install» section, block «Install on your site». The lines are there with the id filled in, next to a «Copy the code» button.

  • widget.js is about a kilobyte: it loads the chat from https://api.tolkly.io, and if the chat has not come up within 2.5 s, from our next address (visitors in Russia).
  • The tag needs no endpoint: the chat talks to the address its script came from.
  • Load the script from https://api.tolkly.io. Do not copy it into your repository or bundle. This way new releases reach your site on their own.
  • Add no other attributes. The chat's name, greeting, colours and who answers (the AI or only people) come from the server. The element reads them from GET https://api.tolkly.io/v1/projects/<project>/widget when it connects. Attributes are only for the rare site that wants its own values (attributes).

Where they go

Add both lines once, just before </body>, in the template every page uses: the root layout, the base template or the theme's footer. Do not add them page by page. For your stack, see Frameworks.

Single-page apps

In React, Vue, Angular, Next.js or Nuxt, the chat must stay on the page when the route changes:

  • Render <tolkly-chat> once, outside the router outlet: in index.html or the root layout. Inside a page component, the element is removed and created again on every navigation.
  • Load the script once, not on every page.
  • In TypeScript JSX, declare the element instead of casting it to any (TypeScript and JSX).

What loads on every page

Most visitors never open the chat. So most page views load only the launcher:

RequestWhenCaching
https://api.tolkly.io/widget.jsevery pageno-cache with an ETag: a repeat view gets a 304 with no body
GET …/v1/projects/<project>/widgetevery pagethe browser keeps it for WIDGET_SETTINGS_MAX_AGE_SECONDS (60 s by default)
POST …/v1/projects/<project>/identifyevery page with data-identity, and on identify()a POST, never cached (identity)
https://api.tolkly.io/widget/chat-<hash>.jswhen the visitor first reaches for the chatnamed by its content, kept for a year

The chat window loads in three cases:

  • the visitor reaches for it: hovers, focuses or taps the launcher;
  • the page opens it;
  • the visitor already has a conversation going (then it loads at once).

Scripts are sent compressed, with brotli or gzip, whichever the browser accepts. The window's code comes from the same place as widget.js, under /widget/.

Visitors in Russia

Since June 2025 Russian providers cut connections to Cloudflare after the first ~16 KB, so for a visitor in Russia without a VPN the chat from https://api.tolkly.io may never finish loading. widget.js is small enough to arrive whole; it waits for the chat itself, not for a script's load event (a stalled download fires neither load nor error), and then loads the chat from our address in Russia. The address that worked is remembered in the site's localStorage for a day, so the next page starts with it; a browser that refuses storage only loses that memory. The chat then sends every request to the address it was loaded from: https://api.tolkly.io https://ru.tolkly.io all serve the same chat and the same conversations.

Check that it works

  1. Open two different pages of the site. The chat button shows in the bottom right corner of both.
  2. In the browser's network panel, GET https://api.tolkly.io/v1/projects/<project>/widget answers 200. Its JSON has no refused field. Check this in the browser on your site, not with curl. The server decides by the page's Origin header, and it never refuses a request without one.
  3. Within a few minutes of the deploy, the project's «Install» section in the app says «The chat is live on example.com · 2 min ago». Until then it says «Not installed yet».

How the site gets recorded:

  • The server takes the site from the Origin header of the settings request.
  • It updates the last-seen time at most once per WIDGET_ORIGIN_TOUCH_SECONDS (300 s by default).
  • A page on the same host as https://api.tolkly.io sends no Origin, so it is not recorded.
  • A site the owner blocked is never shown there.

If the chat does not show

What you seeWhy, and what to do
No request to https://api.tolkly.io in the network panelThe browser got a page without the lines. Check the page source, the layout the site really uses and any page cache.
/widget answers {"refused": "origin_blocked"}The owner blocked this domain. They can unblock it in the project's «Install» section, block «Domains» (Domains).
/widget answers {"refused": "origin_not_allowed"}The project is in strict mode and this domain is not on its list. The owner can allow it (Domains).
/widget answers 404The project attribute does not match any project on this server.
The console reports a Content-Security-Policy violationThe site's policy blocks the widget. See Content-Security-Policy.
The console says tolkly: the identity token was refused (…)The chat works, but anonymously. See Signed visitor identity.

A refused site gets 200, not 403. So the page's console stays clean, and the widget simply draws nothing.

To hand the install to a coding agent, see AI agents.