diff --git a/docs.json b/docs.json
index c2183be45..3c9d036f8 100644
--- a/docs.json
+++ b/docs.json
@@ -234,9 +234,10 @@
"icon": "bot",
"pages": [
"guides/automate-agent",
- "guides/geo",
+ "guides/assistant-embed",
"guides/claude-code",
"guides/cursor",
+ "guides/geo",
"guides/windsurf"
]
},
diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx
new file mode 100644
index 000000000..c5d84d71a
--- /dev/null
+++ b/guides/assistant-embed.mdx
@@ -0,0 +1,222 @@
+---
+title: "Tutorial: Build an in-app documentation assistant"
+sidebarTitle: "Build an in-app assistant"
+description: "Embed the assistant in your application to answer questions with information from your documentation"
+---
+
+## What you will build
+
+A reusable widget that embeds the [assistant](/ai/assistant) directly in your application. The widget provides:
+
+- A floating button that opens a chat panel when clicked
+- Real-time streaming responses based on information from your documentation
+- Message rendering with Markdown support
+
+Users can use the widget to get help with your product without leaving your application.
+
+
+
+
+
+## Prerequisites
+
+- [Mintlify Pro or Custom plan](https://mintlify.com/pricing)
+- Your domain name, which appears at the end of your dashboard URL. For example, if your dashboard URL is `https://dashboard.mintlify.com/org-name/domain-name`, your domain name is `domain-name`
+- An [assistant API key](https://dashboard.mintlify.com/settings/organization/api-keys)
+- Node.js v18 or higher and npm installed
+- Basic React knowledge
+
+### Get your assistant API key
+
+1. Navigate to the [API keys](https://dashboard.mintlify.com/settings/organization/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.
+
+
+ The assistant API key is a public token that can be used in frontend code. Calls using this token count toward your plan's message allowance and can incur overages.
+
+
+## Set up the example
+
+The quickest way to get started is to clone the [example repository](https://github.com/mintlify/assistant-embed-example) and customize it for your needs.
+
+
+
+
+```bash
+git clone https://github.com/mintlify/assistant-embed-example.git
+cd assistant-embed-example
+npm install
+```
+
+
+
+
+Open `src/config.js` and update with your Mintlify project details:
+
+```js src/config.js
+export const ASSISTANT_CONFIG = {
+ domain: 'your-domain',
+ docsURL: 'https://yourdocs.mintlify.app',
+};
+```
+
+Replace:
+- `your-domain` with your Mintlify project domain found at the end of your dashboard URL.
+- `https://yourdocs.mintlify.app` with your actual documentation URL.
+
+
+
+
+Create a `.env` file in the project root:
+
+```bash .env
+VITE_MINTLIFY_TOKEN=mint_dsc_your_token_here
+```
+
+Replace `mint_dsc_your_token_here` with your assistant API key.
+
+
+
+
+```bash
+npm run dev
+```
+
+Open your application in a browser and click the **Ask** button to open the assistant widget.
+
+
+
+
+## Project structure
+
+The example uses a component-based architecture.
+
+```text
+src/
+├── App.css # App styles
+├── App.jsx # Main app component that renders the widget
+├── config.js # Configuration (domain and docsURL)
+├── index.css # Global styles
+├── main.jsx # Entry point
+├── utils.js # Helper functions for parsing suggestions and extracting sources
+└── components/
+ ├── AssistantWidget.jsx # Main widget component with chat state and API logic
+ └── Message.jsx # Individual message component for rendering user and assistant messages
+```
+
+**Key files:**
+
+- **`src/App.jsx`**: Main app component. Shows how to import and use the `AssistantWidget` component.
+- **`src/config.js`**: Centralized configuration. Update this file with your domain and docs URL.
+- **`src/components/AssistantWidget.jsx`**: The main widget component. Manages the open/close state, chat messages, and API calls.
+- **`src/utils.js`**: Contains utility functions for parsing the assistant's response format and extracting sources.
+- **`src/components/Message.jsx`**: Renders individual messages with support for Markdown and suggestion links.
+
+## 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 (
+
+ {/* message content */}
+ {sources.length > 0 && (
+
+ )}
+
+ );
+})}
+```
+
+### Track conversation thread IDs
+
+Store thread IDs to maintain conversation history across sessions:
+
+```jsx
+import { useState, useEffect } from 'react';
+
+export function AssistantWidget({ domain, docsURL }) {
+ const [threadId, setThreadId] = useState(null);
+
+ useEffect(() => {
+ // Retrieve saved thread ID from localStorage
+ const saved = localStorage.getItem('assistant-thread-id');
+ if (saved) {
+ setThreadId(saved);
+ }
+ }, []);
+
+ const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
+ api: `https://api-dsc.mintlify.com/v1/assistant/${domain}/message`,
+ headers: {
+ 'Authorization': `Bearer ${import.meta.env.VITE_MINTLIFY_TOKEN}`,
+ },
+ body: {
+ fp: 'anonymous',
+ retrievalPageSize: 5,
+ ...(threadId && { threadId }), // Include thread ID if available
+ },
+ streamProtocol: 'data',
+ sendExtraMessageFields: true,
+ fetch: async (url, options) => {
+ const response = await fetch(url, options);
+ const newThreadId = response.headers.get('x-thread-id');
+ if (newThreadId) {
+ setThreadId(newThreadId);
+ localStorage.setItem('assistant-thread-id', newThreadId);
+ }
+ return response;
+ },
+ });
+
+ // ... rest of component
+}
+```
+
+### Add keyboard shortcuts
+
+Allow users to open the widget and submit messages with keyboard shortcuts:
+
+```jsx
+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]);
+```
diff --git a/images/assistant/assistant-embed-demo.gif b/images/assistant/assistant-embed-demo.gif
new file mode 100644
index 000000000..85594ede5
Binary files /dev/null and b/images/assistant/assistant-embed-demo.gif differ