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/login for JWT, then Authorization: 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/.env and set DATABASE_URL, JWT_SECRET (≥ 8 chars), optional GEMINI_API_KEY for 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 dev

Health: 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.com

Use 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 error string (e.g. agent_not_found, validation_error, rate_limited).
  • POST /api/behavior is rate-limited per visitor + IP window.
  • POST /api/chat is 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.