Skip to content

ResearchAI Embed Integration Guide

This document describes how to integrate ResearchAI as an embeddable widget using an iframe in external applications.

Overview

ResearchAI provides an embed mode that allows the research assistant to be embedded as an iframe widget within other applications. The embed mode uses postMessage API for parent-child communication and provides a streamlined UI optimized for embedded contexts.

Routes

Route Description
/research-ai/embed Entry point for creating new research chats
/research-ai/embed/:token Displays an existing chat by token
/research-ai/embed/demo Demo page for testing embed integration
/research-ai/chat/summary Creates a chat from an AI summary token

Demo Page

Visit /research-ai/embed/demo to test all embed patterns interactively. The demo page provides buttons for each integration pattern and displays an event log showing postMessage communication.

Quick Start

Basic iframe Implementation

<iframe
  src="https://your-domain.com/research-ai/embed?q=your+search+query&locale=en&embed=true"
  title="Research AI Widget"
  style="width: 100%; height: 600px; border: 1px solid #ccc; border-radius: 8px;"
  sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation allow-top-navigation-by-user-activation allow-downloads"
></iframe>

URL Parameters

Entry Point (/research-ai/embed)

Parameter Type Required Description
q string No The initial research question. If provided, automatically creates a new chat and redirects to the token route.
contentId string No A Statista content ID (e.g., statistic ID) to attach as context to the new chat. Only used when q is also provided.
locale string No Locale for the interface (en, de). Defaults to detected locale or en.
embed string Yes Must be set to "true" to enable embed mode styling.

Summary Route (/research-ai/chat/summary)

Use this route to create a chat from an existing AI summary.

Parameter Type Required Description
token string Yes The cache key from an AI summary. Creates a chat from the summary.
q string No A question to send immediately after creating the chat from the summary.
embed string No Set to "true" to redirect to embed mode after chat creation.
origin string No Origin identifier for tracking purposes.

Token Route (/research-ai/embed/:token)

Parameter Type Required Description
q string No A follow-up question to send to an existing chat.
locale string No Locale for the interface.
embed string Yes Must be set to "true" to enable embed mode styling.

Parent-Child Communication

The embed widget communicates with the parent window using the postMessage API.

Message Types

research-ai:chat-created

Sent from the iframe to the parent window when a new chat is created or when navigating to an existing chat.

type EmbedChatCreatedPayload = {
  type: "research-ai:chat-created";
  chatId: string; // The unique chat token/ID
  question?: string | null; // The question that was asked (if available)
  timestamp: number; // Unix timestamp when the message was sent
};

research-ai:close

Sent from the iframe to the parent window when the user clicks the close button in embed mode. The parent application should listen for this message to close the modal/iframe container.

type EmbedClosePayload = {
  type: "research-ai:close";
  messageCount: number; // The number of messages in the chat when the user closed
  timestamp: number; // Unix timestamp when the message was sent
};

research-ai:content-loaded

Sent from the iframe to the parent window when the embed content has finished loading. The parent application should listen for this message to hide any loading indicators shown while the iframe was loading.

type EmbedContentLoadedPayload = {
  type: "research-ai:content-loaded";
  timestamp: number; // Unix timestamp when the message was sent
};

Listening for Messages (Parent Window)

window.addEventListener("message", (event) => {
  // Validate origin for security
  if (event.origin !== "https://your-domain.com") {
    return;
  }

  const data = event.data;

  // Handle chat created
  if (data?.type === "research-ai:chat-created") {
    console.log("Chat created:", data.chatId);
    console.log("Question:", data.question);

    // Store the chatId for future interactions
    localStorage.setItem("research-ai:embed:last-chat-id", data.chatId);
  }

  // Handle close request
  if (data?.type === "research-ai:close") {
    console.log("Close requested by embed");
    console.log("Message count:", data.messageCount);
    // Close your modal/iframe container here
    closeModal();
  }

  // Handle content loaded - hide loading spinner
  if (data?.type === "research-ai:content-loaded") {
    console.log("Embed content loaded");
    // Hide your loading spinner here
    hideLoadingSpinner();
  }
});

Usage Patterns

Pattern 1: New Question

Opens the widget with a fresh question, creating a new chat session:

const question = "What are the latest e-commerce trends in Germany?";
const iframeSrc = `/research-ai/embed?q=${encodeURIComponent(question)}&embed=true`;

document.getElementById("research-iframe").src = iframeSrc;

Pattern 2: Resume Existing Chat

Navigate to an existing chat using its token:

const chatToken = "abc123-def456-ghi789";
const iframeSrc = `/research-ai/embed/${chatToken}?embed=true`;

document.getElementById("research-iframe").src = iframeSrc;

Pattern 3: Send Follow-up Question

Send a follow-up question to an existing chat:

const chatToken = "abc123-def456-ghi789";
const followUpQuestion = "How does this compare to last year?";
const iframeSrc = `/research-ai/embed/${chatToken}?q=${encodeURIComponent(followUpQuestion)}&embed=true`;

