Saltar al contenido principal

API de eventos

Rastrea eventos y comportamiento de usuarios mediante programación con la API REST.

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​

Todas las solicitudes requieren autenticación mediante clave de API:

Authorization: Bearer jry_live_your_api_key_here
Content-Type: application/json

El evento de compra​

Los informes de ingresos, la atribución, el CLV y los modelos predictivos leen un solo evento. Si envías las compras con otro nombre, no se cuentan - en ningún sitio.

Nombre del eventoOrder Completed
Propiedades obligatoriastotal, currency
Recomendadastotal_base, base_currency, orderId
{
"eventName": "Order Completed",
"userId": "user_123",
"properties": {
"total": 149.90,
"currency": "EUR",
"total_base": 162.35,
"base_currency": "USD",
"orderId": "1024"
}
}

Por qué solo un nombre​

Otras plataformas usan otros nombres - Placed Order, purchase (GA4). No los aceptamos deliberadamente, porque aceptar varios nombres implica sumarlos: una tienda que ejecute una etiqueta de GA4 junto a nuestro conector enviaría una misma venta con dos nombres y vería sus ingresos duplicados. Una cifra de ingresos silenciosamente multiplicada por dos es mucho más difícil de detectar que una que es evidentemente cero.

Por eso la regla es un único contrato publicado. Si tus pedidos no aparecen, la causa se ve de inmediato durante la integración - y se puede corregir - en lugar de estar en silencio equivocada durante meses.

Sobre los importes​

total y total_base son dos cifras distintas, no alternativas:

  • total - lo que pagó el cliente, en la moneda en que pagó (presentment).
  • total_base + base_currency - el mismo pedido convertido a tu moneda de referencia. Envíalos si vendes en más de una moneda, para que los totales entre monedas sumen correctamente.

Envía solo total si tienes una única moneda. orderId es opcional pero recomendable: deduplica un pedido que llega más de una vez (un reintento, una recarga de la página de pago).

Si ya envías otro nombre​

Tus eventos históricos se conservan, pero no se tratan como pedidos. Cambia los pedidos nuevos a Order Completed y los informes de ingresos empezarán desde ese momento. No reescribimos eventos pasados, así que nada se reinterpreta en silencio por debajo.

Códigos de cupón en el evento​

Cuando el pedido llega de una tienda conectada o de Orders API, Order Completed también incluye:

PropiedadTipoDescripción
coupon_codesstring[]Cada código de cupón del pedido, en minúsculas (["summer10", "vip"]); [] si no hay ninguno
coupon_countnumberCuántos códigos usó el pedido

Un recorrido puede empezar con Order Completed donde coupon_codes contiene summer10: la comparación ignora mayúsculas y revisa cada código.

Tienda y canal de venta en el evento​

Order Completed, Order Refunded y Order Cancelled indican dónde se vendió el pedido y su tienda:

PropiedadTipoDescripción
sales_channelstringonline, pos, app, phone, marketplace u other - siempre presente
store_idstringLa tienda a la que pertenece el pedido (el ID de una tienda conectada o el storeId que envió tu pedido por la API) - solo si la tiene

Una app puede enviarlos en su propio Order Completed: sales_channel (también se acepta salesChannel; un valor fuera de la lista se guarda como other), store_id y store_name. Sin sales_channel, un pedido registrado desde una app iOS o Android es app y uno desde un sitio web es online. Un recorrido puede empezar con Order Completed cuando sales_channel es pos. Consulta Tienda y canal de venta.

Enviar eventos​

Los eventos se ingieren a través de POST /users/track, que acepta atributos, eventos y compras en una sola llamada.

curl -X POST https://api-eu1.joryio.com/api/users/track \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"events": {
"externalId": "user_123",
"name": "Product Viewed",
"properties": { "productId": "SKU-456", "price": 49.99 }
}
}'

events acepta un único objeto o una matriz de hasta 500. Consulta Track para la referencia completa de campos, las reglas de identidad y la forma de la respuesta.

Order Completed no se envía como evento

Se emite automáticamente a partir de una compra. Envía las compras en la matriz purchases en su lugar - consulta El evento de compra más arriba para entender por qué enviarlo a mano o bien registra ingresos sin ningún pedido detrás, o bien cuenta la venta dos veces.

Consultar eventos​

Recupera eventos con filtrado y paginación. No existe un endpoint para obtener por ID de evento - filtra esta consulta en su lugar.

Endpoint​

GET /events/query

Parámetros de consulta​

