Workflows::Step

Add a new step to a workflow. step_type_settings must contain exactly one key naming the step type.

Placement: omit parent_step_id to append to the trunk sequence. Set parent_step_id + branch to place inside a split branch. Use position (0-based) or after_step_id to control order within a container.

For split_test_step: provide variants with exactly 2 entries whose weight values sum to 100. For conditional_split_step: optionally provide condition.filter_id (integer id or obfuscated RefineFilter public_id).

A send_email_step requires a complete business mailing address in the workspace's email settings. Configure it first with PUT /workspaces/{workspace_id}/emails/settings; an incomplete address returns 422.

post/workflows/{workflow_id}/steps

Request

Path parameters

workflow_idstring required

Integer id or obfuscated public_id of the workflow.

Request body

Example request

{
  "workflows_step": {
    "name": "Send Welcome Email",
    "step_type_settings": {
      "send_email_step": {
        "subject": "Welcome!",
        "template_id": "TmPl01",
        "from_address_id": "FrAd01"
      }
    }
  }
}

Response

Created

idinteger

Step ID

public_idstring nullable

Obfuscated step ID

workflow_idinteger

Integer ID of the parent workflow

step_typestring

The step class key (same as the single key in step_type_settings).

namestring

Human-readable step name.

sort_orderinteger

Zero-based position within this step's own container (the trunk, or a split's branch/convergence sequence). It is therefore container-local, not globally unique — it restarts at 0 for steps after a split. In /structure, follow the steps array order for execution flow, not sort_order.

state'active' | 'notSetup' | 'notDefined'

Setup state of the step.

parent_step_idinteger nullable

Raw integer id of the parent split step when this step lives inside a branch. Null for trunk steps.

branchstring nullable

For conditional splits — matched or unmatched. For split tests — the 0-based variant index as a string. Null for trunk steps.

convergence_step_idinteger nullable

Id of the step a split's branches reconverge at — the first step that runs after the split, which every branch continues to. Example: a conditional_split_step (id 203) whose matched/unmatched branches both lead to the same next step (id 206) → convergence_step_id 206. It is null in two cases: a split that ends the flow (nothing follows it), and any non-split step (the field only applies to conditional_split_step / split_test_step). Use step_type to tell them apart — a null on a non-split step just means the field does not apply. When non-null, the referenced step always appears in this response.

step_type_settingsobject

A single-key map: the one key is the step type (identical to step_type) and its value carries that type's settings. This is the canonical reference for step settings — the Create Step and Update Step request bodies point here.

Available types —

Action steps (non-branching): send_email_step, send_asset_step, send_chat_message_step, contact_tag_step, contact_custom_attribute_step, delay_step, notify_step, deliver_webhook_step, enroll_step, unenroll_contact_step, grant_community_access_step, revoke_community_access_step, create_opportunity_step, move_step

Split steps (branching): conditional_split_step, split_test_step

Per-type fields

  • send_email_step: subject, preheadline, template_id, from_address_id, reply_to_address_id, plus write-only html_body/text_body (see "send_email_step inline body" below)
  • send_asset_step: asset_id
  • send_chat_message_step: inbox_key, message, subject, start_new_conversation
  • contact_tag_step: action ("Add Tags" or "Remove Tags"), contacts_tag_ids (array of raw integer tag ids)
  • contact_custom_attribute_step: action, value, field_id (a Forms::Field id — discoverable via the Forms API)
  • delay_step: delay_type (relative/day_of_week/day_of_month/specific_date), duration, interval, time_zone, use_contact_time_zone, use_execution_window, day_of_week, date_of_month, specific_date, past_date_action, start_time, end_time, execution_window_days. For specific_date, send specific_date (YYYY-MM-DD, no more than a year away) and start_time (HH:MM, 24-hour), read in time_zone (or each contact's own zone with use_contact_time_zone: true). A moment that has already passed is accepted. past_date_action (continue, the default, or end) decides what happens to a contact who reaches the step after that moment: continue to the next step right away, or end their run. time_zone is a human-readable time zone name — the label shown in the builder's dropdown, e.g. Eastern Time (US & Canada), London, Tokyo. A TZ database id is also accepted and is stored as the name that keeps the same clock (America/New_York and America/Toronto → Eastern Time (US & Canada), Europe/London → London), so the response echoes the name, not the id you sent. Any other value is rejected 422. The full list of names and how they map to TZ ids is in our Time Zones guide. Set use_contact_time_zone: true to use each contact's own zone instead.
  • notify_step: message, notify_type (array), user_ids (array of user ids — must be team members; discover via GET /teams/{team_id}/memberships → user_id. A non-member id is rejected 422.)
  • deliver_webhook_step: webhooks_outgoing_endpoint_id
  • enroll_step: course_ids (array), section_ids (array). Enrollment happens per section_ids — provide the section ids to enroll in (discover via GET /api/v2/courses/{course_id}/sections). course_ids alone is accepted and the step reads active, but enrolls nothing at run time.
  • unenroll_contact_step: course_ids (array), section_ids (array). Unenroll honors both course_ids and section_ids.
  • grant_community_access_step: space_ids (array)
  • revoke_community_access_step: space_ids (array)
  • create_opportunity_step: name, value, assignment_strategy, move_existing, sales_pipelines_stage_id, sales_pipeline_id, membership_id, membership_ids (array)
  • move_step: workflow_id
  • conditional_split_step: condition object with filter_id (raw integer RefineFilter id on read; integer id or public_id on write) and is_setup boolean
  • split_test_step: variants array, each with weight (0..100, two entries summing to 100)

send_email_step — template & inline body

A send_email_step always owns its email. On create, a provided template_id is copied into a fresh step-owned template (it is never a live reference to the original), and an inline html_body/text_body instead mints a new step-owned template. This deliberately mirrors the builder UI, which always makes its own copy so edits to a step's email never touch the source. template_id takes precedence when both a template_id and an inline body are supplied. The create response's step_type_settings.send_email_step.template_id is the step-owned copy's id, not the source template's id.

Because the step owns its template, template_id is create-only and cannot be changed on update — a PATCH that includes template_id returns 422. To change an existing step's email, edit it in place by sending html_body/text_body (this patches the step's own template — no new template is created); to use a different template, create a new step. html_body/text_body are write-only and are never returned in step_type_settings. For from_address_id/reply_to_address_id, pick an address whose top-level usable_as_sender is true, not one whose sending domain is merely verified. See Inline body on a workflow send-email step in the Emails Skill.

FK id serialization

Response FK values (*_id, *_ids) are serialized as raw integers. On write, those same FK fields accept either integer ids or obfuscated public_ids in the same workspace/team scope.

created_atstring date-time
updated_atstring date-time

Example response

{
  "id": 201,
  "public_id": "StEm01",
  "workflow_id": 101,
  "step_type": "send_email_step",
  "name": "Send Email",
  "sort_order": 0,
  "state": "active",
  "parent_step_id": null,
  "branch": null,
  "convergence_step_id": null,
  "step_type_settings": {
    "send_email_step": {
      "subject": "Welcome!",
      "preheadline": "We're glad you're here.",
      "template_id": 3001,
      "from_address_id": 4001,
      "reply_to_address_id": null
    }
  },
  "created_at": "2025-06-01T00:00:00.000Z",
  "updated_at": "2025-06-01T00:00:00.000Z"
}

Changes

    • info

      added the optional property to the response with the status

    • info

      added the optional property to the response with the status

    • info

      added the optional property to the response with the status

    • info

      added the optional property to the response with the status