MerrchAI · Developer documentation
Integrate from scratch
MerrchAI ships as a single-file browser embed plus a REST API you call with fetch, curl, or any backend. There is no proprietary npm SDK required—the contract is HTTP + JSON (and SSE for chat).
Overview
Your site loads widget.iife.js. The script reads public agent config from GET /api/widget/:agentId, tracks visitor behavior in the browser, periodically posts to POST /api/behavior, opens chat, and calls POST /api/chat (streaming) and POST /api/leads when visitors convert.
Default local API base URL: http://localhost:3001. In production, use HTTPS and set data-api-url on the embed tag to your deployed API origin.
Architecture
- apps/widget — Vite app + IIFE embed entry (
src/embed.tsx), shadow DOM, behavior engine. - apps/api — Express + Prisma + Postgres.
- packages/behavior-core — shared intent / psych scoring and canonical SHA-256 fields used by both API and widget.
- apps/dashboard — Next.js merchant UI (agents, embed snippet, analytics). Uses
POST /api/auth/loginfor JWT, thenAuthorization: Bearer …on merchant routes.
Prerequisites
- Node.js 20+ and pnpm 9+ for local development.
- PostgreSQL (local Docker Compose is supported from repo root).
- API env: copy
apps/api/.env.example→apps/api/.envand setDATABASE_URL,JWT_SECRET(≥ 8 chars), optionalGEMINI_API_KEYfor chat. - Chat requires
GEMINI_API_KEY; behavior + widget config work without it.
Run the stack locally
From the monorepo root:
# 1) Postgres
docker compose up -d
# 2) API env
cp apps/api/.env.example apps/api/.env
# Edit DATABASE_URL if needed; set JWT_SECRET; optional GEMINI_API_KEY
# 3) Install & migrate & seed
pnpm install
pnpm --filter api run db:migrate
pnpm --filter api run db:seed
# 4) Run API + dashboard + widget
pnpm devHealth: GET http://localhost:3001/health (includes DB check). Liveness without DB: GET /health/live.
Embed the widget (IIFE)
Build the embed bundle from repo root: pnpm --filter widget build. Host apps/widget/dist-embed/widget.iife.js on your CDN or static origin. For a local smoke test, see pnpm --filter widget run serve:embed in the repo README (serves on port 4173 with CORS defaults).
Minimal HTML snippet
<script
async
src="https://your-cdn.example.com/widget.iife.js"
data-agent-id="YOUR_AGENT_ID"
data-api-url="https://your-api.example.com"
data-thank-you-paths="/thanks,/order-complete"
></script>data-agent-id— required. UUID of an active agent.data-api-url— required. Origin of the MerrchAI API (no trailing slash).data-thank-you-paths— optional. Comma-separated path substrings for thank-you / conversion goal tracking.data-debug="1"— optional. Enables behavior debug overlay in dev-style builds.
SPA dev (no IIFE)
With pnpm --filter widget dev, open http://localhost:5173/?agentId=…. Optional env: copy apps/widget/.env.example → apps/widget/.env for VITE_API_URL / VITE_AGENT_ID.
Visitor identity
The widget generates a stable anonymous visitorId (stored in localStorage) per browser profile. All POST /api/behavior, POST /api/chat, and POST /api/leads calls for that session must use the same visitorId so the API can correlate behavior, conversation, and outcomes.
If you build a custom client instead of the IIFE, generate and persist your own visitor id (UUID recommended) the same way.
Public HTTP API
Base URL: your API origin, e.g. https://api.example.com.
GET /api/widget/:agentId
Public agent configuration for the embed. Cached up to 60s. Returns 404 if agent is missing or inactive.
curl -s "https://api.example.com/api/widget/AGENT_ID"POST /api/behavior
Upserts visitor behavior for (agentId, visitorId). Optional behaviorHash must match the SHA-256 of canonical fields or the API returns 400 behavior_hash_mismatch. When unchanged vs stored row, response may be { "skipped": true }.
curl -s -X POST "https://api.example.com/api/behavior" \
-H "Content-Type: application/json" \
-d '{"agentId":"AGENT_ID","visitorId":"v-1","behavior":{"pagesVisited":["/"],"intentScore":10,"intentLevel":"warm","psychProfile":"social"}}'POST /api/leads
Creates a lead; can attach to the latest pending conversation when conversationId is omitted. Body fields include optional email, name, summary, intentScore, psychProfile.
curl -s -X POST "https://api.example.com/api/leads" \
-H "Content-Type: application/json" \
-d '{"agentId":"AGENT_ID","visitorId":"v-1","email":"lead@example.com","summary":"Interested in team plan"}'POST /api/events/goal
Records a goal event (cta_click or thank_you_page) for attribution. Optional conversationId; otherwise links to latest pending conversation when possible.
curl -s -X POST "https://api.example.com/api/events/goal" \
-H "Content-Type: application/json" \
-d '{"agentId":"AGENT_ID","visitorId":"v-1","type":"thank_you_page"}'Chat streaming (SSE)
POST /api/chat with JSON body. Response is text/event-stream. Each SSE data line is JSON:
{ "type": "meta", "conversationId": "…" }— first frame.{ "type": "token", "text": "…" }— streamed model deltas.{ "type": "done" }— stream complete.{ "type": "error", "message": "…" }— upstream or validation failure; connection then closes.
Body includes agentId, visitorId, messages (array of { "role": "user" | "model", "content": "…" }), optional conversationId, optional rollingSummary, optional behaviorContext.
Requires GEMINI_API_KEY on the API.
curl -sN -X POST "https://api.example.com/api/chat" \
-H "Content-Type: application/json" \
-d '{"agentId":"AGENT_ID","visitorId":"v-1","messages":[{"role":"user","content":"Hello"}]}'Merchant auth (JWT)
Dashboard and server-side automation use the same API JWT issued after email/password login or register.
POST /api/auth/register
curl -s -X POST "https://api.example.com/api/auth/register" \
-H "Content-Type: application/json" \
-d '{"email":"you@company.com","password":"your-secure-password","name":"You"}'POST /api/auth/login
curl -s -X POST "https://api.example.com/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"you@company.com","password":"your-secure-password"}'Both return JSON with token and user.
GET /api/me
Pass Authorization: Bearer <token> to verify the session.
Merchant routes (Bearer required)
All routes under /api/agents (list/create/update agents, learning digest rebuild where exposed) require the Bearer token. Create an agent with fields such as businessName, welcomeMessage, ctaUrl, behaviorSyncIntervalSec (5–3600, default 30), etc.—see apps/api/src/routes/agents.ts for the full schema.
CORS & production
The API reads CORS_ORIGINS (comma-separated). Defaults always include http://localhost:3000, http://localhost:5173, and http://localhost:4173. Add every browser origin that will load the widget or dashboard, e.g. https://shop.example.com.
# apps/api/.env
CORS_ORIGINS=https://shop.example.com,https://app.example.comUse HTTPS in production, set strict DATABASE_URL, rotate JWT_SECRET, and never expose service keys to the browser.
Errors & rate limits
- JSON errors use an
errorstring (e.g.agent_not_found,validation_error,rate_limited). POST /api/behavioris rate-limited per visitor + IP window.POST /api/chatis rate-limited per agent + visitor + IP.
Behavior hash (advanced)
If you send behaviorHash on POST /api/behavior, it must equal the SHA-256 of the same canonical field set the API uses. The reference implementation lives in @sales-ai-agent/behavior-core and apps/api/src/lib/behaviorHash.ts. Custom clients should reuse that package or copy the algorithm to stay compatible with deduplication and skipped: true behavior.