Skip to main content

The complete Buildx manual.

Everything you need to configure, build, theme, and embed your own self-hosted AI chatbot — from the first API key to the last troubleshooting tip.

Quick start

Get a chatbot live on a website in five minutes.

  1. 1

    Deploy & sign in

    Follow the deploy guide to spin up Buildx on your own infra. Sign in with the ADMIN_EMAIL /ADMIN_PASSWORD you configured.

  2. 2

    Configure API keys

    In Settings, add your Gemini API key and Qdrant connection (URL + API key + collection name). Hit Test to verify both.

  3. 3

    Create a bot

    From My Bots → Create Bot, give it a name and an initial system prompt. Pick a Gemini model (Flash is a sensible default).

  4. 4

    Upload knowledge

    Switch to the Knowledge tab, drop a PDF / DOCX / TXT. Indexing is automatic — chunks land in Qdrant within seconds.

  5. 5

    Embed

    Copy the embed snippet from the bot editor and paste it before </body> on your site. Done.

Settings

Required keys and connection settings — environment variables + in-app fields.

Buildx stores credentials encrypted (AES-256-GCM) at the application level. Two integration points are required: a Gemini API key for the LLM & embeddings, and a Qdrant collection for vector search.

Environment variables

Variable Required Purpose
ADMIN_EMAIL Yes Dashboard login email.
ADMIN_PASSWORD Yes Dashboard login password.
AUTH_SECRET Yes Session JWT signing secret.
MONGODB_URI Yes MongoDB connection string. The DB name is forced to buildx.
ENCRYPTION_KEY Recommended 64-char hex string. If absent, stored API keys are kept in plaintext (warning logged).
Production checklist

Set ENCRYPTION_KEY before the first time you save a Gemini or Qdrant API key — keys saved without it will be stored in plaintext until re-saved.

In-app settings

  • Gemini API key — used for chat completion AND the 768-dim embedding model.
  • Qdrant URL — host URL of your Qdrant instance, e.g. https://your-cluster.cloud.qdrant.io.
  • Qdrant API key — encrypted at rest.
  • Qdrant collection name — created on first save (idempotent), 768-dim cosine.
  • The System Health widget polls /api/health every 30s — green dots mean Mongo is reachable and a Gemini key is on file.

Bots

Each bot is a separately-themed conversation surface with its own prompt, model, and knowledge base.

System prompt

The single most important setting. Be specific about role, scope, and tone.

Sharper prompts win

Instead of "You are a helpful assistant", write:

You are a Customer Support Agent for Acme Corp.
You ONLY answer questions about Acme products.
If asked anything else, politely redirect to support@acme.com.
Be concise (max 3 sentences) and professional.

Models

Flash

gemini-2.5-flash

Default. Best speed-to-quality. Use for support chat & FAQ.

Pro

gemini-2.5-pro

Highest quality. Slower + more expensive. For complex tasks.

Flash Lite

gemini-2.5-flash-lite

Fastest. Cheapest. Light-weight Q&A only.

Temperature

0.0–0.3 for factual support. 0.4–0.7 for general chat. 0.8–1.0 for creative writing. Default is 0.7.

IDs & status

  • Public ID — 12-char nanoid, used in the embed snippet and chat API. Never changes.
  • Internal ID — Mongo ObjectId. Used by the dashboard for editing.
  • Active toggle — soft-disables the bot. Disabled bots return 403 from the chat API.

Knowledge base (RAG)

Documents you upload are chunked, embedded, and retrieved at query time.

How it works

  1. Extract — text is pulled from the file (PDF via unpdf, DOCX via mammoth, TXT direct).
  2. Chunk — split greedily on newlines / spaces into overlapping windows (1000 chars, 200 overlap).
  3. Embed — each chunk becomes a 768-dim vector via Gemini's embedding API.
  4. Index — vectors are upserted into Qdrant with botId + sourceId payload filters.
  5. Retrieve — at chat time, the user's message is embedded and the top-3 chunks (cosine similarity) are appended to the system prompt.

Supported formats

.pdf · .docx · .txt · max 10 MB per file.

Tips for high-quality retrieval
  • Prefer well-structured documents (clear headings, short paragraphs).
  • One concept per chunk works best — avoid 50-page wall-of-text PDFs without sectioning.
  • The chat handler swallows RAG errors silently — if your bot stops citing your docs, check Qdrant connectivity in Settings.
Deletion cascade

Deleting a document drops its vectors from Qdrant + the KnowledgeBase row. Deleting a bot wipes everything: Qdrant points, KnowledgeBase rows, KnowledgeChunk rows, and any captured leads.

Design studio

A full-screen editor with live preview for theming the chat widget.

Open from the bot editor sidebar via Embed & Distribution → Design Studio →. Every change updates the iframe preview instantly via postMessage — saved to the database only when you press Save.

