Headless micro-analytics for AI agents. A one-line snippet on your site, and your agent reads visitor behavior directly — no dashboard, no UI.
Part of SilverBackBase — a library of agent-first primitives for AI-powered marketing and product work.
Mark has two surfaces:
- HTTP server — accepts events from any browser via a one-line JS snippet, and exposes query endpoints for agents or any LLM with function calling
- MCP stdio — exposes tools for Claude Code, Codex CLI, and Claude Desktop
Data is stored in PostgreSQL. Every event is scoped to a workspace_id — a string you choose freely (e.g. "local", "my-project").
- Node.js 18+
- PostgreSQL database
npx -y @silverbackbase/markOr install globally:
npm install -g @silverbackbase/mark
markThe server starts on the port defined by PORT (default 7331). Migrations run automatically on startup.
Paste before </body> on every page:
<script async src="https://your-instance.com/mark.js?slug=my-site&wid=local"></script>slug identifies the site. wid is your workspace ID — use any consistent string.
Once loaded, auto-tracking activates: page_view, clicks on buttons/links, form_submit, page_exit, and scroll_depth at 25/50/75/100%. Custom events:
window.markjs.track('signup_complete', { plan: 'pro' })
window.markjs.identify('user-123') // link events to an entity
window.markjs.setTag('variant-a') // tag events for segmentationmark_funnel("my-site", ["page_view", "scroll_50", "scroll_100"])
// → { drop_at: "scroll_100", rates: [1.0, 0.61, 0.28] }
mark_funnel("my-site", ["page_view", "form_submit", "merci"])
// → { drop_at: "form_submit", rates: [1.0, 0.43, 0.31] }
mark_friction("my-site")
// → where sessions stop, ordered by sequence
mark_compare("my-site", pivot="2026-06-01", event="form_submit")
// → { before: { completions: 48 }, after: { completions: 71 }, delta: "+47.9%" }
| Tool | Description |
|---|---|
mark_snippet |
Returns the <script> tag to embed on the site |
mark_ingest |
Injects a synthetic event from the agent (testing, seeding) |
mark_list |
Lists all active slugs with session and event counts |
mark_summary |
Overview: sessions, events, top events over N days |
mark_funnel |
Conversion rate through an ordered list of events |
mark_compare |
Behavior before vs after a date pivot |
mark_friction |
Where sessions stop progressing |
mark_journey |
Full event history for a specific entity |
mark_breakdown |
Group an event by a property value — e.g. page_view by url to see top pages |
mark_purge |
Delete all data for a slug (irreversible) |
POST /e Ingest an event (open — called from browsers)
GET /mark.js?slug=:slug&wid=:wid Serve the browser tracker script
GET /health Health check
GET /logs/recent?limit=50 Recent events
GET /q/list List active slugs
GET /q/summary/:slug?days=7 Session and event overview
GET /q/funnel/:slug?steps=a,b,c Funnel conversion by step
GET /q/compare/:slug?pivot=ISO Before vs after comparison
GET /q/friction/:slug Drop-off points
GET /q/journey/:slug?entity_id=ID Entity event history
GET /q/breakdown/:slug?event=&property= Group event by property value
GET /q/schema Full endpoint schema
{
"workspace_id": "local",
"slug": "my-site",
"session_id": "abc123",
"event_name": "signup_start",
"properties": { "optional": "metadata" },
"tag": "variant-a",
"entity_id": "user-123"
}workspace_id is required — all data is scoped by it.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
PORT |
No | HTTP server port (default 7331) |
MARK_PUBLIC_URL |
No | Public base URL for snippet generation (default http://localhost:PORT) |
MARK_WORKSPACE_ID |
No | Workspace ID used by MCP stdio tools (default "local") |
MARK_INTERNAL_SECRET |
No | If set, query endpoints (/q/*, /logs/*) require x-internal-secret: <value>. Ingestion (POST /e) and /mark.js remain open. |
Add to ~/.claude.json:
{
"mcpServers": {
"mark": {
"command": "npx",
"args": ["-y", "@silverbackbase/mark"],
"env": {
"DATABASE_URL": "postgresql://user:pass@host/db",
"MARK_PUBLIC_URL": "https://your-instance.com",
"MARK_WORKSPACE_ID": "local"
}
}
}
}Same config in ~/Library/Application Support/Claude/claude_desktop_config.json.
Ad blockers may block requests to mark.silverbackbase.com. To proxy through your own domain, add a rewrite rule in your Next.js config:
// next.config.js
rewrites: async () => [
{
source: "/m/:path*",
destination: "https://your-instance.com/:path*",
},
]Then update your snippet to use /m/mark.js and /m/e as the ingestion endpoint.
Related primitives: Trail (multi-touch attribution), Range (local SEO position tracking), Root (business memory).