API de monitorización
La API de monitorización refleja la superficie del dashboard Configuración → Registros y monitorización. Úsala para crear alertas desde CI, auditar activaciones con un script o enviar el historial a un SIEM.
Todos los endpoints requieren un JWT (sesión del dashboard) y el permiso settings:read o settings:write, según la acción. No se pueden llamar con una clave de API de espacio de trabajo normal: la gestión de alertas es una operación de nivel dashboard.
Todos los endpoints de esta página son relativos a la URL base: https://api-eu1.joryio.com. Consulta el Resumen de la API.
Objeto de alerta
La estructura canónica de alerta que devuelve cada endpoint CRUD:
{
"id": "ak_01HXYZ...",
"organizationId": "org_...",
"workspaceId": "ws_...",
"name": "Server rejecting payloads",
"description": "Joryio returned 5xx on ingestion.",
"enabled": true,
"direction": "inbound",
"metric": "calls",
"codes": ["5xx"],
"mode": "absolute",
"op": ">",
"threshold": "100",
"duration": "5m",
"changeDir": null,
"changeKind": null,
"vsWindow": null,
"vsComparison": "previous",
"scopeApiKeyPrefix": null,
"scopeEndpoint": null,
"scopeWebhookUrl": null,
"scopeEventName": null,
"notifyChannel": "email",
"recipients": ["ops@your-company.com"],
"webhookUrl": null,
"webhookSecret": null,
"cooldown": "10m",
"status": "healthy",
"lastTriggeredAt": null,
"snoozedUntil": null,
"createdBy": "usr_...",
"createdAt": "2026-05-29T05:00:00.000Z",
"updatedAt": "2026-05-29T05:00:00.000Z"
}
Referencia de campos
| Campo | Tipo | Notas |
|---|---|---|
name | cadena (1–255) | Obligatorio. Se muestra en el dashboard y los emails activados. |
description | cadena (≤2000) | Opcional. Se muestra en el cuerpo del email. |
enabled | booleano | De forma predeterminada es true. Con false, el estado pasa a paused y el evaluador omite la alerta. |
direction | inbound | webhook | events | deliverability | Flujo que se vigila. events cuenta eventos de clientes registrados desde la tabla events. deliverability vigila tasas de salud de mensajes (% de enviados). |
metric | calls | total_calls | rps | event_count | total_events | unique_users | bounceRate | hardBounceRate | softBounceRate | complaintRate | unsubscribeRate | deliveryRate | Qué medir. Los tres primeros se aplican a inbound/webhook; los tres siguientes, a events; las seis métricas *Rate, a deliverability. |
codes | cadena[] | Códigos o grupos de estado HTTP (2xx, 4xx, 5xx). Solo es significativo con metric: calls. |
mode | absolute | change | Modelo de umbral. deliverability siempre es absolute. |
op | > | < | Solo modo absoluto. Dirección del umbral. |
threshold | cadena (numérica) | Obligatorio. Se almacena como cadena numérica. Para deliverability, un porcentaje (por ejemplo, "5" = 5%). |
duration | 1m | 5m | 10m | 30m | 1h | Solo modo absoluto. Ventana de incumplimiento sostenido. Para deliverability, la ventana retrospectiva de la tasa: usa 1h | 4h | 1d | 7d. |
changeDir | increased | decreased | Solo modo de cambio. Dirección del cambio. |
changeKind | percent | value | Solo modo de cambio. Interpreta threshold como porcentaje o recuento absoluto. |
vsWindow | 15m | 1h | 4h | 1d | 7d | Solo modo de cambio. Tamaño de la ventana de comparación. |
vsComparison | previous | last_week | average | same_weekday_median | Solo modo de cambio. Línea base de comparación: previous = la ventana inmediatamente anterior (predeterminada, y la menos indulgente: un domingo tranquilo se lee como caída frente al sábado); last_week = la misma ventana de hace 7 días, tiene en cuenta la estacionalidad pero es un solo día, así que uno atípico envenena la comparación; average = la MEDIA de la misma ventana durante los últimos avgDays días (2-30, 7 por defecto), suaviza el ruido pero un día excepcional eleva la base durante todo ese periodo; same_weekday_median = la MEDIANA de la misma ventana 7/14/21/28 días atrás - recomendado para alertas de caída porcentual: tiene en cuenta la estacionalidad Y no se mueve por una campaña, un artículo o un Black Friday. Necesita datos en al menos dos de las cuatro semanas; si no, la alerta informa "not enough history yet" y no se dispara. |
scopeApiKeyPrefix | cadena | null | Limita a una clave de API concreta. Usa el prefijo visible de la clave (por ejemplo, jry_live_98f31a72). Solo entrante. |
scopeEndpoint | cadena | null | Limita a una ruta concreta (por ejemplo, /users/:id). Usa la forma canonizada. Solo entrante. |
scopeWebhookUrl | cadena | null | Limita a una URL de webhook concreta. La cadena de consulta se elimina antes de comparar. Solo saliente. |
scopeEventName | cadena | null | Dirección de eventos. Qué event_name contar. null = contar todos los eventos. Se ignora con metric: total_events. |
notifyChannel | email | webhook | Cómo se entrega la alerta. De forma predeterminada es email. |
recipients | cadena[] (1–20) | Direcciones de email notificadas al activarse. Obligatorio con notifyChannel: email. |
webhookUrl | cadena | null | URL de destino del POST. Obligatoria con notifyChannel: webhook. |
webhookSecret | cadena | null | Secreto de firma HMAC-SHA256 opcional. Al configurarlo, las solicitudes incluyen el encabezado X-Joryio-Signature. |
cooldown | 5m | 10m | 30m | 1h | Tiempo mínimo entre nuevas activaciones. |
status | healthy | triggered | snoozed | paused | Estado de ejecución. Es de solo lectura desde la API; usa endpoints de posponer/reanudar para cambiarlo. |
Endpoints
Enumerar alertas
GET /monitoring/alerts
Devuelve todas las alertas del espacio de trabajo actual, de la más reciente a la más antigua.
Respuesta: 200 OK - MonitoringAlert[]
Obtener una alerta
GET /monitoring/alerts/:id
Respuesta: 200 OK - MonitoringAlert, o 404 si el ID no pertenece a este espacio de trabajo.
Crear una alerta
POST /monitoring/alerts
Content-Type: application/json
{
"name": "5xx error rate",
"direction": "inbound",
"metric": "calls",
"codes": ["5xx"],
"mode": "absolute",
"op": ">",
"threshold": "100",
"duration": "5m",
"recipients": ["ops@example.com"],
"cooldown": "10m"
}
Los campos name, direction, metric, mode y threshold siempre son obligatorios. Los campos específicos de canal y modo se validan semánticamente:
-
El canal de email (
notifyChannel: email, el predeterminado) requiere al menos una entrada enrecipients. -
El canal de webhook (
notifyChannel: webhook) requiere unwebhookUrlhttp(s)válido;recipientses opcional.webhookSecretes opcional. -
Los campos específicos de cada modo se validan según
mode(por ejemplo, no se puede estableceropen el modo de cambios;vsComparisonsolo se aplica en ese modo). -
Para
direction: events, usascopeEventNamepara contar un evento concreto, u omítelo para contarlos todos.metric: total_eventssiempre cuenta todos los eventos, independientemente descopeEventName. -
Para
direction: deliverability, usamode: absolutecon una de las métricas*Rate, unop, unthresholdporcentual y unadurationde1h/4h/1d/7d(el período de análisis). Los campos de ámbito ycodesse ignoran. Si no se envió ningún mensaje durante el período, la alerta no se activa.
Respuesta: 201 Created - MonitoringAlert con id asignado.
La alerta nueva comienza con status: healthy (o paused si enabled: false) y se evaluará en el siguiente ciclo del evaluador (en un máximo de 60 segundos).
Actualizar una alerta
PATCH /monitoring/alerts/:id
Content-Type: application/json
{ "threshold": "200" }
Todos los campos son opcionales. Envía solo los que quieras cambiar. Cambiar enabled a false mueve la alerta a paused; volver a true la devuelve a healthy (el siguiente ciclo de evaluación la activará de nuevo si la métrica sigue superando el umbral).
Respuesta: 200 OK - MonitoringAlert actualizado.
Eliminar una alerta
DELETE /monitoring/alerts/:id
Elimina la alerta de forma permanente. Sus filas de historial también se eliminan en cascada.
Respuesta: 200 OK - { "ok": true }.
Posponer una alerta
POST /monitoring/alerts/:id/snooze
Content-Type: application/json
{ "window": "1h" }
window es opcional. Si se indica, la alerta pasa a snoozed, se define snoozedUntil y se reanuda automáticamente al terminar el período. Sin window, la alerta pasa a paused de forma indefinida.
Valor de window | Comportamiento |
|---|---|
1h | Posponer durante 1 hora. |
4h | Posponer durante 4 horas. |
24h | Posponer durante 24 horas. |
until_morning | Posponer hasta las 09:00 del servidor del día siguiente. |
| (omitido) | Pausar de forma indefinida. |
Respuesta: 200 OK - MonitoringAlert actualizado.
Reanudar una alerta
POST /monitoring/alerts/:id/resume
Elimina snoozedUntil, establece enabled: true y pasa a status: healthy. El siguiente ciclo del evaluador vuelve a comprobar la métrica y puede pasar inmediatamente la alerta a triggered si sigue superando el umbral.
Respuesta: 200 OK - MonitoringAlert actualizado.
Duplicar una alerta
POST /monitoring/alerts/:id/duplicate
Crea una alerta nueva con la misma configuración. Al nombre de la copia se le añade el sufijo (copy). La copia comienza con status: healthy y lastTriggeredAt: null, independientemente del estado de ejecución de la alerta de origen.
Respuesta: 201 Created - el nuevo MonitoringAlert.
Vista previa en vivo
POST /monitoring/preview
Content-Type: application/json
{
"direction": "inbound",
"metric": "calls",
"codes": ["5xx"],
"mode": "absolute",
"op": ">",
"threshold": "100",
"duration": "5m"
}
Evalúa la especificación de alerta proporcionada frente a los datos actuales sin guardar nada. No se crea ninguna fila de alerta ni se envía ninguna notificación. Úsalo para validar los umbrales antes de crear la alerta.
El cuerpo acepta los mismos campos de evaluación que la creación; name, recipients, enabled y cooldown no son necesarios y se ignoran.
Respuesta: 200 OK
{
"currentValue": 142,
"displayValue": "142",
"thresholdLabel": "> 100 in 5m",
"wouldFire": true
}
| Campo | Significado |
|---|---|
currentValue | Valor sin procesar de la fuente de métricas. |
displayValue | Versión legible del valor. En el modo de cambios, incluye la dirección (por ejemplo, ↓ 92%). |
thresholdLabel | Expresión del umbral legible y correspondiente a la regla. |
wouldFire | true si la regla se encontraría ahora en estado activado. |
Listar el historial
GET /monitoring/history?alertId={id}&state={state}&limit={n}
Devuelve el registro de auditoría de las transiciones de activación/resolución, de la más reciente a la más antigua.
Parámetros de consulta:
| Parámetro | Tipo | Predeterminado | Notas |
|---|---|---|---|
alertId | cadena | - | Limita el resultado a una alerta. |
state | firing | resolved | snoozed | - | Limita el resultado a un tipo de transición. |
limit | entero | 200 | Límite de filas devueltas. Máximo estricto: 1000. |
Respuesta: 200 OK - MonitoringAlertHistoryEvent[]
[
{
"id": "ev_...",
"alertId": "ak_...",
"alertName": "Server rejecting payloads",
"metric": "Calls returning 5xx",
"valueAtFire": "184",
"valueLabel": "184",
"thresholdLabel": "> 100 in 5m",
"state": "firing",
"resolvedAt": null,
"recipients": ["ops@example.com"],
"notificationsSent": 1,
"firedAt": "2026-05-29T14:38:00.000Z"
}
]
Las filas de historial son instantáneas: guardan el nombre, la métrica, el umbral y los destinatarios de la alerta en el momento de la transición. Cambiar el nombre o eliminar la alerta más tarde no altera el historial.
Endpoints auxiliares
Estos endpoints alimentan los selectores y la campana de notificaciones del dashboard. Todos requieren settings:read.
Listar nombres de eventos
GET /monitoring/event-names
Devuelve los nombres de evento distintos observados en el espacio de trabajo durante los últimos 30 días, ordenados por frecuencia (los 200 principales). Alimenta el selector scopeEventName de la dirección Events para que los clientes vean su propio vocabulario de eventos.
Respuesta: 200 OK
[
{ "name": "purchase_complete", "count": 18422 },
{ "name": "add_to_cart", "count": 51904 },
{ "name": "signup", "count": 1203 }
]
Listar fuentes de webhook
GET /monitoring/webhook-sources
Devuelve las URL de destino de webhook distintas configuradas en los nodos de webhook activos de los Journeys activos y en borrador del espacio de trabajo (se excluyen los canvas archivados). Alimenta el selector scopeWebhookUrl de la dirección de salida.
Respuesta: 200 OK
[
{
"url": "https://hooks.your-company.com/joryio",
"canvasId": "cv_...",
"canvasName": "Win-back flow",
"nodeId": "node_...",
"nodeLabel": "Notify CRM"
}
]
Notificaciones recientes
GET /monitoring/notifications/recent
Devuelve las activaciones de alerta más recientes del espacio de trabajo para la campana del encabezado del dashboard.
Número de activaciones sin leer
GET /monitoring/notifications/unread-count
Respuesta: 200 OK - { "count": 3 }. Es el contador de la insignia de la campana del encabezado.
Cuerpo de la notificación por webhook
Cuando cambia el estado de una alerta con notifyChannel: webhook, Joryio envía un POST HTTP a webhookUrl. A diferencia del email, que solo se envía al estado triggered, el canal webhook publica tanto en triggered como en resolved, para que el receptor pueda seguir cada incidencia de principio a fin.
Solicitud:
POST {webhookUrl}
Content-Type: application/json
User-Agent: Joryio-Monitoring/1.0
X-Joryio-Signature: {hex hmac-sha256, only when a signing secret is set}
{
"alert": "Server rejecting payloads",
"status": "triggered",
"metric": "Calls returning 5xx",
"value": 184,
"displayValue": "184",
"accountName": "Acme Inc",
"workspaceName": "Production",
"firedAt": "2026-05-29T14:38:00.000Z"
}
| Campo | Tipo | Notas |
|---|---|---|
alert | cadena | Nombre de la alerta. |
status | triggered | resolved | Transición que representa este POST. |
metric | cadena | Etiqueta legible de la métrica monitorizada. |
accountName | cadena | Cuenta (organización) a la que pertenece la alerta. |
workspaceName | cadena | Espacio de trabajo al que pertenece la alerta. |
value | número | Valor sin procesar de la métrica al producirse la transición. |
displayValue | cadena | Valor formateado para personas (en el modo de cambios incluye la dirección, por ejemplo, ↓ 92%). |
firedAt | cadena (ISO 8601) | Momento en que ocurrió la transición. |
Verificación de firma. Cuando se define webhookSecret, Joryio calcula HMAC-SHA256(rawBody) con ese secreto como clave y lo envía como una cadena hexadecimal en minúsculas (sin prefijo) en X-Joryio-Signature. Vuelve a calcularlo sobre el cuerpo exacto de la solicitud sin procesar y compáralo con una comprobación de tiempo constante antes de confiar en el cuerpo.
Semántica de entrega. Joryio espera una respuesta 2xx. Si falla, reintenta hasta 3 veces con espera exponencial (≈0,5 s, 1 s y 2 s); cada intento tiene un tiempo de espera de 10 s. Tras 3 fallos se descarta la entrega, aunque la transición de estado permanece en el historial.
Protección frente a SSRF. La URL se valida al crear o actualizar la alerta y se vuelve a validar al enviar (el DNS puede cambiar entre la escritura y la activación). Se bloquean las entregas a direcciones privadas, link-local o de metadatos de nube. No se siguen redirecciones (maxRedirects: 0), ya que un 3xx hacia una dirección interna eludiría esa comprobación.
Fuente de métricas
Las fuentes de métricas viven en el almacén de eventos analíticos y se rellenan automáticamente. Son las mismas fuentes que usan los gráficos del dashboard, por lo que el motor de alertas y cualquier análisis personalizado comparten una única fuente de verdad.
El registro de solicitudes de API entrantes y el registro de entregas de webhooks salientes se pueden activar o desactivar por organización y tienen una ventana de retención configurable (de 7 a 365 días; 90 de forma predeterminada), gestionada por el equipo de Joryio en la consola de administración. Cuando se desactiva un flujo para una organización, no se escriben filas y las alertas de esa dirección dejan de evaluarse. La retención se aplica fila a fila mediante una columna delete_at (las filas existentes se completaron con ts + 90d).
api_request_logs
Una fila por cada solicitud autenticada con clave de API a la API REST de Joryio.
| Columna | Tipo | Notas |
|---|---|---|
ts | DateTime | Marca de tiempo UTC al completarse la solicitud. |
organization_id | String | Organización propietaria. |
workspace_id | String | Espacio de trabajo propietario. |
api_key_id | Nullable(String) | UUID de la fila de la clave de API. |
api_key_prefix | String | Prefijo público de la clave (por ejemplo, jry_live_98f31a72). |
method | LowCardinality(String) | Verbo HTTP. |
endpoint | LowCardinality(String) | Ruta canonizada. Los UUID y segmentos numéricos largos se sustituyen por :id. |
raw_path | String | Ruta original con cadena de consulta, limitada a 512 caracteres. |
status | UInt16 | Estado de respuesta HTTP. |
duration_ms | UInt32 | Latencia en milisegundos. |
request_ip | Nullable(String) | IP de origen, tras resolver X-Forwarded-For. |
- Partición: mensual (
toYYYYMM(ts)). - Retención: TTL por fila mediante una columna
delete_at, definida comots + retentionDaysal escribir. El valor predeterminado es de 90 días y se puede configurar por organización (de 7 a 365). El registro se puede desactivar por organización. - Excluido: tráfico del dashboard autenticado con JWT y rutas de comprobación de estado (
/health,/metrics).
webhook_delivery_logs
Una fila por cada intento de entrega de webhook saliente, tenga éxito o falle.
| Columna | Tipo | Notas |
|---|---|---|
ts | DateTime | UTC timestamp of the attempt completion. |
organization_id | String | Owning organization. |
workspace_id | String | Owning workspace. |
canvas_id | Nullable(String) | ID del User Journey de origen. |
execution_id | Nullable(String) | ID de ejecución del Journey de origen. |
node_id | Nullable(String) | ID del nodo webhook de origen. |
url | String | URL de destino completa. |
url_canonical | String | URL sin cadena de consulta ni barra final. Se usa en los filtros de alertas. |
method | LowCardinality(String) | Verbo HTTP. |
status | UInt16 | Estado de respuesta. 0 para errores de transporte (tiempo de espera, fallo de DNS o conexión rechazada). |
duration_ms | UInt32 | Latencia en milisegundos. |
attempt | UInt8 | Número de intento (1 en el primer intento). |
error | Nullable(String) | Mensaje de error en respuestas que no son 2xx. |
- Partición: mensual.
- Retención: TTL por fila mediante
delete_at. El valor predeterminado es de 90 días y se puede configurar por organización (de 7 a 365). El registro se puede desactivar por organización. - Excluido: webhooks activados antes del lanzamiento de esta funcionalidad (los trabajos en cola más antiguos no incluyen los metadatos de tenant necesarios para atribuirlos).
events
La dirección Events lee la tabla existente events, la misma en la que llega cada evento de cliente rastreado, en vez de un flujo de monitorización dedicado. Se usan dos agregaciones:
| Métrica | Consulta |
|---|---|
event_count / total_events | count() over (organization_id, workspace_id, [event_name], time range). |
unique_users | uniqExact(user_id) over the same filter. |
scopeEventName añade un predicado event_name = …; al omitirlo, se cuentan todos los nombres de evento. Esto observa el recuento de eventos almacenados: una solicitud que Joryio acepta pero cuyo cuerpo rechaza aparece en Inbound API, no aquí.
Respuestas de error
| Estado | Cuándo |
|---|---|
400 | Error de validación: falta un campo obligatorio, mode y op no coinciden, etc. El cuerpo de respuesta enumera los campos problemáticos. |
401 | JWT ausente o no válido. |
403 | El JWT es válido, pero no tiene settings:read (listar/obtener/historial/vista previa) o settings:write (crear/actualizar/eliminar/posponer/reanudar/duplicar). |
404 | No se encontró el ID de alerta en el espacio de trabajo actual. |
Ejemplo de integración
Crear, previsualizar y posponer una alerta desde la terminal:
TOKEN=eyJ...
BASE=https://api-eu1.joryio.com
# 1) Vista previa antes de guardar: ¿se activaría ahora?
curl -sX POST "$BASE/monitoring/preview" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"direction":"inbound","metric":"calls","codes":["5xx"],"mode":"absolute","op":">","threshold":"100","duration":"5m"}'
# → {"currentValue":42,"displayValue":"42","thresholdLabel":"> 100 in 5m","wouldFire":false}
# 2) Se ve bien: crea la alerta.
ALERT_ID=$(curl -sX POST "$BASE/monitoring/alerts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"5xx error rate","direction":"inbound","metric":"calls","codes":["5xx"],"mode":"absolute","op":">","threshold":"100","duration":"5m","recipients":["ops@example.com"]}' \
| jq -r .id)
# 3) Posponla durante 4 horas en una ventana de mantenimiento programada.
curl -sX POST "$BASE/monitoring/alerts/$ALERT_ID/snooze" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"window":"4h"}'