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

# Quickstart

> Install the widget and make one authenticated API call.

Two things to get working: the widget on your pages, and one server-to-server
call. Fifteen minutes.

## 1. Get your keys

In the Peeve dashboard, open **Settings → API keys**. Create a publishable key
and a secret key.

<Warning>
  Both are shown once. Copy them now. The secret key is stored hashed and
  cannot be retrieved later — if you lose it, rotate it.
</Warning>

## 2. Set your domain

Open **Settings → Workspace** and set your domain (for example
`app.example.com`).

This is what makes your publishable key safe to publish. In production, Peeve
compares the request `Origin` against this domain and rejects anything else.
Without a domain set, a production key only works from `localhost`.

See [Origins](/authentication/origins) for the exact rule, including the
www-insensitive match.

## 3. Install the widget

Drop the script tag on every page you want the agent to work on.

```html theme={null}
<script
  src="https://cdn.peeve.ai/widget.js"
  data-publishable-key="pk_live_xxx"
  defer
></script>
```

That is the whole install. The tag auto-boots — no `init()` call needed.

<Note>
  Nothing appears yet. The widget stays hidden for end users until your app has
  been mapped for the first time, so a real visitor never meets an unmapped
  cursor. Run the baseline capture from the dashboard to map it.
</Note>

If you prefer a package to a script tag:

```bash theme={null}
npm i @peeve/sdk
```

```js theme={null}
import { Peeve } from "@peeve/sdk";

Peeve.init({ publishableKey: "pk_live_xxx" });
```

`Peeve.init` injects the same script tag with the same key, and is idempotent —
if you already have the tag on the page, it will not add a second one.

## 4. Tell Peeve who the user is

Once someone signs in, identify them. This is what turns an anonymous visitor
into a named contact, and it is what lets the agent give plan-aware answers.

```js theme={null}
window.peeve.identify({
  id: "user_8412",
  email: "dana@example.com",
  name: "Dana Whitfield",
  plan: "Growth",
});

window.peeve.setContext({ seats: 12, role: "admin" });
```

`identify` carries modelled identity and is encrypted at rest. `setContext` is
an open key/value bag for business context — plaintext, non-secret. Put
identity in the first, everything else in the second. See
[Identity and context](/widget/identity-and-context).

## 5. Make one server call

Push a user from your backend. This one uses your **secret** key, so it must
run on a server.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.peeve.ai/v1/users \
    -H "Authorization: Bearer sk_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "external_user_id": "user_8412",
      "email": "dana@example.com",
      "name": "Dana Whitfield",
      "plan": "Growth"
    }'
  ```

  ```js Node theme={null}
  const res = await fetch("https://api.peeve.ai/v1/users", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PEEVE_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      external_user_id: "user_8412",
      email: "dana@example.com",
      name: "Dana Whitfield",
      plan: "Growth",
    }),
  });

  const user = await res.json();
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://api.peeve.ai/v1/users",
      headers={"Authorization": f"Bearer {os.environ['PEEVE_SECRET_KEY']}"},
      json={
          "external_user_id": "user_8412",
          "email": "dana@example.com",
          "name": "Dana Whitfield",
          "plan": "Growth",
      },
  )
  user = res.json()
  ```
</CodeGroup>

A successful response:

```json theme={null}
{
  "id": "00000000-0000-4000-8000-000000000000",
  "external_user_id": "user_8412",
  "workspace": "example-corp"
}
```

<Warning>
  If this returns `401 { "error": "a secret key (sk_*) is required" }`, you sent
  a publishable key. The endpoint rejects `pk_…` outright and emits no CORS
  headers — it is not callable from a browser, by construction.
</Warning>

## What to read next

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/authentication/overview">
    The full key and origin model.
  </Card>

  <Card title="Widget JavaScript API" icon="code" href="/widget/javascript-api">
    Every method the widget exposes on the page.
  </Card>

  <Card title="MCP server" icon="plug" href="/mcp/overview">
    Let Claude, ChatGPT or Cursor discover your product.
  </Card>

  <Card title="Rate limits" icon="gauge" href="/reference/rate-limits">
    The per-plan ceilings, and what happens when you hit one.
  </Card>
</CardGroup>
