Skip to main content

Installation

Choose your preferred installation method:
Add the script before </body>:
<script
  src="https://dashboard.ourguide.ai/ourguide-b2b-widget.iife.js"
  data-api-url="https://dashboard.ourguide.ai"
  data-product-id="YOUR_PRODUCT_ID"
  data-agent-name="Your Agent Name"
></script>
AttributeRequiredDescription
data-api-urlYeshttps://api.ourguide.ai
data-product-idYesYour product ID from dashboard
data-agent-nameNoDisplay name (defaults to “Assistant”)
After the script loads, use window.ourguide() to pass functions and objects:
CommandDescription
setIdentityTokenFetcherAuto-refreshing JWT function. See Identity
setContextPage context object. See Context
onToolResultCallback on tool completion. See Callbacks
registerToolRenderersCustom inline visuals. See Renderers
identifyManual JWT token (alternative to fetcher)
registerToolsClient-side tool handlers

Find Your Product ID

  1. Go to dashboard.ourguide.ai/dashboard/deploy/widget
  2. Copy your Product ID

User Identity

Authenticate users for persistent conversations and user-specific actions.
Option A — Auto-refresh (recommended). Pass a function that fetches a JWT. The SDK calls it on mount and re-calls it automatically when the token expires (401):
window.ourguide('setIdentityTokenFetcher', async () => {
  const res = await fetch('/api/ourguide-token');
  const { token } = await res.json();
  return token;
});
Option B — Manual. Pass a static token (you handle refresh yourself):
window.ourguide('identify', {
  token: 'jwt-from-your-backend',  // Required
  name: 'John Doe'                  // Optional
})

// On logout
window.ourguide('resetUser')
See Identity Verification for backend JWT setup.

Client-Side Tools

Register tools the agent can call. The return value from your handler is sent back to the agent, so it can confirm success or handle errors intelligently. See Client-Side Tools for full setup.
window.ourguide('registerTools', {
  navigateToPage: async ({ path }) => {
    router.push(path)
    return { success: true }  // Agent sees this result
  },

  showUpgradeModal: async () => {
    document.getElementById('upgrade-modal').showModal()
    return { shown: true }
  }
})
Return meaningful results from your tools. The agent uses these to provide accurate feedback like “Done! I’ve navigated you to Settings” or handle errors gracefully.

Tool Result Callbacks

React to server-side tool completions — refresh data, show a diff UI, dispatch events, etc.
window.ourguide('onToolResult', (toolName, result) => {
  if (toolName === 'propose_revision') {
    showDiffUI(result);
  }
});

Tool Renderers

Render custom visuals inline in the chat when a tool completes — data cards, tables, charts, and more. Your renderer receives { result, status } and returns one of:
Return typeUse case
HTML stringData cards, tables, simple visuals
HTMLElementCharts via Chart.js, D3, ECharts, Plotly
ReactNodeFull React components (React SDK only)

Data Card

Show key metrics with change indicators:
window.ourguide('registerToolRenderers', {
  query_analytics_chart: ({ result }) => {
    const r = result || {};
    const color = r.changePercent > 0 ? '#059669' : '#ef4444';
    const arrow = r.changePercent > 0 ? '↑' : '↓';
    return `<div style="padding:12px; border:1px solid #e2e8f0; border-radius:8px; background:#fff; font-family:system-ui,sans-serif;">
      <div style="font-size:13px; font-weight:600; color:#0f172a;">${r.title || 'Metric'}</div>
      <div style="font-size:24px; font-weight:700; color:#2563eb; margin-top:4px;">${r.currentValue || 'N/A'}</div>
      ${r.changePercent != null
        ? `<div style="font-size:12px; color:${color};">${arrow} ${Math.abs(r.changePercent).toFixed(1)}%</div>`
        : ''}
      ${r.dashboard_url
        ? `<a href="${r.dashboard_url}" target="_blank" style="font-size:11px; color:#2563eb; text-decoration:none; margin-top:8px; display:inline-block;">View full chart ↗</a>`
        : ''}
    </div>`;
  }
});

Table

Display structured data in rows and columns:
window.ourguide('registerToolRenderers', {
  query_data_table: ({ result }) => {
    const r = result || {};
    const headers = r.columns || [];
    const rows = r.rows || [];
    return `<div style="border:1px solid #e2e8f0; border-radius:8px; overflow:hidden; font-family:system-ui,sans-serif;">
      <table style="width:100%; border-collapse:collapse; font-size:12px;">
        <thead>
          <tr style="background:#f8fafc;">
            ${headers.map(h =>
              '<th style="padding:8px 12px; text-align:left; font-weight:600; border-bottom:1px solid #e2e8f0;">' + h + '</th>'
            ).join('')}
          </tr>
        </thead>
        <tbody>
          ${rows.map(row =>
            '<tr style="border-bottom:1px solid #f1f5f9;">' +
              row.map(cell =>
                '<td style="padding:6px 12px; color:#334155;">' + cell + '</td>'
              ).join('') +
            '</tr>'
          ).join('')}
        </tbody>
      </table>
    </div>`;
  }
});