ParámetroTipoPredeterminadoDescripción
userIdcadena-Filtra por ID de usuario
eventNamecadena-Filtra por nombre de evento
startDatecadena-Filtra eventos posteriores a esta fecha (ISO 8601)
endDatecadena-Filtra eventos anteriores a esta fecha (ISO 8601)
limitnúmero100Resultados por página (máx. 1000)
offsetnúmero0Número de eventos que se omitirán

Solicitud de ejemplo​

# Get all "Order Completed" events in January 2024
curl -X GET "https://api-eu1.joryio.com/api/events/query?eventName=Order+Completed&startDate=2024-01-01T00:00:00Z&endDate=2024-02-01T00:00:00Z&limit=100" \
-H "Authorization: Bearer jry_live_your_api_key"

Respuesta​

Una matriz JSON sin envoltorio, del más reciente al más antiguo. Los campos de evento usan snake_case (proceden del almacén de analítica):

[
{
"event_id": "9b2f6c1e-4a8d-4f0b-9c3d-2e1f5a6b7c8d",
"user_id": "665f1e2a9b3c4d5e6f7a8b9c",
"anonymous_id": "",
"event_name": "Order Completed",
"properties": {
"orderId": "order_456",
"total": 99.99
},
"timestamp": "2024-01-20 14:30:00",
"session_id": "",
"device_id": ""
},
{
"event_id": "1c3e5a7b-9d2f-4b6c-8e0a-3f5d7b9c1e2a",
"user_id": "665f1e2a9b3c4d5e6f7a8b9d",
"anonymous_id": "",
"event_name": "Order Completed",
"properties": {
"orderId": "order_789",
"total": 149.99
},
"timestamp": "2024-01-19 10:15:00",
"session_id": "",
"device_id": ""
}
]

Agregación de eventos​

Obtiene estadísticas agregadas de eventos con agrupaciones y métricas.

Endpoint​

POST /events/aggregate

Cuerpo de la solicitud​

CampoTipoObligatorioDescripción
eventNamecadenaSíNombre del evento que se agregará
startDatecadenaNoFecha inicial (ISO 8601)
endDatecadenaNoFecha final (ISO 8601)
groupBycadenaNoAgrupación temporal: hour, day, week, month (predeterminado: day)
metricsmatrizNoMétricas que se calcularán: count, sum, avg, min, max (predeterminado: ['count'])
sumFieldcadenaCondicionalNombre de la clave de properties que se sumará/agregará (por ejemplo, total). Obligatorio cuando metrics incluye sum, min o max
avgFieldcadenaCondicionalNombre de la clave de properties de la que se calculará la media. Obligatorio cuando metrics incluye avg
Una métrica sin su campo se rechaza

Un nombre de métrica desconocido, o una métrica a la que le falta su campo (sum sin sumField), devuelve 400. Antes ambos devolvían 200 con la métrica simplemente ausente del resultado, algo indistinguible de un cero real.

| eventProperties | objeto | No | Filtra por propiedades de evento |

Solicitud de ejemplo​

curl -X POST https://api-eu1.joryio.com/api/events/aggregate \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"eventName": "Order Completed",
"startDate": "2024-01-01T00:00:00Z",
"endDate": "2024-02-01T00:00:00Z",
"groupBy": "day",
"metrics": ["count", "sum"],
"sumField": "total"
}'

Respuesta​

{
"success": true,
"data": {
"eventName": "Order Completed",
"groupBy": "day",
"metrics": ["count", "sum"],
"results": [
{
"period": "2024-01-01T00:00:00Z",
"count": 45,
"sum_value": 4567.89
},
{
"period": "2024-01-02T00:00:00Z",
"count": 52,
"sum_value": 5123.45
},
{
"period": "2024-01-03T00:00:00Z",
"count": 38,
"sum_value": 3890.12
}
],
"total": 3
}
}

Métricas compatibles​

  • count: número total de eventos
  • sum: suma de los valores del campo indicado
  • avg: media de los valores del campo indicado
  • min: valor mínimo del campo indicado
  • max: valor máximo del campo indicado

Ejemplo: análisis de ingresos​

# Get daily revenue from purchases
curl -X POST https://api-eu1.joryio.com/api/events/aggregate \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"eventName": "Order Completed",
"startDate": "2024-01-01T00:00:00Z",
"endDate": "2024-01-31T00:00:00Z",
"groupBy": "day",
"metrics": ["count", "sum", "avg"],
"sumField": "total"
}'

Ejemplo: uso de funcionalidades por hora​

# Track feature usage patterns by hour
curl -X POST https://api-eu1.joryio.com/api/events/aggregate \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"eventName": "Feature Used",
"startDate": "2024-01-20T00:00:00Z",
"endDate": "2024-01-21T00:00:00Z",
"groupBy": "hour",
"metrics": ["count"]
}'

