Resumen de la API de Canvas
Gestiona los journeys de Canvas - automatizaciones de varios pasos creadas en el constructor visual de journeys - mediante 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.
Los endpoints de Canvas se encuentran bajo la ruta /journeys: el nombre del recurso de API para un canvas es «journey». canvasId y el ID de journey se refieren al mismo identificador.
Autenticación
Todas las solicitudes requieren autenticación mediante clave de API:
Authorization: Bearer jry_live_your_api_key_here
Content-Type: application/json
Scopes necesarios por grupo de endpoints:
| Scope | Endpoints |
|---|---|
canvas:read | Listar, obtener, ejecuciones, estadísticas, analítica de nodos, versiones |
canvas:write | Crear, actualizar, duplicar, archivar, etiquetas, variantes, experimentos |
canvas:activate | Activar, pausar, reanudar, publicar, ejecutar de nuevo, introducir usuario, revertir |
canvas:enter | Introducir usuario (disparador API) - y nada más. Incluido en canvas:activate |
canvas:delete | Eliminar, eliminar en bloque |
Listar journeys
GET /journeys
Parámetros de consulta
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
status | cadena | Filtra por estado | |
tags | cadena | Filtra por etiquetas, separadas por comas | |
q | cadena | Búsqueda de texto libre | |
createdBy / editedBy | cadena | Filtra por creador o último editor; ID de usuario separados por comas | |
page | número | 1 | Número de página |
limit | número | Resultados por página, máximo 100 |
Solicitud de ejemplo
curl -X GET "https://api-eu1.joryio.com/api/journeys?status=active&limit=20" \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
{
"data": [
{ "id": "3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f", "name": "Welcome journey", "status": "active" }
],
"pagination": {
"total": 8,
"page": 1,
"limit": 20,
"offset": 0,
"totalPages": 1,
"hasMore": false
}
}
Crear un journey
POST /journeys
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | cadena | Sí | Nombre del journey, máximo 255 caracteres |
description | cadena | No | Descripción, máximo 1000 caracteres |
nodes | matriz | No | Nodos del grafo, máximo 500. Un borrador creado por el asistente puede comenzar vacío |
edges | matriz | No | Conexiones del grafo, máximo 1000 |
entryTrigger | objeto | No | Cómo entran los usuarios; consulta más abajo |
variants | matriz | No | Variantes A/B/n de todo el journey; consulta más abajo |
settings | objeto | No | timezone, quietTime, conversionTracking, reEntryPolicy, personalizedVariants y su configuración |
sendType | cadena | No | immediate, scheduled, recurring o trigger |
scheduledAt | cadena | No | Fecha ISO 8601 para sendType: "scheduled" |
recurringSchedule | objeto | No | frequency (daily/weekly/monthly/custom), cron, dayOfWeek, dayOfMonth, timeOfDay, timezone, endDate, maxOccurrences timeOfDay es HH:MM en formato de 24 horas (09:00, 17:30); cualquier otro valor se rechaza con 400. |
targeting | objeto | No | Audiencia para journeys programados o inmediatos: userIds, filterGroups, excludeFilterGroups, filterOperator, subscriptionPreference |
exitCriteria | objeto | No | Misma estructura de filtros que targeting; los usuarios activos que coincidan salen |
tags | cadena[] | No | Etiquetas |
Estructura de los nodos
Cada entrada de nodes tiene id, type, config (específica del tipo) y una position opcional (x/y para el editor). Valores válidos de type:
trigger, message, in_app_message, delay, condition, behavior_split,
context, update_user, update_wallet, connector, experiment,
ai_decision, ai_agent
Hay un solo nodo message; el canal es config.channel (email, sms, push, whatsapp, viber, in_app, webhook). Los antiguos tipos de nodo por canal - email, sms, push, whatsapp, whatsapp_message, wallet, webhook y el alias in_app - se eliminaron y ahora se rechazan al crear y al actualizar.
Cada entrada de edges tiene id, source, target y sourceHandle / targetHandle / label opcionales. sourceHandle selecciona el puerto de salida de los nodos con varias salidas (grupos de ramificación, behavior-split did / timed_out).
Disparador de entrada
entryTrigger tiene un type y una config específica del tipo. Tipos válidos: event, api, entity_change, whatsapp_inbound, sms_inbound, viber_inbound, attribute_change, subscription_status, schedule.
La entrada en segmento no es un desencadenante. Para entradas causadas por algo que hizo la persona, usa un desencadenante de evento con un filtro; para entradas causadas por el paso del tiempo, usa un recorrido recurrente sobre el segmento.
Variantes de todo el journey
Cada entrada de variants incluye id, name, percentage (de 0 a 100; todas las variantes deben sumar 100), isControlGroup (una variante de control es un grupo de control medido sin flujo) y triggerNodeId (el nodo disparador desde el que comienza esta variante de tratamiento). Consulta analítica de journeys para saber cómo se presentan los resultados de las variantes. El campo se llamaba antes isControl; esa forma se sigue aceptando al escribir y se normaliza a isControlGroup.
Solicitud de ejemplo
curl -X POST https://api-eu1.joryio.com/api/journeys \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome journey",
"sendType": "trigger",
"entryTrigger": {
"type": "event",
"config": { "eventName": "signed_up" }
},
"nodes": [
{ "id": "n1", "type": "trigger", "config": { "type": "event", "eventName": "signed_up" } },
{ "id": "n2", "type": "delay", "config": { "delayType": "duration", "value": 1, "unit": "days" } },
{ "id": "n3", "type": "email", "config": { "subject": "Welcome!", "html": "<p>Hi {{ user.firstName }}</p>" } }
],
"edges": [
{ "id": "e1", "source": "n1", "target": "n2" },
{ "id": "e2", "source": "n2", "target": "n3" }
]
}'
Devuelve el objeto journey creado, con estado draft.
Obtener un journey
GET /journeys/:canvasId
curl -X GET https://api-eu1.joryio.com/api/journeys/3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f \
-H "Authorization: Bearer jry_live_your_api_key"
Devuelve el journey completo, incluidos nodes, edges, entryTrigger, variants y settings.
Actualizar un journey
PUT /journeys/:canvasId
El cuerpo acepta los mismos campos que Crear un journey; todos son opcionales y solo se actualizan los campos enviados.
curl -X PUT https://api-eu1.joryio.com/api/journeys/3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Welcome journey v2" }'
Eliminar un journey
DELETE /journeys/:canvasId
Devuelve 204 No Content. Los journeys con historial de ejecuciones deben archivarse en su lugar (POST /journeys/:canvasId/archive): así se detienen las inscripciones y los envíos, pero se conserva la analítica.
Endpoints de estado
| Método | Ruta | Scope | Descripción |
|---|---|---|---|
POST | /journeys/:canvasId/activate | canvas:activate | Activar: los usuarios pueden empezar a entrar |
POST | /journeys/:canvasId/pause | canvas:activate | Pausar: detiene nuevas entradas y avances |
POST | /journeys/:canvasId/resume | canvas:activate | Reanudar un journey pausado |
POST | /journeys/:canvasId/archive | canvas:write | Archivar: detiene inscripciones y envíos, conserva el historial |
POST | /journeys/:canvasId/unarchive | canvas:write | Restaurar a un estado no activo; actívalo explícitamente para reanudarlo |
POST | /journeys/:canvasId/publish | canvas:activate | Publicar el borrador actual como nueva versión. Cuerpo: changeSummary opcional, userTransition (keep_on_version / force_exit / migrate_to_new). Solo para un recorrido que ya está en directo (status: active); si no, 409 con code: "journey_not_live" - la puesta en marcha se hace con POST /journeys/:canvasId/activate. |
POST | /journeys/:canvasId/rerun | canvas:activate | Volver a ejecutar la versión publicada actual sin crear una nueva |
curl -X POST https://api-eu1.joryio.com/api/journeys/3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f/activate \
-H "Authorization: Bearer jry_live_your_api_key"
Introducir un usuario (disparador de API)
Inscribe a un usuario concreto en un journey: es la ruta de entrada de entryTrigger.type: "api".
POST /journeys/:canvasId/enter/:userId
Permiso: canvas:enter (incluido en canvas:activate), para que una integración como Zapier pueda añadir personas a un recorrido sin poder iniciarlo, pausarlo ni editarlo.
Cuerpo opcional: context, un objeto JSON disponible para la ejecución y que puede leerse en mensajes y condiciones como variables de contexto del journey.
curl -X POST https://api-eu1.joryio.com/api/journeys/3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f/enter/user_123 \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "context": { "source": "crm-sync", "priority": "high" } }'
Listar los recorridos en los que puede entrar una persona
GET /journeys/enterable?q=
Los journeys activos (active), como { "data": [{ "id", "name", "activatedAt" }] }, ordenados por nombre, como máximo 100; q filtra por parte del nombre. Permiso: canvas:enter (o canvas:read): una integración que puede añadir personas a recorridos elige uno sin leer los propios journeys.
Ejecuciones y analítica
| Método | Ruta | Descripción |
|---|---|---|
GET | /journeys/:canvasId/executions | Lista ejecuciones (status, limit máximo 100, offset) |
GET | /journeys/:canvasId/executions/:executionId | Obtiene una ejecución. Cada paso de webhook en history incluye webhook (estado, correcto, duración, intentos, error, campos guardados); una llamada fallida (no 2xx) añade webhook.response ({ status, contentType, body, truncated }) - con los secretos ocultos, los primeros 2 KB del cuerpo, conservada 30 días, devuelta solo a quien tiene pii:view (una clave de API solo con ese permiso; los demás reciben webhook.responseHidden: true). El cuerpo de una respuesta correcta nunca se guarda |
GET | /journeys/:canvasId/executions/:executionId/context | Variables de contexto de la ejecución con tipos inferidos |
GET | /journeys/:canvasId/stats | Estadísticas del journey (startDate, endDate) |
GET | /journeys/:canvasId/node-analytics | Analítica por nodo (startDate, endDate) |
GET | /journeys/:canvasId/variant-stats | Rendimiento de las variantes de todo el journey (A/B/n y control) |
GET | /journeys/:canvasId/recovered-carts | Los carritos abandonados que recuperó el recorrido: la lista detrás de revenue.recoveredCarts en /stats, de la misma consulta, así que total es igual a ese recuento para los mismos startDate/endDate (carritos recuperados en el intervalo; un final solo con fecha incluye el día entero, zona horaria del espacio de trabajo). limit 1-100 (25 por defecto), offset; los más recientes primero. Filas: orderNumber (el de la tienda), placedAt, recoveredAt, match (recovery_link, order = mismo carrito, reported), cartValue/cartCurrency, creditedRevenue/creditedCurrency (neto de reembolsos), contact (el dueño del carrito) y buyer (solo cuando el pedido se guardó a nombre de otra persona); los nombres de contacto se ocultan sin pii:view. Devuelve { data, total, limit, offset, summary } |
GET | /journeys/:canvasId/experiments/:nodeId/stats | Estadísticas del nodo de experimento con prueba de significancia |
POST | /journeys/:canvasId/experiments/:nodeId/declare-winner | Declara un ganador (cuerpo: pathId) |
POST | /journeys/:canvasId/experiments/:nodeId/reset | Restablece un experimento a recopilación |
GET | /journeys/:canvasId/personalization-status | Estado de la fase de variantes personalizadas |
Endpoints adicionales
| Método | Ruta | Descripción |
|---|---|---|
PUT | /journeys/:canvasId/variants | Define las variantes de todo el journey (cuerpo: matriz variants) |
GET | /journeys/:canvasId/versions | Lista versiones |
GET | /journeys/:canvasId/versions/compare?v1=&v2= | Compara dos versiones |
GET | /journeys/:canvasId/versions/compare-draft | Compara el borrador actual con la última versión publicada |
GET | /journeys/:canvasId/versions/migration-preview | Previsualiza la compatibilidad de ejecuciones antes de publicar una migración |
GET | /journeys/:canvasId/versions/:versionId | Obtiene una versión |
POST | /journeys/:canvasId/versions/:versionId/rollback | Revierte a una versión. Solo recorridos en directo; si no, 409 journey_not_live. |
GET | /journeys/:canvasId/versions/:versionId/stats | Estadísticas específicas de la versión |
GET | /journeys/:canvasId/versions/:versionId/executions | Ejecuciones de una versión concreta |
GET | /journeys/:canvasId/version-execution-counts | Recuentos de ejecuciones por versión |
POST | /journeys/:canvasId/versions/:versionId/migrate | Fuerza la migración de ejecuciones compatibles a una versión |
GET | /journeys/:canvasId/history | Historial del registro de auditoría (limit, máximo 200) |
POST | /journeys/bulk-delete / bulk-duplicate / bulk-archive / bulk-unarchive | Acciones en bloque; cuerpo { "ids": [...] }, devuelve { succeeded, failed } |
POST | /journeys/bulk-tag | Etiquetado en bloque; cuerpo { "ids": [...], "tags": [...] } |
POST | /journeys/webhook/test | Ejecuta una vez la configuración de un nodo webhook y devuelve la respuesta en vivo junto con la solicitud enviada (credenciales ocultas). user.userId procesa un contacto real del espacio de trabajo exactamente como un envío real; si no, se usan valores de ejemplo, con límite de frecuencia |
POST | /journeys/preview-context-value | Renderiza una expresión Liquid contra un ámbito de ejemplo |
POST | /journeys/preview-user-updates | Simula las filas de un nodo Update User frente a un usuario de ejemplo |
POST | /journeys/preview-user-update-json | Simula el JSON personalizado de un nodo Update User: entra { json, sample }, sale { errors, preview: { errors, result } }. Los errors bloquean guardar o activar; preview es el JSON renderizado con el ejemplo. No escribe nada |
POST | /journeys/:canvasId/nodes/:nodeId/send-test-email / send-test-sms / send-test-whatsapp / send-test-push | Envía una prueba desde un nodo de mensaje |
POST | /journeys/:canvasId/cleanup-stuck-executions | Limpia ejecuciones bloqueadas |
GET | /journeys/:canvasId/executions/:executionId/rendered-message/:nodeId | Mensaje renderizado de una ejecución y un nodo |
Revisión de lanzamiento
La misma revisión previa al envío que reciben las campañas (ver la guía de Revisión de lanzamiento), ejecutada sobre cada nodo de mensaje: enlace de baja, campos de combinación, verificación del remitente, enlaces inutilizables, longitud del asunto, falta de texto plano, horas de silencio, más node_unreachable para los nodos a los que ningún disparador llega, cart_variable_no_cart_trigger para un mensaje que lee cart en un recorrido que no empieza con un evento de carrito, y channel_not_set_up para un canal que usa el recorrido sin proveedor ni remitente (params.channel; un bloqueo, o una recomendación con params.sandbox: 1 en un espacio de trabajo de demostración, donde los envíos se simulan para todos). La activación y la publicación también la ejecutan.
GET /journeys/{canvasId}/launch-review
Alcance: canvas:read. La respuesta tiene la forma documentada en la API de campañas; un hallazgo de nodo lleva scope.kind = "node", scope.id = el id del nodo y fix.nodeId.
Cuando el espacio de trabajo exige una lista limpia (settings.launchReview.requireClean, por defecto true) y hay bloqueos, POST /journeys/{canvasId}/activate y POST /journeys/{canvasId}/publish responden 409 con code: "launch_review_blocked" y los bloqueos en el cuerpo.
Respuestas de error
Todos los errores comparten la estructura estándar. Consulta respuesta de error en el resumen de la API para ver el formato, los códigos de estado y el comportamiento de límite de frecuencia.
{
"statusCode": 404,
"message": "Canvas with ID 8f14e45f-ceea-467f-a11d-2f4b6a1c9e3b not found",
"timestamp": "2026-07-12T09:00:00.000Z",
"path": "/journeys/8f14e45f-ceea-467f-a11d-2f4b6a1c9e3b"
}