Workflows run multi-step sandbox scenarios with a single command. The engine chains resource IDs and HAL links between steps automatically, timestamps emails so repeated runs never collide, and tolerates the sandbox quirks (like auto-verified banks) that would otherwise break a naive script.
There are two kinds of workflow:
- Built-in workflows — 9 curated scenarios compiled into the CLI. Run them
by slug:
dwolla workflows run send-money. - Custom workflows — JSON files you author. Run them by path:
dwolla workflows run ./my-workflow.json.
dwolla workflows list # catalog, grouped by tier
dwolla workflows list --slug send-money # flags for one workflow
dwolla workflows run send-money # run with defaults
dwolla workflows run send-money --dry-run # preview the step plan, no API callsWorkflows are sandbox-only and refuse to run against production credentials.
| Slug | Tier | Description |
|---|---|---|
send-money |
Payment | Create a customer with a bank, then transfer funds from the main account balance to the customer's bank. |
receive-money |
Payment | Create a customer with a verified bank, then pull funds from their bank into the main account's connected bank (default) or balance funding source. |
me-to-me |
Payment | Create one verified personal customer with two bank accounts, then transfer between them. |
facilitate-payment |
Payment | Marketplace pattern: a payer sends funds to a receiver; the main account collects a facilitator fee. |
onboard-receive-only |
Onboarding | Create a receive-only customer (name + email only). |
onboard-unverified |
Onboarding | Create an unverified customer; by default attaches and verifies a checking bank (--with-bank=false to skip). |
onboard-personal-verified |
Onboarding | Create a verified personal customer (full KYC) with a verified checking bank. |
onboard-business-verified |
Onboarding | Create a verified business customer with controller, add a beneficial owner, certify ownership, attach a verified bank. |
seed-sandbox |
Setup | Create one customer of each type with funding sources, plus one sample transfer. |
These matrices mirror what the engine validates in BuildSteps. If the CLI
rejects a value, check this table (or run dwolla workflows list --slug <slug>,
which is always authoritative).
| Flag | Default | Options |
|---|---|---|
--customer-type |
receive-only |
receive-only, unverified, personal-verified, business-verified |
--rail |
ach |
ach, ach-same-day, ach-next-day, instant |
--amount |
25.00 |
Any USD amount |
--source |
balance |
balance |
--first-name |
Jane |
Receiver first name |
--last-name |
Receiver |
Receiver last name |
| Flag | Default | Options |
|---|---|---|
--customer-type |
personal-verified |
unverified, personal-verified, business-verified |
--rail |
ach |
ach, ach-same-day, ach-next-day, instant |
--amount |
50.00 |
Any USD amount |
--destination |
bank |
bank, balance |
--first-name |
John |
Sender first name |
--last-name |
Sender |
Sender last name |
--destination bank(default) credits an active bank on the main account — the list is queried with?removed=false, and a receiving bank need not be verified. If the account has no active bank, one is created and used automatically.--destination balancesends the funds into the main account's balance.
| Flag | Default | Options |
|---|---|---|
--rail |
ach |
ach, ach-same-day, ach-next-day, instant |
--amount |
100.00 |
Any USD amount |
--first-name |
Pat |
Customer first name |
--last-name |
Saver |
Customer last name |
| Flag | Default | Options |
|---|---|---|
--sender-type |
personal-verified |
unverified, personal-verified, business-verified |
--receiver-type |
receive-only |
receive-only, unverified, personal-verified, business-verified |
--rail |
ach |
ach, ach-same-day, ach-next-day, instant |
--amount |
100.00 |
Any USD amount |
--fee |
2.50 |
Facilitator fee in USD |
--charge-to |
sender |
sender, receiver |
Note: a receiver of type
receive-onlycan be charged the facilitator fee —--charge-to receiveris valid with the default--receiver-type.
Built-in flags can be passed two equivalent ways:
# Named flags (autocompletes, validates)
dwolla workflows run send-money --customer-type business-verified --rail instant
# Generic --flag name=value (handy for scripting)
dwolla workflows run send-money --flag customer-type=business-verified --flag rail=instantPatch an individual step parameter that has no dedicated flag with --override:
dwolla workflows run send-money --override receiver:firstName=AliceA custom workflow is a JSON file. The top-level schema is:
Each entry in steps is one HTTP call:
| Field | Type | Description |
|---|---|---|
name |
string | Required. Unique key. Later steps reference this step's results as ${name:...}. |
description |
string | Optional. Shown in --dry-run output. |
method |
string | Required. GET, POST, PUT, DELETE, etc. |
path |
string | Required. API path (/customers) or a fully-resolved URL from a prior step (${bank:location}/micro-deposits). |
params |
object | Request body. Values may contain ${...} references. |
headers |
object | Extra request headers (e.g. { "Idempotency-Key": "${.uuid}" }). |
expect |
object | Assertions run after the step. { "status": 201 } requires an exact status; { "body": { "type": "personal" } } requires those keys/values in the response. |
extract |
object | { "aliasName": "dotted.path" } — pull values out of the response for readable reuse as ${name:aliasName}. |
allow_statuses |
array<int> | Non-2xx statuses that must not fail the workflow. Use for best-effort steps (e.g. micro-deposit verification returns 403/404 when a bank is already auto-verified in sandbox). |
skip_if_set |
string | A stepName:path reference (no ${}). When it resolves to a non-empty value, the step is skipped (no request sent). Combine with a coalescing reference in a later step to express find-or-create — look a resource up first, create it only when the lookup was empty. |
References use ${...} and are resolved recursively inside every string in
params, headers, path, env, and on_complete.summary.
| Form | Resolves to |
|---|---|
${stepName:id} |
Resource ID extracted from the Location header or _links.self.href |
${stepName:location} |
Full Location header URL of the created resource |
${stepName:extractName} |
A value named in that step's extract map (shadows JSONPath) |
${stepName:dotted.path} |
JSONPath into the step's response body |
${stepName:list[@key=val].field} |
Inline array filter, then field access |
${stepA:path|stepB:path} |
Coalesce: the first alternative that resolves to a non-empty value wins (a |-separated list of references). Used for find-or-create. |
${.timestamp} |
Unix seconds captured at workflow start (stable across all steps) |
${.uuid} |
Fresh random UUID, regenerated per step (ideal for idempotency keys) |
${.env:VAR} |
Environment variable VAR; empty string if unset |
${.env:VAR|default} |
Environment variable VAR, or default if unset |
If a reference can't be resolved, the engine prints a warn: line to stderr and
leaves the placeholder in place — so a typo shows up in the request rather than
silently vanishing.
envis a map of names to variable references. After the run, the resolved values are printed (and included in structured output), so you can capture IDs for follow-up commands.on_complete.summaryis a list of human-readable lines, each interpolated the same way as step params. Use it to print a friendly recap.
Ready-to-run sample workflows live in examples/:
| File | Demonstrates |
|---|---|
minimal-customer.json |
The smallest useful workflow — a single step. |
verified-customer-with-bank.json |
Step chaining, extract, env, and on_complete. |
two-customer-transfer.json |
A full transfer: two customers, allow_statuses for micro-deposits, and ${.env:VAR|default}. |
Run any of them:
dwolla workflows run ./examples/verified-customer-with-bank.json
dwolla workflows run ./examples/verified-customer-with-bank.json --dry-runEvery workflow command honors the global output and diagnostics flags:
# Structured output for scripting / agents
dwolla workflows list -o json --jq '.[].name'
dwolla workflows run send-money -o json
# Preview the resolved step plan without calling the API
dwolla workflows run send-money --dry-run
# Full request/response logging (auth headers masked)
dwolla workflows run send-money --debug 2>&1 | head -40See the command reference for the complete flag list and agent mode for structured-output details.
{ "_meta": { "template_version": 1, // required; only version 1 is supported "name": "my-workflow", // display name "description": "What it does.", // shown in dry-run and logs "tags": ["custom", "ach"] // optional, free-form }, "steps": [ /* one object per API call, executed in order */ ], "env": { /* optional: name -> variable reference, printed on completion */ }, "on_complete": { "summary": [ "human-readable lines with ${...} references" ] } }