# Connect a website to Context Assistant

## Public chat: one script and two keys

Public catalogs and help content need no customer token endpoint. Add website origins in assistant setup, approve public access to its knowledge, and select "Get both keys".

- `ca_pk_...`: the public identifier of one assistant; browser-visible.
- `ca_sk_...`: the workspace synchronization secret with `knowledge:read`, `knowledge:write`, `assistants:read`. Server-only import, updates and status checks; it cannot issue private embed tokens.
- Save both keys and Assistant ID in `.env` and ignore the file in git. Do not share the secret, put it in AI conversations or expose it to browser code. On WordPress use server-side plugin settings instead of .env.

```dotenv
CONTEXT_ASSISTANT_API_URL=https://api.contextassistant.io
CONTEXT_ASSISTANT_ID=YOUR_ASSISTANT_ID
CONTEXT_ASSISTANT_PUBLIC_KEY=YOUR_PUBLIC_KEY
CONTEXT_ASSISTANT_SYNC_KEY=SET_PRIVATELY_ON_SERVER
```

```html
<script async src="https://contextassistant.io/assets/embed.js"
  data-public-key="YOUR_PUBLIC_KEY"
  referrerpolicy="no-referrer"></script>
```

The script auto-mounts the widget. Do not add a second `mount()`, a local SDK copy or a separate loader. Choose Lite/Advanced and greeting settings in the cabinet. Compatible widget updates arrive from our website when a page loads.

### React

For chat throughout the app, add the script once in the shared layout. No Context Assistant packages or customer token endpoint are needed.

