# Web SDK overview

> Embed a Runbear agent as a chat widget in your own web app with the Runbear React SDK, and pick how it authenticates.

Source: https://docs.runbear.io/api/web-sdk

Last updated: 2026-10-07

The **Runbear React SDK** (`@runbear-io/react`) embeds a Runbear agent as a chat widget inside your
own web app: a complete chat UI with streaming replies, conversation history, the agent's thinking
and tool progress, and clickable [response components](./web-sdk/response-components). The widget
talks to the same `/v1` API described in the [REST API overview](./overview).

The package bundles its own copy of React and has no peer dependencies, so it works in any web app,
React or not. You create a client, create a chat, and mount it into an element on your page.

> **Private package**
>
> The SDK is published to GitHub Packages, so installing it needs a GitHub token with
> `read:packages`. See [Install](./web-sdk/install), which also lists which features need which SDK
> version.
>
> The current release is **0.4.0**. Behavior marked **0.5.0** on these pages is coming in 0.5.0,
> which is not yet released; the pages say what 0.4.0 and earlier do instead.

## How the widget authenticates

A Runbear API key is an organization-level credential, so it must never be shipped in public
browser JavaScript. The SDK offers three modes; pick one with the `auth` option.

| Mode      | Where the credential lives                                 | Who relays the chat                                    | Use it when                                                                                      |
| --------- | ---------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `session` | A short-lived, thread-bound session pass your server mints | Nobody. The browser talks to `api.runbear.io` directly | Production: public and signed-in visitors                                                        |
| `proxy`   | Your server. The browser sends no `Authorization` header   | Your server relays every request                       | You must inspect, log or redact every turn, or you need file attachments or suggested follow-ups |
| `direct`  | The organization API key, in the browser                   | Nobody                                                 | Local prototyping and fully internal tools only                                                  |

Session mode needs `@runbear-io/react` 0.2.0 or newer. [Authentication](./web-sdk/authentication)
covers all three modes and the session endpoint you write for session mode.

## Quick start

This React component mounts the widget in session mode. `/api/runbear/session` is **your**
endpoint: it authenticates the visitor and mints the session pass with your API key.
[Your session endpoint](./web-sdk/authentication#your-session-endpoint) has its full
implementation.

```tsx
import Runbear, { SessionDeclinedError } from "@runbear-io/react"
import { useEffect, useRef } from "react"

export function AgentWidget() {
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!containerRef.current) return

    const client = new Runbear({
      assistantId: "0fade940-133f-49e6-bf4b-8f662186479b",
      auth: {
        mode: "session",
        fetchSessionToken: async ({ resumeToken, threadId, signal }) => {
          const res = await fetch("/api/runbear/session", {
            method: "POST",
            headers: { "content-type": "application/json" },
            credentials: "include", // your own session cookie
            body: JSON.stringify({ resumeToken, threadId }),
            signal,
          })
          if (res.status === 401 || res.status === 403) {
            // Your endpoint refused this visitor: end the session now.
            throw new SessionDeclinedError(`session refused: ${res.status}`)
          }
          // Anything else is treated as transient and retried with backoff.
          if (!res.ok) throw new Error(`session endpoint failed: ${res.status}`)
          return await res.json()
        },
      },
    })

    const chat = client.createChat()
    chat.mount(containerRef.current)

    return () => chat.destroy()
  }, [])

  return <div ref={containerRef} style={{ width: "100%", height: "100vh" }} />
}
```

Throw `SessionDeclinedError` when your endpoint refuses the visitor. It ends the session at once;
any other error is retried. See [Declining a session](./web-sdk/authentication#declining-a-session).

### Without React

The widget mounts imperatively, so a page without React uses the same calls. Run this in the
browser once the element exists:

```ts
import { Runbear, SessionDeclinedError } from "@runbear-io/react"

const client = new Runbear({
  assistantId: "0fade940-133f-49e6-bf4b-8f662186479b",
  auth: {
    mode: "session",
    fetchSessionToken: async ({ resumeToken, threadId, signal }) => {
      const res = await fetch("/api/runbear/session", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ resumeToken, threadId }),
        signal,
      })
      if (res.status === 401 || res.status === 403) {
        throw new SessionDeclinedError(`session refused: ${res.status}`)
      }
      if (!res.ok) throw new Error(`session endpoint failed: ${res.status}`)
      return await res.json()
    },
  },
})

const chat = client.createChat()
chat.on("containerReady", ({ threadId }) => console.log("chatting on", threadId))
// An empty element with a height, such as <div id="runbear-chat" style="height: 600px">
chat.mount("#runbear-chat")
```

The package is ESM-only, so load it through your bundler or another tool that resolves npm
packages.

## Limitations

- **One widget per page.** The widget renders into a fixed container id and keeps a module-level
  reference to its UI, so a second widget on the same page removes the first one's container.
- **Browser only.** Construct the client and call `mount()` in the browser: in a React effect, or
  after the page has loaded. Outside a browser `mount()` does nothing, so a server-side render shows
  no widget.
- **Mount into an empty element.** On 0.4.0 and earlier, the current release, mounting into an
  element that already has children clears it and renders nothing, and a second `mount()` throws
  "Chat widget is already mounted". Coming in 0.5.0 (not yet released), the widget removes the
  existing content, renders, and logs one console warning.
- **The headless client is not a public API.** The client's `api` property (`runbear.api.v1.*`) is
  what the widget uses internally. It is not a supported public surface and can change in any
  release; call the [REST API](./overview) from your server instead.

## Response components

An agent can attach a small interactive block to its reply: a `confirm` question, a `select` list or
a `card` of details. Response components are in limited availability and are enabled per
organization. The widget renders `confirm` and `select` from 0.3.0 and `card` from 0.4.0. See
[Response components](./web-sdk/response-components) for how the widget renders them, and the
[Response Components API](/api/web-sdk/response-components.md) for the per-agent setting.

## Reference

- [Install](./web-sdk/install): the registry, CI, CSP and version support
- [Authentication](./web-sdk/authentication): session, proxy and direct modes, and your session
  endpoint
- [Configuration](./web-sdk/configuration): every `config` option
- [Chat API](./web-sdk/chat-api): methods, events and the widget lifecycle
- [Theming](./web-sdk/theming): CSS variables, dark mode and class hooks
- [Response components](./web-sdk/response-components): `confirm`, `select` and `card`
- [Errors](./web-sdk/errors): session failures, rate limits and `RunbearApiError`
- [Types](./web-sdk/types): every exported type
- [Changelog](./web-sdk/changelog): what changed in each release

## Related

- [REST API overview](./overview)
- [Authentication and API keys](./api-keys): the key your session endpoint uses
- [Errors and limits](./errors-and-limits): rate limits shared by every visitor of a widget
- [OpenAPI reference](https://api.runbear.io/v1/docs): exact schemas for `POST /v1/sessions` and
  `POST /v1/sessions/refresh`
