> ## 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 **Connect → Website**.
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/v7/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, v7 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; v7 copies it to its style
element.

## Advanced integration

Use widget v3 when you operate your own session broker and need runtime
configuration from your application. Your backend must call
`POST /v1/omni/sessions`; do not use publishable or secret keys in browser
voice code.

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