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

# Kin Plugin

> AI-powered chat assistant for job seekers

# Kin Plugin

Kin is an AI-powered chat assistant that helps candidates find jobs, answer questions, and navigate your career site.

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install @inploi/plugin-kin
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add @inploi/plugin-kin
    ```
  </Tab>

  <Tab title="CDN">
    ```html theme={null}
    <script defer src="https://sdk.inploi.com/@inploi/sdk/cdn/index.js"></script>
    <script defer src="https://sdk.inploi.com/@inploi/plugin-kin/cdn/index.js"></script>
    ```
  </Tab>
</Tabs>

<Warning>
  If your site uses a server-side caching or minification plugin (e.g. WP Rocket, W3 Total Cache, Autoptimize), exclude all `https://sdk.inploi.com/*` URLs from it. These plugins can serve a stale cached copy of the SDK, preventing updates from reaching your site. See [CDN caching and minification](/sdk/getting-started#cdn-caching-and-minification) for details.
</Warning>

## Basic usage

<Tabs>
  <Tab title="Package">
    ```typescript theme={null}
    import { initialiseSdk } from '@inploi/sdk';
    import { kinPlugin } from '@inploi/plugin-kin';

    const sdk = initialiseSdk({ publishableKey: 'pk_...', env: 'production' });

    const kin = sdk.register(kinPlugin({
      appearance: { mode: 'light', accentColor: '#FF4D00' },
    }));

    kin.prepare();
    ```
  </Tab>

  <Tab title="CDN">
    ```html theme={null}
    <script defer src="https://sdk.inploi.com/@inploi/sdk/cdn/index.js"></script>
    <script defer src="https://sdk.inploi.com/@inploi/plugin-kin/cdn/index.js"></script>
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        const sdk = inploi.initialiseSdk({ publishableKey: 'pk_...', env: 'production' });
        const kin = sdk.register(inploi.kinPlugin({
          appearance: { mode: 'light', accentColor: '#FF4D00' },
        }));
        kin.prepare();
      });
    </script>
    ```
  </Tab>
</Tabs>

## Configuration

| Parameter        | Type                    | Required | Description                                                                                                                                         |
| ---------------- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agentName`      | `string`                | No       | Customer-facing display name for the agent. Defaults to `"Agent"`. Max 32 characters — longer values are silently truncated with a console warning. |
| `appearance`     | `KinAppearance`         | No       | Visual styling, layout & stacking options                                                                                                           |
| `starterPrompts` | `StarterPrompt[]`       | No       | Suggested prompts shown in empty state                                                                                                              |
| `container`      | `HTMLElement \| string` | No       | Container element or CSS selector for `embedded` placement                                                                                          |
| `serviceUrl`     | `string`                | No       | Override the agents service URL. Defaults to production (`wss://agents.inploi.com`) or sandbox based on `env`                                       |

<Note>
  The publishable key is automatically inherited from the SDK initialisation — you don't need to pass it again.
</Note>

### Agent name

`agentName` controls the display name shown to candidates. It appears in the chat header, the iframe accessibility label, message author labels, and the empty-state greeting. If you omit it, the agent is shown as **"Agent"**.

```typescript theme={null}
const kin = sdk.register(kinPlugin({
  agentName: 'Acme Careers Assistant',
}));
```

Keep it short — values longer than 32 characters are truncated automatically so the header doesn't wrap or clip on mobile.

### Appearance

All visual and layout options are grouped under `appearance`:

| Option         | Type                       | Default                                | Description                                                                                                                                                           |
| -------------- | -------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `accentColor`  | `string`                   | `'#FF4D00'`                            | Primary brand color (hex)                                                                                                                                             |
| `neutralColor` | `string`                   | `'#555555'`                            | Neutral tone color (hex)                                                                                                                                              |
| `mode`         | `'light'` \| `'dark'`      | `'light'`                              | Color scheme                                                                                                                                                          |
| `rounded`      | `'sm'` \| `'md'` \| `'lg'` | `'lg'`                                 | Border radius preset                                                                                                                                                  |
| `placement`    | `string` \| `object`       | `'center'`                             | Widget position. Pass a string (`'left'`, `'center'`, `'right'`, `'embedded'`) for both breakpoints, or `{ desktop, mobile }` for independent positioning (see below) |
| `launcher`     | `object`                   | `{ desktop: 'input', mobile: 'icon' }` | Launcher style per breakpoint: `{ desktop: 'input' \| 'icon', mobile: 'input' \| 'icon' }`                                                                            |
| `offset`       | `object`                   | `{}`                                   | Closed launcher offset from the viewport edge, per breakpoint                                                                                                         |
| `zIndex`       | `number`                   | `2147483647`                           | CSS stacking order of the widget                                                                                                                                      |
| `logo`         | `string` \| `null`         | Kin logo                               | URL for a custom launcher icon. Pass `null` to use the default                                                                                                        |

#### Placement

Pass a single string to use the same position on all devices, or an object for independent desktop/mobile positioning:

