Multi-tenant RAG chatbot for East African safari/trek operators.
- API — Next.js route handlers under
/api/v1/* - Client — chat UI at
/(deployed together on Vercel) - DB — Supabase Postgres + pgvector
- Embeddings — Gemini
embedding-001 - LLM — Groq Llama 3.1 (primary), Gemini fallback
If create-tenant fails with:
new row violates row-level security policy for table "tenants"
you are almost always using the anon key instead of the service_role key.
- Open Supabase → Project Settings → API
- Copy
service_role(secret) — notanon/public - Put it in
.envasSUPABASE_SERVICE_KEY - Run SQL in Supabase SQL editor:
supabase/migrations/0001_init.sql(if not already)supabase/migrations/0002_fix_rls.sql
Restart the app after updating .env.
# clean old Netlify deps if you installed them earlier
rm -rf node_modules package-lock.json
npm install
cp .env.example .env
# fill SUPABASE_* (service_role!), GEMINI_*, GROQ_*, ADMIN_API_KEY
npm run devOpen:
# one-time
npx vercel login
# link project + set env vars when prompted, or:
npx vercel env add SUPABASE_URL
npx vercel env add SUPABASE_SERVICE_KEY
npx vercel env add GEMINI_API_KEY
npx vercel env add GROQ_API_KEY
npx vercel env add ADMIN_API_KEY
# preview
npx vercel
# production
npx vercel --prod
# or: npm run deployIn the Vercel dashboard you can also paste the same env vars under Settings → Environment Variables.
# 1) Create tenant (save api_key — shown once)
curl -s -X POST http://localhost:3000/api/v1/tenants \
-H "Content-Type: application/json" \
-H "x-admin-key: $ADMIN_API_KEY" \
-d '{
"name": "Serengeti Trails",
"system_prompt": "You are a helpful safari booking assistant for Serengeti Trails. Be concise and friendly."
}'
# 2) Ingest knowledge chunks
curl -s -X POST http://localhost:3000/api/v1/ingest \
-H "Content-Type: application/json" \
-H "x-admin-key: $ADMIN_API_KEY" \
-d '{
"tenant_id": "TENANT_UUID",
"chunks": [
{
"content": "3-day Serengeti safari from Arusha includes game drives and camping. From $890 per person.",
"metadata": { "type": "package", "days": 3 }
}
]
}'
# 3) Chat (or use the web UI with the tenant api_key)
curl -s -X POST http://localhost:3000/api/v1/chat \
-H "Content-Type: application/json" \
-H "x-api-key: $TENANT_API_KEY" \
-d '{
"message": "How much is a 3-day Serengeti safari?",
"session_id": "web-session-001"
}'| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/v1/health |
none | Liveness |
| POST | /api/v1/tenants |
x-admin-key |
Create tenant (returns api_key once) |
| GET | /api/v1/tenants/:id |
x-admin-key |
Tenant metadata (no key) |
| POST | /api/v1/ingest |
x-admin-key |
Insert embedded chunks |
| POST | /api/v1/chat |
x-api-key |
RAG chat |
Errors always look like: { "error": "...", "code": "..." }.
app/
page.tsx # chat client
api/v1/health|chat|ingest|tenants/
src/lib/ # supabase, auth, embeddings, llm, rate-limit
src/types/
supabase/migrations/
Legacy Netlify function files under netlify/ are unused; Vercel is the deploy target.
- Never put
SUPABASE_SERVICE_KEYorADMIN_API_KEYin the browser orNEXT_PUBLIC_*. - The chat UI only stores the tenant API key in
localStorage. - Rate limit: 30 chat requests / tenant / 60s.
- Message max length: 2000 characters.