Saltar al contenido principal

API de telemetría de Pulse

POST /api/v1/pulse/telemetry es el endpoint que Clione Pulse — y cualquier scanner de terceros — usa para reportar un scan de página única de vuelta a Clione. El endpoint es solo-escritura: acepta un payload, lo guarda y devuelve 204 No Content. Para leer scans de vuelta, usa los endpoints del dashboard (scope auth) o GET /api/v1/public/pulse/badge para badges públicos.

Para el producto Pulse completo, mira Pulse dashboard y Extensión de Pulse.


Cuándo usarlo

Estás integrando un scanner que no es Chrome (paso de CI, script Node, crawler custom, herramienta de partner) y quieres que los resultados aparezcan en tu dashboard de Clione Pulse. La extensión de Chrome ya llama a este endpoint — no necesitas hacer nada para opt in ahí.

Si solo quieres leer scans para un dominio (badges, scoreboards públicos), mira los endpoints públicos de Pulse, no este.


Endpoint

POST /api/v1/pulse/telemetry
Content-Type: application/json

URL base de producción: https://api.clione.ai.

El endpoint responde con 204 No Content en éxito. Modos de fallo:

CódigoSignificado
400JSON malformado, faltan campos obligatorios.
401Faltan headers de autenticación o son inválidos.
401Mismatch de firma HMAC, timestamp obsoleto (> 5 min de skew) o token revocado.
500La persistencia falló. El body contiene { error, detail }.

Autenticación — dos modos

El endpoint acepta cualquiera de dos modos de auth mutuamente exclusivos. Elige el que coincida con tu contexto.

Modo 1 — Token público (X-Pulse-Token)

Usado por la build pública de la Chrome Web Store de la extensión de Pulse y por cualquiera que se diera de alta vía la landing page pública de Pulse. El modo más simple.

Headers:

X-Pulse-Token: <PulseLead.apiToken>

Cómo conseguir un token:

  1. Haz que tu usuario se dé de alta en la landing page pública de Pulse (o vía el popup de la extensión de Chrome).
  2. El sistema emite un apiToken opaco ligado a un registro PulseLead.
  3. El token está verificado, el apiToken es lo que pones en el header.

Formato del token: string aleatorio opaco de 32+ bytes. No hay HMAC — el token en sí es el secret. Trátalo como una contraseña, no lo commitees al código fuente.

Rotación: contacta a tu superadmin de Clione. Un token rotado invalida el viejo de inmediato.

Modo 2 — API key firmada con HMAC (X-Pulse-Key + X-Pulse-Timestamp + X-Pulse-Signature)

Usado por las tools internas de Clione y por partners con una API key sk_live_* cuyos scopes incluyan pulse:scan o admin. Obligatorio cuando quieres el tier de rate-limit más alto.

Headers:

X-Pulse-Key: sk_live_<...>
X-Pulse-Timestamp: <milisegundos epoch, tiempo actual>
X-Pulse-Signature: <HMAC-SHA256 en hex>

Donde:

  • X-Pulse-Timestamp es el tiempo actual en milisegundos epoch. El servidor rechaza peticiones donde |now - timestamp| > 5 minutos (ventana de replay).
  • X-Pulse-Signature es HMAC-SHA256(apiKey, "${timestamp}.${rawJsonBody}"), codificado en hex. El cuerpo JSON crudo — bytes tal cual se envían — es lo que se firma; no re-stringifiques.

El servidor busca la API key por su hash SHA-256, luego recalcula el HMAC usando la key cruda del header. Usa la semántica de crypto.timingSafeEqual también en tu lado si construyes un verificador.


Payload

