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

# JavaScript API

> The methods the widget exposes on the page, and the ones the npm package exposes.

There are two objects, and they are not the same thing.

<CardGroup cols={2}>
  <Card title="window.peeve" icon="window">
    The **running widget**, lowercase. Available once `widget.js` has booted.
    This is what the script tag gives you.
  </Card>

  <Card title="Peeve" icon="box">
    The **npm package export**, capital. `import { Peeve } from "@peeve/sdk"`.
    It boots the widget and forwards calls to it.
  </Card>
</CardGroup>

Use whichever matches how you installed. If you use the package, prefer `Peeve` —
it queues calls made before the widget has loaded, which `window.peeve` cannot
do because it does not exist yet.

## `window.peeve`

### `identify(user)`

Record who the signed-in user is.

```js theme={null}
window.peeve.identify({
  id: "user_8412",
  email: "dana@example.com",
  name: "Dana Whitfield",
  company: "Example Corp",
  plan: "Growth",
  phone: "+44 20 7946 0000",
  createdAt: "2026-02-14T09:31:00Z",
});
```

| Field       | Notes                                                                                                                        |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `id`        | Your own id for this user. Becomes `externalUserId` on the wire.                                                             |
| `email`     | Encrypted at rest.                                                                                                           |
| `name`      | Encrypted at rest.                                                                                                           |
| `company`   | Encrypted at rest.                                                                                                           |
| `phone`     | Encrypted at rest.                                                                                                           |
| `plan`      | The user's plan in **your** product, as a display name. Plaintext — not PII, and it does not affect your Peeve entitlements. |
| `createdAt` | When the account was created in your product. Distinguishes a genuine new signup from a returning user browsing logged out.  |

Extra keys are accepted, but business context belongs in `setContext`, which is
built for it.

Returns the widget, so calls chain.

### `setContext(ctx)`

Attach any business context to the current user, as open key/value.

```js theme={null}
window.peeve.setContext({
  seats: 12,
  mrr: 4800,
  role: "admin",
  lifecycle: "trial",
});
```

Merged across calls — the last value for a key wins. Plaintext and non-secret.
See [Identity and context](/widget/identity-and-context) for what goes where.

### `open()`

Open the ask panel programmatically, for your own "Get help" button.

```js theme={null}
document.querySelector("#help").addEventListener("click", () => {
  window.peeve.open();
});
```

Boots the widget first if it has not started. Focuses the panel if it is already
open.

### `init(options)`

Mount the widget manually. You do not normally need this — the script tag
auto-boots — but it lets you pass options that have no script attribute.

```js theme={null}
window.peeve.init({
  workspaceKey: "pk_live_xxx",
  name: "Guide",
  position: "bottom-left",
  brandAccent: "#B8FF00",
  suggestions: ["Change my plan", "Invite a teammate"],
});
```

Idempotent: calling it after auto-boot does nothing, so it cannot double-mount.
Script tag options underlie programmatic ones field by field, so a bare tag plus
a partial `init()` keeps both.

Useful options:

| Option           | Purpose                                                                       |
| ---------------- | ----------------------------------------------------------------------------- |
| `workspaceKey`   | Publishable key, if not on the tag.                                           |
| `endpoint`       | Override the API origin.                                                      |
| `name`           | The agent's display name.                                                     |
| `position`       | `bottom-right`, `bottom-left`, `top-right`, `top-left`.                       |
| `edge`           | Base edge offset, in pixels.                                                  |
| `brandAccent`    | Accent colour for the launcher and cursor.                                    |
| `logo`           | White-label logo tile.                                                        |
| `suggestions`    | Starter chips for the ask panel.                                              |
| `lang`           | Locale hint.                                                                  |
| `attachments`    | Allow file attachments in chat. Default off.                                  |
| `dictation`      | Show the dictation mic. Default on.                                           |
| `emoji`          | Show the emoji picker in live chat. Default on.                               |
| `voiceEnabled`   | Narrate spoken lines. Default off.                                            |
| `responseWindow` | The stated reply window on an offline hand-off. Default "within a few hours". |
| `brandName`      | Your company name, used in the badge explainer.                               |
| `maskSelectors`  | Selectors you mask, named in the badge's "It never sees" list.                |

