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 evento | Order Completed |
| Propiedades obligatorias | total, currency |
| Recomendadas | total_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:
| Propiedad | Tipo | Descripción |
|---|---|---|
coupon_codes | string[] | Cada código de cupón del pedido, en minúsculas (["summer10", "vip"]); [] si no hay ninguno |
coupon_count | number | Cuá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:
| Propiedad | Tipo | Descripción |
|---|---|---|
sales_channel | string | online, pos, app, phone, marketplace u other - siempre presente |
store_id | string | La 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 eventoSe 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ámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
userId | cadena | - | Filtra por ID de usuario |
eventName | cadena | - | Filtra por nombre de evento |
startDate | cadena | - | Filtra eventos posteriores a esta fecha (ISO 8601) |
endDate | cadena | - | Filtra eventos anteriores a esta fecha (ISO 8601) |
limit | número | 100 | Resultados por página (máx. 1000) |
offset | número | 0 | Nú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
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
eventName | cadena | Sí | Nombre del evento que se agregará |
startDate | cadena | No | Fecha inicial (ISO 8601) |
endDate | cadena | No | Fecha final (ISO 8601) |
groupBy | cadena | No | Agrupación temporal: hour, day, week, month (predeterminado: day) |
metrics | matriz | No | Métricas que se calcularán: count, sum, avg, min, max (predeterminado: ['count']) |
sumField | cadena | Condicional | Nombre de la clave de properties que se sumará/agregará (por ejemplo, total). Obligatorio cuando metrics incluye sum, min o max |
avgField | cadena | Condicional | Nombre de la clave de properties de la que se calculará la media. Obligatorio cuando metrics incluye avg |
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ímite | Valor |
|---|---|
| Longitud máxima del nombre de evento | 255 caracteres |
| Longitud máxima de identificador (userId, anonymousId, sessionId, deviceId, clientEventId) | 255 caracteres |
| Máximo de claves de propiedad de nivel superior por evento | 200 |
| Tamaño máximo de propiedades (JSON) | 50 KB |
| Profundidad máxima de anidación de propiedades | 5 niveles |
| Máximo de eventos por solicitud con cuerpo de matriz | 500 |
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
- Ve a Usuarios → busca el usuario
- Haz clic en la pestaña Actividad
- 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:
- SDK web: guía de integración
- SDK de iOS: Swift Package / CocoaPods
- SDK de Android: Gradle
- SDK de React Native: npm install @joryio/react-native-sdk