{
"url": "https://shop.example.com/products/widget-001",
"hostname": "shop.example.com",
"scannedAt": "2026-06-05T14:23:45.000Z",
"platform": { "platform": "shopify", "confidence": 0.95 },
"pageType": "product",
"clione": { "detected": true, "version": "0.4.1" },
"signals": {
"title": { "value": "Widget 001 — Acme", "length": 21 },
"description": { "value": "...", "length": 142 },
"canonical": { "value": "https://shop.example.com/products/widget-001" },
"robots": "index,follow",
"lang": "en",
"headings": { "h1": ["Widget 001"], "h2": ["Specifications"] },
"openGraph": {
"og:title": "Widget 001 — Acme",
"og:type": "product",
"og:image": "..."
},
"jsonLd": [
{ "@context": "https://schema.org", "@type": "Product", "name": "Widget 001", "sku": "W-001", "brand": { "name": "Acme" } }
]
},
"signalPresence": { "title": true, "description": true, "jsonLd": true, "openGraph": true, "h1": true, "canonical": true, "robots": true },
"quality": {
"grade": "B",
"percentage": 82,
"title": 2, "description": 2, "keywords": 1, "jsonLd": 2, "openGraph": 1
},
"country": "ES",
"vertical": "ecommerce"
}

Referencia de campos (solo los obligatorios-para-persistir se listan como obligatorios; el resto son best-effort y se degradan con elegancia):

CampoObligatorioTipoNotas
urlrecomendadostringLa URL completa con path. Quita query string y hash antes de enviar. Si está ausente, el servidor cae a https://${hostname}/.
hostnamestringHost pelado, sin esquema, sin path.
scannedAtrecomendadoISO-8601Por defecto el now del servidor si se omite.
platform.platformrecomendadostringshopify, bigcommerce, woocommerce, magento, custom, unknown.
pageTyperecomendadostringproduct, collection, category, page, home, cart, checkout, unknown.
clione.detectedrecomendadobooleanTrue si el scanner encontró el huella del embed o schema de Clione.
signalsobjectSeñales crudas extraídas. La forma de arriba refleja lo que la extensión de Chrome envía.
signalPresencerecomendadoobjectMapa booleano. Usado por el dashboard para agregaciones rápidas de presencia.
quality.graderecomendadoAFLa calificación propia del scanner. Si está ausente, por defecto F.
quality.percentagerecomendado0–100El score propio del scanner. Si está ausente, por defecto 0.
countryopcionalISO 3166-1 alpha-2Usado por filtros del dashboard. Si está ausente, el servidor usa GeoIP desde la IP de la petición.
verticalopcionalstringFree-form, usado para segmentación de analytics de tenant.

Cualquier cosa no listada se preserva en el blob JSON crudo data — puedes enviar campos diagnósticos arbitrarios y persistirán para inspección, simplemente no estarán indexados.


Ejemplos curl

Modo 1 — Token público

curl -X POST https://api.clione.ai/api/v1/pulse/telemetry \
-H "Content-Type: application/json" \
-H "X-Pulse-Token: $PULSE_TOKEN" \
--data @scan-payload.json

Modo 2 — API key firmada con HMAC

TS=$(node -e 'process.stdout.write(Date.now().toString())')
BODY=$(cat scan-payload.json)
SIG=$(node -e "
const c = require('crypto');
const ts = process.argv[1];
const body = process.argv[2];
const key = process.env.PULSE_API_KEY;
process.stdout.write(c.createHmac('sha256', key).update(\`\${ts}.\${body}\`).digest('hex'));
" "$TS" "$BODY")

curl -X POST https://api.clione.ai/api/v1/pulse/telemetry \
-H "Content-Type: application/json" \
-H "X-Pulse-Key: $PULSE_API_KEY" \
-H "X-Pulse-Timestamp: $TS" \
-H "X-Pulse-Signature: $SIG" \
--data "$BODY"

Una llamada exitosa devuelve 204 No Content con body vacío.


Rate limits

Este endpoint hereda los mismos rate limits basados en tier que el resto de /api/v1/pulse/*:

TierCómo se reconoceScans por minuto
anonymoussin headers de auth, o token no reconocido10
authedX-Pulse-Token válido para un lead verificado50
agentX-Pulse-Key con scope pulse:scan o admin1000

429 Too Many Requests incluye un body JSON indicando el tier activo y el límite para que puedas hacer backoff apropiadamente.


Idempotencia y dedup

El endpoint no es idempotente — enviar el mismo payload dos veces crea dos filas PulseScan. Es intencional: un re-scan tras un deploy es un evento real.

Si necesitas dedup, dedupea en el lado cliente, o usa el endpoint bulk (POST /pulse/scan-bulk) que hace skipDuplicates en el insert por (url, scannedAt).


Relacionados