FodyChat · Developer docs

Integration guide

From chat to app actions: embedding FodyChat safely

A chatbot can suggest a destination, but it cannot declare that a driver has accepted a ride. Here is how to connect a chat widget to an application without letting conversation become business state.

Start with an ordinary website embed

Create a project in the FodyChat dashboard, add your FAQ or other approved sources, and allow your site's exact origin in the project settings. Then paste the embed snippet on your site:

<script src="https://chat.fodylabs.com/chat-engine.js" defer></script>
<script>
  window.addEventListener('load', function () {
    ChatEngine.mount({
      endpoint: 'https://chat.fodylabs.com/api/chat',
      historyEndpoint: 'https://chat.fodylabs.com/api/history',
      title: 'Trip assistant',
      payload: { project: 'YOUR_PROJECT_ID', key: 'pub_YOUR_PUBLIC_KEY' }
    });
  });
</script>

The pub_… key is publishable, not a secret or an authorization token. The widget loads its main interface on demand. If you only need answers from text or documents, you can stop here.

Follow the host site's theme

The widget follows prefers-color-scheme by default. If your site has its own light/dark switch, set data-theme="light" or data-theme="dark" on the root <html> element, then dispatch fodychat:themechange on document. Pass light and dark CSS-variable palettes in theme; the widget reapplies the matching palette without remounting or losing the conversation:

function setSiteTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  document.dispatchEvent(new CustomEvent('fodychat:themechange'));
}

ChatEngine.mount({
  endpoint: 'https://chat.fodylabs.com/api/chat',
  theme: {
    light: { '--chat-primary': '#4f46e5', '--chat-surface': '#ffffff', '--chat-text': '#1f2937' },
    dark: { '--chat-primary': '#818cf8', '--chat-surface': '#1e293b', '--chat-text': '#e2e8f0' }
  }
});

For all supported theme variables and a single-palette example, see the embed API documentation.

Let the host app own its actions

Suppose a visitor says, “Take me to Beau Vallon.” In a taxi app, preparing a route is useful; silently booking a ride is not. Register a narrowly scoped client action with a JSON schema. The model can propose the action, but the host code checks the arguments and decides what to do:

const chat = ChatEngine.mount({
  endpoint: 'https://chat.fodylabs.com/api/chat',
  historyEndpoint: 'https://chat.fodylabs.com/api/history',
  payload: { project: 'YOUR_PROJECT_ID', key: 'pub_YOUR_PUBLIC_KEY' },
  clientActions: [{
    name: 'prepareRoute',
    description: 'Find a destination and prepare a route. Never book a ride.',
    label: 'Prepare route to {destination}',
    confirm: false,
    parameters: {
      type: 'object',
      properties: { destination: { type: 'string' } },
      required: ['destination'],
      additionalProperties: false
    }
  }],
  onAction: async function (name, args) {
    if (name !== 'prepareRoute' ||
        !args || typeof args.destination !== 'string' ||
        !args.destination.trim() || args.destination.length > 120) {
      throw new Error('Invalid route request');
    }
    const places = await app.searchPlaces(args.destination.trim());
    if (!places.length) return 'No matching place. Please try another name.';
    app.showPlaceChoices(places); // The visitor chooses the actual destination.
    return 'Choose a destination on the map before booking.';
  }
});

The example's app.searchPlaces and app.showPlaceChoices are placeholders for your app's search and map UI. They are not FodyChat methods. An action with confirm: false is appropriate for preparing a route; for changes to persistent data, require explicit user confirmation in the app and enforce authorization on its backend.

Client actions need a project and publishable key on a plan that supports tools (Pro or higher). The project itself can stay in text or RAG mode; client actions and backend tools are different integrations. See the tools-mode API guide for connecting live backend endpoints.

Keep a draft separate from a real order

Our Taxi demo uses this boundary for excursions. The assistant helps find an excursion and collect the rider's date, exact time and passenger count. Taxi owns the selection, checks the input and shows the booking form. Saying “cash” in chat does not pay, submit, or confirm anything.

Only Taxi's order API can create an order. After submission, an offered order is waiting for a driver, not assigned. An accepted or in_progress order has a driver; an expired order means none accepted it. The assistant should read actual application state rather than repeat a confident-sounding earlier chat reply.

The model proposes. The visitor confirms. The host app validates, persists and reports the outcome. A client action result is not proof of booking or dispatch.

Handle multi-turn UI without losing the conversation

For interactive choices, onAction can return { render(container, complete) { … } } so the app can display its own card inside the conversation. Call complete(text) when the visitor chooses an option. The host can then use chat.reply(text, true) to continue the conversation and persist its reply. If a later message depends on a pending app step, getActionContext() supplies a short, ephemeral hint to the action router; the host still owns the actual draft and must validate every proposed argument.

In Taxi, the excursion catalog can remain open beside the chat. Clicking either pane brings it forward rather than erasing the other. That matters as much as the API: a visitor can check a route and ask a follow-up question without starting over.

GEO and MCP: reuse the knowledge where it helps

Connecting a site's knowledge to an AI chat is a practical starting point for generative engine optimization (GEO): visitors can ask in their own words and get answers grounded in the business's information. It reuses the same content and AI integration, so the additional infrastructure can be small.

That is not a promise that Google, ChatGPT or another external AI search engine will crawl, cite or recommend the site. The embedded widget runs in the visitor's browser; it does not rewrite the host site's pages or robots.txt, and external AI systems do not automatically inherit its knowledge. GEO work such as publishing crawler-readable content or auditing bot access is a separate website-level task.

When information should be available directly to compatible AI clients rather than only through the website chat, expose the relevant API as tools or an MCP server. We use a public MCP endpoint for Radioller's live station catalog. That endpoint is specific to Radioller; it does not mean every FodyChat project automatically gets its own MCP server. The right level depends on the job: make knowledge accessible to a site's visitors, expose live data to AI clients where useful, or keep things simple with a fast FAQ chat.

Try a small integration first

  1. Embed the chat and provide approved FAQ content.
  2. Add one read-only action, such as a place lookup; test ambiguous and misspelled input.
  3. Keep selections and pending steps in your app, not solely in chat history.
  4. For an actual booking, require a separate app confirmation and expose real order status to the assistant.

Explore the widget playground or read the API integration guide to go further.