> ## Documentation Index
> Fetch the complete documentation index at: https://docs.osvi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Security and limits

> Who can start a chat, which limits apply, and what the browser holds and sees

What controls access to a website chat, what is exposed to the browser and which limits apply. Read it before you switch on Anonymous usage for a live site. The settings themselves are described in [Dashboard settings](/website-chat/dashboard-settings); deployment steps are in [Production setup](/website-chat/production).

## At a glance

| | Anonymous visitors (widget or share link) | Logged-in users (server mode) |
| - | - | - |
| Who can start a chat | Anyone on an allowed website, once **Anonymous usage** is on | Only users your backend authenticates and mints a token for |
| Needs Anonymous usage switched on | Yes | No |
| What the browser holds | Agent ID (public), a visitor ID and session hint in `localStorage`, a short-lived stream token | A short-lived stream token for one session. Nothing persistent |
| What the token allows | Read and write that one session's user stream | The same, for one session only |
| Never leaves your server | Not applicable. There is no account credential in the browser | Your API-Token |
| Limits | 20 messages per minute per conversation, 60 per hour per visitor IP per agent, a daily conversation cap per agent, an optional message cap per conversation | None. Logged-in chats are never counted |
| Allowed websites checked | Yes | No |

## Who can start a chat

Three things decide whether an anonymous visitor can chat:

