ARVEXA
← WorkAI Agents

RAG-Based AI Chatbot

A multi-tenant, white-label AI chatbot platform for websites. One codebase serves every customer, and each gets a branded widget that answers questions using retrieval-augmented generation (RAG) over their own uploaded documents.

Client
Solo project
Next.js 14Supabase PostgresSupabase pgvectorSupabase AuthOpenAI text-embedding-3-smallClaude (Anthropic)Stripe SubscriptionsTailwind CSSshadcn/uiZodJestPlaywright
RAG-Based AI Chatbot screenshot
01

The Problem

The goal was a chatbot that knows a customer's business, matches their brand, and lives on their website, sold as a single multi-tenant platform instead of a bespoke build for every customer.

02

The Starting Point

Built from scratch as a multi-tenant SaaS platform from day one, so branding, billing, and per-tenant data isolation are part of the core architecture instead of being retrofitted onto a single-customer chatbot later.

03

The Solution

Each tenant gets a branded chat widget (colors, logo, name, position), their own uploaded knowledge base (PDF, DOCX, TXT, web pages), their own system prompt, and either the platform's shared Anthropic key or their own key on the top plan. The branding runs deeper than a color swap. A demo travel site's widget introduces itself as "Journey, Driftwood's trip planner" in a coral-branded header, while a demo cloud-hosting site's widget is "Byte," a purple-branded support bot with a completely different opening line. Same underlying widget, two distinct products. The widget ships in two parts: a tiny (~8KB) vanilla JS snippet that customers paste into their site, which injects a shadow-DOM iframe for CSS/DOM isolation from the host page, and the chat UI itself, which lives inside that iframe. A retrieval-augmented generation pipeline embeds each user message, retrieves the top five tenant-scoped chunks by cosine similarity from pgvector, and streams a Claude response back over SSE.

04

Architecture

widget.js (~8KB): embed snippet loading a shadow-DOM iframe
/app/widget/[tenant]: chat UI, isolated inside the iframe
/api/chat: RAG pipeline, embed, retrieve top 5 chunks, Claude (streamed)
search_web tool: fallback for questions outside the uploaded knowledge base
/api/widget-config: public branding only, open-CORS by design
Supabase Postgres + pgvector: tenant data and vector search
Stripe Subscriptions: plan billing and usage gating
05

Engineering Work

Multi-tenant isolation is enforced at the database layer, not just in application code. Row Level Security is enabled on every Supabase table, anonymous reads are blocked by default, and every API route re-verifies the authenticated tenant_id server-side, so cross-tenant data access is architecturally impossible rather than merely checked for. The widget's security model is deliberately layered. allowed_domains drives a Content-Security-Policy frame-ancestors header on the widget route itself, which is the actual enforced boundary, applied by the browser, so an unlisted site simply cannot render the widget. /api/chat separately validates the request's Origin and cross-checks it against that same allowed-domains list for attribution. /api/widget-config is intentionally left open-CORS, since it only returns public branding data with no secrets and has to be fetchable before any iframe exists; the real cost and abuse surface is /api/chat, which is where the strict checks live. Known gaps are tracked rather than hidden: the BYOK Anthropic API key is currently stored in plaintext, flagged as needing a key-management decision before Scale-plan customers rely on it, and the internal /api/admin/* routes still go through an unnecessary HTTP hop slated to collapse into direct server-side calls.

06

AI Contribution

Retrieval-augmented generation runs over each tenant's own uploaded documents: chunked into 512-token segments with 50-token overlap, embedded with OpenAI's text-embedding-3-small (1536 dimensions), and retrieved at query time via tenant-scoped cosine similarity in pgvector. The retrieved chunks, the tenant's own system prompt, and recent message history are assembled into a single prompt and answered by Claude, streamed back to the widget over SSE. Claude also has a search_web tool available for questions the tenant's own uploaded documents don't cover. Each call is logged with its input, result, and elapsed time, an 18-match web search resolving in 342ms, for example, giving that fallback path the same auditability as the RAG retrieval step.

07

Results

Phase 1 (core RAG pipeline, embed widget, admin tenant/document management, message logging) and most of Phase 2 (Stripe billing and webhooks, plan-based rate limiting, an admin analytics dashboard, document management) are built; onboarding email automation is the one Phase 2 item still open. Phase 3 (a customer-facing self-serve portal, custom domains, webhook notifications, prompt A/B testing) hasn't started yet. Roughly 80 files at MVP: 6 API routes, 12 React components, 8 lib utilities, 6 test files, 4 migrations.

08

Final Product

RAG-Based AI Chatbot screenshot
RAG-Based AI Chatbot screenshot

Have a project like this one?