workflows_create
Create a workflow. trigger_type is one of "entity_event" (fires on contact.created etc.), "scheduled" (cron/one-time), "inbound_webhook" (when a Groundbase inbound webhook receives a POST), or "manual" (only fired by workflows_run_now). For entity_event, set trigger_event to e.g. "contact.created", "deal.stage_changed", "task.completed". For scheduled, pass trigger_config like { recurrence: "one_time"|"daily"|"weekly"|"hourly", run_at?, time_of_day?, days_of_week?, minute_of_hour?, timezone? }. For inbound_webhook, trigger_event is the inbound webhook id from inbound_webhooks_list. Steps run sequentially. Step shapes: Each step is { type, config: { ...props } } — the per-type props below go INSIDE config, e.g. { "type": "create_note", "config": { "body": "hi" } } (flat props on the step are also accepted). The server validates on save and answers 400 INVALID_WORKFLOW listing every problem — unknown step type or operator, unknown trigger event, a schedule that would never fire, a bad timezone, a missing required prop, a webhook url on a private host — so read the message and fix the definition rather than retrying. Step types: send_sms { to?, body }; send_email { to?, subject, body_html?, body_text?, contact_id? } — blank to means the run's contact (on a deal, task or note, its contact); add_to_campaign { campaign_id } — puts the run's contact into an ONGOING campaign (enrolment_mode=ongoing, status sending) at its first step, under the campaign's rules; fails on a one-time or not-sending campaign; apply_tag { tag_name, entity_id?, entity_type? }; remove_tag { tag_name, entity_id?, entity_type? }; create_note { body, entity_type?, entity_id? }; create_task { title, description?, due_at?, due_in_days?, kind?, location?, contact_id? }; fire_webhook { url, method?, headers?, body? }; update_field { entity_type?, entity_id?, field, value } — restricted to a safe allow-list of native columns (first_name, last_name, email, phone, position, city, source, notes, primary_phone, company_id); set_custom_field { entity_type?, entity_id?, field_key, value } — writes to a custom field defined in Settings → Custom fields. field_key accepts "loan_amount" or "cf.loan_amount" (the cf. prefix is optional); delay { delay_seconds, stop_if? } — stop_if { reply?, meeting?, optout? } ends the run instead of continuing when, by the time the wait is over, the contact has replied (text or email), booked a meeting, or opted out since the run started. Only on a top-level wait longer than 60 seconds; branch { condition: { all|any: [{ field, op, value }] }, then: [Step], else: [Step] }. Templates supported in string fields: {{first_name}}, {{trigger.event}}, {{steps..output.}}. Step ids auto-generated as s_<8hex> if omitted; pass an explicit "id" to make {{steps.X.output.Y}} references stable across edits.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Display name (required). | |
| steps | Yes | Ordered list of steps. Each step is { type, config: { ...props } } — the per-type props below go INSIDE `config`, e.g. { "type": "create_note", "config": { "body": "hi" } } (flat props on the step are also accepted). The server validates on save and answers 400 INVALID_WORKFLOW listing every problem — unknown step type or operator, unknown trigger event, a schedule that would never fire, a bad timezone, a missing required prop, a webhook url on a private host — so read the message and fix the definition rather than retrying. Step types: `send_sms` { to?, body }; `send_email` { to?, subject, body_html?, body_text?, contact_id? } — blank to means the run's contact (on a deal, task or note, its contact); `add_to_campaign` { campaign_id } — puts the run's contact into an ONGOING campaign (enrolment_mode=ongoing, status sending) at its first step, under the campaign's rules; fails on a one-time or not-sending campaign; `apply_tag` { tag_name, entity_id?, entity_type? }; `remove_tag` { tag_name, entity_id?, entity_type? }; `create_note` { body, entity_type?, entity_id? }; `create_task` { title, description?, due_at?, due_in_days?, kind?, location?, contact_id? }; `fire_webhook` { url, method?, headers?, body? }; `update_field` { entity_type?, entity_id?, field, value } — restricted to a safe allow-list of native columns (first_name, last_name, email, phone, position, city, source, notes, primary_phone, company_id); `set_custom_field` { entity_type?, entity_id?, field_key, value } — writes to a custom field defined in Settings → Custom fields. field_key accepts "loan_amount" or "cf.loan_amount" (the cf. prefix is optional); `delay` { delay_seconds, stop_if? } — stop_if { reply?, meeting?, optout? } ends the run instead of continuing when, by the time the wait is over, the contact has replied (text or email), booked a meeting, or opted out since the run started. Only on a top-level wait longer than 60 seconds; `branch` { condition: { all|any: [{ field, op, value }] }, then: [Step], else: [Step] }. Templates supported in string fields: {{first_name}}, {{trigger.event}}, {{steps.<id>.output.<key>}}. Step ids auto-generated as s_<8hex> if omitted; pass an explicit "id" to make {{steps.X.output.Y}} references stable across edits. | |
| isActive | No | Defaults to true (workflow starts active). | |
| condition | No | Optional workflow-level filter — if present, the workflow is skipped (status=skipped) when this evaluates false. Shape: { all|any: [{ field, op, value }] } — `all` requires every clause, `any` requires one. Field paths: entity.<column> (entity.email, entity.source, entity.tags), cf.<field_key> for a custom field, trigger.event / trigger.entity_type / trigger.entity_id, and trigger.payload.<key> for what the event carried (e.g. trigger.payload.tag_name on tag.applied) — NOT payload.<key>, which never resolves; the server refuses it on save. Text ops: equals, not_equals, contains, not_contains, starts_with, ends_with. Presence ops: is_set, is_not_empty, is_empty, includes_tag. Numeric/date ops: greater_than, greater_or_equal, less_than, less_or_equal (numeric when both sides parse as numbers, else lexicographic, which sorts ISO timestamps correctly), plus older_than_days and newer_than_days which take a day count and compare against a date field such as entity.last_contacted_at. | |
| description | No | ||
| triggerType | Yes | Required. | |
| triggerEvent | No | For entity_event: the event name (e.g. "contact.created"). For inbound_webhook: the inbound webhook id. | |
| triggerConfig | No | For scheduled triggers, the schedule shape. Otherwise null. |