๐ NanoAnalytics
Lightweight, self-hostable web analytics. One line of JS. Privacy-friendly. AI-ready API.
No cookies ยท No GDPR banner ยท No third-party services ยท ~90 MB/year at 1k daily visitors
Deploy in 2 minutes
Click a button โ fill in your app name โ get a live URL with your own private database. Each user gets a completely independent instance. Your data never touches anyone else's server.
| Platform | Cost | Storage | One-click deploy |
|---|---|---|---|
| Render | Free tier | 1 GB Disk (auto) | |
| Railway | Free tier | Volume | |
| Fly.io | Free tier | Volume | see Fly.io deploy below |
| Docker | Self-host | Bind mount | docker compose up -d |
โ Render is the easiest path:
API_TOKENis auto-generated for you. After deploy, find it in your Render dashboard โ Settings โ Environment Variables.โ๏ธ Railway: After deploy:
- Right-click your NanoAnalytics service on the Railway canvas โ Attach volume โ set mount path to
/data- Go to Variables โ add
API_TOKENwith any random stringโ ๏ธ Critical โ checkDB_PATH: In Variables, make sureDB_PATHis set to/data/analytics.db. Railway sometimes auto-populates it as/tmp/analytics.dbโ/tmpis ephemeral and gets wiped on every redeploy, losing all your data. If you see/tmp/..., change it to/data/analytics.dband redeploy.๐ก How the deploy buttons work: They pull the source code from this public repository and deploy it as your own private instance on your chosen platform. You own the deployment, the URL, and the data โ there is no shared server.
After Deploy
1. Add the tracker to your site
Paste this before </body> on every page you want to track:
<script async src="https://YOUR-DEPLOY-URL/a.js"></script>
That's it. No configuration. Sessions are tracked via sessionStorage โ no cookies, no consent banner needed.
2. Open your dashboard
Visit https://YOUR-DEPLOY-URL/dashboard
Enter your site hostname (e.g. mysite.com) and your API_TOKEN โ charts and stats appear immediately.
What you get
| Route | Description |
|---|---|
/dashboard |
Visual analytics dashboard (Chart.js) |
/docs |
Interactive Swagger API explorer |
/mcp |
MCP setup guide for Claude / Cursor |
/openapi.json |
Machine-readable OpenAPI 3.1 spec |
/a.js |
The beacon script |
API Reference
All /api/* endpoints require Authorization: Bearer YOUR_API_TOKEN.
Common query parameters: ?site=example.com&start=1735689600&end=1738368000
(start and end are Unix timestamps in seconds โ optional, defaults to all time)
| Endpoint | Description | Extra params |
|---|---|---|
GET /api/pageviews |
Total views + unique sessions | โ |
GET /api/pages |
Top pages by view count | &limit=10 |
GET /api/referrers |
Top referrer domains | &limit=10 |
GET /api/timeseries |
Daily pageviews (UTC) | โ |
GET /api/devices |
mobile / tablet / desktop breakdown | โ |
GET /api/languages |
Top browser languages | &limit=10 |
Example with curl
# Replace with your actual URL and token URL="https://your-instance.railway.app" TOKEN="your-api-token" # Total views this month curl -H "Authorization: Bearer $TOKEN" \ "$URL/api/pageviews?site=mysite.com&start=$(date -v-30d +%s)" # Top 5 pages curl -H "Authorization: Bearer $TOKEN" \ "$URL/api/pages?site=mysite.com&limit=5"
MCP / AI Agent Setup
Your NanoAnalytics instance exposes a live OpenAPI spec at /openapi.json. Any AI agent that speaks OpenAPI can query your analytics in natural language.
Interactive guide: visit
https://YOUR-DEPLOY-URL/mcpfor a page that auto-fills your instance URL and has one-click copy buttons for every config block below.
Claude Desktop
Prerequisites: Node.js must be installed on your computer (the config uses npx to run the MCP bridge automatically โ no manual install needed beyond Node.js itself).
Config file location:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Open the file and add the nano-analytics block inside mcpServers:
{
"mcpServers": {
"nano-analytics": {
"command": "npx",
"args": ["-y", "@ivotoby/openapi-mcp-server"],
"env": {
"API_BASE_URL": "https://YOUR-DEPLOY-URL",
"OPENAPI_SPEC_PATH": "https://YOUR-DEPLOY-URL/openapi.json",
"API_HEADERS": "Authorization:Bearer YOUR_API_TOKEN"
}
}
}
}Save the file and restart Claude Desktop. You can now ask:
"What were my top pages last week on mysite.com?" "Is mobile traffic growing this month?" "Show me the daily trend for the past 30 days."
Cursor
Option A โ Project scope (analytics context only in this repo):
Create .cursor/mcp.json at the root of your project:
{
"mcpServers": {
"nano-analytics": {
"url": "https://YOUR-DEPLOY-URL/openapi.json",
"headers": {
"Authorization": "Bearer YOUR_API_TOKEN"
}
}
}
}Option B โ Global (available in all Cursor workspaces):
Go to Cursor Settings โ MCP โ Add Server and paste:
{
"nano-analytics": {
"url": "https://YOUR-DEPLOY-URL/openapi.json",
"headers": {
"Authorization": "Bearer YOUR_API_TOKEN"
}
}
}Windsurf (Codeium)
Go to Windsurf Settings โ Cascade โ MCP Servers โ Add:
{
"mcpServers": {
"nano-analytics": {
"serverUrl": "https://YOUR-DEPLOY-URL/openapi.json",
"headers": {
"Authorization": "Bearer YOUR_API_TOKEN"
}
}
}
}GPT Actions (ChatGPT / custom GPTs)
- Go to My GPTs โ Create โ Configure โ Add Action
- Set Authentication โ API Key โ Header:
Authorization, Value:Bearer YOUR_API_TOKEN - Import schema from URL:
https://YOUR-DEPLOY-URL/openapi.json - Save โ your GPT can now answer analytics questions
Any other OpenAPI-compatible client
Spec URL: https://YOUR-DEPLOY-URL/openapi.json
Auth type: Bearer Token
Token: YOUR_API_TOKEN
Works with LangChain tools, LlamaIndex, n8n AI nodes, Make.com HTTP modules, or any tool that accepts an OpenAPI 3.1 spec.
Example queries your agent can answer
| Question | Endpoint used |
|---|---|
| "How many visitors did I get this week?" | /api/pageviews |
| "What are my top 5 pages?" | /api/pages?limit=5 |
| "Where is my traffic coming from?" | /api/referrers |
| "Is traffic growing or dropping?" | /api/timeseries |
| "What's my mobile vs desktop split?" | /api/devices |
| "What languages do my visitors use?" | /api/languages |
Telegram & Discord Bots
The bots query your NanoAnalytics API and reply with formatted stats. The recommended approach is to deploy each bot as a separate lightweight service โ its own private repo, its own Railway service. No volume needed (bots store nothing on disk).
Recommended: Deploy as a standalone Railway service
Step 1 โ Create a bot repo
Create a new private GitHub repository with these 4 files:
bot.py โ copy from bots/telegram_bot.py (Telegram) or bots/discord_bot.py (Discord)
requirements.txt
# Telegram
python-telegram-bot==21.*
httpx>=0.27
# Discord (use this instead)
# discord.py>=2.4
# httpx>=0.27
Dockerfile
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY bot.py . CMD ["python", "bot.py"]
.env.example
TELEGRAM_BOT_TOKEN=your-token-from-botfather
ANALYTICS_URL=https://your-nanoanalytics-instance.up.railway.app
ANALYTICS_API_TOKEN=your-api-token
ANALYTICS_SITE=mysite.com
Step 2 โ Deploy on Railway
- Railway โ New Service โ GitHub โ select your bot repo
- Go to Variables โ add the 4 variables from
.env.examplewith real values - No volume needed โ the bot has no disk storage
Railway will build the Docker image and keep the bot running 24/7.
Telegram setup
- Message @BotFather on Telegram โ
/newbotโ copy the token โ use it asTELEGRAM_BOT_TOKEN - Open your bot in Telegram and send
/start
Commands: /stats /pages /referrers /countries /devices /trend /languages
Discord setup
- Go to discord.com/developers/applications โ New Application โ Bot
- Enable Message Content Intent under Privileged Gateway Intents
- Copy the bot token โ use it as
DISCORD_BOT_TOKEN - Invite the bot to your server with Send Messages + Read Messages permissions
Commands: !stats !pages !referrers !devices !trend !languages
Quick local test (no hosting needed)
Run directly in your terminal โ the bot stays alive as long as the window is open:
pip install python-telegram-bot httpx # or: discord.py httpx TELEGRAM_BOT_TOKEN="..." \ ANALYTICS_URL="https://your-instance.up.railway.app" \ ANALYTICS_API_TOKEN="..." \ ANALYTICS_SITE="mysite.com" \ python bots/telegram_bot.py
Docker
# Clone and run git clone https://github.com/callmefredcom/NanoAnalytics.git cd NanoAnalytics # Set your token echo "API_TOKEN=your-secret-token" > .env # Start (data persists in a named volume) docker compose up -d # View logs docker compose logs -f # Stop docker compose down
Your instance is at http://localhost:8000
pip (local / VPS)
pip install nano-analytics # Run (adjust DB_PATH to a writable location) API_TOKEN=secret DB_PATH=/tmp/analytics.db \ flask --app "nano_analytics:create_app()" run --host 0.0.0.0
For production with gunicorn:
API_TOKEN=secret DB_PATH=/data/analytics.db \ gunicorn wsgi:app --bind 0.0.0.0:8000 --workers 2
Fly.io manual deploy
# Install flyctl: https://fly.io/docs/hands-on/install-flyctl/ fly auth login # Create the app and volume fly apps create nano-analytics fly volumes create analytics_data --region iad --size 1 # Set your token fly secrets set API_TOKEN=your-secret-token fly secrets set BASE_URL=https://nano-analytics.fly.dev # Deploy fly deploy
Storage sizing
SQLite on a persistent volume. Each hit row is ~200 bytes.
| Daily visitors | 1 year |
|---|---|
| 1,000 | ~90 MB |
| 10,000 | ~900 MB |
| 100,000 | ~9 GB |
At >50k visitors/day, consider adding a periodic job to aggregate old rows into daily summaries.
Security model
- No setup page. Your
API_TOKENlives behind your hosting platform's own login. Find it in the Environment Variables panel. - The dashboard is a static HTML shell โ no sensitive data is served without the token.
- The beacon (
/hit,/a.js) is public and setsAccess-Control-Allow-Origin: *so it works from any domain. - All stats endpoints require
Authorization: Bearer YOUR_TOKEN.
License
MIT