Saltar al contenido principal

API de analítica

La API de analítica ofrece acceso programático a las funciones de analítica de producto, incluidas las consultas de eventos, los embudos, el análisis de retención y el análisis de rutas.

Todos los endpoints de esta página son relativos a la URL base: https://api-eu1.joryio.com/api. Consulta el resumen de la API.

Autenticación​

Todos los endpoints requieren autenticación mediante clave de API:

Authorization: Bearer YOUR_API_KEY
X-Workspace-Id: YOUR_WORKSPACE_ID

Explorador de eventos​

Listar eventos​

Obtiene un resumen de todos los eventos rastreados.

GET /analytics/events

Parámetros de consulta:

ParámetroTipoDescripción
startDatecadenaFecha inicial (AAAA-MM-DD)
endDatecadenaFecha final (AAAA-MM-DD)
searchcadenaFiltra por nombre de evento
limitnúmeroMáximo de resultados (predeterminado: 100)
channelsstringOpcional. Contar los eventos de mensajes e in-app solo en estos canales, separados por comas - ver Filtro de canales

Respuesta:

{
"events": [
{
"eventName": "signup_completed",
"totalCount": 15420,
"uniqueUsers": 12350,
"lastSeen": "2024-01-15T14:30:00Z"
}
],
"total": 45
}

Filtro de canales​

GET /analytics/events, POST /analytics/events/trend y POST /analytics/events/query aceptan un filtro opcional channels. Solo se aplica a los eventos de mensajes (message.*) y a los eventos in-app (in_app.*): estos se cuentan solo cuando su canal está en la lista. Los eventos sin canal - compras, sesiones, eventos personalizados - se cuentan siempre, diga lo que diga la lista.

  • Canales: email, sms, whatsapp, push, viber, webhook, wallet, in_app, connector. Cualquier otro valor se rechaza con 400.
  • GET acepta una lista separada por comas (channels=email,sms); las rutas POST aceptan un array JSON ("channels": ["email", "sms"]).
  • Omitido: sin filtro - las mismas cifras que antes de que existiera el parámetro. Vacío (channels= o []): ningún evento de mensaje ni in-app.

Cuando se envía channels, GET /analytics/events también devuelve hiddenByChannel: los eventos de mensajes e in-app que el filtro dejó fuera, por canal.

{
"events": [],
"total": 45,
"hiddenByChannel": [{ "channel": "webhook", "count": 4012 }]
}

Obtener tendencia de eventos​

Obtiene los recuentos de eventos a lo largo del tiempo.

POST /analytics/events/trend

Acepta el filtro opcional channels - ver Filtro de canales.

Cuerpo de la solicitud:

{
"eventNames": ["signup_completed", "purchase_completed"],
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"timeGranularity": "day"
}

Respuesta:

[
{
"date": "2024-01-01",
"count": 523,
"uniqueUsers": 498
}
]

Obtener propiedades de un evento​

Lista las propiedades de un evento concreto.

GET /analytics/events/:eventName/properties

Respuesta:

[
{
"propertyName": "plan_type",
"valueCount": 4
},
{
"propertyName": "source",
"valueCount": 12
}
]

Obtener desglose de una propiedad​

Obtiene la distribución de valores de una propiedad de evento.

POST /analytics/events/property-breakdown

Cuerpo de la solicitud:

{
"eventName": "purchase_completed",
"property": "payment_method",
"startDate": "2024-01-01",
"endDate": "2024-01-31"
}

Respuesta:

{
"total": 5420,
"values": [
{ "value": "credit_card", "count": 3250, "percentage": 59.96 },
{ "value": "paypal", "count": 1520, "percentage": 28.04 },
{ "value": "bank_transfer", "count": 650, "percentage": 11.99 }
]
}

Análisis de embudos​

Crear un embudo​

Guarda una definición de embudo nueva.

POST /analytics/funnels

Cuerpo de la solicitud:

{
"name": "Signup to Purchase",
"steps": [
{
"id": "step_1",
"order": 0,
"eventName": "signup_completed",
"filters": []
},
{
"id": "step_2",
"order": 1,
"eventName": "onboarding_completed",
"filters": []
},
{
"id": "step_3",
"order": 2,
"eventName": "purchase_completed",
"filters": []
}
],
"conversionWindowDays": 7
}