<Warning>
  Several flags are **server-decided and cannot be turned on from the page** —
  your page can only turn them off. `booking`, `liveChatEnabled`,
  `escalationEnabled`, `interventionEnabled` and `npsEnabled` come from
  the server, because they depend on your plan, your connectors and whether a
  teammate is actually available. Setting `booking: true` locally will not make
  the slot picker appear.
</Warning>

### `state`

A read-only snapshot, useful when debugging.

```js theme={null}
const { identity, context, config } = window.peeve.state;
```

### `destroy()`

Tear the widget down and remove it from the page.

## `Peeve` (npm package)

### `Peeve.init({ publishableKey, name? })`

Boot the widget. Throws if `publishableKey` is missing. No-op during SSR.

```js theme={null}
Peeve.init({ publishableKey: process.env.NEXT_PUBLIC_PEEVE_PUBLISHABLE_KEY });
```

### `Peeve.identify(userIdOrTraits, traits?)`

Two call shapes, both equivalent:

```js theme={null}
Peeve.identify({ userId: "user_8412", email: "dana@example.com" });
Peeve.identify("user_8412", { email: "dana@example.com" });
```

Note the field is `userId` here, where the widget's own `identify` uses `id`.
The package maps between them.

Accepts `userId`, `email`, `name`, `company`, `phone`, `plan`, `createdAt`.
`createdAt` may be an ISO string or epoch milliseconds — it is normalised for
you.

Calls made before the widget has loaded are queued and flushed when it appears
(the package polls for about 20 seconds, then gives up).

### `Peeve.setContext(ctx)`

```js theme={null}
Peeve.setContext({ seats: 12, role: "admin" });
```

Merged across calls. SSR-safe.

### `Peeve.reset()`

Clear the identity and context. **Call this on logout** — otherwise the next
person to use that browser inherits the previous user's identity.

```js theme={null}
async function logout() {
  await api.logout();
  Peeve.reset();
}
```

<Note>
  `window.peeve` has no `reset()`. From the raw widget, clear identity by
  calling the underlying `identify(null)`; if you are managing sessions, use the
  package, where `reset()` is the supported path.
</Note>

## Framework examples

<CodeGroup>
  ```jsx React theme={null}
  import { useEffect } from "react";
  import { Peeve } from "@peeve/sdk";

  export function PeeveProvider({ user, children }) {
    useEffect(() => {
      Peeve.init({ publishableKey: process.env.NEXT_PUBLIC_PEEVE_PUBLISHABLE_KEY });
    }, []);

    useEffect(() => {
      if (!user) {
        Peeve.reset();
        return;
      }
      Peeve.identify({
        userId: user.id,
        email: user.email,
        name: user.name,
        plan: user.plan,
        createdAt: user.createdAt,
      });
      Peeve.setContext({ seats: user.seats, role: user.role });
    }, [user]);

    return children;
  }
  ```

  ```vue Vue theme={null}
  <script setup>
  import { onMounted, watch } from "vue";
  import { Peeve } from "@peeve/sdk";

  const props = defineProps({ user: Object });

  onMounted(() => {
    Peeve.init({ publishableKey: import.meta.env.VITE_PEEVE_PUBLISHABLE_KEY });
  });

  watch(
    () => props.user,
    (user) => {
      if (!user) return Peeve.reset();
      Peeve.identify({ userId: user.id, email: user.email, plan: user.plan });
    },
    { immediate: true }
  );
  </script>
  ```

  ```js Plain JS theme={null}
  // The script tag already booted the widget.
  if (currentUser) {
    window.peeve.identify({
      id: currentUser.id,
      email: currentUser.email,
      plan: currentUser.plan,
    });
    window.peeve.setContext({ seats: currentUser.seats });
  }
  ```
</CodeGroup>