1. **Anonymous usage** must be on. It is off by default for every agent, a duplicated agent starts with it off, and it also gates the [share link](/platform/public-chat). Agents that were reachable publicly before SDK 0.1.2 were switched off when it shipped.
2. The page must be on an **allowed website**. The browser sets the `Origin` header and the Osvi AI API checks it against the list. This stops other websites from embedding your agent in a browser. It does **not** stop scripts or bots: a non-browser client can omit or forge `Origin`. The list is an embed control, not authentication; the [limits](#limits) are what bound a script. The share link is not checked against the list.
3. The agent must be active.

A wildcard entry trusts every matching subdomain, including one taken over through a dangling DNS record; prefer exact entries for sensitive agents. The entry rules are in [Allowed websites](/website-chat/dashboard-settings#allowed-websites).

Logged-in chats need none of the three: your backend decides who can chat.

## Anonymous visitors

* **The agent ID is public by design.** It is in your page source. It identifies which agent to talk to; it is not a secret and does not grant anything on its own.
* **The visitor ID** is generated in the visitor's browser and stored in `localStorage`. It is the key to that visitor's own history. Anyone with access to that browser profile can read that history, and clearing site storage starts a fresh chat.
* **Sessions start on the first message**, not when the panel opens. Opening the chat creates no session and no database record.
* **What a visitor can send.** Messages, and answers to buttons, cards and date pickers the agent showed. An anonymous visitor cannot inject context, system variables or state; those are only available through your server-side API.

## Logged-in users (server mode)

* Your **API-Token stays on your backend**. The browser receives a single-session, user-stream-only token valid for about 5 minutes. The SDK asks your `getToken()` again about every 4.5 minutes while connected.
* The browser then calls the history load and the live WebSocket with that token. These calls work with Anonymous usage off, are not subject to the anonymous limits, and are not checked against allowed websites.
* **Your token endpoint is your security boundary.** Authenticate the user, rate-limit the endpoint, and never mint a token for a customer ID taken from the request. Derive it from the signed-in user.
* **Customer ID rules.** Do not use the public visitor format (`web:` followed by 16-64 URL-safe characters); Rails rejects it with `422 invalid_customer_id`. Phone-shaped IDs are normalised to `+91…`.
* **Destroy the client** when the user logs out or switches accounts, and create a new one for the next identity. See [Logged-in users](/website-chat/server-mode).

## Limits

Limits apply to anonymous chats only. Logged-in chats are never counted.

| Limit | Value | When it is reached | Recovery |
| - | - | - | - |
| Per conversation | 20 messages per minute | The message is not sent and the widget shows a countdown | Retried automatically up to three times, then shown as not sent with Retry |
| Per visitor IP, per agent | 60 messages per hour | Same as above, with the time until the window resets | Same automatic retry; the hour window resets later. Shared office or proxy IPs share this limit |
| Daily cap, per agent | 1,000 conversations per day by default; editable from 1 to 100,000 in [Conversations per day](/website-chat/dashboard-settings#conversations-per-day). Resets at 00:00 UTC | A new conversation's first message is not sent and the widget shows "This chat is busy right now. Please try again later." Conversations already started that day keep working | The composer re-enables after up to 5 minutes so visitors can try again; new conversations are accepted again after the reset |
| Messages per conversation (optional) | Off by default; when on, 1 to 10,000 messages per conversation, set in [Messages per conversation](/website-chat/dashboard-settings#messages-per-conversation) | The message is not sent and the widget shows "This conversation has reached its message limit. Start a new chat to continue." with a **New chat** button | Starting a new chat. The new conversation counts toward the daily cap |
| Page-load check | 6,000 per hour per IP | The check fails open: the launcher still shows | None needed |
| Session start | 60 per hour per IP | The widget shows a rate-limit message | Wait and retry; honor `Retry-After` |

**What counts.** For the per-minute, per-IP and per-conversation limits, each visitor message and each button or card choice counts once. For the daily cap, a conversation counts once per day (UTC), on its first accepted message; opening the widget, or a conversation that never sends a message, does not count. A conversation that carries on past midnight counts again on its first message of the new day. Agent replies, tokens, follow-ups and nudges never count. A message that is retried, resent after a reconnect or rejected for a busy agent is not counted twice.

**Where you see usage.** [Usage today](/website-chat/dashboard-settings#usage-today) shows today's conversation count against the daily cap. The first time an agent reaches its daily cap on a given day, Osvi AI writes a `public_chat_daily_cap_hit` line to its server logs with the agent, account and limit, for its own monitoring; it is not shown in the dashboard.

**Sizing.** Estimate expected conversations per day, add headroom, and set the daily cap just above it. A cap far above real traffic gives an attacker more room; a cap below it locks out real visitors. The daily cap alone does not bound the length of a conversation: a single conversation can keep sending up to the per-minute and per-IP limits. If cost per conversation matters, also turn on the per-conversation message limit, set well above a normal conversation (support chats typically run 5 to 20 messages).

## Abuse and availability

Because anonymous chat needs no login, someone can deliberately use up an agent's daily cap and make chat unavailable until 00:00 UTC. The per-IP limits (60 messages and 60 new conversations per hour) and the per-conversation limits slow a single source, but a distributed attacker can still reach a cap.

What you can do:

* Keep the allowed websites list tight.
* Size the daily cap to real traffic, and watch the usage counter.
* Turn on the per-conversation message limit to bound what one conversation can cost.
* Turn **Anonymous usage** off if an agent is under attack.
* Use [server mode](/website-chat/server-mode) for high-value flows. Logged-in chats are not affected by the daily cap.

## What the browser holds and sees

* **Visible:** agent replies, team replies after a human takes over, and the buttons, cards and date pickers the agent shows.
* **Never visible:** internal operator coaching (whispers), other sessions, account data, tool credentials and error internals.
* **Images** in carousels and the welcome message are fetched by Osvi AI's image proxy. The visitor's browser never contacts your image host. Only public HTTPS hosts are fetched, with size and time limits.
* **The welcome message** is shown from the agent's settings before any session exists: no model call is made and nothing is stored until the visitor replies.
* **The stream token** travels in the WebSocket query string, so it may appear in access logs of proxies you operate. Limit log access and retention, and never write the token into your own logs or analytics.
* **Anonymous mode** stores a visitor ID (`osvi.chat.visitor`), a session hint (`osvi.chat.session.<agentId>`) and small UI markers in `localStorage`. See [Browser storage](/website-chat/configuration#browser-storage).
* **Server mode** writes nothing persistent to the browser.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.