Descubrir qué envía un espacio de trabajo​

Antes de construir sobre los eventos de un espacio de trabajo, pregúntale qué recibe realmente. Estas cuatro rutas son la forma en que una integración lo averigua en vez de suponerlo.

Listar nombres de eventos​

GET /events/names

Todos los nombres de evento vistos en este espacio de trabajo, como un array JSON sin envoltorio ordenado de la A a la Z. Este es el vocabulario real, no el que alguien pretendía.

Respuesta:

["Order Completed", "Page Viewed", "Product Viewed"]

Estadísticas de eventos​

GET /events/stats?startDate=2026-08-20T00:00:00Z&endDate=2026-08-27T00:00:00Z

Volumen por evento en una ventana - la forma más rápida de confirmar que una integración sigue entregando, y de notar cuándo una ha dejado de hacerlo en silencio. startDate y endDate son marcas de tiempo ISO 8601 opcionales (no existe el parámetro days); sin ellas se cuenta todo el historial.

Respuesta - un array JSON, los eventos con más volumen primero, como máximo 1.000 filas. count son eventos distintos y unique_users personas distintas:

[
{ "event_name": "Page Viewed", "count": 1204331, "unique_users": 88012 },
{ "event_name": "Order Completed", "count": 48210, "unique_users": 31877 }
]

Esquemas de eventos​

GET /events/schema?limit=50&offset=0
GET /events/schema/Order%20Completed

La ruta de lista pagina todos los eventos que el espacio de trabajo ha recibido (limit 50 por defecto, máximo 1000; offset 0 por defecto) con recuentos pero sin propiedades. La ruta por evento toma el nombre del evento en la ruta, codificado para URL - no existe el parámetro de consulta ?event= - y devuelve las propiedades observadas en él: el tipo al que se resolvió cada una y de dónde salió ese tipo (typeSource es declared, canonical o inferred), en cuántos eventos aparece (occurrences), valores de ejemplo y si los valores parecen un identificador. No existe el campo fillRate - divide occurrences entre el totalCount del evento de la ruta de lista. Una propiedad declarada y nunca enviada es la causa habitual de un segmento que no coincide con nadie.

Respuesta de la lista:

{
"data": [
{ "name": "Order Completed", "totalCount": 48210, "uniqueUsers": 31877,
"lastSeen": "2026-08-27T09:12:00.000Z", "propertyCount": 6,
"description": "A purchase was completed", "descriptionSource": "platform", "isPlatform": true }
],
"pagination": { "total": 42, "limit": 50, "offset": 0, "hasMore": false }
}

Respuesta por evento:

{
"eventName": "Order Completed",
"properties": [
{ "name": "total", "type": "number", "typeSource": "canonical", "typeDeclared": false,
"typeMixed": false, "typeCounts": { "string": 0, "number": 48210, "boolean": 0, "array": 0, "object": 0 },
"typeReason": "single", "occurrences": 48210, "exampleValues": [129.9, 42.5],
"cardinalityVerdict": "high_cardinality", "uniquenessRatio": 0.31, "looksLikeIdentifier": false,
"pii": false },
{ "name": "coupon", "type": "string", "typeSource": "inferred",
"typeMixed": false, "typeCounts": { "string": 3857, "number": 0, "boolean": 0, "array": 0, "object": 0 },
"typeReason": "single", "occurrences": 3857, "exampleValues": ["SPRING10"],
"cardinalityVerdict": "low_cardinality", "uniquenessRatio": 0.004, "looksLikeIdentifier": false,
"pii": false }
]
}

exampleValues está vacío en una propiedad PII cuando quien llama no puede ver PII (pii indica cuáles). typeMixed con sus typeCounts señala una propiedad cuyos valores llegan con más de un tipo.

Declarar un esquema de evento​

POST /events/schema

Registra la descripción de un evento. Los eventos siguen sin esquema: la descripción se guarda en el diccionario de datos del espacio de trabajo, que leen los selectores de propiedades y el asistente de IA - nada se valida ni se rechaza, y aquí no se declaran propiedades.

Cuerpo de la petición:

{ "name": "Order Completed", "description": "A purchase was completed" }

Respuesta:

{ "success": true, "message": "Event schema defined successfully",
"data": { "name": "Order Completed", "description": "A purchase was completed" } }