Chart (Chart.js, D3, ECharts)

For interactive charts, return an HTMLElement instead of a string. The widget mounts it directly:
<!-- Load Chart.js (or any charting library) -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<script>
window.ourguide('registerToolRenderers', {
  query_analytics_chart: ({ result }) => {
    const canvas = document.createElement('canvas');
    canvas.style.width = '100%';
    canvas.style.maxHeight = '200px';
    new Chart(canvas, {
      type: 'line',
      data: {
        labels: result.data.map(d => d.date),
        datasets: [{
          label: result.title,
          data: result.data.map(d => d.value),
          borderColor: '#2563eb',
          tension: 0.3,
        }]
      },
      options: { responsive: true, plugins: { legend: { display: false } } }
    });
    return canvas;  // Return the DOM element
  }
});
</script>
Timing: window.ourguide() must be called after ourguide-widget.js has loaded. In plain HTML this happens naturally (scripts execute in order). In Next.js, use onLoad:
<Script
  src="https://api.ourguide.ai/static/ourguide-widget.js"
  data-product-id="YOUR_PRODUCT_ID"
  data-api-url="https://api.ourguide.ai"
  strategy="afterInteractive"
  onLoad={() => {
    window.ourguide('registerToolRenderers', { ... });
  }}
/>
The widget uses Shadow DOM for CSS isolation. Use inline style attributes — Tailwind classes and external CSS won’t apply inside the widget.

Quick Q&A

Some users find it more intuitive to read setup instructions in Q&A format. Here’s everything above as quick answers:
Install the SDK and drop in one component:
npm install @ourguide-ai/client @ourguide-ai/ui
<OurguideWidget
  productId="YOUR_PRODUCT_ID"
  apiUrl="https://dashboard.ourguide.ai"
/>
That’s it. Chat works anonymously out of the box. No provider wrapping needed.
Two steps:
  1. Create a backend endpoint (~15 lines) that reads your existing session and mints a JWT signed with your Ourguide verification secret. Ourguide never sees your auth system — the JWT is the bridge.
  2. Pass it as a prop:
<OurguideWidget
  productId="YOUR_PRODUCT_ID"
  apiUrl="https://dashboard.ourguide.ai"
  getIdentityToken={async () => {
    const res = await fetch('/api/ourguide-token');
    const { token } = await res.json();
    return token;
  }}
/>
The SDK calls this on mount and auto-refreshes when the token expires. No useEffect, no setInterval, no manual calls.This enables: conversation history, personalized responses, and user-specific tool actions.See Identity Verification for the backend JWT setup.
Pass a context object. It’s reactive — when it changes (e.g., user navigates), the AI automatically knows.
const context = useMemo(() => {
  const match = pathname.match(/\/items\/([^/]+)/);
  return match ? { itemId: match[1] } : {};
}, [pathname]);

<OurguideWidget
  ...
  context={context}
/>
This gets injected into the AI’s system prompt so it can perform page-aware actions like “edit this item” without asking which one.
React SDK — use onToolResult and toolRenderers as props:
<OurguideWidget
  onToolResult={(toolName, result) => {
    if (toolName === 'propose_revision') showDiffUI(result);
  }}
  toolRenderers={{
    query_chart: ({ result }) => <MyChart data={result.data} />,
  }}
/>
Script Tag — use window.ourguide():
window.ourguide('onToolResult', (toolName, result) => {
  if (toolName === 'propose_revision') showDiffUI(result);
});

window.ourguide('registerToolRenderers', {
  query_chart: ({ result }) => `<div>${result.title}: ${result.value}</div>`,
});
Everything is a prop on one component:
<OurguideWidget
  productId="..."
  apiUrl="https://dashboard.ourguide.ai"
  getIdentityToken={fetchToken}
  navigate={(path) => router.push(path)}
  onToolResult={(name, result) => ...}
  toolRenderers={{ ... }}

/>
No providers, no initializers, no polling.

Troubleshooting

IssueSolution
Widget not appearingCheck Product ID, verify script/component is rendered
Console errorsCheck apiUrl is correct
CORS errorsContact support to whitelist your domain