v1

latestOpenAPI 3.1.0MIT2026-07-243952451019.0 KB
Workflows::Step

Create 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).

post/workflows/{workflow_id}/steps

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), duration, interval, time_zone, use_contact_time_zone, use_execution_window, day_of_week, date_of_month, start_time, end_time, execution_window_days
  • notify_step: message, notify_type (array), user_ids (array of user ids — must be team members; discover via GET /teams/{team_id}/membershipsuser_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.

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. Prefer a verified sending address for from_address_id/reply_to_address_id.

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"
}