Lucy AI/Docs
GitHubOpen app

Getting started

  • Introduction
  • Quick start
  • Desktop app

Using Lucy

  • Chat & models
  • Memory
  • Personas
  • Connectors
  • Workflows
  • Voice
  • Security & 2FA
  • Themes & account

Developers

  • Architecture
  • Self-hosting
  • Embedding Lucy
  • HTTP API
  • CLI
  • MCP server
  • Contributing

Embedding Lucy

Lucy drops into any project you own — three doors, increasing depth.

1. The one-line widget

<script src="https://lucy.your-company.com/api/embed?project=your-app" async></script>

That's the whole integration: a floating Lucy chat bubble appears in your app. The loader is served by GET /api/embed. It injects a <div id="lucy-widget-root"> and an iframe pointing at Lucy's /embed page — so Lucy runs in its own document and never collides with your app's React, styles, or globals. It also sets window.__LUCY_ORIGIN__ so chat requests go back to your Lucy deployment.

Query params (all optional):

ParamValuesDefault
projectA registered integration id — gives Lucy your business context(none)
modelDefault model id, e.g. gpt-4ogpt-4o
positionbottom-right, bottom-left, inlinebottom-right
themedark, light, autodark

Inputs are sanitised server-side; unknown position/theme values fall back to the defaults. The loader is cached for 5 minutes and sent with Access-Control-Allow-Origin: *, so the one script tag works from any origin.

The injected iframe is a fixed 340×520 panel in the chosen bottom corner (position only chooses the corner here — bottom-left snaps it left, anything else right). inline is a layout mode for the React component below, not the floating loader.

Import the React component directly

In a Next.js app that shares Lucy's source you can skip the loader and render the widget yourself:

import { LucyWidget } from '@/components/embed/LucyWidget';

<LucyWidget projectId="your-app" position="inline" theme="dark" height="520px" />
PropTypeDefaultNotes
projectIdstring—Registered integration id
position'bottom-right' | 'bottom-left' | 'inline'bottom-rightinline renders flat (no bubble)
theme'dark' | 'light' | 'auto'dark
defaultModelstringgpt-4o
heightstring480pxCSS length, e.g. 520px or 100vh
onAction(action, params) => void—Fires when Lucy proposes a host-app action

The /embed page (what the loader's iframe loads) renders a full-height <LucyWidget position="inline" height="100vh" />, passing the project, theme, and model URL params straight through to the component.

2. Shared authentication

If your app and Lucy share the same Supabase project, your users are already signed in to Lucy — the widget and the full app pick up the session cookie. One auth, your whole stack.

3. Business context (integration registry)

Register your app's schema and actions so Lucy answers with live data. Call registerProject once at startup (the way the built-in contractors-room integration does):

import { registerProject } from '@/lib/integrations/registry';

registerProject({
  id: 'your-app',
  name: 'Your App',
  description: 'What this app is for — used in the AI context',
  supabaseSchema: 'your_schema', // prefixes table queries; per-table `schema` overrides it
  tables: [
    {
      name: 'orders',
      description: 'Customer orders',
      accessPolicy: 'user', // 'user' (scoped to the caller) | 'public' | 'admin' (skipped in context)
      columns: [
        { name: 'id', type: 'integer', description: 'Order ID' },
        { name: 'status', type: 'text', description: 'Order status' },
      ],
    },
  ],
  actions: [
    {
      id: 'create-note',
      name: 'Create note',
      description: 'Add a note to an order',
      parameters: [
        { name: 'order_id', type: 'number', required: true, description: 'Order ID' },
        { name: 'text', type: 'string', required: true, description: 'Note body' },
      ],
      handler: 'supabase-insert',
      config: { table: 'notes', schema: 'your_schema' },
    },
  ],
});

id, name, description, tables, and actions are required; tables need an accessPolicy and typed columns; actions need parameters, a handler, and handler-specific config. The registry is in-memory — registrations live for the life of the process and re-registering the same id replaces it.

When a chat request carries that projectId, Lucy reads up to ~10 rows from each registered table (within a token budget), summarises them, and prepends that context to the system prompt — so "how many open orders?" gets a real answer. accessPolicy: 'user' tables are filtered to the caller (by a user_id or sender_id column); admin tables are skipped. If Supabase isn't configured, Lucy still gets a schema-only summary so it knows what data could exist.

Action handlers let Lucy act, not just read:

HandlerDoes
supabase-insertInserts a row (auto-injects the caller's id as sender_id if absent)
supabase-updateUpdates rows matching config.matchColumn
api-callPOSTs the params to config.endpoint
workflowTriggers a Lucy workflow by config.workflowId

The identity used for context and inserts is resolved server-side from the session (or Lucy API key) — the request body's user id is never trusted.

Self-hostingHTTP API