document.getElementById("research-iframe").src = iframeSrc;

Pattern 4: Open Without Question (Login/Empty State)

Open the widget without a question to show a welcome or login state:

const iframeSrc = `/research-ai/embed?embed=true`;

document.getElementById("research-iframe").src = iframeSrc;

Pattern 5: Create Chat from AI Summary

Create a chat using a summary token (cache key) from an existing AI summary:

const summaryToken = "summary-cache-key-123";
const iframeSrc = `/research-ai/chat/summary?token=${encodeURIComponent(summaryToken)}&embed=true`;

document.getElementById("research-iframe").src = iframeSrc;

Optionally include a question to send immediately after creating the chat:

const summaryToken = "summary-cache-key-123";
const question = "What are the key insights from this data?";
const iframeSrc = `/research-ai/chat/summary?token=${encodeURIComponent(summaryToken)}&q=${encodeURIComponent(question)}&embed=true`;

document.getElementById("research-iframe").src = iframeSrc;

This pattern is useful when the parent application has already generated an AI summary (e.g., from search results) and wants to continue the conversation in the ResearchAI widget. The route shows a loading state while processing, then redirects to /research-ai/embed/:token.

Pattern 6: New Question with Content Context

Opens the widget with a question and attaches a Statista content (e.g., statistic) as context. The AI will use the referenced content to provide a more relevant answer:

import { useState } from "react";

const BASE_URL = "https://your-domain.com";

function StatisticWidget({ statisticId }: { statisticId: string }) {
  const [iframeSrc, setIframeSrc] = useState("");
  const [isOpen, setIsOpen] = useState(false);

  const askAboutStatistic = (question: string) => {
    const src = `${BASE_URL}/research-ai/embed?q=${encodeURIComponent(question)}&contentId=${encodeURIComponent(statisticId)}&embed=true`;
    setIframeSrc(src);
    setIsOpen(true);
  };

  return (
    <>
      <button
        onClick={() =>
          askAboutStatistic("What are the key insights from this statistic?")
        }
      >
        Ask Research AI
      </button>

      {isOpen && (
        <iframe
          src={iframeSrc}
          title="Research AI Widget"
          style={{ width: "100%", height: "600px" }}
          sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation allow-top-navigation-by-user-activation"
        />
      )}
    </>
  );
}

The contentId parameter tells ResearchAI to attach the referenced content as context to the chat. This allows the AI to ground its response on the specific statistic data, providing more accurate and relevant answers.

Note: contentId only takes effect when q is also provided. It has no effect on its own without a question.

Complete Integration Example (React)

import { useCallback, useEffect, useState } from "react";

const STORAGE_KEY = "research-ai:embed:last-chat-id";
const BASE_URL = "https://your-domain.com"; // Replace with actual domain

type EmbedChatCreatedMessage = {
  type: "research-ai:chat-created";
  chatId: string;
  question?: string | null;
};

type EmbedCloseMessage = {
  type: "research-ai:close";
  messageCount: number;
};

type EmbedMessage = EmbedChatCreatedMessage | EmbedCloseMessage;

