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

# Add your Agent to a website

> Publish a secure browser voice agent with one script tag and no customer backend.

The hosted website widget connects visitors to a saved PyAI Agent without
putting an API key in your site or requiring you to run a token server.

## Publish

1. Open **Agents**, choose an Agent, then open **Website & phone**.
2. Add every exact website origin that may host the widget, for example
   `https://www.example.com`. Production origins must use HTTPS.
3. Choose the launcher, position, colors, labels, and branding.
4. Publish and copy the generated snippet:

```html theme={null}
<script src="https://cdn.pyai.com/widget/v11/pyai-widget.js"
  data-widget="wdgt_public_x" async></script>
```

The widget id is an opaque public identifier. It is not an organization,
project, Agent, or key id.

## Security model

On page load, v11 resolves only safe display configuration. When a visitor
starts a call, PyAI checks the exact browser `Origin`, the widget and Agent
status, account credit, and daily widget/IP limits. It then mints one
short-lived key with only `omni:session`, concurrency `1`, the visitor origin,
and the Agent as its session label. The browser never receives a long-lived
credential.

If recording is enabled on the Agent, the widget shows the Agent's consent
notice before requesting microphone permission.

## Runtime errors

Listen for `pyai:widget-error`. Its `detail` is a stable, sanitized object:

```js theme={null}
window.addEventListener("pyai:widget-error", ({ detail }) => {
  console.log(detail.code, detail.message, detail.retryable, detail.request_id);
});
```

Supported codes are `mic_permission_denied`, `unsupported_browser`,
`origin_not_allowed`, `credit_exhausted`, `daily_cap_exceeded`,
`session_unavailable`, `websocket_failed`, `config_unavailable`, and
`consent_required`. Error events never include session tokens, internal
identifiers, upstream responses, or visitor addresses.

Listen for `pyai:widget:transcript` to receive the same sanitized transcript
rows shown in the widget. The current public capability is
`capabilities.transcript: "caller"`: production emits caller text only, so the
widget never fabricates Agent text from audio. `detail.version` is `1`; its
fields are `role`, `text`, and `final`. Partial caller updates replace one open
row and freeze once at the response boundary. A future runtime can advertise a
broader transcript capability without changing this versioned event envelope.

Listen for `pyai:widget:state` to receive semantic `listening`, `thinking`, and
`agent_speaking` transitions derived from session controls and response audio.
Neither event nor the UI exposes raw frames, tool arguments, tokens, or
identifiers.

For a strict Content Security Policy, allow the versioned script from
`https://cdn.pyai.com`, API requests to `https://api.pyai.com`, and voice
WebSockets to `wss://api.pyai.com`. The runtime does not evaluate strings as
code or inject customer-provided HTML. If your policy requires nonces for
inline styles, put a nonce on the script element; v11 copies it to its style
element.

## Opening audio and older embeds

Widget v11 keeps the consent line and turn-0 greeting isolated from browser
speaker echo. It buffers server PCM until the playback graph is running, sends
real-time digital silence instead of microphone/self-audio through the opening
drain, and restores normal barge-in on later replies.

Versioned widget URLs are immutable. Existing v1-v10 embeds do not update in
place. Replace their script URL with `/widget/v11/pyai-widget.js`; v11 is the
certified, published widget (since 2026-08-24).
v11 retains protected opening and browser-valid closes,
and narrowly normalizes the deployed playback-position advisory while older
engine revisions drain. If you operate your own session broker or media UI, follow the
[browser voice agent guide](/guides/browser-voice-agent) and mint a short-lived,
origin-locked session token with `POST /v1/omni/sessions`. Never put a secret
key in browser code.

The hosted widget supports the same safe declarative launcher variants and URL,
telephone, and event actions. It never evaluates custom JavaScript or HTML.


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