```typescript theme={null}
// Same position everywhere
appearance: { placement: 'right' }

// Different per breakpoint
appearance: { placement: { desktop: 'center', mobile: 'right' } }
```

Available values: `'left'`, `'center'`, `'right'`. Use `'embedded'` together with the `container` parameter to render the widget inline in a specific element instead of as a floating overlay.

#### Launcher

Controls whether the trigger displays as an input bar or a circular icon. By default, desktop shows the input bar and mobile shows the icon.

```typescript theme={null}
// Icon on mobile, input on desktop (default)
appearance: { launcher: { desktop: 'input', mobile: 'icon' } }

// Icon everywhere
appearance: { launcher: { desktop: 'icon', mobile: 'icon' } }
```

The breakpoint between desktop and mobile is determined by `(hover: none) and (pointer: coarse)` — devices without a precise pointer (phones, tablets) use the `mobile` setting.

#### Closed launcher offset

Use `appearance.offset` when a page has a fixed CTA, cookie banner, notification button, or accessibility widget near the bottom of the viewport. Offset applies only while the widget is closed. When the chat panel opens, Kin expands as a normal overlay.

```typescript theme={null}
const kin = sdk.register(kinPlugin({
  appearance: {
    placement: { desktop: 'left', mobile: 'left' },
    launcher: { desktop: 'icon', mobile: 'icon' },
    offset: {
      desktop: { x: 16, y: 72 },
      mobile: { x: 16, y: 72 },
    },
  },
}));
```

You can also update the offset at runtime with `kin.setOffset(...)`. This is useful when a sticky page element appears only after scrolling.

```typescript theme={null}
const applyBar = document.querySelector('[data-current-vacancies-bar]');

const syncLauncherOffset = () => {
  const rect = applyBar?.getBoundingClientRect();
  const overlap = rect ? Math.max(0, window.innerHeight - rect.top) : 0;
  const y = overlap > 0 ? overlap + 12 : 16;

  kin.setOffset({
    desktop: { y },
    mobile: { y },
  });
};

window.addEventListener('scroll', syncLauncherOffset, { passive: true });
window.addEventListener('resize', syncLauncherOffset);
if (applyBar) new ResizeObserver(syncLauncherOffset).observe(applyBar);
syncLauncherOffset();
```

<Note>
  When floating and closed, Kin sizes its host iframe to the launcher bounds so surrounding page controls remain clickable. When opened, the iframe expands to the viewport to render the chat panel.
</Note>

### Starter prompts

```typescript theme={null}
type StarterPrompt = {
  label: string;   // Text displayed on the button
  message: string; // Message sent when clicked
};
```

**Example:**

```typescript theme={null}
const kin = sdk.register(kinPlugin({
  agentName: 'Acme Careers Assistant',
  appearance: {
    mode: 'light',
    accentColor: '#FF4D00',
    placement: { desktop: 'center', mobile: 'right' },
    launcher: { desktop: 'input', mobile: 'icon' },
    zIndex: 999999,
  },
  starterPrompts: [
    { label: 'Find jobs near me', message: 'What jobs are available in my area?' },
    { label: 'Help with my application', message: 'I need help with my job application' },
  ],
}));
```

## API

### `prepare()`

Preloads the UI and establishes a connection. Call this early for the best user experience.

```typescript theme={null}
kin.prepare();
```

### `open()`

Opens the chat panel.

```typescript theme={null}
kin.open();
```

### `close()`

Closes the chat panel.

```typescript theme={null}
kin.close();
```

### `toggle()`

Toggles the chat panel open/closed.

```typescript theme={null}
kin.toggle();
```

### `setAppearance(options)`

Updates visual properties at runtime. Accepts any combination of `accentColor`, `neutralColor`, `mode`, and `rounded`. Omitted fields keep their current values.

<Note>
  Layout options (`placement`, `launcher`, `zIndex`, `logo`) cannot be changed at runtime — call `destroy()` and re-create the plugin to change them.
</Note>

```typescript theme={null}
kin.setAppearance({ mode: 'dark', accentColor: '#6366f1' });
```

### `setOffset(offset)`

Updates the closed launcher offset at runtime. This does not move an open chat panel.

```typescript theme={null}
kin.setOffset({
  desktop: { y: 72 },
  mobile: { y: 96 },
});
```

### `destroy()`

Removes the widget and cleans up resources.

```typescript theme={null}
kin.destroy();
```

## Full configuration reference

```typescript theme={null}
type KinPluginParams = {
  agentName?: string;
  serviceUrl?: string;
  appearance?: {
    accentColor?: string;
    neutralColor?: string;
    mode?: 'light' | 'dark';
    rounded?: 'sm' | 'md' | 'lg';
    placement?: 'left' | 'center' | 'right' | 'embedded' | {
      desktop?: 'left' | 'center' | 'right';
      mobile?: 'left' | 'center' | 'right';
    };
    launcher?: {
      desktop?: 'input' | 'icon';
      mobile?: 'input' | 'icon';
    };
    offset?: {
      desktop?: { x?: number; y?: number };
      mobile?: { x?: number; y?: number };
    };
    zIndex?: number;
    logo?: string | null;
  };
  starterPrompts?: { label: string; message: string }[];
  container?: HTMLElement | string;
};
```

