> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.onesignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update journey

> Apply a partial update to a journey with JSON Merge Patch, and activate a draft journey by transitioning its state.

<Info>
  **Beta.** The Journeys API is in beta. Endpoints and response fields can still change.
</Info>

## Overview

Update an existing [Journey](/docs/en/journeys-overview) programmatically. The request is a [JSON Merge Patch (RFC 7396)](https://datatracker.ietf.org/doc/html/rfc7396): send only the fields you want to change, and omitted fields are left unchanged. Send `state: "active"` to activate a `draft` journey. Editing rules tighten once a journey is active, because it carries in-flight users. See [Editing an active journey](#editing-an-active-journey).

<Warning>
  Arrays are replaced wholesale, not merged. Sending `nodes` replaces the entire node graph. To keep in-flight users on a node, preserve that node's server-assigned `id` from a prior [View journey](/reference/view-journey) fetch. See [Preserve node ids](#preserve-node-ids).
</Warning>

To change a single node without re-sending the whole graph, use [Update journey node](/reference/update-journey-node).

***

## How to use this API

Authenticate with your [App API Key](/docs/en/keys-and-ids). The authenticated key must have permission to update journeys. Find a journey's `id` from the [View journeys](/reference/view-journeys) API or in the dashboard URL.

Fetch the journey first with [View journey](/reference/view-journey), then send only the fields you are changing. A minimal rename touches only one field:

```json theme={null}
{
  "name": "Welcome series v2"
}
```

### Merge patch behavior

The request body is merged onto the journey's current representation:

* **A field you send replaces the current value.** Omitted fields are untouched.
* **A `null` value clears a nullable field.** `description`, `schedule`, `early_exit`, and `reentry_rules` accept `null` to reset them to their defaults.
* **An individual early-exit rule accepts `null`.** Send `early_exit: { "rules": { "on_session": null } }` to drop one rule while keeping the others. Clearing the last remaining rule is rejected, since an `early_exit` with no rule configures nothing; send `early_exit: null` to remove early exit entirely.
* **Arrays are replaced as a unit.** `nodes`, a node's `branches`, and `windows` are not merged element-wise. Send the full array you want.
* **Changing the audience `kind` swaps the whole object.** The `audience` is a tagged union identified by its `kind`. Changing the `kind` (`segment` ↔ `event_trigger`) is only allowed while the journey is a draft or scheduled; once it is live the audience type is frozen (see [Editing an active journey](#editing-an-active-journey)). When you do change it, send only the new variant's fields; the previous variant's fields are dropped rather than merged, so you do not need to `null` them.

```json theme={null}
{
  "description": null,
  "reentry_rules": { "duration_seconds": 3600 }
}
```

On a draft or scheduled journey, swap the entry `audience` from a segment to an event trigger by sending the new variant alone; the old `included_segment_ids` and `excluded_segment_ids` are dropped:

```json theme={null}
{
  "audience": { "kind": "event_trigger", "name": "purchase_completed" }
}
```

### Preserve node ids

Because `nodes` is an array, a request that includes it replaces the whole graph. A node sent without an `id` is treated as a new node, so omitting the `id`s of nodes you meant to keep discards those nodes and recreates them under new ids. Any users in flight on them are lost. Node fields follow the same schema and validation as [Create journey](/reference/create-journey).

Fetch the journey with [View journey](/reference/view-journey) first, then send back every node you want to keep, each with the `id` the server assigned it:

```json theme={null}
{
  "nodes": [
    { "id": "SERVER_NODE_ID_1", "kind": "send_push", "template_id": "YOUR_TEMPLATE_ID" },
    { "id": "SERVER_NODE_ID_2", "kind": "wait", "duration_seconds": 43200 }
  ]
}
```

A branching node's `branches` array works the same way. Echo each branch's `id` to keep it, and note that an active journey's branches cannot be added or removed at all.

To change one node without re-sending the graph, use [Update journey node](/reference/update-journey-node) instead. It addresses a single node by its `id` and leaves every other node untouched.

### Optimistic concurrency

To avoid overwriting a concurrent change, pass the `concurrency_key` returned by a prior [View journey](/reference/view-journey) fetch. If the journey has changed since that fetch, the request is rejected with `409 journey-stale` and nothing is written. Omit `concurrency_key` to skip the check.

<Note>
  Treat `concurrency_key` as an opaque token: read it from the journey you are editing and send it back unchanged. Do not construct, parse, or compare it yourself.
</Note>

```json theme={null}
{
  "name": "Welcome series v2",
  "concurrency_key": "dcae4794fee16e450e448e37a6f8d0a5a7335755ff8cc76606e3d04b2f574e46"
}
```

### Journey states

A journey reports one of five states. `draft`, `scheduled`, `active`, and `archived` are settable through this endpoint; `processing` is reported by the server only.

| State        | Meaning                                                          |
| ------------ | ---------------------------------------------------------------- |
| `draft`      | Being built. Not running, and no users are entering.             |
| `scheduled`  | Waiting for its `schedule.start_at` to arrive.                   |
| `processing` | Activation is underway. Reported by the server, not settable.    |
| `active`     | Running. Users are entering and moving through the graph.        |
| `archived`   | Stopped. Still readable, but cannot resume sending or be edited. |

### Activate a journey

Set `state` to transition a journey through its lifecycle. The common case is activating a draft:

<CodeGroup>
  ```json Activate now theme={null}
  {
    "state": "active"
  }
  ```

  ```json Schedule a future start theme={null}
  {
    "state": "scheduled",
    "schedule": { "start_at": "2026-08-01T14:00:00Z" }
  }
  ```
</CodeGroup>

* Only `draft` journeys can transition to `scheduled`, and it requires a `schedule` with a `start_at`.
* A journey can return to `draft` only from `scheduled` or `processing`. There is no way to move a running journey back to `draft`.
* Send `state: "archived"` to stop a running journey. Archiving halts all user progress and is permanent: an archived journey can still be read, but it cannot resume sending, and later writes return `422 journey-archived`. A `draft` journey cannot be archived.
* `schedule` timestamps must use UTC (`Z` or `+00:00`).

<Note>
  Setting `state` to `active` starts activation, it does not complete it. The request returns once the activation work is enqueued, so a `200` does not mean the journey is running yet. Until activation finishes, the journey reports `state: "processing"` and its `started_at` stays `null`. Poll [View journey](/reference/view-journey) until `state` reads `active` before you treat the journey as live.
</Note>

### Editing an active journey

An active journey carries in-flight users, so its structure is largely frozen. The API rejects edits that would strand users:

* Non-structural nodes (such as `send_push` or `wait`) can be removed. Users already in flight on a removed node continue forward through the rest of the journey.
* Structural nodes (`split_range`, `yes_no`, `wait_until`) cannot be removed. A new branching node can be added.
* An existing branching node's set of branches cannot change.
* The schedule `start_at` is frozen once the journey has left `draft` or `scheduled`. Change it while the journey is still a draft or scheduled.
* The schedule `stop_at` stays editable in every state, including while active, so you can extend or shorten a running journey's end date. It must be in the future and later than `start_at`.
* A node's `client_node_id` is immutable once the journey is live; change it while the journey is still `draft` or `scheduled`.
* The audience **type** is immutable once the journey is live. That covers both switching between a `segment` and an `event_trigger` audience and toggling `future_additions_only` on a segment audience, since a future-only audience counts as its own type.
* You can still retarget within the same type: a segment audience's segments, or an event-trigger audience's event name and attributes. The one exception is a future-only segment audience, whose segment set is frozen once live.

Edits that violate these rules return `400` with a field-level error describing the conflict.

## Response

A successful request returns `200 OK` with the full updated journey, including server-assigned `id` fields and a `concurrency_key`. Pass that `concurrency_key` unchanged on a later update to avoid overwriting a concurrent change.

### Error responses

| Status | Code                   | Description                                                                                                                                                                                                                           |
| ------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid-payload`      | The request failed validation. This covers a body that is not a JSON object, schema failures, where `meta.path` points to the offending field, and business-logic failures such as removing a structural node from an active journey. |
| 403    | `journey-not-entitled` | Journeys are not enabled for this app.                                                                                                                                                                                                |
| 404    | `journey-not-found`    | No journey with that `id` exists for this app.                                                                                                                                                                                        |
| 409    | `journey-stale`        | The supplied `concurrency_key` no longer matches the journey; it changed since it was last fetched.                                                                                                                                   |
| 422    | `journey-archived`     | Archived journeys cannot be edited.                                                                                                                                                                                                   |
| 429    |                        | Rate limit exceeded. Wait the number of seconds in the `Retry-After` header before retrying. See [Rate limits](/reference/rate-limits).                                                                                               |

Coded errors use the shape `{ "errors": [{ "code", "title", "meta" }] }`; for validation errors the failing field is in `meta.attribute`, with `meta.path` for the offending property.


## OpenAPI

````yaml PATCH /apps/{app_id}/journeys/{id}
openapi: 3.1.0
info:
  title: api.onesignal.com
  version: '11.6'
servers:
  - url: https://api.onesignal.com
security:
  - {}
paths:
  /apps/{app_id}/journeys/{id}:
    patch:
      summary: Update journey
      description: >-
        Apply a partial update to a journey using JSON Merge Patch ([RFC
        7396](https://datatracker.ietf.org/doc/html/rfc7396)). Send only the
        fields you want to change; omitted fields are left unchanged. A `null`
        value clears a nullable field, and arrays such as `nodes` are replaced
        wholesale rather than merged element-wise. Set `state` to `active` to
        activate a draft journey.
      operationId: update-journey
      parameters:
        - name: app_id
          in: path
          description: >-
            Your OneSignal App ID in UUID v4 format. See [Keys &
            IDs](/docs/en/keys-and-ids).
          schema:
            type: string
            default: YOUR_APP_ID
          required: true
        - name: id
          in: path
          description: UUID of the journey to update.
          required: true
          schema:
            type: string
            default: YOUR_JOURNEY_ID
        - name: Authorization
          in: header
          description: >-
            Your App API key with prefix `Key `. See [Keys &
            IDs](/docs/en/keys-and-ids).
          required: true
          schema:
            type: string
            default: Key YOUR_APP_API_KEY
        - name: Content-Type
          in: header
          required: true
          schema:
            type: string
            default: application/json; charset=utf-8
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Journey name.
                description:
                  type:
                    - string
                    - 'null'
                  description: Journey description. Send `null` to clear it.
                audience:
                  $ref: '#/components/schemas/JourneyAudience'
                early_exit:
                  $ref: '#/components/schemas/JourneyEarlyExit'
                reentry_rules:
                  $ref: '#/components/schemas/JourneyReentryRules'
                schedule:
                  $ref: '#/components/schemas/JourneySchedule'
                nodes:
                  type: array
                  items:
                    $ref: '#/components/schemas/JourneyNode'
                  description: >-
                    Full ordered list of nodes, which replaces the existing
                    graph wholesale. Preserve each node's server-assigned `id`
                    from a prior fetch to keep in-flight users on that node;
                    omit `id` to add a new node.
                state:
                  type: string
                  enum:
                    - draft
                    - scheduled
                    - active
                    - archived
                  description: >-
                    Target state. Set `active` to activate a `draft` journey, or
                    `scheduled` together with a future `schedule.start_at` to
                    activate it later. Set `archived` to stop a running journey;
                    archiving is permanent, and later writes return `422
                    journey-archived`. Only `scheduled` and `processing`
                    journeys can return to `draft`.
                concurrency_key:
                  type:
                    - string
                    - 'null'
                  description: >-
                    Optional optimistic-concurrency token. Pass the
                    `concurrency_key` from a prior fetch to reject the update
                    with `409` if the journey changed in the meantime. Omit to
                    skip the check.
            examples:
              Request:
                value:
                  name: Welcome series v2
                  description: Updated onboarding copy.
      responses:
        '200':
          description: '200'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyDetail'
              examples:
                Result:
                  value:
                    id: 0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9
                    app_id: 1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809
                    name: Welcome series v2
                    description: Updated onboarding copy.
                    state: draft
                    created_at: '2026-06-01T14:00:00Z'
                    updated_at: '2026-06-03T11:15:00Z'
                    started_at: null
                    archived_at: null
                    created_source: public_api
                    audience:
                      kind: segment
                      included_segment_ids:
                        - 3f7c1e90-0000-0000-0000-000000000001
                      excluded_segment_ids: []
                      future_additions_only: false
                    early_exit: null
                    reentry_rules: null
                    schedule: null
                    nodes:
                      - id: 11111111-0000-0000-0000-000000000001
                        kind: send_push
                        template_id: 9a8b7c6d-0000-0000-0000-000000000001
                    concurrency_key: >-
                      dcae4794fee16e450e448e37a6f8d0a5a7335755ff8cc76606e3d04b2f574e46
        '400':
          description: '400'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyValidationErrorResponse'
              example:
                errors:
                  - code: invalid-payload
                    title: >-
                      the property '#/bogus' is not defined and the schema does
                      not allow additional properties
                    meta:
                      attribute: base
                      path: bogus
        '403':
          description: '403'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyCodedErrorResponse'
              example:
                errors:
                  - code: journey-not-entitled
                    title: Journeys are not enabled for this app
                    meta: {}
        '404':
          description: '404'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyCodedErrorResponse'
              example:
                errors:
                  - code: journey-not-found
                    title: Journey not found
                    meta: {}
        '409':
          description: '409'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyCodedErrorResponse'
              example:
                errors:
                  - code: journey-stale
                    title: Journey has changed since it was last fetched
                    meta: {}
        '422':
          description: '422'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyCodedErrorResponse'
              example:
                errors:
                  - code: journey-archived
                    title: Archived Journeys cannot be edited
                    meta: {}
        '429':
          description: '429'
          headers:
            Retry-After:
              description: >-
                Number of seconds to wait before retrying the request. Always
                emitted on 429 responses.
              schema:
                type: integer
                minimum: 0
components:
  schemas:
    JourneyAudience:
      oneOf:
        - type: object
          title: segment
          required:
            - kind
          properties:
            kind:
              type: string
              const: segment
            included_segment_ids:
              type: array
              items:
                type: string
              description: Segment UUIDs whose users enter the journey.
            excluded_segment_ids:
              type: array
              items:
                type: string
              description: Segment UUIDs whose users are excluded.
            future_additions_only:
              type: boolean
              description: >-
                When true, only users who newly match the segment after
                activation enter the journey. Defaults to false.
        - type: object
          title: event_trigger
          required:
            - kind
          properties:
            kind:
              type: string
              const: event_trigger
            name:
              type: string
              description: Event name that triggers entry, up to 255 characters.
            attributes:
              $ref: '#/components/schemas/JourneyEventTriggerAttributes'
      description: >-
        The journey entry audience. Either a segment-based or event-triggered
        audience.
    JourneyEarlyExit:
      type:
        - object
        - 'null'
      description: >-
        Conditions that remove a user from the journey before it completes. At
        least one rule must be set under `rules`; an early_exit that configures
        no rule is rejected. Send `null` to remove early exit entirely, or
        `null` for an individual rule to drop just that rule.
      properties:
        rules:
          type: object
          properties:
            on_segment:
              type:
                - object
                - 'null'
              properties:
                included_segment_ids:
                  type: array
                  items:
                    type: string
                  description: Exit when the user enters any of these segments.
            when_not_in_audience:
              type:
                - boolean
                - 'null'
              description: >-
                Exit when the user no longer matches the journey audience.
                Defaults to false.
            on_session:
              type:
                - boolean
                - 'null'
              description: Exit on a new session start. Defaults to false.
            on_event:
              type:
                - object
                - 'null'
              required:
                - name
              properties:
                name:
                  type: string
                  description: Exit when this event occurs. Up to 255 characters.
        tag_on_early_exit:
          type: object
          additionalProperties:
            type: string
          description: Tag key-value pairs applied when a user exits early.
    JourneyReentryRules:
      type:
        - object
        - 'null'
      description: >-
        Controls whether and how soon a user can re-enter the journey. `null`
        means re-entry is not allowed.
      properties:
        duration_seconds:
          type: integer
          minimum: 600
          description: >-
            Minimum seconds before a user can re-enter. Must be at least `600`
            (10 minutes).
    JourneySchedule:
      type:
        - object
        - 'null'
      description: >-
        Optional future start and/or stop time. `null` means no scheduled
        activation.
      properties:
        start_at:
          type:
            - string
            - 'null'
          description: >-
            ISO 8601 start time. Use UTC (`Z` or `+00:00`). Must be at least 5
            minutes in the future.
        stop_at:
          type:
            - string
            - 'null'
          description: >-
            ISO 8601 stop time. Use UTC (`Z` or `+00:00`). Must be in the future
            and later than `start_at`.
        error:
          type:
            - string
            - 'null'
          description: Read-only. Present when a scheduling error occurred.
    JourneyNode:
      oneOf:
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: wait
              description: Holds the user for a fixed duration before continuing.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            duration_seconds:
              type: integer
              description: >-
                Seconds to hold the user. Minimum `60`, maximum `31556952` (1
                year).
              minimum: 60
              maximum: 31556952
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: time_window
              description: Holds the user until the next configured time window opens.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            relative_to:
              type: string
              enum:
                - schedule_in_timezone
                - last_active_time
              description: >-
                `schedule_in_timezone` uses the configured windows;
                `last_active_time` holds relative to the user's last active
                time.
            windows:
              type: array
              items:
                $ref: '#/components/schemas/JourneyTimeWindow'
              description: >-
                One or more time windows. A window with no `day_of_week` applies
                to every day, and is returned in that same day-agnostic form.
                Required when `relative_to` is `schedule_in_timezone`; must be
                omitted when it is `last_active_time`, and is absent from
                responses for those nodes.
            time_zone:
              type: string
              description: >-
                IANA timezone identifier used when the user's timezone is
                unavailable.
            use_user_time_zone:
              type: boolean
              description: When true, uses the user's timezone if available.
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              enum:
                - send_push
                - send_email
                - send_sms
              description: Sends a message on the given channel using a template.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            template_id:
              type: string
              description: UUID of the template to send.
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: send_iam
              description: Sends an in-app message.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            iam_id:
              type: string
              description: UUID of the in-app message to send.
            user_ttl_seconds:
              type: integer
              minimum: 1
              description: Optional time-to-live for the in-app message, in seconds.
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: send_webhook
              description: Sends a webhook.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            webhook_id:
              type: string
              description: UUID of the webhook to send.
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: tag
              description: Assigns key-value tags to the user.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            assignments:
              type: object
              additionalProperties:
                type: string
              description: >-
                Tag key-value pairs to assign. An empty string value removes the
                tag. Keys are limited to 255 characters and values to 1024.
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: split_range
              description: >-
                Routes users into weighted branches that converge to the next
                sibling node.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            randomize_on_entry:
              type: boolean
              description: >-
                When true, assigns each user to a branch at random on entry.
                Defaults to false.
            branches:
              type: array
              items:
                $ref: '#/components/schemas/JourneyBranch'
              description: >-
                Weighted branches. Between 2 and 20. Weights must sum to 100.
                Order determines display order.
              minItems: 2
              maxItems: 20
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: yes_no
              description: Routes users into a yes or no branch based on a condition.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            branches:
              type: array
              minItems: 2
              maxItems: 2
              items:
                $ref: '#/components/schemas/JourneyBranch'
              description: >-
                Exactly two branches. The branch with a `condition` is the "yes"
                branch; the branch without one is the "no" branch.
        - type: object
          required:
            - kind
          properties:
            id:
              type: string
              description: >-
                Server-assigned node UUID. Read-only. Returned on reads; sending
                it on create is rejected with a `400` validation error.
            kind:
              type: string
              const: wait_until
              description: >-
                Holds the user until any branch condition is met, or an optional
                expiration timer fires.
            client_node_id:
              type: string
              description: >-
                Optional client-assigned identifier, unique within the journey.
                Use it to reference this node from elsewhere in the same request
                (for example as `client_node_id` on an `on_notification_action`
                condition). Persisted and returned on reads.
            annotation:
              type: string
              description: >-
                Optional free-text label, up to 255 characters. Stored and
                returned as-is with no effect on journey behavior.
            branches:
              type: array
              items:
                $ref: '#/components/schemas/JourneyBranch'
              description: >-
                Condition branches. At least one required, at most 10. Order
                determines priority.
            expiration:
              type:
                - object
                - 'null'
              properties:
                duration_seconds:
                  type: integer
                  minimum: 60
                  description: >-
                    Seconds to wait before the timer fires. Minimum `60`,
                    maximum `31556952` (1 year).
                  maximum: 31556952
                exits:
                  type: boolean
                  description: >-
                    When true, the user exits the journey when the timer fires;
                    when false, the user continues to convergence.
              description: Optional expiration timer. `null` waits indefinitely.
      description: >-
        A journey node. The `kind` field selects the shape. Branching nodes
        (`split_range`, `yes_no`, `wait_until`) nest their sub-graphs inline via
        `branches[].nodes`.
    JourneyDetail:
      type: object
      description: Full journey representation returned by the detail and create endpoints.
      properties:
        id:
          type: string
          description: Journey UUID. Read-only.
        app_id:
          type: string
          description: UUID of the app the journey belongs to. Read-only.
        name:
          type: string
          description: Journey name, up to 300 characters.
        description:
          type:
            - string
            - 'null'
          description: >-
            Journey description, up to 1024 characters. Defaults to an empty
            string.
        state:
          type: string
          enum:
            - draft
            - scheduled
            - processing
            - active
            - archived
          description: >-
            Journey state. Read-only. New journeys are created as `draft`.
            `processing` is a transient state while an activation is in
            progress, and `archived` is a journey that has been stopped. Change
            it through the `state` field on [Update
            journey](/reference/update-journey).
        created_at:
          type: string
          description: ISO 8601 creation time. Read-only.
        updated_at:
          type: string
          description: ISO 8601 last-update time. Read-only.
        started_at:
          type:
            - string
            - 'null'
          description: >-
            ISO 8601 time the journey was activated, or `null`. Read-only. May
            stay `null` briefly after you set `state` to `active`: activation is
            enqueued for processing, and `started_at` populates once the journey
            finishes processing and becomes active.
        archived_at:
          type:
            - string
            - 'null'
          description: ISO 8601 time the journey was archived, or `null`. Read-only.
        created_source:
          type:
            - string
            - 'null'
          description: >-
            Origin of the journey, for example `public_api` or `dashboard`.
            Read-only.
        audience:
          $ref: '#/components/schemas/JourneyAudience'
        early_exit:
          $ref: '#/components/schemas/JourneyEarlyExit'
        reentry_rules:
          $ref: '#/components/schemas/JourneyReentryRules'
        schedule:
          $ref: '#/components/schemas/JourneySchedule'
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/JourneyNode'
          description: Ordered list of journey nodes.
        concurrency_key:
          type: string
          description: >-
            Opaque optimistic-concurrency token. Read-only. Pass it back on
            update to guard against overwriting a concurrent change (`409
            journey-stale`). Send it back exactly as read from this response; do
            not construct or parse it.
    JourneyValidationErrorResponse:
      type: object
      description: >-
        Validation error response. Uses the same `code`/`title`/`meta` shape as
        every other error; the failing field and any positional detail travel in
        `meta`.
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
                description: >-
                  Stable, kebab-case error identifier. Always `invalid-payload`
                  for validation failures.
              title:
                type: string
                description: Human-readable message. Wording may change between releases.
              meta:
                type: object
                description: >-
                  Structured context. Always includes `attribute` (the field, or
                  `base` for request-level errors); may also include `path`,
                  `node_id`, or `client_node_id`.
    JourneyCodedErrorResponse:
      type: object
      description: Error response with a stable machine-readable `code`.
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
                description: >-
                  Stable, kebab-case error identifier. Does not change once
                  shipped.
              title:
                type: string
                description: Human-readable message. Wording may change between releases.
              meta:
                type: object
                description: Optional structured context. Shape varies by error.
    JourneyEventTriggerAttributes:
      type: array
      description: >-
        Event attribute matchers, as a list of condition groups. Send a single
        group whose conditions are AND'd together. More than one group is
        rejected.
      items:
        type: array
        items:
          type: object
          required:
            - key
            - operator
          properties:
            key:
              type: string
              description: Event attribute key.
            operator:
              type: string
              enum:
                - equal
                - not_equal
                - less
                - less_or_equal
                - greater_or_equal
                - greater
                - is
                - is_not
                - exists
                - not_exists
                - before
                - after
              description: Comparison operator.
            value:
              type: string
              description: >-
                Value to compare against. Not required for `exists` and
                `not_exists`.
    JourneyTimeWindow:
      type: object
      properties:
        start:
          allOf:
            - $ref: '#/components/schemas/JourneyTimePoint'
          description: When the window opens.
        end:
          allOf:
            - $ref: '#/components/schemas/JourneyTimePoint'
          description: When the window closes.
        day_of_week:
          type: integer
          minimum: 1
          maximum: 7
          description: Day of week, 1 = Monday. Omit to apply the window to every day.
      description: A wall-clock window. Each window must span at least 15 minutes.
    JourneyBranch:
      type: object
      properties:
        id:
          type: string
          description: Server-assigned branch identifier. Read-only.
        condition:
          $ref: '#/components/schemas/JourneyCondition'
        weight:
          type: number
          description: >-
            Branch weight for `split_range` nodes. Weights across a node's
            branches must sum to 100.
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/JourneyNode'
          description: >-
            Nodes run when this branch is taken, before flow converges to the
            next sibling node.
    JourneyTimePoint:
      type: object
      properties:
        hour:
          type: integer
          minimum: 0
          maximum: 23
          description: Hour of day, 0-23.
        minute:
          type: integer
          minimum: 0
          maximum: 59
          description: Minute of hour, 0-59. Defaults to 0.
    JourneyCondition:
      oneOf:
        - type: object
          title: segment_membership
          required:
            - kind
          properties:
            kind:
              type: string
              const: segment_membership
            included_segment_ids:
              type: array
              items:
                type: string
              description: Segment UUIDs the user must belong to.
            excluded_segment_ids:
              type: array
              items:
                type: string
              description: Segment UUIDs the user must not belong to.
        - type: object
          title: on_notification_action
          required:
            - kind
          properties:
            kind:
              type: string
              const: on_notification_action
            action:
              type: string
              enum:
                - received
                - clicked
                - opened
              description: >-
                The notification action to branch on. Which actions apply
                depends on the sending node's channel.
            sending_node_id:
              type: string
              description: >-
                `id` of the sending node this action refers to. Returned on
                reads; accepted on write.
            client_node_id:
              type: string
              description: >-
                Write-only alternative to `sending_node_id`. References the
                sending node by its `client_node_id`, which is resolved to that
                node's `id`.
        - type: object
          title: event_trigger
          required:
            - kind
          properties:
            kind:
              type: string
              const: event_trigger
            name:
              type: string
              description: Event name, up to 255 characters.
            attributes:
              $ref: '#/components/schemas/JourneyEventTriggerAttributes'
            entry_event_match_attributes:
              type:
                - array
                - 'null'
              items:
                type: object
              description: >-
                Match incoming event properties against the journey's entry
                event. Only valid on event-triggered journeys.
      description: A branch condition. The `kind` field selects the shape.

````