# Install on a site

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

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](#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](https://tolkly.io/docs/en/widget-attributes.md)).

## 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](https://tolkly.io/docs/en/frameworks.md).

### 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](https://tolkly.io/docs/en/frameworks.md#typescript-and-jsx)).

## What loads on every page

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

| Request | When | Caching |
|---|---|---|
| `https://api.tolkly.io/widget.js` | every page | `no-cache` with an ETag: a repeat view gets a `304` with no body |
| `GET …/v1/projects/<project>/widget` | every page | the browser keeps it for `WIDGET_SETTINGS_MAX_AGE_SECONDS` (60 s by default) |
| `POST …/v1/projects/<project>/identify` | every page with `data-identity`, and on `identify()` | a `POST`, never cached ([identity](https://tolkly.io/docs/en/identity.md)) |
| `https://api.tolkly.io/widget/chat-<hash>.js` | when the visitor first reaches for the chat | named 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 see | Why, and what to do |
|---|---|
| No request to `https://api.tolkly.io` in the network panel | The 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](https://tolkly.io/docs/en/domains.md)). |
| `/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](https://tolkly.io/docs/en/domains.md)). |
| `/widget` answers `404` | The `project` attribute does not match any project on this server. |
| The console reports a Content-Security-Policy violation | The site's policy blocks the widget. See [Content-Security-Policy](https://tolkly.io/docs/en/csp.md). |
| The console says `tolkly: the identity token was refused (…)` | The chat works, but anonymously. See [Signed visitor identity](https://tolkly.io/docs/en/identity.md#rejected). |

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](https://tolkly.io/docs/en/agents.md).
