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

# Data handling

> What is encrypted, what is plaintext, and what is never stored.

## Personal data is encrypted per workspace

Identity fields — email, name, company, phone, and any vendor token — are
encrypted at rest under a key belonging to your workspace alone. That key is
itself stored wrapped by a master key held only in the application environment,
so a dump of the key table is inert on its own.

The workspace key is minted on first use. Concurrent first writes re-read rather
than overwrite, because replacing a key would make everything already encrypted
under it permanently unreadable.

## Encrypted, but still searchable

Encrypted fields cannot be searched directly — the same address encrypts to
different bytes each time. Email is therefore also stored as a keyed hash, which
is what contact lookup and email search actually query.

This has a consequence worth knowing: when a visitor gives an address that
already belongs to a known contact, they are matched through that index rather
than recorded as a new lead. An existing customer arriving anonymously is not a
fresh prospect.

## What is plaintext, and why

| Field                       | Stored    | Reason                                                                        |
| --------------------------- | --------- | ----------------------------------------------------------------------------- |
| `plan` on a user            | Plaintext | A display name, not personal data. Truncated at 80 characters.                |
| `setContext` / `attributes` | Plaintext | Business context by design — seats, MRR, role. Sanitized and capped on write. |
| Publishable key             | Plaintext | Public by construction; it ships in your page.                                |

<Warning>
  Because `setContext` is plaintext, do not put personal data in it. Identity
  belongs in `identify` or the top-level fields of `/v1/users`, which are
  encrypted.
</Warning>

## What is never stored

* **Raw IP addresses.** IPs are used to bucket rate limits and to mint a visitor
  contact for sites with no login. They are hashed first; the raw address is not
  kept.
* **Recoverable secret keys.** Only a hash.
* **Recoverable vendor tokens in history.** The live token is stored encrypted;
  token history keeps a fingerprint and a status only.
* **MCP tool arguments.** The audit trail records which capability was called, by
  what method, with what outcome — never the arguments.

## What is never logged

`/v1/users` logs nothing from its payload — not the token, not the hash, not the
email. On failure it logs the error *shape* only, because database error
messages can echo the offending row, which for that table means ciphertext at
best and the plaintext body at worst.

Billing webhook payloads are likewise never logged, since they carry billing
detail.

## Identity is never an authorization key

Anything the browser asserts about who the user is — `contact`,
`externalUserId` — is **enrichment only**. It cannot resolve a workspace, grant
access, or read another person's data.

The clearest example is the widget's session-history command: identity is
anchored server-side from the session id or the unguessable visitor id, and the
client-asserted `externalUserId` is deliberately not trusted as an authorization
key, so a page cannot read someone else's history by asserting their id.

The workspace always comes from the validated key, never from the request body.
That is the tenant boundary.

## Vendor tokens never reach the browser

A vendor token is pushed server-to-server through
[`/v1/users`](/api/server/users/push-a-user), stored encrypted, and used server-side.
There is no browser-side equivalent: anything in the DOM is reachable by any
script on the page, including a prompt-injection payload.

A freshly pushed token is marked valid but unverified, so it is re-checked
against your API on the next agent request — which is what stops a token you
have already revoked staying valid inside Peeve.

## Grounding is data, not instructions

The agent receives several kinds of external content each turn: your product map,
retrieved knowledge, customer context from connectors, and the names and
descriptions of any MCP tools you have connected.

All of it is treated as **reference data, never as instructions**. Text embedded
in a page, a tool description or a connector result cannot direct the agent's
behaviour. In particular, no instruction from any of those sources can waive the
confirm-before-write rule — that is enforced in code, on the server, after the
model has produced its plan.

On any conflict between retrieved knowledge and what the live page shows, the
page wins.

## Attachments

Files sent in chat go to private storage, scoped by workspace and session. They
are served through short-lived signed URLs valid for one hour, minted per
request — so a cross-site `<img>` can display a private image without any
storage credential reaching the browser.

Limits: 10 MB, images and common document types only.

## Transcript retention

Each workspace has a transcript policy governing when a session transcript is
kept and attached to a hand-off — on request (the default), always, or never.
Set it in **Settings → Transcripts & data**, alongside the retention window.
