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

# Setup Guide

> Add AI chat to any website with a drop-in JavaScript widget

The Sigmic AI chat widget lets you embed a fully-featured AI chat interface into any website. Your end users interact with the widget in the browser while your backend handles authentication and configuration.

## Architecture

```
Your Backend                         Sigmic AI API                      Browser Widget
     |                                    |                                |
     |  1. POST /api/v1/auth/token        |                                |
     |    { appId, appSecret, endUserId,  |                                |
     |      env, secrets }                |                                |
     |----------------------------------->|                                |
     |    { token, expiresAt }            |                                |
     |<-----------------------------------|                                |
     |                                    |                                |
     |  2. Return JWT to browser          |                                |
     |------------------------------------------------------->|           |
     |                                    |                                |
     |                                    |  3. Chat via widget            |
     |                                    |    Authorization: Bearer eyJ... |
     |                                    |<-------------------------------|
     |                                    |    Streaming response          |
     |                                    |------------------------------->|
```

1. **Your backend** calls the token endpoint with credentials + optional `env`/`secrets` (credentials stay server-side)
2. **Your backend** sends the short-lived JWT to the browser
3. **The browser** uses the JWT to chat via the embedded widget

## Setup

### 1. Create a Widget App

Create a widget app in the [Console](/console). Navigate to the **Widget Apps** section, click **Create Widget App**, and configure your app name and allowed origins.

After creation, copy the **App ID** (`appId`) and **App Secret** (`appSecret`) from the portal.

<Warning>
  The `appSecret` is only shown once at creation time. Store it securely on your backend server — never expose it in client-side code.
</Warning>

### 2. Exchange Credentials for a JWT

From your backend, call the token endpoint:

```http theme={null}
POST /api/v1/auth/token
Content-Type: application/json
```

```json theme={null}
{
  "appId": "wapp_a1b2c3d4e5f6",
  "appSecret": "wsec_7890abcdef...",
  "endUserId": "user-123",
  "expiresIn": "1h",
  "env": {
    "TENANT": "acme-corp",
    "REGION": "us-east-1"
  },
  "secrets": {
    "DB_URL": "postgres://...",
    "API_TOKEN": "sk-..."
  }
}
```

| Field       | Type   | Required | Default | Description                                                       |
| ----------- | ------ | -------- | ------- | ----------------------------------------------------------------- |
| `appId`     | string | Yes      | —       | Widget app's public App ID                                        |
| `appSecret` | string | Yes      | —       | Widget app's secret                                               |
| `endUserId` | string | Yes      | —       | Your end user's identifier (scopes conversations to this user)    |
| `expiresIn` | string | No       | `"1h"`  | Token lifetime: `"30m"`, `"1h"`, `"7d"`, etc.                     |
| `env`       | object | No       | —       | Non-sensitive environment variables (stored as JSON)              |
| `secrets`   | object | No       | —       | Sensitive environment variables (encrypted at rest, never logged) |

**Response:**

```json theme={null}
{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expiresAt": "2026-01-31T12:00:00.000Z"
  }
}
```

### 3. Embed the Widget

Add two script tags to your page — no build step required:

```html theme={null}
<script>
  window.SigmicChat = {
    token: 'JWT_TOKEN_FROM_YOUR_BACKEND',
    host: 'https://api.sigmic.ai'
  };
</script>
<script src="https://api.sigmic.ai/widget/sigmic-chat.js" async></script>
```

This creates a floating chat button in the bottom-right corner. Clicking it opens a full chat UI.

### Live Demo

See the widget in action on a live customer website:

<Card icon="globe" href="https://simple-agent-chat.space" title="Live Customer Demo">
  A live website that embeds the Sigmic AI chat widget with custom branding, logo, and per-user context. Visit **simple-agent-chat.space** to try it.
</Card>

## Passing Environment Variables & Secrets

You can pass per-user `env` and `secrets` at token exchange time to connect the agent to each end user's data. These values are stored server-side (never in the JWT or browser) and injected into your project's MCP servers as `{{mcp.KEY}}` template variables.

For example, you can configure an MCP server with `"Authorization": "Bearer {{mcp.API_TOKEN}}"` in its headers, then pass each end user's API token via `secrets` — the agent connects to the right service for each user automatically.

<Card icon="key" href="/widget-env-secrets" title="Environment Variables & Secrets Guide">
  Full guide with examples for HTTP, stdio, and SSE MCP servers, template reference, key naming rules, and a complete multi-tenant backend example.
</Card>

## Widget Configuration

```javascript theme={null}
window.SigmicChat = {
  token: 'JWT_TOKEN',         // Required — JWT from your backend
  host: '...',                // Backend URL (auto-detected from script src if omitted)
  position: 'bottom-right',   // Or 'bottom-left'
  title: 'Chat with AI',      // Chat header title
  open: false,                 // Set true to start with widget open
  fetchToken: async () => {    // Optional — callback to refresh expired JWTs
    const res = await fetch('/api/sigmic-token');
    const data = await res.json();
    return data.token;
  }
};
```