Las descripciones de propiedades se escriben con PUT /events/schema/:eventName ({ "description"?, "properties"?: [{ "name", "description" }] }). POST /events/schema/:eventName/properties ({ "name", "type", "description"? }) declara el tipo de una propiedad - string, number, boolean o date - en el mismo diccionario de datos; la ruta por evento lo informa después con typeSource: "declared" y los filtros de segmentos comparan así. Un tipo desconocido, y una propiedad estándar cuyo tipo fija la plataforma (total, price...), se rechazan con 400.

Catálogo de eventos de la plataforma​

GET /events/platform-catalog

Los eventos canónicos que Joryio entiende de forma nativa, con las propiedades que espera cada uno. Si usas estos nombres, los informes, disparadores y funciones de e-commerce integrados funcionan sin mapeo; con nombres propios, no.

Eventos comunes​

Eventos de e-commerce​

// Product Viewed
POST /users/track
{
"userId": "user_123",
"eventName": "Product Viewed",
"properties": {
"productId": "prod_456",
"productName": "Premium Plan",
"category": "Subscription",
"price": 99.99,
"currency": "USD"
}
}

// Added To Cart
POST /users/track
{
"userId": "user_123",
"eventName": "Added To Cart",
"properties": {
"productId": "prod_456",
"quantity": 1,
"price": 99.99
}
}

// Order Completed
POST /users/track
{
"userId": "user_123",
"eventName": "Order Completed",
"properties": {
"orderId": "order_789",
"total": 249.99,
"currency": "USD",
"itemCount": 3,
"discount": 25.00,
"paymentMethod": "credit_card"
}
}

Eventos de ciclo de vida de usuario​

// Signup Completed
POST /users/track
{
"userId": "user_123",
"eventName": "Signup Completed",
"properties": {
"method": "email",
"source": "homepage_cta"
}
}

// Onboarding Completed
POST /users/track
{
"userId": "user_123",
"eventName": "Onboarding Completed",
"properties": {
"stepsCompleted": 5,
"timeSpent": "8m 30s"
}
}

// Trial Started
POST /users/track
{
"userId": "user_123",
"eventName": "Trial Started",
"properties": {
"plan": "premium",
"trialDays": 14
}
}

Eventos de interacción​

// Feature Used
POST /users/track
{
"userId": "user_123",
"eventName": "Feature Used",
"properties": {
"featureName": "export",
"exportFormat": "csv",
"recordCount": 1500
}
}

// Page Viewed
POST /users/track
{
"userId": "user_123",
"eventName": "Page Viewed",
"properties": {
"page": "/pricing",
"category": "Marketing",
"referrer": "google"
}
}

Propiedades de evento​

Prácticas recomendadas​

Usa nombres de propiedad descriptivos:

Bien:

{
"properties": {
"productId": "prod_123",
"productName": "Premium Plan",
"price": 99.99,
"currency": "USD"
}
}

Mal:

{
"properties": {
"pid": "prod_123",
"n": "Premium Plan",
"p": 99.99
}
}

Tipos de datos compatibles​

{
"properties": {
"string": "value",
"number": 99.99,
"integer": 5,
"boolean": true,
"date": "2024-01-15T10:30:00Z",
"array": ["tag1", "tag2"],
"object": {
"nested": "value",
"deep": {
"property": "value"
}
}
}
}

Propiedades reservadas​

Las propiedades que empiezan por $ están reservadas para uso del sistema:

  • $app_id - identificador de app
  • $app_name - nombre de app
  • $platform - plataforma (web, ios, android)
  • $session_id - identificador de sesión
  • $anonymous_id - ID de usuario anónimo

No uses estos nombres en propiedades personalizadas.

Nombres de evento reservados​

Los nombres de evento que empiezan por canvas. están reservados para los eventos de recorrido de Joryio - canvas.step.entered, canvas.step.left, canvas.experiment.entered y cualquier otro nombre canvas. solo los registra el motor de recorridos (eventos de plataforma). Un evento que envíes con un nombre que empiece por canvas. se rechaza (cámbiale el nombre) - figura como fallido, con el motivo, en la respuesta de POST /users/track, un rechazo definitivo (rejectedIndices) en un lote del SDK, y una fila omitida con el motivo en una importación CSV o una sincronización de almacén de datos. La propiedad $emitted_by también se elimina de todos los eventos que envíes.

