> ## 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.

# Chat for logged-in users

> Connect a browser chat to a customer identity through your backend

Use server mode when chat history must follow a signed-in customer across devices. The browser never receives your account-wide API-Token. Your backend authenticates the customer, finds or creates their active chat session, and mints a short-lived user stream token.

Server mode works with **Anonymous usage** switched off, is not checked against allowed websites, and is not subject to message limits or the daily cap, and nothing is counted. History loads still have generous abuse throttles. See [Security and limits](/website-chat/security#logged-in-users-server-mode).

## What the browser calls

The browser calls only your own token endpoint and then, with the token your backend returned, the Osvi AI chat history load and the live WebSocket. The token is valid for one session and the user stream only (about 5 minutes); the SDK refreshes it through `getToken()`. It never holds your API-Token.

## Session flow

1. The browser calls your backend's token endpoint.
2. For a normal start, your backend calls Rails `GET /v1/chat/session/active?customer_id=…&agent_uuid=…` for the authenticated customer and agent.
3. If Rails returns 404, create a session with `POST /v1/chat/session` using your server-held API-Token. When the SDK calls `getToken({ newSession: true })`, skip the active-session lookup and create a fresh session even if one already exists.
4. Request `POST /v1/chat/session/{id}/ws_token`. Take `sessionId` from the active-session or create-session response; the token response supplies `token`, `ws_url`, and `expires_at`, which your backend returns as `token`, `wsUrl`, and `expiresAt`.
5. The browser SDK connects to the returned `wsUrl`.

History follows the customer while their session is active. After a period of inactivity the session goes dormant, and the active-session lookup in step 2 no longer returns it, so your backend starts a new session. To keep older history visible, read the customer's past sessions server-side and present them yourself.

```ts theme={null}
const chat = createChatClient({
  agentId: "agent_yourAgentId",
  auth: {
    mode: "server",
    getToken: async ({ newSession } = {}) => {
      const response = await fetch("/api/chat/token", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ newSession: Boolean(newSession) })
      });
      if (!response.ok) throw new Error("Unable to start chat");
      return response.json();
    }
  }
});
await chat.start();
```

The callback returns `{ sessionId, token, wsUrl, expiresAt }`. It may receive `{ newSession: true }` when the visitor selects New chat; your backend must then create a fresh session instead of reusing the active one.

## Node backend sketch

```js theme={null}
app.post("/api/chat/token", requireUser, async (req, res) => {
  const customerId = String(req.user.id);
  let session;

  if (!req.body.newSession) {
    const active = await rails.get(`/v1/chat/session/active?customer_id=${encodeURIComponent(customerId)}&agent_uuid=${encodeURIComponent(process.env.OSVI_AGENT_ID)}`);
    if (active.ok) session = active.data.data;
    else if (active.status !== 404) return res.sendStatus(active.status);
  }

  if (!session) {
    const created = await rails.post("/v1/chat/session", {
      agent_uuid: process.env.OSVI_AGENT_ID,
      customer_id: customerId
    });
    if (!created.ok) return res.sendStatus(created.status);
    session = created.data.data;
  }
  const stream = await rails.post(`/v1/chat/session/${session.id}/ws_token`, { stream: "user" });
  if (!stream.ok) return res.sendStatus(stream.status);
  res.json({ sessionId: session.id, token: stream.data.data.token, wsUrl: stream.data.data.ws_url, expiresAt: stream.data.data.expires_at });
});
```

The `rails` client in these sketches is an authenticated server-side HTTP client that returns the Rails status and JSON envelope. Keep its API-Token on the server; never expose it to browser code.

## Python backend sketch

```python theme={null}
class TokenRequest(BaseModel):
    newSession: bool = False

@app.post("/api/chat/token")
async def chat_token(body: TokenRequest, user: User = Depends(current_user)):
    session = None
    if not body.newSession:
        response = await rails.get_active_session(
            customer_id=str(user.id), agent_uuid=settings.osvi_agent_id
        )
        if response.status_code == 200:
            session = response.json()["data"]
        elif response.status_code != 404:
            raise HTTPException(response.status_code)

    if session is None:
        response = await rails.create_session(
            agent_uuid=settings.osvi_agent_id,
            customer_id=str(user.id),
        )
        response.raise_for_status()
        session = response.json()["data"]
    token_response = await rails.create_ws_token(session["id"], stream="user")
    token_response.raise_for_status()
    stream = token_response.json()["data"]
    return {
        "sessionId": session["id"],
        "token": stream["token"],
        "wsUrl": stream["ws_url"],
        "expiresAt": stream["expires_at"],
    }
```

The `rails` client represents an authenticated server-side HTTP client. Preserve the Rails status and JSON envelope handling in your implementation.

Welcome carousel images also load for logged-in chats when Anonymous usage is off.

## Identity and rotation

A server-mode client belongs to one identity. Destroy it and create a new client when the user logs in, logs out, or switches accounts. The SDK stores no token, session id, or visitor id in browser storage in server mode. It asks `getToken()` again on startup, token rotation (about every 4.5 minutes while connected), reconnect after an unauthorized close, and when a new session is needed.

Phone-shaped customer ids are normalized to `+91…`. Do not use the public visitor format (`web:` followed by 16–64 URL-safe characters) as a server-mode `customer_id`; Rails rejects it with `422 invalid_customer_id` because it would be addressable on the public rail.


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