Listar embudos​

GET /analytics/funnels

Analizar un embudo​

Ejecuta un análisis sobre un embudo guardado.

POST /analytics/funnels/:id/analyze

Cuerpo de la solicitud:

{
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"breakdown": {
"type": "event_property",
"property": "utm_source"
}
}

Respuesta:

{
"totalUsers": 10000,
"overallConversion": 12.5,
"steps": [
{
"id": "step_1",
"order": 0,
"eventName": "signup_completed",
"enteredCount": 10000,
"conversionRate": 100,
"dropOffRate": 0
},
{
"id": "step_2",
"order": 1,
"eventName": "onboarding_completed",
"enteredCount": 6500,
"conversionRate": 65,
"dropOffRate": 35
},
{
"id": "step_3",
"order": 2,
"eventName": "purchase_completed",
"enteredCount": 1250,
"conversionRate": 19.23,
"dropOffRate": 80.77
}
],
"breakdown": [
{
"breakdownValue": "google",
"overallConversion": 15.2,
"steps": [...]
}
]
}

Análisis rápido de embudo​

Analiza sin guardar.

POST /analytics/funnels/quick-analysis

Análisis de retención​

Analizar la retención​

POST /analytics/retention/analyze

Cuerpo de la solicitud:

{
"startEvent": "signup_completed",
"returnEvent": "session_started",
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"timeUnit": "week",
"periods": 8
}

Respuesta:

{
"timeUnit": "week",
"cohorts": [
{
"cohortDate": "2024-01-01",
"cohortSize": 1250,
"periods": [
{ "period": 0, "retained": 1250, "retentionRate": 100 },
{ "period": 1, "retained": 562, "retentionRate": 44.96 },
{ "period": 2, "retained": 375, "retentionRate": 30.0 }
]
}
],
"overallRetention": [100, 45.2, 31.5, 25.8, 22.1, 19.5, 17.8, 16.2]
}

Exportar retención​

POST /analytics/retention/export

Devuelve datos CSV.

Comparar la retención de segmentos​

Ejecuta el mismo análisis de retención sobre entre 1 y 10 segmentos guardados y devuelve una fila por segmento, para poder comparar sus curvas. Sustituye a POST /analytics/retention/compare-cohorts, que ya no existe.

POST /analytics/retention/compare-segments

Cuerpo de la solicitud:

{
"segmentIds": ["seg_9f2a", "seg_71bc"],
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"entryEvent": "signup_completed",
"returnEvent": "session_started",
"timeUnit": "week",
"periods": 8
}
CampoObligatorioDescripción
segmentIdssíDe 1 a 10 identificadores de segmento.
startDate, endDatesíLa ventana de la que se leen los eventos de entrada.
entryEventnoLa acción que mete a un usuario en una cohorte.
returnEventnoLa acción que cuenta como regreso.
timeUnitnoday, week o month.
periodsnoCuántos períodos seguir.

Respuesta:

[
{
"segmentId": "seg_9f2a",
"segmentName": "Power users",
"retention": { "timeUnit": "week", "cohorts": [], "overallRetention": [] },
"resolvedUsers": 4820,
"analyzedUsers": 4820,
"truncated": false
},
{
"segmentId": "seg_71bc",
"segmentName": "All customers",
"retention": { "timeUnit": "week", "cohorts": [], "overallRetention": [] },
"resolvedUsers": 480000,
"analyzedUsers": 50000,
"truncated": true
}
]
CampoDescripción
resolvedUsersEl tamaño real del segmento: todos los miembros a los que se resolvió.
analyzedUsersCuántos miembros se usaron para calcular retention. Igual a resolvedUsers salvo que el segmento superase el límite.
truncatedtrue cuando el segmento superó el límite de 50.000 miembros, de modo que retention describe una muestra aleatoria determinista de analyzedUsers miembros, nunca los primeros N, que estarían sesgados. Revela ambas cifras allí donde muestres las tasas.
errorAparece cuando ese segmento concreto no pudo analizarse. El resto de filas se devuelven igualmente y la fila fallida no lleva tasas: no la dibujes como 0 %.