Los nombres que empiezan por message. o in_app. están reservados de la misma forma, para los eventos de mensajería de Joryio - los envíos, entregas, aperturas y clics (message.sent, message.opened, message.clicked, ...) y las impresiones, clics y cierres de mensajes in-app (in_app.displayed, in_app.clicked, ...) solo los registra Joryio, para que un evento personalizado nunca cuente como uno de ellos en informes, filtros de segmento o disparadores de recorridos. Un evento que envíes con un nombre así se rechaza de la misma forma, incluidas las filas de una importación CSV histórica o de una sincronización de almacén de datos - cámbiale el nombre (por ejemplo Newsletter Opened). La comprobación no distingue mayúsculas y minúsculas. Las confirmaciones de entrega y los clics de push que informan los SDK, y los eventos in-app que se informan por los endpoints in-app de los SDK, son eventos propios de Joryio y siguen funcionando.


Límites de eventos​

Límites de tamaño​

LímiteValor
Longitud máxima del nombre de evento255 caracteres
Longitud máxima de identificador (userId, anonymousId, sessionId, deviceId, clientEventId)255 caracteres
Máximo de claves de propiedad de nivel superior por evento200
Tamaño máximo de propiedades (JSON)50 KB
Profundidad máxima de anidación de propiedades5 niveles
Máximo de eventos por solicitud con cuerpo de matriz500

Límites de frecuencia​

La API de eventos no tiene hoy límites de frecuencia fijos por endpoint - consulta resumen de la API: límites de frecuencia.


Respuestas de error​

Todos los errores usan el cuerpo de error estándar - consulta resumen de la API: respuesta de error.

400 Bad Request - error de validación​

{
"statusCode": 400,
"message": "Bad Request Exception",
"timestamp": "2026-01-15T10:30:00.000Z",
"path": "/api/users/track",
"errors": [
"eventName should not be empty"
]
}

400 Bad Request - propiedades demasiado grandes​

{
"statusCode": 400,
"message": "Event properties exceed maximum size of 50KB (received 63KB)",
"timestamp": "2026-01-15T10:30:00.000Z",
"path": "/api/users/track"
}

Prácticas recomendadas​

1. Agrupar eventos cuando sea posible​

Bien - agrupa varios eventos con un cuerpo de matriz:

await fetch('/api/users/track', {
method: 'POST',
body: JSON.stringify([event1, event2, event3])
});

Mal - solicitudes individuales:

await fetch('/api/users/track', { method: 'POST', body: JSON.stringify(event1) });
await fetch('/api/users/track', { method: 'POST', body: JSON.stringify(event2) });
await fetch('/api/users/track', { method: 'POST', body: JSON.stringify(event3) });

2. Usar nombres de evento coherentes​

Sigue el patrón "objeto + verbo en pasado":

Bien: Product Viewed, Order Completed, Trial Started Mal: view_product, clicked, user_action_123

3. Incluir marcas de tiempo​

Para los eventos históricos, incluye siempre marcas de tiempo precisas:

{
"userId": "user_123",
"eventName": "Order Completed",
"timestamp": "2024-01-15T10:30:00.000Z", // Actual event time
"properties": { ... }
}

4. Mantener las propiedades ligeras​

Incluye solo propiedades relevantes:

Bien:

{
"eventName": "Order Completed",
"properties": {
"orderId": "order_123",
"total": 99.99,
"currency": "USD"
}
}

Mal:

{
"eventName": "Order Completed",
"properties": {
"orderId": "order_123",
"total": 99.99,
"currency": "USD",
"userAgent": "Mozilla/5.0...", // Too much detail
"sessionData": { /* large object */ },
"cookies": [ /* array of cookies */ ]
}
}

5. Gestionar errores correctamente​

Implementa lógica de reintento con espera exponencial:

async function trackWithRetry(event, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('/api/users/track', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(event)
});

if (response.ok) return await response.json();

if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
await sleep(retryAfter * 1000);
continue;
}

throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000); // 1s, 2s, 4s
}
}
}

Depuración​

Activar el modo de depuración (SDK)​

Al usar el SDK web:

import JoryioSDK from '@joryio/web-sdk';

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
enableDebug: true // Log all events to console
});

Verificar eventos en el dashboard​

  1. Ve a Usuarios → busca el usuario
  2. Haz clic en la pestaña Actividad
  3. Consulta todos los eventos rastreados

Problemas comunes​

Los eventos no aparecen:

  • Verifica que la clave de API sea correcta
  • Comprueba que el usuario esté identificado
  • Asegúrate de que el nombre de evento y las propiedades sean válidos
  • Comprueba los límites de frecuencia

Las propiedades no se muestran:

  • Verifica que los nombres de propiedad sean correctos
  • Comprueba que los tipos de datos sean compatibles
  • Evita nombres de propiedades reservados (prefijo $)

SDK​

Para una integración más sencilla, usa los SDK oficiales:


Siguientes pasos​