Sections

  • Global Settings — font family, corner radius (cascades to bubbles, input, card), shadow.
  • Header — title, subtitle text + font, icon (custom SVG, color, bg, size), refresh icon color.
  • Chat Window — page background, empty-state icon (custom SVG + colors), widget size (drives the iframe container).
  • Bot & User Message — bg, text, font (size/weight/family), border radius (preset + slider + per-corner mode), avatar.
  • Input Area & Footer — input background, text, border, placeholder text + color, send button.
  • Launcher — color, icon SVG, shape, border, size sliders, close-state colors.
  • Advanced — element visibility toggles, text overrides.

Lead capture

Bots can save user contact details into a leads inbox using Gemini function calling.

Enable on a bot via the editor's Tools tab. When the bot detects intent (pricing, demo, contact request), it asks for the required fields conversationally, then saves a Lead via the capture_lead tool. Captured leads appear in the dashboard's Leads inbox.

Configuration

  • Required fields — chips for name, email, phone. The bot won't save until it has these.
  • Qualification prompt — appended to the system prompt. Useful for BANT-style questions.
  • Duplicate window (hours) — same email + same bot inside this window updates the existing lead instead of creating a duplicate. Default 24h. Set to 0 to disable.

Three-layer dedup

  1. Model — system prompt instructs the bot to call capture_lead at most once per conversation.
  2. Conversation — every chat session has a UUID; subsequent calls in the same UUID merge into one lead row (DB-level unique partial index).
  3. Identity — within the dedup window, captures matching the same email update the existing row.
No new endpoints required

Lead capture rides on the existing POST /api/chat handler. Bots without the toggle on are unaffected.

Embedding

A single <script> tag drops the chat widget anywhere — no build step required.

Copy the snippet from the bot editor (Public ID copy button) and paste it before the closing </body> tag of your site:

<script
  src="https://your-buildx-domain.com/embed.js"
  data-bot-id="PUBLIC_ID_HERE">
</script>

What gets injected

  • A floating launcher button at bottom-right by default (positionable per bot).
  • A hidden iframe-container that loads /share/[publicId]?embed=true when the launcher is clicked.
  • All sizing, colors, icons, and shadows are driven by the saved theme — no data-* attributes to set.

Allowed domains

By default a bot is embeddable anywhere. To restrict it, add hostnames to Security → Allowed Domains. The check runs server-side both on the public config endpoint and on every chat request.

Testing locally

Embed against http://localhost:3000/embed.js while developing. The /share/* route sets frame-ancestors * in CSP so it works inside an iframe on any host.

Mobile

Below 480px viewport width, the iframe expands to fill the screen automatically. The launcher remains in its configured corner.

API reference

Public, CORS-enabled endpoints used by the embed widget — and available to your own integrations.

POST /api/chat

Stateless. Rate-limited (default 20 req / 60s per IP). CORS *.

curl -X POST https://your-buildx-domain.com/api/chat \
  -H "Content-Type: application/json" \
  -d '{
    "botId":          "PUBLIC_ID",
    "message":        "Hello",
    "history":        [],
    "conversationId": "<uuid-or-omit>"
  }'

# Response
{ "message": "...markdown text...", "model": "gemini-2.5-flash", "leadCaptured": false }

GET /api/bots/public/[publicId]

Returns the renderable bot config — no auth required. CORS *.

{ "config": {
  "publicId":       "...",
  "name":           "...",
  "theme":          { ... },
  "widgetPosition": "bottom-right",
  "allowedDomains": [],
  "isActive":       true
}}

GET /api/health

Liveness + service connectivity. No auth.

{ "status":"healthy", "uptime":123.4, "timestamp":"...",
  "services": {
    "database": { "status":"up", "latency":12 },
    "gemini":   { "status":"configured" }
  }
}
Domain enforcement

/api/chat uses substring match against Origin/Referer. /api/bots/public/[publicId] uses stricter hostname match. If you whitelist example.com, requests from example.com.evil.tld may pass /api/chat — keep the list tight.

Troubleshooting

The most common things that go wrong, and how to spot them.

Bot returns generic answers and ignores my uploaded docs

Check Settings → Qdrant connection ("Test" button). The chat handler logs RAG errors to the server console but continues without context — so the bot keeps replying, just without your knowledge. Verify the collection name matches and the API key isn't stale.

Embed snippet doesn't show a launcher

Open the host site's devtools console. Look for Bot CMS: errors. Common causes: (1) the public ID is wrong, (2) the bot is marked inactive, (3) Allowed Domains doesn't include this host.

Lead capture isn't firing

Verify the Tools tab toggle is on AND the system prompt fragment is appended (open the bot in design studio, the live preview shows it). Required fields must all be present. The model only calls capture_lead after detecting clear intent — try saying "I want a demo, my email is x@y.com".

Theme changes save but don't appear on the live widget

Browser caching. Hard-refresh the host page (the embed.js is served with no cache-busting by default). The iframe content updates without caching since it loads /share/[publicId] fresh each open.

"Rate limit exceeded" on chat

Default is 20 requests / 60s per IP. Behind a proxy, ensure x-forwarded-for is set correctly — without it, all traffic shares one bucket.

API keys stored in plaintext warning in logs

Set ENCRYPTION_KEY in your environment (64-char hex string). After setting, save the key in Settings again — it'll be encrypted on write.

Need more help?

Open an issue at github.com/MayonLabs/buildx/issues — we read every report.