Journey graph schema
Journeys are stored as directed acyclic graphs (DAGs) — a list of nodes connected by edges. The same schema is enforced by JourneyValidator._validate_graph_schema() at write time and is used by the journey builder UI, by playbook component templates (component_templates.config), and by inline playbook definitions (playbook_templates.journey_config).
Node schema
{
"id": "node_abc123",
"type": "send",
"label": "Send welcome email",
"config": {
"channel": "email",
"template_id": "tmpl_welcome_v2"
},
"next_nodes": [],
"position": { "x": 200, "y": 400 }
}| Field | Type | Description |
|---|---|---|
id | string | Unique within the graph |
type | string | Node type — always lowercase_snake_case (see types below) |
label | string | Optional human-readable label |
config | object | Type-specific configuration |
next_nodes | array | Deprecated — always empty. Routing is via edges. |
position | object | Canvas coordinates for the visual builder |
Node types
Node type values are a fixed enum of lowercase snake_case strings. Common types used in most journeys:
| Type | Purpose | Config fields |
|---|---|---|
event_trigger | Entry point — fires on an event | event_name, re_entry, optional event filters |
segment_trigger | Entry point — fires on segment entry/exit | segment_id, direction |
date_trigger | Entry point — schedule-based | cron, timezone |
api_trigger | Entry point — external API push | trigger key |
wait | Pause | wait_type (duration/until_event/until_time/dynamic), duration, timeout |
send | Deliver message | channel, template_id, fallback_channel, quiet_hours |
in_app_message | Queue an in-app message | template_id, surface |
branch | Conditional split | conditions, default_path |
experiment_split | A/B test | variants (array with percentage), winner_metric |
parallel_fork | Run multiple downstream paths concurrently | branches |
exit | End journey | exit_reason (optional label) |
Additional specialised types cover loyalty (loyalty_check_balance, loyalty_award_points, …), gamification, AI checks (contact_intelligence_check, send_time_optimization, …), audience operations (audience_sync, segment_move, …), and ad creative orchestration. The visual journey builder lists every available node type in its sidebar; that catalogue is authoritative.
Edge schema
{
"id": "edge_node_1_to_node_2_0",
"source_node_id": "node_1",
"target_node_id": "node_2",
"label": "true",
"routing": [{ "x": 150, "y": 250 }]
}| Field | Type | Description |
|---|---|---|
id | string | Unique within the graph |
source_node_id | string | ID of the source node |
target_node_id | string | ID of the target node |
label | string | Output port label — e.g., "true", "false", "timeout", "variant_a" |
routing | array | Optional canvas waypoints for edge rendering |
Rules
- Node types are always
lowercase_snake_case— neverUPPERCASE. The string must match a value in theNodeTypeenum. - Routing is always via edges — never via
next_nodeson nodes (deprecated field, always empty). - Edges are dumb — they carry labels (e.g.
"true","false","timeout","variant_a"), not logic. Branch evaluation lives in the branch node’sconfig, not on the edge. - No cycles — the graph must be a DAG (directed acyclic graph).
- Exactly one trigger per journey — exactly one node whose
typeis a trigger variant (event_trigger,segment_trigger,date_trigger,api_trigger,operational_event_trigger). - Every path must terminate — either at an
exitnode or at a node with no outgoing edges.
Wait polymorphism
Wait nodes support four wait_type values:
| Wait type | Behavior |
|---|---|
duration | Pause for a fixed time (minutes, hours, days) |
until_event | Pause until the contact fires a specific event (with optional timeout) |
until_time | Pause until a specific date/time or contact property date |
dynamic | Pause for a duration computed from a contact/event property |
Validation
The journey API validates the graph schema on every save:
- All node IDs must be unique
- All edge source/target IDs must reference existing nodes
- Node types must be from the allowed set
- Config must match the schema for the node type
- No cycles allowed
Invalid graphs are rejected with descriptive error messages.
Where else this schema applies
The same node/edge shape is reused outside of journeys:
component_templates.config— control-plane catalog of reusable journey componentsplaybook_templates.journey_config— inline journey definitions inside playbook templates
Validation is the same in every location.
What’s next
- Journey builder — build journeys visually
- Data model — how journeys relate to contacts and events