Canvas API Overview
Manage Canvas journeys - multi-step automations built in the visual journey builder - via the REST API.
All endpoints on this page are relative to the base URL: https://api-eu1.joryio.com - see API Overview.
Canvas endpoints live under the /journeys path - the API resource name for a canvas is a journey. canvasId and journey id refer to the same identifier.
Authentication
All requests require API key authentication:
Authorization: Bearer jry_live_your_api_key_here
Content-Type: application/json
Required scopes per endpoint group:
| Scope | Endpoints |
|---|---|
canvas:read | List, get, executions, stats, node analytics, versions |
canvas:write | Create, update, duplicate, archive, tags, variants, experiments |
canvas:activate | Activate, pause, resume, publish, rerun, enter user, rollback |
canvas:delete | Delete, bulk delete |
List Journeys
GET /journeys
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | - | Filter by status |
tags | string | - | Filter by tags (comma-separated) |
q | string | - | Free-text search |
createdBy / editedBy | string | - | Filter by creator / last editor (comma-separated user ids) |
page | number | 1 | Page number |
limit | number | - | Results per page (max 100) |
Example Request
curl -X GET "https://api-eu1.joryio.com/journeys?status=active&limit=20" \
-H "Authorization: Bearer jry_live_your_api_key"
Response
{
"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
}
}
Create Journey
POST /journeys
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Journey name (max 255 chars) |
description | string | No | Description (max 1000 chars) |
nodes | array | No | Graph nodes (max 500). A wizard-created draft may start empty |
edges | array | No | Graph edges (max 1000) |
entryTrigger | object | No | How users enter - see below |
variants | array | No | Whole-journey A/B/n variants - see below |
settings | object | No | timezone, quietTime, conversionTracking, reEntryPolicy, personalizedVariants + config |
sendType | string | No | immediate, scheduled, recurring, or trigger |
scheduledAt | string | No | ISO 8601 date for sendType: "scheduled" |
recurringSchedule | object | No | frequency (daily/weekly/monthly/custom), cron, dayOfWeek, dayOfMonth, timeOfDay, timezone, endDate, maxOccurrences |
targeting | object | No | Audience for scheduled/immediate journeys: userIds, filterGroups, excludeFilterGroups, filterOperator, subscriptionPreference |
exitCriteria | object | No | Same filter shape as targeting; matching active users are exited |
tags | string[] | No | Tags |
Node structure
Each entry in nodes has id, type, config (type-specific), and optional position (x/y for the editor). Valid type values:
trigger, delay, condition, behavior_split, context, message,
email, sms, push, in_app, in_app_message, whatsapp, whatsapp_message,
webhook, update_user, connector, experiment, ai_decision,
wallet, update_wallet
Each entry in edges has id, source, target, and optional sourceHandle / targetHandle / label - sourceHandle selects the output port on multi-output nodes (branch groups, behavior-split did / timed_out).
Entry trigger
entryTrigger has a type and a type-specific config. Valid types: event, segment, api, entity_change, whatsapp_inbound, sms_inbound, viber_inbound, attribute_change, subscription_status, schedule.
Whole-journey variants
Each entry in variants: id, name, percentage (0-100; all variants must sum to 100), isControl (a control variant is a measured holdout with no flow), and triggerNodeId (the trigger node this treatment variant roots at). See journey analytics for how variant results are reported.
Example Request
curl -X POST https://api-eu1.joryio.com/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" }
]
}'
Returns the created journey object (status draft).
Get Journey
GET /journeys/:canvasId
curl -X GET https://api-eu1.joryio.com/journeys/3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f \
-H "Authorization: Bearer jry_live_your_api_key"
Returns the full journey including nodes, edges, entryTrigger, variants, and settings.
Update Journey
PUT /journeys/:canvasId
The body accepts the same fields as Create Journey - all optional; only the provided fields are updated.
curl -X PUT https://api-eu1.joryio.com/journeys/3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Welcome journey v2" }'
Delete Journey
DELETE /journeys/:canvasId
Returns 204 No Content. Journeys with run history should be archived instead (POST /journeys/:canvasId/archive), which halts enrollment and sending while preserving analytics.
Status Endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
POST | /journeys/:canvasId/activate | canvas:activate | Activate - users can start entering |
POST | /journeys/:canvasId/pause | canvas:activate | Pause - stops new entries and advancement |
POST | /journeys/:canvasId/resume | canvas:activate | Resume a paused journey |
POST | /journeys/:canvasId/archive | canvas:write | Archive (halts enrollment/sending, keeps history) |
POST | /journeys/:canvasId/unarchive | canvas:write | Restore to a non-running state; activate explicitly to resume |
POST | /journeys/:canvasId/publish | canvas:activate | Publish the current draft as a new version. Body: optional changeSummary, userTransition (keep_on_version / force_exit / migrate_to_new) |
POST | /journeys/:canvasId/rerun | canvas:activate | Re-run the current published version without creating a new one |
curl -X POST https://api-eu1.joryio.com/journeys/3c9d2f1a-5e8b-4a7c-9f0d-1b2a3c4d5e6f/activate \
-H "Authorization: Bearer jry_live_your_api_key"
Enter a User (API trigger)
Enroll a specific user into a journey - the entry path for entryTrigger.type: "api".
POST /journeys/:canvasId/enter/:userId
Body (optional): context - a JSON object made available to the execution (readable in messages and conditions as journey context variables).
curl -X POST https://api-eu1.joryio.com/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" } }'
Executions and Analytics
| Method | Path | Description |
|---|---|---|
GET | /journeys/:canvasId/executions | List executions (status, limit max 100, offset) |
GET | /journeys/:canvasId/executions/:executionId | Get one execution |
GET | /journeys/:canvasId/executions/:executionId/context | Execution context variables with inferred types |
GET | /journeys/:canvasId/stats | Journey-level stats (startDate, endDate) |
GET | /journeys/:canvasId/node-analytics | Per-node analytics (startDate, endDate) |
GET | /journeys/:canvasId/variant-stats | Whole-journey variant performance (A/B/n + control) |
GET | /journeys/:canvasId/experiments/:nodeId/stats | Experiment node stats with significance testing |
POST | /journeys/:canvasId/experiments/:nodeId/declare-winner | Declare a winner (body: pathId) |
POST | /journeys/:canvasId/experiments/:nodeId/reset | Reset an experiment to collecting |
GET | /journeys/:canvasId/personalization-status | Personalized-variants phase status |
Additional Endpoints
| Method | Path | Description |
|---|---|---|
PUT | /journeys/:canvasId/variants | Set whole-journey variants (body: variants array) |
GET | /journeys/:canvasId/versions | List versions |
GET | /journeys/:canvasId/versions/compare?v1=&v2= | Diff two versions |
GET | /journeys/:canvasId/versions/compare-draft | Diff current draft against the last published version |
GET | /journeys/:canvasId/versions/migration-preview | Preview execution compatibility before a migrate-publish |
GET | /journeys/:canvasId/versions/:versionId | Get a version |
POST | /journeys/:canvasId/versions/:versionId/rollback | Roll back to a version |
GET | /journeys/:canvasId/versions/:versionId/stats | Version-specific stats |
GET | /journeys/:canvasId/versions/:versionId/executions | Executions on a specific version |
GET | /journeys/:canvasId/version-execution-counts | Execution counts per version |
POST | /journeys/:canvasId/versions/:versionId/migrate | Force-migrate compatible executions to a version |
GET | /journeys/:canvasId/history | Audit-log history (limit, max 200) |
POST | /journeys/bulk-delete / bulk-duplicate / bulk-archive / bulk-unarchive | Bulk actions; body { "ids": [...] }, returns { succeeded, failed } |
POST | /journeys/bulk-tag | Bulk tagging; body { "ids": [...], "tags": [...] } |
POST | /journeys/webhook/test | Fire a webhook node config once against a sample context (rate limited) |
POST | /journeys/preview-context-value | Render a Liquid expression against a sample scope |
POST | /journeys/preview-user-updates | Dry-run an Update User node's rows against a sample user |
POST | /journeys/:canvasId/nodes/:nodeId/send-test-email / send-test-sms / send-test-whatsapp / send-test-push | Test-send a message node |
POST | /journeys/:canvasId/cleanup-stuck-executions | Clean up stuck executions |
GET | /journeys/:canvasId/executions/:executionId/rendered-message/:nodeId | Rendered message for one execution + node |
Error Responses
All errors share the standard shape - see Error Response in the API Overview for the format, status codes, and rate-limiting behavior.
{
"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"
}