Reference for managing Courier journeys programmatically. Create journey shells, add scoped notification templates, wire them into the DAG, publish, and invoke runs.
Use this file to discover all available pages before exploring further.
If you’re building onboarding flows, re-engagement sequences, or multi-step messaging workflows, you can define and manage them entirely through code. Courier’s Journeys API gives you full control over the journey lifecycle without touching the UI.This page covers everything from creating a journey shell to invoking a live run. For a visual overview of how journeys work in the UI, see the Journeys overview.
Journeys API requests are authenticated by passing your workspace API key as a Bearer token in the Authorization header: Authorization: Bearer <your-api-key>. Requests without this header are rejected.
Each journey manages its own notification templates, created and managed exclusively within that journey’s scope. These endpoints mirror the workspace-level /notifications endpoints but are nested under a specific journey.
A journey is a directed acyclic graph (DAG) of nodes. Each node performs one action (send a notification, wait, branch, fetch data, etc.), and the sequence defines execution order.
Every journey starts with a trigger node that determines how runs begin. You’ve got two options:
API invoke (trigger_type: "api-invoke"): Starts a run when you call POST /journeys/{templateId}/invoke. You can optionally attach a JSON Schema to the trigger via the schema field. Courier validates the data payload against it when the trigger node executes.
Segment (trigger_type: "segment"): Starts a run when a matching Segment event arrives. Requires request_type (identify, group, or track) and optionally an event_id.
Nodes are the building blocks of your journey. Each node has a type field that determines its behavior, and some node types have a secondary discriminator. Here’s the full set:
Node type
Description
trigger
Entry point. Discriminated by trigger_type: api-invoke or segment.
send
Send a notification using a journey-scoped template. References the template via message.template.
delay
Pause the run. Discriminated by mode: duration (e.g., "PT1H" for one hour) or until (a specific timestamp).
fetch
Make an HTTP request and merge the response into run state. Discriminated by method: get, delete, post, or put.
branch
Conditional routing. Evaluates paths[] in order and routes to the first match, with a default fallback.
throttle
Rate-limit runs. Discriminated by scope: user, global, or dynamic.
ai
Run an LLM prompt with optional web search. Returns structured output per output_schema.
Several node types support a conditions field for conditional execution. Conditions are expressed as tuples:
Binary (3 elements): [path, operator, value], for example ["data.plan", "is equal", "pro"]
Unary (2 elements): [path, operator], for example ["data.email", "exists"]
Available operators for binary conditions: is equal, is not equal, contains, does not contain, starts with, ends with, greater than, greater than or equal, less than, less than or equal.Available operators for unary conditions: exists, does not exist.Conditions can be grouped with AND / OR logic, and groups can be nested for complex expressions.
Here’s the typical process for building a journey programmatically. Each step builds on the previous one, so follow them in order:
Create the journey shell with POST /journeys. Pass a name and at least one node (typically a trigger). This gives you a journey ID and a draft you can build on.
Create journey-scoped templates with POST /journeys/{id}/templates. These are the notification templates your send nodes will reference. Define the content using Elemental format and specify the channel (email, sms, push, etc.).
Wire templates into the journey with PUT /journeys/{id}. Replace the draft with your full node graph, including send nodes that reference the template IDs you created in step 2.
Publish with POST /journeys/{id}/publish. This locks in the current draft as a versioned snapshot. All new runs execute against the published version.
Invoke with POST /journeys/{id}/invoke. Pass a user_id (or profile with contact info) and optional data payload. Courier starts a run that walks through the DAG.
The invoke call returns a runId immediately (HTTP 202). Courier processes the run asynchronously, validating data against the trigger’s JSON Schema and walking through the DAG.
These two template types serve different purposes and are managed differently.Journey-scoped templates are created under POST /journeys/{id}/templates and live exclusively within that journey. They use Elemental format for content, can’t be shared across journeys, and can’t be referenced from the Send API. They’re published either alongside the journey (when you publish the journey itself) or independently via their own publish endpoint.Workspace templates are the ones you create at POST /notifications or in Design Studio. They live at the workspace level, can be used from the Send API, and aren’t tied to any specific journey.If you need a template that’s reusable across multiple journeys or callable from the Send API, use a workspace template. If the template is specific to one journey’s logic, keep it scoped to the journey.