assistant embed.md

Tutorial: Build an in-app documentation assistant

Build and embed an in-app documentation assistant that answers user questions with cited information from your Mintlify documentation site.

What you'll build

A reusable widget that embeds the assistant directly in your application. The widget provides:

Users can use the widget to get help with your product without leaving your application.

Prerequisites

Get your assistant API key

  1. Navigate to the API keys page in your dashboard.
  2. Click Create Assistant API Key.
  3. Copy the assistant API key (starts with mint_dsc_) and save it securely.

Set up the example

Clone the example repository and customize it for your needs.

```bash git clone https://github.com/mintlify/assistant-embed-example.git cd assistant-embed-example ``` The repository includes Next.js and Vite examples. Choose the tool you prefer to use. ```bash title="Next.js" cd nextjs npm install ```
cd vite
npm install
      ```
    </CodeGroup>
  </Step>

<Step title="Configure your project">
    Open `src/config.js` and update with your Mintlify project details.

```js
export const ASSISTANT_CONFIG = {
  domain: 'your-domain',
  docsURL: 'https://yourdocs.mintlify.site',
};
    ```

Replace:

* `your-domain` with your Mintlify project domain found at the end of your dashboard URL.
    * `https://yourdocs.mintlify.site` with your actual documentation URL.
  </Step>

<Step title="Set up your API key">
    Store your assistant API key as a server-side environment variable. Avoid the `VITE_` prefix, which bundles the value into your client code:

```bash
MINTLIFY_TOKEN=mint_dsc_your_token_here
    ```

Replace `mint_dsc_your_token_here` with your assistant API key.

Then add a backend route (for example, `/api/assistant`) to proxy requests to the Mintlify API:

1. Accept the user's message from the widget.
    2. Attach the `Authorization: Bearer $MINTLIFY_TOKEN` header and forward the request to `https://api.mintlify.com/discovery/v1/assistant/{domain}/message`.
    3. Stream the upstream response back to the client unchanged, so token streaming and `X-Thread-Id` / `X-Thread-Key` headers reach the widget.
    4. Point the widget's `api` option at your backend route instead of the Mintlify API directly.
  </Step>

<Step title="Start the development server">
    ```bash
npm run dev
    ```

Open your application in a browser and click the **Ask** button to open the assistant widget.
  </Step>
</Steps>

## Customization ideas

### Source citations

Extract and display sources from assistant responses:

```jsx
const extractSources = (parts) => {
  return parts
    ?.filter(p => p.type === 'tool-invocation' && p.toolInvocation?.toolName === 'search')
    .flatMap(p => p.toolInvocation?.result || [])
    .map(source => ({
      url: source.url || source.path,
      title: source.metadata?.title || source.path,
    })) || [];
};

// In your message rendering:
{messages.map((message) => {
  const sources = message.role === 'assistant' ? extractSources(message.parts) : [];
  return (
    <div key={message.id}>
      {/* message content */}
      {sources.length > 0 && (
        <div className="mt-2 text-xs">
          <p className="font-semibold">Sources:</p>
          {sources.map((s, i) => (
            <a key={i} href={s.url} target="_blank" rel="noopener noreferrer" className="text-blue-600">
              {s.title}
            </a>
          ))}
        </div>
      )}
    </div>
  );
})}

Track conversation threads

Store thread IDs and thread keys to maintain conversation history across sessions.

When a user creates a new conversation thread, the server returns two values in the response headers:

  • X-Thread-Id: The thread identifier
  • X-Thread-Key: A secret key for the thread (only returned once, when you create the thread)

You must capture and persist both values on the first response. On every subsequent message, include both threadId and threadKey in the request body. If you send a threadId without the corresponding threadKey, the server returns a 404 error.

import { useState, useEffect } from 'react';

export function AssistantWidget({ domain, docsURL }) {
  const [threadId, setThreadId] = useState(null);
  const [threadKey, setThreadKey] = useState(null);

useEffect(() => {
    // Retrieve saved thread ID and key from localStorage
    const savedId = localStorage.getItem('assistant-thread-id');
    const savedKey = localStorage.getItem('assistant-thread-key');
    if (savedId && savedKey) {
      setThreadId(savedId);
      setThreadKey(savedKey);
    }
  }, []);

const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/assistant',
    body: {
      fp: 'anonymous',
      retrievalPageSize: 5,
      ...(threadId && { threadId }),
      ...(threadKey && { threadKey }),
    },
    streamProtocol: 'data',
    sendExtraMessageFields: true,
    fetch: async (url, options) => {
      const response = await fetch(url, options);
      const newThreadId = response.headers.get('x-thread-id');
      const newThreadKey = response.headers.get('x-thread-key');
      if (newThreadId) {
        setThreadId(newThreadId);
        localStorage.setItem('assistant-thread-id', newThreadId);
      }
      if (newThreadKey) {
        setThreadKey(newThreadKey);
        localStorage.setItem('assistant-thread-key', newThreadKey);
      }
      return response;
    },
  });

// ... rest of component
}

Add keyboard shortcuts

Allow users to open the widget and submit messages with keyboard shortcuts:

useEffect(() => {
  const handleKeyDown = (e) => {
    // Cmd/Ctrl + Shift + I to toggle widget
    if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') {
      e.preventDefault();
      setIsOpen((prev) => !prev);
    }

// Enter (when widget is focused) to submit
    if (e.key === 'Enter' && !e.shiftKey && document.activeElement.id === 'assistant-input') {
      e.preventDefault();
      handleSubmit();
    }
  };

window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [handleSubmit]);