export function ResearchAIWidget() {
  const [isOpen, setIsOpen] = useState(false);
  const [chatId, setChatId] = useState<string | null>(null);
  const [iframeSrc, setIframeSrc] = useState("");

  // Load stored chat ID on mount
  useEffect(() => {
    const stored = localStorage.getItem(STORAGE_KEY);
    if (stored) setChatId(stored);
  }, []);

  // Listen for messages from iframe
  useEffect(() => {
    const handleMessage = (event: MessageEvent<EmbedMessage>) => {
      if (event.origin !== BASE_URL) return;

      // Handle close request
      if (event.data?.type === "research-ai:close") {
        setIsOpen(false);
        return;
      }

      // Handle chat created
      if (event.data?.type === "research-ai:chat-created") {
        const { chatId: newChatId } = event.data;
        setChatId(newChatId);
        localStorage.setItem(STORAGE_KEY, newChatId);
      }
    };

    window.addEventListener("message", handleMessage);
    return () => window.removeEventListener("message", handleMessage);
  }, []);

  const openWidget = useCallback(
    (options?: {
      question?: string;
      summaryToken?: string;
      contentId?: string;
    }) => {
      let src: string;

      if (options?.summaryToken) {
        // Create chat from AI summary (optionally with a question)
        src = `${BASE_URL}/research-ai/chat/summary?token=${encodeURIComponent(options.summaryToken)}&q=${encodeURIComponent(options.question || "")}&embed=true`;
      } else if (options?.question && options?.contentId) {
        // New question with content context (e.g., statistic reference)
        src = `${BASE_URL}/research-ai/embed?q=${encodeURIComponent(options.question)}&contentId=${encodeURIComponent(options.contentId)}&embed=true`;
      } else if (chatId) {
        // Resume existing chat
        src = `${BASE_URL}/research-ai/embed/${chatId}?embed=true`;
      } else {
        // Empty state
        src = `${BASE_URL}/research-ai/embed?embed=true`;
      }

      setIframeSrc(src);
      setIsOpen(true);
    },
    [chatId],
  );

  const sendFollowUp = useCallback(
    (question: string) => {
      if (!chatId) {
        openWidget({ question });
        return;
      }

      setIframeSrc(
        `${BASE_URL}/research-ai/embed/${chatId}?q=${encodeURIComponent(question)}&embed=true`,
      );
      setIsOpen(true);
    },
    [chatId, openWidget],
  );

  if (!isOpen) {
    return (
      <div className="flex gap-2">
        <button onClick={() => openWidget()}>Ask Research AI</button>
        <button
          onClick={() =>
            openWidget({
              question: "What are the key insights from this statistic?",
              contentId: "12345",
            })
          }
        >
          Ask about Statistic
        </button>
        <button
          onClick={() => openWidget({ summaryToken: "your-summary-token" })}
        >
          Open from Summary
        </button>
        <button
          onClick={() =>
            openWidget({
              summaryToken: "your-summary-token",
              question: "What are the key insights?",
            })
          }
        >
          Summary with Question
        </button>
        <button
          onClick={() => sendFollowUp("How does this compare to last year?")}
        >
          Send Follow-up
        </button>
      </div>
    );
  }

  return (
    <div className="z-50 bg-black/50 fixed inset-0 flex items-center justify-center">
      <div className="rounded-xl bg-white relative h-[80vh] w-[90vw] max-w-6xl p-4">
        <button
          onClick={() => setIsOpen(false)}
          className="absolute right-4 top-4"
        >
          
        </button>
        <iframe
          src={iframeSrc}
          title="Research AI Widget"
          className="border h-full w-full rounded-lg"
          sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation allow-top-navigation-by-user-activation allow-downloads"
        />
      </div>
    </div>
  );
}

Sandbox Attributes

The recommended sandbox attributes for the iframe are:

Attribute Purpose
allow-same-origin Required for authentication cookies and storage
allow-scripts Required for JavaScript execution
allow-forms Required for form submissions (questions, feedback)
allow-popups Required for opening links in new tabs
allow-top-navigation Required for redirect flows (authentication)
allow-top-navigation-by-user-activation Safer navigation triggered by user action
allow-downloads Required for CSV and file downloads
sandbox="allow-same-origin allow-scripts allow-forms allow-popups
allow-top-navigation allow-top-navigation-by-user-activation allow-downloads"

Authentication

The embed routes use the same authentication mechanism as the main application:

  • Authenticated users: Full access to research features with user limits applied
  • Unauthenticated users: Redirected to the embed entry point (may trigger login flow)

The authentication state is determined by SSO cookies, so users logged into the main Statista platform will automatically be authenticated in the embed widget.

Styling & Layout

When embed=true is set:

  • The UI is optimized for iframe display with full-height layout
  • Navigation elements and headers are minimized
  • Toast notifications appear at the top of the embed container
  • Sources panel is available with expandable/collapsible functionality
  • Chat history sidebar is available with animated expand/collapse

Header Action Buttons

The embed header includes the following action buttons:

Button Visibility Description
Show/Hide Sources Always Toggles the sources panel
History Mobile only Toggles the chat history sidebar
Fullscreen Always Opens the chat in a new tab (full ResearchAI)
Close Always Sends research-ai:close postMessage to parent

Supported Locales

Code Language
en English (default)
de German

Error Handling

Scenario Behavior
Invalid token Redirects to /research-ai/embed?embed=true
Unauthenticated user Redirects to /research-ai/embed?embed=true
API error Redirects to /research-ai/chat (main app)
Rate limit reached Shows user limit modal within embed

Best Practices

  1. Always set embed=true: This query parameter enables embed-specific styling and behavior.

  2. Store chat IDs: Listen for research-ai:chat-created messages and store the chatId to enable chat resumption.

  3. Validate message origin: Always check event.origin when handling postMessage events.

  4. Handle authentication: Users must be logged in to use the research features. Consider implementing a login prompt in your parent application.

  5. Responsive sizing: Use percentage-based or viewport-relative sizing for the iframe to ensure it adapts to different screen sizes.

  6. URL encode questions: Always use encodeURIComponent() when passing questions as URL parameters.

Troubleshooting

Iframe not loading

  • Verify the sandbox attributes are correctly set
  • Check browser console for CORS or CSP errors
  • Ensure the domain is allowed in any Content Security Policy

Messages not being received

  • Confirm you're listening on the correct origin
  • Verify the message type matches research-ai:chat-created
  • Check that the iframe is from the expected domain

Authentication issues

  • Ensure cookies are allowed for cross-origin iframes
  • Check if third-party cookie blocking is affecting the SSO flow
  • Verify the user is logged into the main Statista platform

Chat not resuming

  • Verify the stored chatId is valid
  • Check if the chat session has expired
  • Ensure the token URL is correctly formatted