Real-Time Data API
Access live visitor counts and real-time analytics data through public JSON endpoints. These endpoints are designed for displaying live statistics on your website and require no authentication.
Overview
Real-time endpoints provide:
- Instant access - No authentication required
- Live updates - Data refreshes every 5-30 seconds
- Low latency - Served from Cloudflare's global edge network
- CORS enabled - Safe to call directly from browsers
Base URL
All real-time endpoints are available at:
https://api.zenovay.com/e
Live Visitor Count
Get the current number of visitors on your website:
/e/live/:trackingCodeGet live visitor count
curl -X GET 'https://api.zenovay.com/e/live/ZV_XXXXXXXXXXX'{
"liveCount": 42,
"timestamp": "2025-01-20T14:30:00Z"
}Response Fields:
| Field | Type | Description |
|---|---|---|
liveCount | number | Current visitors online (active in last 5 minutes) |
timestamp | string | ISO 8601 timestamp of the data |
Real-Time Analytics
Get comprehensive real-time analytics including visitors, pageviews, and geographic data:
/e/realtime/:websiteIdGet real-time analytics data
curl -X GET 'https://api.zenovay.com/e/realtime/ws_abc123'{
"visitors": {
"current": 42,
"today": 1234,
"change": 12.5
},
"pageViews": {
"current": 156,
"today": 4567,
"change": 8.3
},
"countries": [
{ "code": "US", "name": "United States", "count": 18 },
{ "code": "GB", "name": "United Kingdom", "count": 8 },
{ "code": "DE", "name": "Germany", "count": 6 }
],
"topPages": [
{ "path": "/", "visitors": 15 },
{ "path": "/pricing", "visitors": 8 },
{ "path": "/features", "visitors": 5 }
],
"timestamp": "2025-01-20T14:30:00Z"
}Response Fields:
| Field | Type | Description |
|---|---|---|
visitors.current | number | Visitors online now |
visitors.today | number | Total unique visitors today |
visitors.change | number | Percentage change vs yesterday |
pageViews.current | number | Pageviews in last 5 minutes |
pageViews.today | number | Total pageviews today |
countries | array | Top countries by current visitors |
topPages | array | Top pages by current visitors |
Visitor Statistics
Get detailed visitor statistics including bounce rate and session duration:
/e/stats/:trackingCodeGet visitor statistics
curl -X GET 'https://api.zenovay.com/e/stats/ZV_XXXXXXXXXXX'{
"visitors": {
"live": 42,
"today": 1234,
"week": 8765,
"month": 34521
},
"bounceRate": 0.42,
"avgSessionDuration": 185,
"pagesPerSession": 2.9,
"newVisitorRate": 0.65,
"returningVisitorRate": 0.35,
"timestamp": "2025-01-20T14:30:00Z"
}Response Fields:
| Field | Type | Description |
|---|---|---|
visitors.live | number | Current visitors online |
visitors.today | number | Unique visitors today |
visitors.week | number | Unique visitors this week |
visitors.month | number | Unique visitors this month |
bounceRate | number | Bounce rate (0-1) |
avgSessionDuration | number | Average session length in seconds |
pagesPerSession | number | Average pages per session |
newVisitorRate | number | Percentage of new visitors |
returningVisitorRate | number | Percentage of returning visitors |
Website Status
Check if tracking is active for a website:
/e/:trackingCode/statusCheck tracking status
curl -X GET 'https://api.zenovay.com/e/ZV_XXXXXXXXXXX/status'{
"active": true,
"tracking_code": "ZV_XXXXXXXXXXX",
"domain": "example.com",
"last_event": "2025-01-20T14:29:55Z"
}JavaScript Integration
Basic Live Counter
Display live visitor count on your website:
class LiveVisitorCounter {
constructor(trackingCode, elementId) {
this.trackingCode = trackingCode;
this.element = document.getElementById(elementId);
this.intervalId = null;
}
async fetchCount() {
try {
const response = await fetch(
`https://api.zenovay.com/e/live/${this.trackingCode}`
);
const data = await response.json();
this.updateDisplay(data.liveCount);
} catch (error) {
console.error('Failed to fetch visitor count:', error);
}
}
updateDisplay(count) {
if (this.element) {
this.element.textContent = count.toLocaleString();
this.element.classList.add('updated');
setTimeout(() => this.element.classList.remove('updated'), 300);
}
}
start(intervalMs = 30000) {
this.fetchCount();
this.intervalId = setInterval(() => this.fetchCount(), intervalMs);
}
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
// Usage
const counter = new LiveVisitorCounter('ZV_XXXXXXXXXXX', 'visitor-count');
counter.start();Real-Time Dashboard
Build a mini analytics dashboard:
async function updateDashboard(websiteId) {
try {
const response = await fetch(
`https://api.zenovay.com/e/realtime/${websiteId}`
);
const data = await response.json();
// Update visitor counts
document.getElementById('live-visitors').textContent = data.visitors.current;
document.getElementById('today-visitors').textContent = data.visitors.today.toLocaleString();
// Update pageviews
document.getElementById('live-pageviews').textContent = data.pageViews.current;
// Update top countries
const countriesList = document.getElementById('top-countries');
countriesList.innerHTML = data.countries
.slice(0, 5)
.map(c => `<li>${c.name}: ${c.count}</li>`)
.join('');
// Update top pages
const pagesList = document.getElementById('top-pages');
pagesList.innerHTML = data.topPages
.slice(0, 5)
.map(p => `<li>${p.path}: ${p.visitors}</li>`)
.join('');
} catch (error) {
console.error('Dashboard update failed:', error);
}
}
// Update every 30 seconds
setInterval(() => updateDashboard('ws_abc123'), 30000);
updateDashboard('ws_abc123');React Hook
Custom React hook for real-time data:
import { useState, useEffect, useCallback } from 'react';
interface RealtimeData {
visitors: {
current: number;
today: number;
change: number;
};
pageViews: {
current: number;
today: number;
change: number;
};
countries: Array<{ code: string; name: string; count: number }>;
topPages: Array<{ path: string; visitors: number }>;
timestamp: string;
}
function useRealtimeAnalytics(websiteId: string, refreshInterval = 30000) {
const [data, setData] = useState<RealtimeData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
try {
const response = await fetch(
`https://api.zenovay.com/e/realtime/${websiteId}`
);
if (!response.ok) throw new Error('Failed to fetch');
const json = await response.json();
setData(json);
setError(null);
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setLoading(false);
}
}, [websiteId]);
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, refreshInterval);
return () => clearInterval(interval);
}, [fetchData, refreshInterval]);
return { data, loading, error, refetch: fetchData };
}
// Usage in component
function AnalyticsDashboard({ websiteId }: { websiteId: string }) {
const { data, loading, error } = useRealtimeAnalytics(websiteId);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return null;
return (
<div className="dashboard">
<div className="stat">
<span className="label">Live Visitors</span>
<span className="value">{data.visitors.current}</span>
</div>
<div className="stat">
<span className="label">Today</span>
<span className="value">{data.visitors.today.toLocaleString()}</span>
</div>
</div>
);
}Vue Composable
Vue 3 composable for real-time data:
import { ref, onMounted, onUnmounted, watch } from 'vue';
interface RealtimeData {
visitors: { current: number; today: number; change: number };
pageViews: { current: number; today: number; change: number };
countries: Array<{ code: string; name: string; count: number }>;
topPages: Array<{ path: string; visitors: number }>;
}
export function useRealtimeAnalytics(websiteId: string, refreshInterval = 30000) {
const data = ref<RealtimeData | null>(null);
const loading = ref(true);
const error = ref<Error | null>(null);
let intervalId: number | null = null;
async function fetchData() {
try {
const response = await fetch(
`https://api.zenovay.com/e/realtime/${websiteId}`
);
if (!response.ok) throw new Error('Failed to fetch');
data.value = await response.json();
error.value = null;
} catch (err) {
error.value = err instanceof Error ? err : new Error('Unknown error');
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchData();
intervalId = window.setInterval(fetchData, refreshInterval);
});
onUnmounted(() => {
if (intervalId) clearInterval(intervalId);
});
return { data, loading, error, refetch: fetchData };
}Rate Limits
Real-time endpoints have generous rate limits:
| Endpoint | Rate Limit | Cache TTL |
|---|---|---|
/e/live/:trackingCode | 1000 req/min | 5 seconds |
/e/realtime/:websiteId | 500 req/min | 10 seconds |
/e/stats/:trackingCode | 500 req/min | 30 seconds |
/e/:trackingCode/status | 100 req/min | 60 seconds |
Data is cached at the edge. Multiple requests within the cache TTL will receive the same data, making it safe to poll frequently from client-side code.
CORS Configuration
All real-time endpoints support CORS and can be called directly from browsers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-TypeError Handling
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (response.status === 429) {
// Rate limited - wait and retry
const retryAfter = response.headers.get('Retry-After') || '60';
await new Promise(r => setTimeout(r, parseInt(retryAfter) * 1000));
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Exponential backoff
await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
}
}
}
// Usage
try {
const data = await fetchWithRetry('https://api.zenovay.com/e/live/ZV_XXXXXXXXXXX');
console.log('Live visitors:', data.liveCount);
} catch (error) {
console.error('Failed after retries:', error);
}Best Practices
- Cache locally - Store data in memory to avoid unnecessary requests during rapid UI updates
- Debounce updates - Don't trigger UI updates faster than the cache TTL
- Handle offline - Show stale data with a timestamp when the network is unavailable
- Animate changes - Smooth transitions when counts change make the UI feel more responsive
- Show loading states - Display skeleton loaders during initial fetch
Next Steps
- Widgets - Pre-built embeddable widgets
- External API - Full API with authentication
- Custom Events - Track custom user actions