Análisis de rutas​

Analizar rutas​

POST /analytics/paths/analyze

Cuerpo de la solicitud:

{
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"startEvent": "landing_page_view",
"direction": "forward",
"maxSteps": 5,
"minPathCount": 10,
"minPathPercent": 0.5,
"maxBranchesPerStep": 5,
"excludeEvents": ["heartbeat", "scroll"]
}

minPathCount (usuarios absolutos) y minPathPercent (porcentaje de usuarios que cumplen los requisitos, 0-100) definen el umbral de frecuencia: una ruta debe superar el mayor de los dos. Así, el porcentaje permite usar la regla en espacios de trabajo de cualquier tamaño. maxBranchesPerStep (de 1 a 50; 8 de forma predeterminada) limita el diagrama de flujo a los eventos siguientes más frecuentes de cada paso para que siga siendo legible con un volumen alto.

Respuesta:

{
"totalUsers": 25000,
"totalPaths": 342,
"paths": [
{
"path": ["landing_page_view", "signup_started", "signup_completed"],
"count": 3250,
"percentage": 13.0
}
],
"sankey": {
"nodes": [
{ "id": "landing_page_view_0", "name": "landing_page_view" }
],
"links": [
{ "source": "landing_page_view_0", "target": "signup_started_1", "value": 5200 }
]
}
}

Dashboards​

Crear un dashboard​

POST /analytics/dashboards

Cuerpo de la solicitud:

{
"name": "Executive Dashboard",
"description": "Key metrics overview",
"widgets": [
{
"id": "widget-1",
"type": "metric",
"title": "Active Users",
"config": {
"eventName": "session_started",
"metric": "unique_users"
},
"layout": { "x": 0, "y": 0, "width": 3, "height": 2 }
}
]
}

Listar dashboards​

GET /analytics/dashboards

Obtener un dashboard​

GET /analytics/dashboards/:id

Actualizar un dashboard​

PUT /analytics/dashboards/:id

Eliminar un dashboard​

DELETE /analytics/dashboards/:id

Analítica en tiempo real​

Obtener usuarios activos​

GET /analytics/realtime/active-users

Respuesta:

{
"count": 1523,
"trend": 5.2
}

Flujo de eventos en vivo (SSE)​

GET /analytics/realtime/events/stream

Devuelve un flujo Server-Sent Events de eventos en vivo.


Incrementalidad​

Dos endpoints responden preguntas que los informes habituales no pueden: ¿los mensajes causaron algo? y ¿estoy enviando demasiado?. Ambos se apoyan en el grupo de control global - una porción de contactos elegida al azar y excluida de todo el marketing, para que haya algo honesto con lo que comparar.

Incremento frente al grupo de control global​

GET /analytics/global-holdout?startDate=2026-08-01&endDate=2026-08-31

Conversiones e ingresos de todos los que recibieron marketing en el periodo, frente al grupo retenido al azar.

El resultado va envuelto en uplift. Es { "uplift": null } cuando no hay ningún grupo de control global activo - no hay con qué comparar, y un resultado sin medir no es un cero.

Respuesta:

{
"uplift": {
"treatmentRecipients": 41280,
"treatmentConverters": 1650,
"treatmentRevenue": 82560,
"treatmentRevenuePerRecipient": 2.0,
"controlRecipients": 4600,
"controlConverters": 138,
"controlRevenue": 6900,
"controlRevenuePerRecipient": 1.5,
"incrementalRevenue": 20640,
"upliftPct": 33.3,
"significance": {
"controlRate": 0.03,
"treatmentRate": 0.04,
"pValue": 0.002,
"significant": true,
"controlInterval": [0.0255, 0.0353],
"treatmentInterval": [0.0381, 0.0419],
"reason": "significant",
"minConvertersPerArm": 10
},
"currency": "EUR",
"mixedCurrency": false,
"windowHours": 168
}
}