## Token Refresh

Widget JWTs have a limited lifetime (default 1 hour). For long-running sessions, provide a `fetchToken` callback so the widget can automatically refresh expired tokens.

### How it works

1. **Proactive refresh**: \~60 seconds before the token expires, the widget requests a new token from the parent page
2. **Reactive refresh**: If an API call returns 401, the widget requests a new token and retries the request once
3. **No `fetchToken`**: If `fetchToken` is not provided, behavior is unchanged — a 401 on expiry is surfaced as an error

### Configuration

```javascript theme={null}
window.SigmicChat = {
  token: 'INITIAL_JWT_TOKEN',
  host: 'https://api.sigmic.ai',
  fetchToken: async function() {
    // Call your backend to get a fresh JWT
    const response = await fetch('/api/sigmic-chat/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId: currentUser.id })
    });
    const data = await response.json();
    return data.token;
  }
};
```

### Backend example (Express)

```javascript theme={null}
app.post('/api/sigmic-chat/token', async (req, res) => {
  const { userId } = req.body;

  // Look up user/tenant context for this refresh
  const user = await db.users.findById(userId);

  // Exchange credentials for a new widget JWT
  // Include env/secrets on every refresh — each token gets its own context
  const response = await fetch('https://api.sigmic.ai/api/v1/auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      appId: process.env.SIGMIC_APP_ID,
      appSecret: process.env.SIGMIC_APP_SECRET,
      endUserId: userId,
      expiresIn: '1h',
      env: { TENANT_ID: user.tenantId },
      secrets: { API_TOKEN: user.apiToken }
    })
  });
  const data = await response.json();
  res.json({ token: data.data.token });
});
```

<Warning>
  Each token has its own `env`/`secrets` context. You must pass them on **every** token exchange, including refreshes — the new token does not inherit values from the previous one. See the [Environment Variables & Secrets guide](/widget-env-secrets#token-refresh-and-variables) for details.
</Warning>

<Note>
  The `fetchToken` callback can be synchronous or asynchronous. The widget wraps the return value in `Promise.resolve()` for compatibility.
</Note>

## Widget Events

The widget communicates with the parent page via `postMessage`:

```javascript theme={null}
window.addEventListener('message', function(e) {
  if (!e.data || !e.data.type) return;
  switch (e.data.type) {
    case 'sigmic-chat-close':    // Widget closed
      break;
    case 'sigmic-chat-maximize': // Widget expanded to fullscreen
      break;
    case 'sigmic-chat-restore':  // Widget restored from fullscreen
      break;
    case 'sigmic-chat-task':     // New task created (e.data.taskId)
      break;
  }
});
```

## End User Scoping

The `endUserId` field isolates data between your end users:

* Conversations are scoped to the `endUserId` embedded in the JWT
* One end user cannot access another's conversation history
* Use a stable identifier from your system (database ID, email hash, etc.)

## Authentication Details

### JWT Claims

| Claim            | Description                                             |
| ---------------- | ------------------------------------------------------- |
| `appId`          | Your Widget App ID                                      |
| `sub`            | The `endUserId` you provided                            |
| `allowedOrigins` | Origins allowed to use this token                       |
| `jti`            | Unique token ID (present when env/secrets are provided) |
| `iat`            | Issued-at timestamp                                     |
| `exp`            | Expiration timestamp                                    |

### Expiration Format

| Format  | Example  | Description |
| ------- | -------- | ----------- |
| Seconds | `"300s"` | 5 minutes   |
| Minutes | `"30m"`  | 30 minutes  |
| Hours   | `"1h"`   | 1 hour      |
| Days    | `"7d"`   | 7 days      |

### Origin Validation

When a widget app has `allowedOrigins` configured, the server validates the `Origin` header on every JWT-authenticated request.

* Set `allowedOrigins` to `["*"]` to allow all origins (not recommended for production)
* Leave `allowedOrigins` empty to skip origin validation
* Origins must match exactly (e.g., `https://mysite.com` does not match `https://www.mysite.com`)

## Error Codes

| Code                  | HTTP Status | Description                              |
| --------------------- | ----------- | ---------------------------------------- |
| `JWT_NOT_CONFIGURED`  | 501         | Server JWT signing not configured        |
| `MISSING_CREDENTIALS` | 400         | `appId` or `appSecret` not provided      |
| `MISSING_END_USER_ID` | 400         | `endUserId` not provided or empty        |
| `INVALID_EXPIRES_IN`  | 400         | `expiresIn` format invalid               |
| `INVALID_ENV`         | 400         | Invalid env var name or non-string value |
| `INVALID_CREDENTIALS` | 401         | App ID or secret is incorrect            |
| `INVALID_TOKEN`       | 401         | JWT is invalid or expired                |
| `ORIGIN_NOT_ALLOWED`  | 403         | Request origin not in allowed list       |