## WordPress installation

WordPress sites that use a Content Security Policy (CSP) with nonces require additional setup. This is common on enterprise WordPress installations.

### CSP nonce setup

If your WordPress site sets a `Content-Security-Policy` header with a `nonce-` value, you need to:

1. **Add `https://sdk.inploi.com` to your CSP directives.** In your theme's `functions.php`, ensure your CSP header includes:

```php theme={null}
"script-src 'self' 'nonce-" . esc_attr($nonce) . "' 'unsafe-inline' https://sdk.inploi.com; " .
"frame-src 'self' https://sdk.inploi.com; " .
"connect-src 'self' https://agents.inploi.com wss://agents.inploi.com; " .
```

2. **Add the nonce to the inline init script.** The two external `<script>` tags don't need a nonce (they're allowed by `https://sdk.inploi.com` in `script-src`), but the inline init script does:

```html theme={null}
<script defer src="https://sdk.inploi.com/@inploi/sdk/cdn/index.js"></script>
<script defer src="https://sdk.inploi.com/@inploi/plugin-kin/cdn/index.js"></script>
<script nonce="<?php echo esc_attr(your_csp_nonce_function()); ?>">
  document.addEventListener('DOMContentLoaded', () => {
    const sdk = inploi.initialiseSdk({ publishableKey: 'pk_...', env: 'production' });
    const kin = sdk.register(inploi.kinPlugin({ /* config */ }));
    kin.prepare();
  });
</script>
```

The nonce value must match the one in your CSP header. If you use a static nonce function with the `static` keyword, the same value is returned for both the header and the script tag within a single request.

### Recommended approach with `wp_enqueue_script`

For the cleanest integration, register scripts through WordPress's script API instead of hardcoding them in your template. This automatically handles CSP nonces if your theme or CSP plugin supports it:

```php theme={null}
function enqueue_inploi_kin() {
    wp_enqueue_script('inploi-sdk', 'https://sdk.inploi.com/@inploi/sdk/cdn/index.js', [], null, true);
    wp_enqueue_script('inploi-kin', 'https://sdk.inploi.com/@inploi/plugin-kin/cdn/index.js', ['inploi-sdk'], null, true);
    wp_add_inline_script('inploi-kin', "
        document.addEventListener('DOMContentLoaded', () => {
            var sdk = inploi.initialiseSdk({ publishableKey: 'pk_...', env: 'production' });
            var kin = sdk.register(inploi.kinPlugin({ /* config */ }));
            kin.prepare();
        });
    ");
}
add_action('wp_enqueue_scripts', 'enqueue_inploi_kin');
```

<Warning>
  If your site uses a server-side caching or minification plugin (e.g. WP Rocket, W3 Total Cache, Autoptimize), exclude all `https://sdk.inploi.com/*` URLs from it. See [CDN caching and minification](/sdk/getting-started#cdn-caching-and-minification) for details.
</Warning>

## Soft navigation

If your site uses client-side navigation (e.g. Turbo, HTMX, SPA routers), the Kin widget's DOM can be removed without it knowing. Use `destroy()` before the page content is replaced, and re-initialise on the new page.

<Tabs>
  <Tab title="Turbo / Turbolinks">
    ```javascript theme={null}
    document.addEventListener('turbo:before-cache', () => kin.destroy());
    document.addEventListener('turbo:load', () => kin.prepare());
    ```
  </Tab>

  <Tab title="HTMX (hx-boost)">
    ```javascript theme={null}
    document.addEventListener('htmx:beforeSwap', () => kin.destroy());
    document.addEventListener('htmx:afterSettle', () => kin.prepare());
    ```
  </Tab>

  <Tab title="SPA routers">
    ```javascript theme={null}
    router.beforeEach(() => kin.destroy());
    router.afterEach(() => kin.prepare());
    ```
  </Tab>
</Tabs>

## Features

<AccordionGroup>
  <Accordion title="Real-time streaming responses">
    Kin streams responses in real-time, providing a natural conversational experience.
  </Accordion>

  <Accordion title="Conversation history">
    The agent remembers the conversation within a session (or persistently, based on your configuration in Studio).
  </Accordion>

  <Accordion title="File uploads">
    Candidates can upload CVs and other documents for the agent to analyse.
  </Accordion>

  <Accordion title="Cross-tab sync">
    Conversations sync across browser tabs for a seamless experience.
  </Accordion>

  <Accordion title="Consent screen">
    Optionally require candidates to accept a consent message before they can use the agent. The screen text, accept/decline labels, and any links are configured in Studio under **Agents → Guidance → Basics → Consent**. Acceptance is stored per device and is automatically re-prompted if the wording changes.
  </Accordion>
</AccordionGroup>