If the widget must mount and unmount with a component, use [react-widget.tsx](https://contextassistant.io/docs/examples/react-widget.tsx) and pass `publicKey` explicitly:

```tsx
<ContextAssistantWidget
  publicKey="YOUR_PUBLIC_KEY"
  page={{ type: "product", id: 184 }}
/>
```

Choose either the component or the automatic script. The component loads the same file without `data-public-key`, calls `ContextAssistant.mount({apiUrl:'https://api.contextassistant.io',publicKey:'YOUR_PUBLIC_KEY'})` and calls `destroy()` on unmount. Update `page` on navigation. Omitting `publicKey` in this compatibility example selects the private server-token flow, so public chat must pass the key.

### Custom interface: JavaScript API and SDK

Use the script for the ready-made chat. For your own interface, get a guest session through `POST /v1/embed/public-token` and call the Conversations API with its temporary token. Your client must handle SSE, token expiry, renewal with `visitorToken` and `Retry-After`. The hosted widget already handles this.

Native SDKs also accept `publicKey`, but packages are not on public npm. Their examples are for projects with separately supplied access; installing an SDK is not required to connect the widget.

### Sessions, content and security

CA issues guest sessions and renews temporary access tokens. Exact allowed origins are required; empty lists deny public chat. To resume guest history the SDK stores a signed session identifier in site localStorage. This is not a permanent API key; do not copy or publish it. Without storage the session is kept in page memory.

A public key is not user authentication. Origin checks restrict browser embedding, not forged server requests. CA limits bootstrap, IP and workspace traffic. Revoke a public key in Developers without changing existing private keys.

Public mode never calls Remote Tools or exposes other visitors' histories. All knowledge of this assistant must be suitable for public answers. Personal orders, private documents and actions require the server-authenticated scheme below. Existing integrations are never made public automatically.

For page facts use `window.ContextAssistantWidget` or event.detail.widget from context-assistant:ready, then setContext({page:{type:'product',id:184}}). Do not send the full DOM or personal data. Import the catalog separately using the sync secret. Knowledge cards require no Remote Tools; images are optional.

In the updated WordPress plugin set Public chat key, Secret synchronization key and Assistant ID. API URL defaults to `https://api.contextassistant.io/`. An empty public_key preserves the existing connection path. Saved API URLs and keys are not overwritten.

Guest exchange contract: [POST /v1/embed/public-token](https://contextassistant.io/docs/api.en.md#api-public-token).
Claude's knowledge tasks reuse the wizard-issued sync secret and ask you to approve the plan with "Start work".

## Private integration and compatibility

The remaining guide describes the existing server-authenticated flow for signed-in users and Remote Tools. Public chat does not need it. Existing keys and getToken callbacks remain supported with unchanged permissions.

This is the implementation reference for developers and Claude Code. Context
Assistant runs as a hosted service. Implement the integration in the website
project using the public API and client widget. The website's server issues
temporary tokens, and the widget displays the chat to visitors.

HTTP reference with request fields, responses, key scopes and limits:
https://contextassistant.io/docs/api.en.md . It covers tokens, conversations,
knowledge document upload, updates and recovery.

Claude offers three tasks: "Integration", "Populate the knowledge base"
(plan, then upload after approval), and "Keep knowledge up to date"
(plan, then implement synchronization after approval). Select a task above the
prompt in the docs or cabinet. Both knowledge tasks end the plan with an invitation
to reply "Start work". This approves the plan and begins implementation; enabling
scheduled runs requires separate approval after verification.

## 1. What the customer supplies

- Create an account at https://contextassistant.io/account/ and verify email.
- Create/select the assistant. Copy its **Public ID** (usually `asst_...`).
- In assistant settings, add exact allowed origins: for example
  `http://localhost:3000` and `https://shop.example`. No path or trailing slash;
  localhost, 127.0.0.1, different ports and www are different origins.
- Create a server key with `embed:token` access in Developers. Set its value
  yourself in the server's secret store. Never paste it into an AI conversation.
- Decide whether the chat is public or requires login, and which page facts
  may be sent. A client must not choose an arbitrary user ID.

API origin for widget options: `https://api.contextassistant.io` (no `/v1`).
REST endpoint prefix: `https://api.contextassistant.io/v1`.
Do not use `/api/core` or `/api/embed-token` from our marketing site: those
routes belong to that site and do not authenticate a customer's integration.

## 2. Obtain the client widget

The browser bundle is available at:
https://contextassistant.io/assets/embed.js

It exposes `window.ContextAssistant.mount()`. Load the script explicitly,
then call `mount`.
There are no supported `data-assistant-id`/`data-token-endpoint` auto-mount tags.
Load the file directly from our domain, without a local SDK copy, separate
loader or automatic fallback:

```html
<script src="https://contextassistant.io/assets/embed.js" referrerpolicy="no-referrer"></script>
```

Call `mount` after the script loads and the DOM is ready. With `defer`, initialize
on `DOMContentLoaded`, not immediately in the following inline script.
Compatible updates arrive on the next page load. In React use the linked example
with one shared script load and widget cleanup on unmount. Lite/Advanced is
selected in the cabinet; the site's brand colors can be preserved.

Searching uploaded content and displaying knowledge-base cards do not require
Remote Tools. Supply `card` alongside `content`; images are optional.
If the catalog is not uploaded yet, choose "Populate the knowledge base".
Query-time access to the customer's system and actions in it are separate
integrations. Server-side temporary token issuance is required for this private
flow. Public chat uses the first section of this guide instead.

The public npm registry currently does **not** provide
`@context-assistant/embed-sdk`, `@context-assistant/react` or
`@context-assistant/js-sdk`. Use `npm install` only with separately provided
package access. If the customer already has authorized SDK
artifacts, use their documented exports. Otherwise use the browser bundle,
including inside React. Headless SDK examples require package access; they
are not a promise of a publicly downloadable npm release.

Runnable/reference files (same examples used by our automated checks):
- https://contextassistant.io/docs/examples/token-handler.mjs
- https://contextassistant.io/docs/examples/embed.html
- https://contextassistant.io/docs/examples/react-widget.tsx

## 3. Server-side token endpoint

Implement `POST /api/context-assistant/token` on **the customer's origin**.
Use the existing backend/framework route or a serverless function with the
same origin. If the site has no server, this is a prerequisite; never put the
permanent key into static JS. Do not configure a generic open proxy.

Set these server-only variables (examples are placeholders, not real keys):

```dotenv
CONTEXT_ASSISTANT_API_KEY=SET_PRIVATELY_ON_SERVER
CONTEXT_ASSISTANT_ID=YOUR_ASSISTANT_ID
CONTEXT_ASSISTANT_SITE_ORIGIN=http://localhost:3000
CONTEXT_ASSISTANT_COOKIE_SECRET=SET_A_RANDOM_SERVER_SECRET_AT_LEAST_32_CHARACTERS
```

The site origin must match the browser address, even if its backend listens
on another internal port. Keep separate origins/configuration per environment.
The cookie secret signs guest identities in the example; it is not the API key.

Server-to-server request (JSON):

```http
POST https://api.contextassistant.io/v1/embed/token
Authorization: Bearer <server API key>
Content-Type: application/json
```

```json
{"assistantId":"YOUR_ASSISTANT_ID","origin":"https://shop.example","user":{"externalId":"member_42"}}
```

The token API returns `200` with `{"token":"<temporary JWT>","expiresAt":"<RFC3339 time>"}`.
Return only these fields to the browser and send `Cache-Control: no-store`.
Do not log either token, the permanent key or raw upstream error bodies.
API errors use `{"code":"origin_not_allowed","error":"human-readable explanation"}`.

Validate the requesting Origin against fixed server configuration. For private
apps verify login before issuing a token. Origin checks are not user authentication.
Derive member identity from the verified server session. For guests generate
a random per-visitor ID and persist it in a signed HttpOnly, SameSite cookie
(Secure on HTTPS). Never use a common empty ID for all guests or trust a
browser-supplied identity. Reset the widget when the signed-in user changes.

The Node 20+ example exports `createTokenHandler`. Mount the returned handler
on your route. Pass `resolveUser(req)` to read your existing verified session
and `requireLogin: true` for private apps. The default example allows guests.
For Express pass the handler to `app.post`; adapt Request/Response for other
frameworks. Its rate limit is per process and peer IP. A production cluster
needs a shared limiter or a trusted reverse-proxy limit; never blindly trust
X-Forwarded-For. Apply a request-body size limit at your framework/proxy.

## 4. Browser and React

Use the complete `embed.html` example. Set the Public ID in the server-only
`CONTEXT_ASSISTANT_ID` setting, retain the hosted API origin, and implement
`getToken()` using the local token endpoint.
Reject non-2xx responses and missing token values. Do not store tokens in
HTML, build-time client environment variables, URLs or localStorage.
The widget fetches tokens through `getToken` as needed.

Mount with `window.ContextAssistant.mount({ apiUrl, getToken, title })`.
Omit `agent`: it selects a behavior profile (`general` by default), not an
assistant. The server sends the Public ID (`asst_...`) as `assistantId` when
issuing the token; the token selects the assistant. Never put the Public ID
in `agent`, whether in widget options or a message request JSON body.

For React use the provided `react-widget.tsx` client component. It loads the
same browser bundle, mounts after the DOM exists, updates page context and
destroys the widget on unmount. No Context Assistant npm package is required.
Change `identityKey` on login/logout to discard the old user's conversation.
For strict CSP, allow the chosen script URL, API connection origin and the
widget's Shadow DOM styling; review the policy rather than disabling CSP.

## 5. Page context is not catalog access

```js
widget.setContext({ page: { type: 'product', id: 184 } });
// On a route/product change, replace the page, including removal of old fields:
widget.setContext({ page: { type: 'product', id: 185 } });
// On a page where context is not allowed:
widget.clearContext();
```

Top-level supported containers: `application`, `workspaceId`, `page`,
`selection`, `entity`, `site`. Pass small, explicitly permitted facts only;
never the full DOM, forms, passwords, cookies or private customer records.
Context is not trusted authorization. `updateContext()` is a shallow merge;
use `setContext()` to avoid keeping fields from the previous page.

A product ID alone does not give the assistant prices, stock or catalog search.
For documents, use the cabinet's Knowledge section and wait for `ready` status.
For automated ingestion, a server key needs `knowledge:write`; create a source
with `POST /v1/knowledge/sources`, then submit documents using
`POST /v1/knowledge/documents` with `sourceId`, `title`, `contentType`, `content`
and an optional stable `externalId` for updates. Use `knowledge:read` to inspect
status. The source must belong to the selected assistant.

Example JSON for `POST /v1/knowledge/sources`:

```json
{"assistantId":"INTERNAL_ASSISTANT_UUID","name":"Marketplace policies","type":"manual"}
```

Save `id` from the HTTP 201 response as `sourceId`, then submit a document:

```json
{"sourceId":"SOURCE_UUID","externalId":"rules:publishing","title":"Publishing rules","contentType":"text","content":"The policy text approved by the marketplace owner."}
```

`contentType` accepts `text`, `markdown`, `html`, `json`, not MIME types.
Direct PDF ingestion is not supported. Supply the actual text: a source URL
does not trigger website crawling. A new document returns HTTP 201; an update
using `externalId` returns HTTP 200. Check its `status` through
`GET /v1/knowledge/documents`, locating its `id` in `items`; inspect `error`
on `failed`, and wait while `pending` or `processing`.

For live catalog search expose a read-only backend tool through the full
contract: https://contextassistant.io/docs/remote-tools.md
It includes authentication, schemas, context, preview and execution. First
agree which data and permissions the customer wants to expose; do not invent
a catalog backend or connect mutations just to make a demo appear complete.

Register a connection server-to-server with a key scoped `integrations:write`:
`POST /v1/integrations/catalog/connect`, JSON fields `baseUrl`, `secret`,
`allowedTools` (e.g. `["search_listings"]`), and `assistantId` when needed.
Here `assistantId` is the **internal UUID**, not the widget Public ID. It can
be obtained from `GET /v1/assistants` using `assistants:read`. With exactly one
assistant it may be omitted. Knowledge source creation uses the internal
assistant UUID too. The callback must be reachable by our service; localhost
or a private IP is not accepted. Use a customer-approved public test endpoint.
Connecting the same provider again updates that workspace's connection; do
not overwrite an existing connection without the customer's approval.

## 6. Troubleshooting and acceptance checks

| Result | Check |
|---|---|
| 400 `invalid_input`: `unknown agent profile` | `agent` contains a Public ID or another unsupported value. Omit `agent` from widget options/message requests. Do not publish a profile or recreate the assistant |
| 401 | Missing/revoked server key or required site login |
| 403 `forbidden` | Key lacks the required scope |
| 403 `origin_not_allowed` | Browser, configured origin and assistant allowlist must agree |
| 404 `assistant_not_found` | Public ID and key must belong to the same workspace |
| 429 | Rate limit; back off, do not loop token requests or weaken safeguards |
| 502/503 | Server configuration, upstream availability or network; no secrets in logs |
| Script/package missing | Use the documented browser bundle; npm packages are not public |
| Chat opens but cannot answer catalog questions | Connect knowledge or a read tool, not more page scraping |

Verify: real token issuance and streamed reply; refresh and token renewal;
context after navigation; separate guest sessions and member sessions;
denied foreign origin; unauthenticated access denied for private apps;
graceful API failure; no permanent keys in browser artifacts/requests.
If credentials or backend access are missing, report that part as unverified.
Opening the widget or running mocked tests does not prove a real model reply.
Check the full chain: local token endpoint, conversation creation, message
submission, a streamed response ending in `done`, and text displayed in the
widget. Record the failing stage, HTTP status and safe error code; a missing
reply alone is not evidence of incorrect cabinet settings.
Do not deploy without a separate customer instruction.
