v1.0
boltBitnodo Analytics API
Esta API permite registrar usuarios, crear portales, insertar un widget unico de rastreo y consultar eventos/alertas en tiempo casi real para sistemas propios o de terceros.
Base URL Produccion
https://analytics.bitnodo.net/api/v1
Formato
Content-Type: application/json
| Plan | Portales maximos | Capacidades |
|---|---|---|
| free | 1 | Vista cliente con visitas, tiempo real, paginas visitadas, tipo de cliente y metricas basicas |
| pro | 50 | Todo lo de free + visibilidad de tracking avanzado, event_payload completo y leads |
La captura en background se realiza completa para todos los planes. Lo que cambia por plan es la informacion visible para el cliente final en los endpoints de consulta.
Quickstart para integrador
- Desarrolladores crea una superclave temporal para el cliente.
- Registrar cuenta y usuario con
POST /auth/register+super_key. - Guardar
access_token,user.id,account.idyapi_key. - Crear portal con
POST /portalsusando Bearer token. - Incrustar el widget JS con
data-keyydata-tenant(modo recomendado) o usar modo manual concompanyId/portalId. - Consumir alertas y eventos con
X-API-Key.
curl -X POST "https://analytics.bitnodo.net/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d '{
"email": "owner@empresa.com",
"password": "SuperPassword123!",
"full_name": "Owner Demo",
"account_name": "Empresa Demo",
"super_key": "ABCD2345EFGH6789IJKL2345MNOP6789"
}'
Aprobacion por superadmin
El alta de integradores no es libre. Cada registro requiere una super_key
temporal emitida por Bitnodo. Esta clave es alfanumerica segura, de un solo uso y
se marca como consumida inmediatamente cuando la cuenta se crea.
La super_key aplica solo al alta inicial de la cuenta/empresa. La creacion,
edicion y eliminacion de portales se realiza con Bearer token y no requiere superclave.
Reglas de superclave
- Formato alfanumerico seguro
- Caducidad por tiempo (TTL)
- Uso unico
- No se almacena en claro, solo hash
Provisionamiento controlado
La emision de superclaves es gestionada internamente por Bitnodo y no forma parte de la API publica para integradores.
Autenticacion
| Metodo | Endpoint | Uso | Auth |
|---|---|---|---|
| POST | /auth/register | Crea usuario + cuenta + api_key + token (requiere super_key) | No |
| POST | /auth/login | Entrega access_token y datos del usuario | No |
| GET | /auth/me | Retorna perfil autenticado e ids | Bearer token |
| POST | /auth/logout | Invalida token actual | Bearer token |
curl "https://analytics.bitnodo.net/api/v1/auth/me" \
-H "Authorization: Bearer TU_ACCESS_TOKEN"
Ejemplo JavaScript — login y autenticacion
// 1. Login y obtener access_token
const loginRes = await fetch('https://analytics.bitnodo.net/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'owner@empresa.com', password: 'SuperPassword123!' })
});
const { access_token, user, account } = await loginRes.json();
// Guardar para uso posterior:
// access_token → Bearer token para portales / configuracion
// account.api_key → X-API-Key para lectura de alertas / visitas
// 2. Verificar sesion activa
const meRes = await fetch('https://analytics.bitnodo.net/api/v1/auth/me', {
headers: { 'Authorization': `Bearer ${access_token}` }
});
const profile = await meRes.json();
console.log(profile.user.id, profile.account.id, profile.tenant.id);
Gestion de portales (CRUD)
Un usuario autenticado puede crear multiples portales/paginas para monitoreo usando su
Bearer token. Este flujo no utiliza super_key.
Limites por plan: free permite 1 portal; pro permite hasta 50 portales.
| Metodo | Endpoint | Uso |
|---|---|---|
| GET | /portals | Lista portales de la cuenta autenticada |
| POST | /portals | Crea portal (name, domain) |
| PUT | /portals/{id} | Edita name, domain, is_active |
| DELETE | /portals/{id} | Elimina portal |
curl -X POST "https://analytics.bitnodo.net/api/v1/portals" \
-H "Authorization: Bearer TU_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Sitio principal","domain":"www.empresa.com"}'
Ejemplo JavaScript — crear y listar portales
const headers = {
'Authorization': 'Bearer TU_ACCESS_TOKEN',
'Content-Type': 'application/json'
};
// Crear portal
const createRes = await fetch('https://analytics.bitnodo.net/api/v1/portals', {
method: 'POST',
headers,
body: JSON.stringify({ name: 'Sitio principal', domain: 'www.empresa.com' })
});
const { portal } = await createRes.json();
console.log('Portal ID:', portal.id); // Guardar para widget y tracking
// Listar portales
const listRes = await fetch('https://analytics.bitnodo.net/api/v1/portals', { headers });
const { portals } = await listRes.json();
Reglas de intencion por portal
Cada portal puede configurar su propio scoring de intencion mediante intent_rules.
Si un portal no define reglas personalizadas, el sistema usa reglas por defecto globales.
| Metodo | Endpoint | Uso |
|---|---|---|
| GET | /portals/{id}/intent-rules | Obtiene reglas actuales y thresholds |
| POST | /portals/{id}/intent-rules | Crea reglas personalizadas del portal |
| PUT | /portals/{id}/intent-rules | Actualiza reglas y thresholds |
| DELETE | /portals/{id}/intent-rules | Elimina reglas personalizadas y vuelve a defaults |
{
"rules_json": {
"pageview": 1,
"scroll_depth": {"50": 1, "75": 2, "100": 3},
"link_click": 2,
"video_play": 2,
"file_download": 4,
"form_submit": 10,
"commercial_url_bonus": 5
},
"threshold_interested": 6,
"threshold_high": 11,
"threshold_very_high": 21
}
Widget unico de rastreo
Desde esta iteracion, el panel de Bitnodo puede generar automaticamente el snippet de instalacion consultando una configuracion publica por tenant.
Flujo de instalacion: panel -> API -> JSON de configuracion -> snippet -> cliente copia y pega.
<script
src="https://analytics.bitnodo.net/widget/tracker.js"
data-key="pk_live_xxxxxxxxx"
data-tenant="23"
></script>
data-key y data-tenant. Con esos datos consulta
GET /v1/widget/config/{tenant_id} y aplica configuracion dinamica
(consentimiento, tracking y claves publicas) sin hardcodear reglas en el panel.
tracker.js inicia con data-tenant. El atributo
data-portal por si solo no inicializa el widget.
Configuracion JSON de widget por tenant
| Metodo | Endpoint | Auth | Uso |
|---|---|---|---|
| GET | /v1/widget/config/{tenant_id} | No | Devuelve configuracion publica para generar snippet e inicializar widget |
curl "https://analytics.bitnodo.net/v1/widget/config/23"
{
"tenant": {
"id": "23",
"name": "Nombre del sitio"
},
"widget": {
"enabled": true,
"script_url": "https://analytics.bitnodo.net/widget/tracker.js",
"public_key": "pk_live_xxxxxxxxx",
"company_id": "ACCOUNT_ID_UNICO",
"portal_id": "PORTAL_ID_OPCIONAL",
"snippet": ""
},
"consent": {
"enabled": true,
"type": "modal",
"storage": "localStorage",
"text": "Este sitio utiliza herramientas de analisis de comportamiento para mejorar nuestros servicios y detectar oportunidades comerciales.",
"terms_title": "Terminos de analisis de datos",
"terms_body": "Este sitio utiliza Bitnodo Analytics para analizar el comportamiento de navegacion y detectar oportunidades comerciales."
},
"tracking": {
"page_views": true,
"scroll_tracking": true,
"click_tracking": true
}
}
Integracion con panel
- El panel consume el endpoint de config por tenant.
- Con la respuesta genera snippet corto y snippet extendido.
- El cliente final solo copia y pega el codigo en su sitio.
Tablas involucradas
tenants: identidad del tenant ypublic_key.widget_config: consentimiento y toggles de tracking.accounts:company_idpublico para/track.
<!-- Snippet extendido opcional generado por panel -->
<script src="https://analytics.bitnodo.net/widget/tracker.js"
data-key="pk_live_xxxxxxxxx"
data-tenant="23"
data-api-base="https://analytics.bitnodo.net/api/v1"
data-config-base="https://analytics.bitnodo.net"></script>
secret_key, tokens internos ni credenciales privadas.
<!-- Modo manual (fallback) -->
<script src="https://analytics.bitnodo.net/widget/tracker.js"></script>
<script>
window.BitnodoAnalytics.init({
apiBase: "https://analytics.bitnodo.net/api/v1",
companyId: "ACCOUNT_ID_UNICO",
portalId: "PORTAL_ID_UNICO",
widgetVersion: "1.0",
captureFormContacts: true,
legalMessage: "Este sitio usa analitica para mejorar la experiencia de usuario."
});
</script>
Que monitorea el widget
El widget de Bitnodo Analytics esta pensado para operar como una capa de observacion del trafico web, similar al enfoque de herramientas de analytics. Detecta apertura de pagina, permanencia activa, navegacion interna y salida del visitante, y transforma esas acciones en eventos que la API puede almacenar y procesar.
| Evento | Cuando ocurre | Uso analitico |
|---|---|---|
| pageview | Cuando la pagina carga y el widget se inicializa | Contabilizar visitas, paginas vistas y origen del trafico |
| heartbeat | Cada intervalo configurado mientras la pestana esta activa | Medir tiempo real de atencion y permanencia efectiva |
| scroll_depth | Al alcanzar hitos de scroll como 25%, 50%, 75% o 100% | Medir lectura real, interes por contenido y profundidad de consumo |
| link_click | Cuando el visitante hace clic en un enlace | Analizar navegacion interna y salidas hacia sitios externos |
| video_play | Cuando se reproduce un video HTML5 dentro de la pagina | Medir interaccion audiovisual y consumo de contenido multimedia |
| file_download | Cuando se detecta clic en un enlace de descarga | Auditar descargas de documentos, recursos o archivos comerciales |
| form_submit | Cuando se envia un formulario | Medir conversiones, leads y puntos de interaccion clave; puede incluir email y telefono |
| unload | Cuando el usuario abandona la pagina o la recarga | Registrar cierre de sesion/pagina y capturar ultima navegacion conocida |
Monitoreo de navegacion
- URL actual visitada
- Pagina anterior por referrer
- Siguiente URL estimada si el usuario navega dentro del mismo sitio
- Frecuencia de actividad durante la sesion
- Profundidad de scroll por hitos
- Clics en enlaces y descargas
- Envio de formularios
- Reproduccion de videos HTML5
- Captura de email y telefono al enviar formularios
Monitoreo de contexto tecnico
- IP publica del visitante
- Navegador detectado a partir del User-Agent
- Pais de conexion
- Proveedor de internet o ISP
- Idioma y zona horaria del navegador
Datos recolectados
La API recibe estos datos desde el widget para construir una vision operativa del trafico, comparable a una implementacion base de analytics orientada a visitas, procedencia, permanencia y navegacion dentro del sitio.
Datos tecnicos capturados
- IP de conexion
- User-Agent y navegador detectado
- Pais de conexion
- ISP / compania de internet
- Pagina actual
- Referrer (pagina anterior)
- Siguiente URL estimada en misma pestana
- Tiempo activo en pantalla
- Idioma y zona horaria
- Payload JSON de interaccion segun tipo de evento
- Email y telefono cuando el formulario se envia con esos campos presentes
Endpoint de ingreso de eventos
POST /track y POST /track/batch
{
"companyId": "ACCOUNT_ID_UNICO",
"portalId": "PORTAL_ID_UNICO",
"visitorId": "vtr_9j2k3lm...",
"sessionId": "ses_c4d5e6f...",
"eventType": "pageview",
"pageUrl": "https://www.empresa.com/home",
"referrer": "https://google.com/",
"nextUrl": "https://www.empresa.com/precios",
"screenActiveSeconds": 43,
"timezone": "America/Santiago",
"language": "es-CL",
"widgetVersion": "1.0",
"eventData": {
"action": "https://www.empresa.com/contacto",
"method": "POST",
"fieldCount": 4,
"lead": {
"email": "cliente@empresa.com",
"phone": "+56912345678",
"emailField": "email",
"phoneField": "telefono"
}
}
}
HTTP 429 con error: rate_limited.
Content-Encoding: gzip en
/track y /track/batch para reducir trafico de red.
{
"events": [
{
"companyId": "ACCOUNT_ID_UNICO",
"portalId": "PORTAL_ID_UNICO",
"visitorId": "vtr_9j2k3lm...",
"sessionId": "ses_c4d5e6f...",
"eventType": "pageview",
"pageUrl": "https://www.empresa.com/home",
"eventData": null
},
{
"companyId": "ACCOUNT_ID_UNICO",
"portalId": "PORTAL_ID_UNICO",
"visitorId": "vtr_9j2k3lm...",
"sessionId": "ses_c4d5e6f...",
"eventType": "scroll_depth",
"pageUrl": "https://www.empresa.com/home",
"eventData": {"milestone":75}
}
]
}
Ejemplo JavaScript — enviar eventos
const BASE = 'https://analytics.bitnodo.net/api/v1';
// Enviar evento individual
await fetch(`${BASE}/track`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
companyId: 'TU_ACCOUNT_ID',
portalId: 'TU_PORTAL_ID',
visitorId: 'vtr_' + Math.random().toString(36).slice(2),
sessionId: 'ses_' + Date.now().toString(36),
eventType: 'pageview',
pageUrl: window.location.href,
referrer: document.referrer,
screenActiveSeconds: 0,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
language: navigator.language,
widgetVersion: '1.0'
})
});
// Enviar batch de eventos acumulados
await fetch(`${BASE}/track/batch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
events: [
{ companyId: 'TU_ACCOUNT_ID', portalId: 'TU_PORTAL_ID',
visitorId: 'vtr_abc', sessionId: 'ses_001',
eventType: 'scroll_depth', pageUrl: window.location.href,
eventData: { milestone: 75 } },
{ companyId: 'TU_ACCOUNT_ID', portalId: 'TU_PORTAL_ID',
visitorId: 'vtr_abc', sessionId: 'ses_001',
eventType: 'link_click', pageUrl: window.location.href,
eventData: { href: 'https://www.miempresa.com/precios' } }
]
})
});
Pipeline de procesamiento de eventos
La API registra cada evento en la tabla events para desacoplar ingestion y
procesamiento. El flujo queda preparado para workers de alto volumen.
Widget
-> POST /track
-> POST /track/batch (opcional)
-> events (queue)
-> processor interno / worker
-> visit aggregation
-> intent score
-> alerts
-> marketplace leads
events: portal_id, company_id,
visit_id, event_type, payload_json, created_at,
processed_at.
Deteccion de intencion de compra
Bitnodo Analytics calcula un intent_score por sesion de visita (visit_id)
usando los eventos del widget. Esto permite detectar en tiempo casi real visitas con alto
interes comercial.
Como funciona
- Los eventos se agrupan por sesion con
visit_id(ventana de 30 minutos) - Se acumula score por comportamiento de navegacion
- Se clasifica el nivel de intencion en normal/interested/high/very_high
- Si el score supera el umbral, se crea alerta
high_intent_visit
Reglas base de score
pageview+1scroll_depth50/75/100 = +1/+2/+3heartbeatmayor a 60s y 120s = +2/+4link_click+2,video_play+2,file_download+3form_submit+10- URL comercial (precios, demo, cotizar, etc.) +5
intent_rules.
Visitas agregadas y sesiones
Cada evento se asocia a una sesion de visita (visit_id) y a dos identificadores
del cliente: visitor_id (persistente) y session_id (rotacion cada 30 minutos).
Esto mejora consistencia de scoring y agregacion multi-evento sin romper compatibilidad de
endpoints existentes.
Campos operativos de visita
visit_id,portal_id,ip,current_pagecountry,device_typesource_type: direct/google/facebook/linkedin/twitter/referralpages(paginas vistas por sesion)duration_seconds(duracion total de sesion)intent_scoreyintent_level
Endpoint
GET /visits?limit=50
curl "https://analytics.bitnodo.net/api/v1/visits?limit=50" \
-H "X-API-Key: TU_API_KEY"
{
"items": [
{
"visit_id": "vst_98ad91",
"portal_id": "PORTAL_ID",
"ip": "186.10.25.44",
"current_page": "https://www.empresa.com/precios",
"pages": 4,
"duration_seconds": 132,
"intent_score": 9,
"intent_level": "interested",
"country": "Chile",
"device_type": "mobile",
"source_type": "google",
"company": {
"name": "Empresa Demo SpA",
"domain": "empresa.cl",
"confidence": 0.82
},
"created_at": "2026-03-11 15:40:12"
}
]
}
Obtener visita actual (sesión específica)
Endpoint para obtener los detalles y tiempo en sitio de una sesión específica (visit_id).
Útil para paneles que necesitan mostrar el tiempo en sitio de un usuario actualmente navegando, sin
sumar sesiones históricas previas.
Endpoint
GET /visits/{visit_id}
curl "https://analytics.bitnodo.net/api/v1/visits/vst_98ad91" \
-H "X-API-Key: TU_API_KEY"
Parámetro requerido
visit_id(en URL): ID único de la sesión
Devuelve todos los campos disponibles: visit_id,
duration_seconds, active_seconds_total, pages,
session_id, etc.
{
"visit_id": "vst_98ad91",
"portal_id": "PORTAL_ID",
"ip": "186.10.25.44",
"current_page": "https://www.empresa.com/precios",
"first_page": "https://www.empresa.com/",
"last_page": "https://www.empresa.com/precios",
"pages": 4,
"duration_seconds": 125,
"active_seconds_total": 85,
"event_count": 12,
"intent_score": 9,
"intent_level": "interested",
"country": "Chile",
"device_type": "mobile",
"source_type": "google",
"session_id": "sess_7f4e2a",
"company": {
"name": "Empresa Demo SpA",
"domain": "empresa.cl",
"confidence": 0.82
},
"created_at": "2026-03-11 15:40:12",
"updated_at": "2026-03-11 15:42:17"
}
visit_id actual para obtener
duration_seconds (tiempo real en sitio de esa sesión), evitando sumar tiempos de
sesiones históricas previas.
Ejemplo JavaScript — obtener tiempo en sitio actual
Polling en el panel cada 5-10 segundos para mostrar el tiempo en sitio actual del usuario que está navegando ahora, sin sumar sesiones históricas.
// Obtener visita actual y mostrar tiempo en sitio real
const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://analytics.bitnodo.net/api/v1';
let currentVisitId = 'vst_98ad91'; // Obtenido del tracker.js widget
async function getTimeOnSite(visitId) {
try {
const response = await fetch(`${BASE_URL}/visits/${visitId}`, {
headers: {
'X-API-Key': API_KEY,
}
});
if (!response.ok) {
console.error(`Error fetching visit: ${response.status}`);
return null;
}
const data = await response.json();
return data; // Contiene: duration_seconds, active_seconds_total, pages, etc.
} catch (error) {
console.error('Error:', error);
return null;
}
}
// Formatear segundos a HH:MM:SS
function formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
// Actualizar panel cada 5 segundos
setInterval(async () => {
const visitData = await getTimeOnSite(currentVisitId);
if (visitData) {
// Actualizar elemento del panel
document.getElementById('time-on-site').innerText = formatDuration(visitData.duration_seconds);
document.getElementById('pages-viewed').innerText = visitData.pages;
document.getElementById('intent-level').innerText = visitData.intent_level || 'N/A';
document.getElementById('current-page').innerText = visitData.current_page;
}
}, 5000);
- ✓ Muestra el tiempo de esta sesión solamente (no acumula históricas)
- ✓ Siempre actualizado cada 5-10 segundos (usuario ve tiempo en tiempo real)
- ✓ Incluye todos los datos de la visita: intención, páginas, fuente, empresa, etc.
- ✓ Ideal para dashboards de monitoreo en vivo
Deteccion de empresas visitantes (B2B Visitor Intelligence)
Bitnodo intenta identificar si una visita proviene de una red corporativa usando la IP publica, metadatos tecnicos y resolucion por dominio. Esta deteccion no identifica personas, solo contexto empresarial de red.
Flujo de deteccion
- Se captura la IP del evento y se asocia a
visit_id. - Se consulta cache local
ip_company_cache(TTL 7 dias). - Se buscan rangos IP en
company_ip_ranges. - Si no hay match, se intenta reverse DNS.
- Para visitas con
intent_score >= 8, se permite enriquecimiento adicional.
Datos B2B guardados en visita
company_idcompany_namecompany_domaincompany_confidence
Metodo de deteccion registrado: ip_range, reverse_dns,
external_enrichment o cache.
Alertas y monitoreo por API key
Para consultar alertas/eventos debes enviar la cabecera
X-API-Key: TU_API_KEY.
| Metodo | Endpoint | Resultado |
|---|---|---|
| GET | /alerts?limit=20 | Alertas recientes (ej: visit.new) |
| GET | /events/recent?limit=50 | Eventos recientes (en free vista basica, en pro incluye metadatos avanzados en event_payload) |
| GET | /visits?limit=50 | Visitas agregadas por sesion con ip, current_page, duracion, score, fuente de trafico y empresa detectada |
| GET | /companies/detected?limit=50 | Resumen de empresas detectadas, visitas, ultima actividad e intent score maximo |
| GET | /leads?limit=50 | Leads detectados desde formularios con email y/o telefono (solo plan pro) |
| GET | /stats/overview | Resumen agregado para dashboards (visitas, leads, top pages) |
| GET | /visits/high-intent?limit=50 | Visitas con mayor probabilidad comercial segun intent_score (plan pro) |
curl "https://analytics.bitnodo.net/api/v1/alerts?limit=20" \
-H "X-API-Key: TU_API_KEY"
{
"event": "visit.company_detected",
"portal_id": "PORTAL_ID",
"visit_id": "VISIT_ID",
"company_name": "Empresa Demo SpA",
"company_domain": "empresa.cl",
"intent_score": 14,
"page_url": "/precios",
"timestamp": "2026-03-11T18:20:00Z"
}
Realtime interno (superadmin)
Para paneles operativos de tiempo real se recomienda usar el endpoint interno
/internal/superadmin/realtime/overview con ventana corta y polling rapido.
Este endpoint requiere cabecera X-Superadmin-Secret.
curl "https://analytics.bitnodo.net/internal/superadmin/realtime/overview?account_id=ACCOUNT_ID&portal_id=PORTAL_ID&since_seconds=30&active_window_seconds=20&active_by=session&limit=100" \
-H "X-Superadmin-Secret: TU_SUPERADMIN_SECRET"
Parametros recomendados
since_seconds: ventana general del feed (recomendado: 30)active_window_seconds: ventana de usuarios activos (recomendado: 15-30)active_by: criterio de activos (visit,session,ip)limit: cantidad maxima de items enevents/visits
Campos realtime
realtime.active_visitors: metrica principal segunactive_byrealtime.active_visits: visitas activas unicasrealtime.active_sessions: sesiones activas unicasrealtime.active_unique_ips: IPs unicas activasrealtime.active_window_seconds: ventana aplicada
Ejemplo JavaScript — polling realtime interno
const BASE_INTERNAL = 'https://analytics.bitnodo.net/internal/superadmin';
const SUPERADMIN_SECRET = 'TU_SUPERADMIN_SECRET';
const params = new URLSearchParams({
account_id: 'TU_ACCOUNT_ID',
portal_id: 'TU_PORTAL_ID',
since_seconds: '30',
active_window_seconds: '20',
active_by: 'session',
limit: '100'
});
async function loadRealtimeOverview() {
const url = `${BASE_INTERNAL}/realtime/overview?${params.toString()}`;
const res = await fetch(url, {
headers: {
'X-Superadmin-Secret': SUPERADMIN_SECRET
}
});
if (!res.ok) {
throw new Error(`Realtime request failed: ${res.status}`);
}
const payload = await res.json();
const data = payload.data || {};
const realtime = data.realtime || {};
// Metrica principal para tarjeta de "activos ahora"
const activeNow = Number(realtime.active_visitors || 0);
// Metricas secundarias utiles para diagnostico
const sessions = Number(realtime.active_sessions || 0);
const ips = Number(realtime.active_unique_ips || 0);
console.log('[Realtime]', {
activeNow,
sessions,
ips,
activeBy: realtime.active_by,
activeWindowSeconds: realtime.active_window_seconds
});
// Ejemplo: actualizar UI
// document.querySelector('#active-now').textContent = String(activeNow);
// document.querySelector('#active-sessions').textContent = String(sessions);
// document.querySelector('#active-ips').textContent = String(ips);
}
async function pollRealtime() {
try {
await loadRealtimeOverview();
} catch (error) {
console.error(error);
}
}
// Primera carga inmediata
pollRealtime();
// Polling recomendado: cada 5 segundos
setInterval(pollRealtime, 5000);
Ejemplo JavaScript — polling de alertas
const API_KEY = 'TU_API_KEY';
const BASE = 'https://analytics.bitnodo.net/api/v1';
// Leer alertas recientes
const alertsRes = await fetch(`${BASE}/alerts?limit=20`, {
headers: { 'X-API-Key': API_KEY }
});
const { items: alerts } = await alertsRes.json();
// Leer visitas con intent score
const visitsRes = await fetch(`${BASE}/visits?limit=50`, {
headers: { 'X-API-Key': API_KEY }
});
const { items: visits } = await visitsRes.json();
// Polling cada 30 segundos para dashboard en tiempo real
setInterval(async () => {
const res = await fetch(`${BASE}/alerts?limit=5`, {
headers: { 'X-API-Key': API_KEY }
});
const data = await res.json();
data.items.forEach(alert => {
if (alert.intent_score >= 11) {
console.log('Alta intencion:', alert.company_name, alert.intent_score);
}
});
}, 30_000);
Estadisticas agregadas para dashboard
El endpoint de overview evita procesar eventos crudos en clientes externos y entrega metricas listas para paneles operativos.
curl "https://analytics.bitnodo.net/api/v1/stats/overview" \
-H "X-API-Key: TU_API_KEY"
{
"visits_today": 124,
"visits_yesterday": 110,
"leads_today": 6,
"high_intent_today": 9,
"top_pages": ["/", "/servicios", "/precios"]
}
curl "https://analytics.bitnodo.net/api/v1/companies/detected?limit=50" \
-H "X-API-Key: TU_API_KEY"
{
"items": [
{
"company_name": "Empresa Demo SpA",
"domain": "empresa.cl",
"visits": 4,
"last_visit": "2026-03-11 17:40:00",
"highest_intent_score": 18
}
]
}
Webhooks para integraciones externas
Puedes registrar webhooks por cuenta para recibir eventos operativos en tiempo real. Los intentos fallidos se reintentan automaticamente.
| Metodo | Endpoint | Uso |
|---|---|---|
| GET | /webhooks | Lista webhooks configurados |
| POST | /webhooks | Crea webhook para visit.new, visit.high_intent, lead.created o lead.marketplace.created |
| PUT | /webhooks/{id} | Actualiza target_url, event_type o estado |
| DELETE | /webhooks/{id} | Elimina webhook |
{
"event": "lead.created",
"portal_id": "PORTAL_ID",
"visit_id": "VISIT_ID",
"email": "cliente@empresa.com",
"phone": "+56912345678",
"page_url": "/contacto",
"timestamp": "2026-03-11T17:20:00Z"
}
Lead Marketplace
El marketplace permite publicar oportunidades comerciales anonimizadas y desbloquear contacto completo solo despues de una compra valida del lead.
Tablas nuevas
marketplace_leads: oportunidades, estado, precio, cupos y contacto protegidolead_purchases: compras efectivas por clientemarketplace_subscriptions: cuentas suscritas por categoria/ciudad para notificaciones
Estructura clave
marketplace_leads guarda: portal_id, visit_id,
category, sub_category, city, intent_score,
lead_quality, lead_identity_hash, last_seen_at,
estimated_budget_min, estimated_budget_max, lead_summary,
contact_name, contact_email, contact_phone,
company_name, status, price,
max_buyers, buyers_count, created_at, updated_at.
Generacion automatica
- Se crea lead marketplace por
form_submit - Tambien cuando
intent_score >= 12 - Tambien cuando la visita toca paginas clave (precios/contacto/servicios/descargas)
- Tambien por evento
file_download - Se publica una sola oportunidad por
visit_id - Deduplicacion por
lead_identity_hash(email+phone+company+domain)
Estados
available: visible para comprareserved: compra en cursosold: alcanzo maximo de compradoresexpired: lead antiguo/inactivo
marketplace_lead
anonimo -> listado para clientes suscritos -> compra -> desbloqueo de contacto completo.
| Metodo | Endpoint | Uso |
|---|---|---|
| GET | /marketplace/leads | Lista oportunidades anonimizadas segun filtros |
| POST | /marketplace/leads/{lead_id}/purchase | Compra y desbloquea datos completos del lead |
| POST | /marketplace/leads/{lead_id}/anonymize | Anonimiza contacto del lead por solicitud de eliminacion (Bearer token owner) |
curl "https://analytics.bitnodo.net/api/v1/marketplace/leads?category=servicios%20legales&city=Santiago&min_intent_score=12&limit=20" \
-H "X-API-Key: TU_API_KEY"
{
"items": [
{
"lead_id": "ld_9231abf0",
"category": "servicios legales",
"sub_category": "abogado laboral",
"city": "Santiago",
"intent_score": 16,
"estimated_budget_min": 300000,
"estimated_budget_max": 800000,
"lead_summary": "Empresa busca abogado laboral en Santiago. Nivel de interes 16. Presupuesto estimado entre $300.000 y $800.000.",
"price": 7000,
"created_at": "2026-03-11 18:40:00"
}
]
}
curl -X POST "https://analytics.bitnodo.net/api/v1/marketplace/leads/ld_9231abf0/purchase" \
-H "X-API-Key: TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"buyer_portal_id":"PORTAL_ID_COMPRADOR"}'
{
"lead_id": "ld_9231abf0",
"contact_name": "Rodrigo Perez",
"contact_email": "rperez@empresa.cl",
"contact_phone": "+56912345678",
"company_name": "Comercial Andina SpA",
"city": "Santiago"
}
lead.marketplace.created se emite al publicar un nuevo lead.
curl -X POST "https://analytics.bitnodo.net/api/v1/marketplace/leads/ld_9231abf0/anonymize" \
-H "Authorization: Bearer TU_ACCESS_TOKEN"
Leads recuperables por API
Cuando un visitante envia un formulario y este contiene email y/o telefono validos, la API guarda esos datos en una entidad separada de leads para facilitar consulta comercial y atribucion de conversiones.
Reglas de captura
- Solo se capturan en el submit del formulario
- No se inspeccionan contrasenas ni campos sensibles ajenos al lead
- Solo se guardan email y telefono si son validos
- Se relacionan con pagina, portal y evento de origen
- Si el formulario no tiene id, el widget genera un identificador estable automaticamente
Consulta
GET /api/v1/leads?limit=50
curl "https://analytics.bitnodo.net/api/v1/leads?limit=50" \
-H "X-API-Key: TU_API_KEY"
| Filtro | Parametro | Ejemplo |
|---|---|---|
| Portal | portal_id | PORTAL_ID_UNICO |
| cliente@empresa.com | ||
| Telefono | phone | +56912345678 |
| Desde fecha | date_from | 2026-03-01 |
| Hasta fecha | date_to | 2026-03-31 |
| Limite | limit | 50 |
curl "https://analytics.bitnodo.net/api/v1/leads?limit=50&portal_id=PORTAL_ID_UNICO&email=cliente@empresa.com&phone=%2B569&date_from=2026-03-01&date_to=2026-03-31" \
-H "X-API-Key: TU_API_KEY"
{
"items": [
{
"id": 12,
"portal_id": "PORTAL_ID_UNICO",
"visit_event_id": 301,
"email": "cliente@empresa.com",
"phone": "+56912345678",
"page_url": "https://www.empresa.com/contacto",
"form_id": "frm_k29a81",
"form_name": "contacto",
"source_event_type": "form_submit",
"metadata": {
"action": "https://www.empresa.com/contacto",
"method": "POST",
"page_identifier": "page_contacto",
"field_count": 4,
"lead_fields": {
"email_field": "email",
"phone_field": "telefono"
}
},
"created_at": "2026-03-11 12:00:00"
}
]
}
Sandbox de pruebas
El sandbox replica el comportamiento de la API de produccion de forma identica en validaciones y respuestas, pero no escribe nada en la base de datos. Ideal para integrar y probar tu cliente antes de usar credenciales reales.
Base URL sandbox
https://analytics.bitnodo.net/sandbox/v1
Caracteristicas
- No requiere API key real — cualquier valor funciona
- Mismas validaciones que produccion (422 si falta campo)
- Respuestas con IDs ficticios generados aleatoriamente
- Todos los responses incluyen
"mode": "sandbox"
| Metodo | Endpoint | Descripcion |
|---|---|---|
| GET | /sandbox/v1/health | Estado del sandbox (siempre ok) |
| POST | /sandbox/v1/track | Simula ingesta de evento individual (valida payload) |
| POST | /sandbox/v1/track/batch | Simula ingesta batch de hasta 20 eventos |
| POST | /sandbox/v1/auth/login | Retorna tokens ficticios con cualquier email/password |
| GET | /sandbox/v1/portals | Lista de portales de ejemplo |
| GET | /sandbox/v1/alerts | Alertas de ejemplo con datos ficticios |
| GET | /sandbox/v1/visits | Visitas de ejemplo con intent scores |
| GET | /sandbox/v1/stats/overview | Metricas de overview con datos ficticios |
1. Health check
curl "https://analytics.bitnodo.net/sandbox/v1/health"
{
"status": "ok",
"service": "bitnodo-analytics",
"version": "v1",
"mode": "sandbox",
"timestamp": "2026-03-11T18:30:00+00:00"
}
2. Enviar evento de tracking
curl -s -X POST "https://analytics.bitnodo.net/sandbox/v1/track" \
-H "Content-Type: application/json" \
-d '{
"companyId": "cualquier-account-id",
"portalId": "cualquier-portal-id",
"visitorId": "vtr_test_001",
"sessionId": "ses_test_001",
"eventType": "pageview",
"pageUrl": "https://www.miempresa.com/home"
}'
{
"ok": true,
"message": "Event captured (sandbox — nothing stored)",
"mode": "sandbox",
"queued_event_id": "a3f8d1e2b4c9...",
"visit_id": "e5b7f2a1c4d8...",
"intent_score": 7,
"intent_level": "normal",
"echo": {
"companyId": "cualquier-account-id",
"portalId": "cualquier-portal-id",
"eventType": "pageview",
"pageUrl": "https://www.miempresa.com/home"
}
}
3. Batch de eventos
curl -s -X POST "https://analytics.bitnodo.net/sandbox/v1/track/batch" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"companyId": "cualquier-account-id",
"portalId": "cualquier-portal-id",
"visitorId": "vtr_test_001",
"sessionId": "ses_test_001",
"eventType": "pageview",
"pageUrl": "https://www.miempresa.com/home"
},
{
"companyId": "cualquier-account-id",
"portalId": "cualquier-portal-id",
"visitorId": "vtr_test_001",
"sessionId": "ses_test_001",
"eventType": "scroll_depth",
"pageUrl": "https://www.miempresa.com/home",
"eventData": {"milestone": 75}
}
]
}'
{
"ok": true,
"mode": "sandbox",
"accepted": 2,
"rejected": 0,
"results": [
{"ok": true, "event_index": 0, "queued_event_id": "c2a1b3d4..."},
{"ok": true, "event_index": 1, "queued_event_id": "f5e6a7b8..."}
]
}
4. Login sandbox
curl -s -X POST "https://analytics.bitnodo.net/sandbox/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"test@ejemplo.com","password":"cualquierpassword"}'
{
"mode": "sandbox",
"user": {
"id": "d4e5f6a7b8c9...",
"email": "test@ejemplo.com",
"full_name": "Sandbox User",
"account_id": "a1b2c3d4e5f6...",
"tenant_id": "f6e5d4c3b2a1..."
},
"account": {
"id": "a1b2c3d4e5f6...",
"name": "Sandbox Account",
"plan": "pro",
"api_key": "bnk_sandbox_test_key",
"public_key": "pk_sandbox_test",
"secret_key": "sk_sandbox_test"
},
"access_token": "atk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
5. Leer alertas / visitas / stats (ejemplo)
curl "https://analytics.bitnodo.net/sandbox/v1/alerts"
curl "https://analytics.bitnodo.net/sandbox/v1/visits"
curl "https://analytics.bitnodo.net/sandbox/v1/stats/overview"
curl "https://analytics.bitnodo.net/sandbox/v1/portals"
Ejemplo completo en JavaScript (fetch)
// 1. Hacer login en sandbox
const loginRes = await fetch('https://analytics.bitnodo.net/sandbox/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'test@ejemplo.com', password: 'test123' })
});
const { access_token } = await loginRes.json();
// 2. Enviar evento
await fetch('https://analytics.bitnodo.net/sandbox/v1/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
companyId: 'cualquier-account-id',
portalId: 'cualquier-portal-id',
visitorId: 'vtr_test_001',
sessionId: 'ses_test_001',
eventType: 'pageview',
pageUrl: window.location.href
})
});
// 3. Consultar overview
const statsRes = await fetch('https://analytics.bitnodo.net/sandbox/v1/stats/overview');
const stats = await statsRes.json();
console.log(stats); // { visits_today: 124, leads_today: 6, ... mode: "sandbox" }
companyId), el sandbox devuelve HTTP 422
con el mismo mensaje de error que producccion. Esto garantiza que tu integracion
funcionara igual al pasar a produccion.
Salud de sistema y control de trafico
/track y /track/batch:
100 req/min por IP y 2000 eventos/min por portal.
Health check publico
GET /health
{
"status": "ok",
"service": "bitnodo-analytics",
"version": "v1",
"timestamp": "2026-03-11T18:30:00Z"
}
Status operativo de portal
GET /portals/{id}/status (Bearer token)
{
"portal_id": "PORTAL_ID",
"widget_detected": true,
"last_event_at": "2026-03-11 16:10:11",
"events_last_24h": 1243,
"visits_last_24h": 301
}
Lineamientos de Desarrollo y Compatibilidad
Bitnodo Analytics evoluciona con foco en estabilidad del contrato publico. La arquitectura interna puede cambiar para mejorar rendimiento, escalabilidad y seguridad, manteniendo compatibilidad con integraciones existentes.
Contrato publico estable
- Autenticacion y gestion de portales.
POST /api/v1/track.POST /api/v1/track/batch.GET /api/v1/visits,/alerts,/stats/overview,/leads.GET /api/v1/marketplace/leads.GET /v1/widget/config/{tenant_id}.
Cambios internos permitidos
- Nuevas tablas, indices y refactor de esquema.
- Batching, caching, rate limiting y mejoras de pipeline.
- Optimizaciones de session handling y scoring.
- Ajustes internos del widget sin romper integracion publica.
Widget -> /track -> almacenamiento inicial -> workers internos -> agregacion y scoring.
/v2/*).
Errores HTTP comunes
401 Unauthorized
Token Bearer invalido/expirado o API key incorrecta.
404 Not Found
Endpoint no existe o id de recurso no valido.
409 Conflict
Intento de registro con email ya existente.
422 Unprocessable Entity
Payload invalido o faltan campos requeridos.
429 Too Many Requests
Rate limit superado en tracking o lecturas. Reintenta en la siguiente ventana.
Seguridad y cumplimiento
- Passwords protegidas con Argon2id.
- Tokens y API keys guardados hashados en base de datos.
- Aislamiento por cuenta (multi-tenant).
- Consultas SQL preparadas (PDO).
- Aviso legal integrado en el widget.
- Captura de datos de contacto solo al enviar formularios.