significance.significant contrasta la TASA de conversión, no los ingresos: los ingresos por destinatario son continuos y de cola pesada, y una prueba de proporciones sobre ellos produce números que parecen seguros y no significan nada. Trata incrementalRevenue ((treatmentRevenuePerRecipient - controlRevenuePerRecipient) x treatmentRecipients) como una estimación cuya credibilidad viene de significance.significant, no de su propia precisión. significance es null cuando alguno de los brazos no tiene destinatarios; reason dice por qué un resultado aún no es significativo (not_enough_conversions, no_difference_detected). upliftPct es null cuando el brazo de control no generó ingresos. windowHours es la ventana de comportamiento en la que se midieron ambos brazos (168 por defecto) y mixedCurrency es true cuando las compras abarcan más de una moneda - no se aplica ningún cambio de divisa.

Frecuencia de mensajes​

GET /analytics/message-frequency?startDate=2026-08-01&endDate=2026-08-31

Conversión según cuántos mensajes recibió una persona, separada por si ya estaba activa antes del periodo.

Respuesta:

{
"buckets": [
{ "bucket": "0", "tier": "engaged", "people": 2300, "buyers": 69, "purchaseRate": 3.0, "converters": 69, "conversionRate": 3.0,
"revenue": 3450, "revenuePerPerson": 1.5, "randomised": true },
{ "bucket": "1", "tier": "engaged", "people": 8100, "buyers": 405, "purchaseRate": 5.0, "converters": 405, "conversionRate": 5.0,
"revenue": 20250, "revenuePerPerson": 2.5 },
{ "bucket": "6-9", "tier": "engaged", "people": 1200, "buyers": 48, "purchaseRate": 4.0, "converters": 48, "conversionRate": 4.0,
"revenue": 4800, "revenuePerPerson": 4.0 }
],
"anchored": true,
"currency": "EUR",
"mixedCurrency": false,
"priorWindowDays": 90
}

Los tramos son 0, 1, 2, 3, 4-5, 6-9, 10+. tier es engaged o quiet, según aperturas y clics en los priorWindowDays anteriores al periodo - nunca dentro de él, lo que sería circular.

Dos campos deciden cómo pueden leerse las cifras:

  • randomised aparece solo en un tramo 0 construido a partir del grupo de control global. Un cero construido de cualquier otra forma son personas que no recibieron nada por casualidad, y no recibieron nada por motivos que también las hacen menos propensas a comprar.
  • anchored es true cuando ese cero existe. Cuando es false no hay ningún tramo 0, deliberadamente: una base inventada quedaría justo en el punto contra el que se mide todo lo demás.

Lee la curva sabiendo que el sesgo va en una sola dirección. Quienes reciben más mensajes están más comprometidos de partida, así que la línea se empuja hacia arriba sea cual sea la verdad. Una línea ascendente no prueba nada, mientras que una que se aplana o cae en las frecuencias altas es creíble - apareció pese a un sesgo que trabajaba en su contra.

converters cuenta pedidos en todas las monedas; revenue suma solo la dominante, indicada en currency. Cuando mixedCurrency es true, la columna de dinero es una porción y no una suma con tipos de cambio.

buyers y purchaseRate son compras (pedidos), no conversiones: la curva abarca todo el espacio de trabajo, así que no hay objetivo de campaña o recorrido que contar. converters y conversionRate son las mismas dos cifras con sus nombres anteriores, conservadas para los clientes existentes.


Rendimiento del contenido​

GET /analytics/content-performance?startDate=2026-08-01&endDate=2026-08-31&channel=email

Tasas de apertura, clic y compra posterior al envío según lo que es el mensaje - tono, tipo de oferta, llamada a la acción, longitud, personalización, imágenes y tema - en cada plantilla, variante de campaña y mensaje de recorrido. channel es opcional.

Respuesta:

{
"startDate": "2026-08-01",
"endDate": "2026-08-31",
"channel": null,
"minReached": 100,
"conversionWindowHours": 168,
"taxonomyVersion": "t1",
"totals": { "sent": 18400, "reached": 12100, "labelledSent": 17900, "unlabelledSent": 500, "identities": 42, "labelledIdentities": 40, "noGoalSent": 2300 },
"fields": {
"tone": [
{ "value": "friendly", "sent": 9100, "reached": 6000, "opened": 2520, "clicked": 420, "converted": 150, "goalReached": 5000, "bought": 180, "openRate": 42.0, "clickRate": 7.0, "conversionRate": 3.0, "purchaseRate": 3.0 },
{ "value": "urgent", "sent": 2200, "reached": 1500, "opened": 720, "clicked": 150, "converted": 0, "goalReached": 0, "bought": 60, "openRate": 48.0, "clickRate": 10.0, "conversionRate": null, "purchaseRate": 4.0 },
{ "value": "premium", "sent": 90, "reached": 80, "opened": 30, "clicked": 4, "converted": 1, "goalReached": 80, "bought": 1, "openRate": null, "clickRate": null, "conversionRate": null, "purchaseRate": null }
],
"offerType": [ "..." ],
"ctaType": [ "..." ]
},
"mergeFields": {
"version": "m2",
"measuredSent": 17400,
"pendingSent": 500,
"buckets": [
{
"field": "user.first_name", "placement": "subject", "usage": "merge", "channels": ["email"],
"with": { "sent": 6100, "reached": 4200, "opened": 1932, "clicked": 315, "converted": 120, "goalReached": 3800, "bought": 130, "openRate": 46.0, "clickRate": 7.5, "conversionRate": 3.16, "purchaseRate": 3.1 },
"without": { "sent": 8800, "reached": 5900, "opened": 2242, "clicked": 354, "converted": 140, "goalReached": 5200, "bought": 160, "openRate": 38.0, "clickRate": 6.0, "conversionRate": 2.69, "purchaseRate": 2.71 }
}
],
"other": null
}
}

fields tiene una lista por etiqueta (tone, urgency, offerType, ctaType, lengthBand, personalizationDepth, imageryPresent, primaryTopic), ordenada por personas alcanzadas. Las tasas cuentan personas únicas, sin bots; openRate y clickRate son una parte de las personas a las que se entregó el mensaje (push: de reached; in-app: de mostrados), la misma regla que las tasas de campaña (consulta Cómo se calculan las tasas de apertura y clic). Una tasa es null por debajo de minReached personas, y en un canal que no puede medirla (push no tiene aperturas). conversionRate y purchaseRate se describen a continuación. totals.unlabelledSent es tráfico cuyo contenido aún no tiene etiqueta; POST /ai/content/backfill (panel) etiqueta el contenido existente.

conversionRate y converted son el objetivo de conversión propio de cada mensaje (el objetivo de su campaña o recorrido, contado con la ventana y el modelo de atribución de ese objetivo y asignado a la variante o al mensaje del recorrido al que el modelo da el crédito) sobre goalReached, las personas alcanzadas por mensajes que tienen objetivo. Una fila en la que ningún mensaje tiene objetivo tiene conversionRate: null, y una plantilla enviada por sí sola nunca cuenta como conversión. totals.noGoalSent es el tráfico sin objetivo. purchaseRate y bought son la cifra de pedidos: una compra de la misma persona en las conversionWindowHours posteriores al envío, una ventana simple sea cual sea el objetivo. Hasta el 2026-10-14 conversionRate era esa tasa de compra.

mergeFields compara el contenido por campo de combinación: un grupo por nombre de campo, placement (subject, preheader, title, body, cta, link - la dirección de un enlace; las variables {{n}} de WhatsApp cuentan como el campo al que están vinculadas) y usage (merge = se muestra, conditional = solo decide qué se muestra), con los mismos recuentos y tasas para los envíos etiquetados que lo usan (with) y para los envíos etiquetados de los mismos channels que no lo usan (without). El umbral minReached se aplica a cada lado. Solo nombres de campo, nunca valores. Se listan hasta 100 combinaciones, primero las que más personas alcanzaron; el resto se suma en other, nunca se descarta. pendingSent es tráfico etiquetado cuyos campos de combinación aún no se han leído; no está en ningún lado. La comparación es correlación, no causa.


Respuestas de error​

Todos los endpoints devuelven respuestas de error estándar:

{
"statusCode": 400,
"message": "Invalid date range",
"error": "Bad Request"
}
Código de estadoDescripción
400Solicitud incorrecta o error de validación
401No autorizado
403Prohibido
404Recurso no encontrado
500Error interno del servidor