Create a drip workflow

Search Documentation

Search across all developer documentation

workflows

Create a drip workflow

POST /v1/workflows
Auth required api.workflows.manage

Creates a managed drip-workflow definition from a .flow source. key is the stable org-unique handle other calls reference the workflow by and is immutable afterwards; a key already in use returns 409 conflict.

A source that does not validate is still a 201. The row is stored with status: invalid and invalid_reason, and the response carries the stored source with every positioned finding in diagnostics — so a definition is never silently dropped, and a caller can see both that its work landed and why it cannot run yet. 422 is reserved for a request that could not be stored at all (no key, no name, no source, a key that is not a slug).

A created workflow is a draft: it enrols nobody and sends nothing until POST /v1/workflows/{id}/activate. Do not write SendFlow from memory — the grammar is published at https://www.sendlang.com/docs/reference/grammar, and a guessed definition usually nearly parses.

Request body

Content type: application/json

key string required

The stable org-unique handle other calls reference this workflow by — a lowercase slug (letters, digits, hyphen, underscore) starting with a letter, up to 63 characters. Immutable afterwards.

name string required

Human-readable name.

description string optional

What this journey is for

source string required

The definition in SendFlow (.flow). Full text, not a patch. The grammar is published at https://www.sendlang.com/docs/reference/grammar — a definition written from memory usually nearly parses.

Responses

Errors follow the RFC 7807 problem format — see the error reference.

201 The created workflow, in draft or invalid application/json
id string<uuid> required
name string required
key string required

The workflow's stable slug within the org.

description string optional
status string required

Lifecycle status: draft, active, paused, archived, or invalid.

invalid_reason string optional

Present only when status is invalid.

require_send_approval boolean optional

When true, sends are held for manual review instead of dispatching automatically.

send_mode string enum required

shadow means the workflow is ACTIVE and enrolling real contacts but mailing nobody: every send, attribute write and list add is recorded on the run timeline instead of performed. A shadow workflow that looks broken is working as configured — POST /v1/workflows/{id}/go-live arms it for real.

One of: live, shadow

shadow_started_at string<date-time> optional

When the current shadow began. Absent while live.

shadow_cohort_list_id string<uuid> optional

The list bounding who may enrol while shadowing. Absent while live or when the shadow is unbounded.

shadow_cohort object optional

That list resolved: which list, and how many people the rehearsal can reach. Absent while live or when the shadow is unbounded. The bare shadow_cohort_list_id stays beside it because it is the handle activate takes back.

current_version integer required

The live definition version.

run_counts object required

By-status run tally, e.g. active/completed → counts. Always present.

created_at string<date-time> required
updated_at string<date-time> required
source string optional

The .flow program. Present only for a caller holding api.workflows.manage — the read scope withholds it — and on every response to a write, which returns what was stored.

content_hash string optional

The optimistic-concurrency token. Pass it back as expected_content_hash on the next update and a concurrent edit is refused rather than overwritten. Present alongside source.

warnings array<string> optional

The analyzer's non-fatal findings from the write that produced this row. A definition can be perfectly valid and still not do what somebody asked for, so these survive a successful save — read them.

diagnostics array<object> optional

Every positioned finding, errors as well as warnings, from validating the source this call wrote. This is what makes a 201 with status: invalid actionable: the row was stored, and this says why it cannot run yet. Absent when none were gathered.

Each entry in diagnostics:

line integer | null optional
column integer | null optional
severity string required

error blocks the definition from running; warning does not.

message string required
requires array<object> optional

Everything this definition still depends on, with each dependency's state. Present on the write responses and on the detail read (the polling surface); absent on the collection read. An empty array means nothing is outstanding — see V1Requirement.

Each entry in requires:

kind string enum required

The namespace key is looked up in. Branch on this; never parse detail.

One of: template, segment, list, topic, identity, channel, attribute, activity_property, asset

key string required

The identifier as the definition names it — a template name, a segment id or key, a from-address. Empty for a requirement about the ABSENCE of a reference: a send with no topic at all names no topic.

status string enum required

ok — resolves and is usable now. missing — nothing in this organization answers to that key. Correct the reference, or create the thing. not_ready — it exists, in a state that cannot be used yet. Usually a wait, and retrying the identical request is the move. Treating this as missing sends you hunting for a typo that is not there.

One of: ok, missing, not_ready

detail string required

The state, in the words that say which state — deploy_state=pending, member_count=null (evaluation queued). For display and logs; do not parse it.

fix string required

The call that clears this requirement, as an HTTP route where there is one and as a plain instruction where there is not (a template deploy is waited for, not called). Empty on an ok entry.

401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
409 The mutation is rejected by a state rule rather than a bad request. The code is conflict. application/problem+json
422 A query parameter, path value or body field failed validation. The body is a validation_failed Problem. When the refusal is about a reference — a segment key, template slug, topic or attribute name the organization does not have — it additionally carries validation_code, field, line/column, missing, candidates and next_step, so a client can correct the call without a second round of guessing. See ValidationProblem. application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json