task_id, and every run of the workflow turns each step into a full task run. Steps execute one at a time, top to bottom. Later steps read earlier outputs with {{ }} references, and any step can be skipped with an if condition.
Example
A reservation manager agent checks the current price of a hotel booking. If the price dropped, a second task rebooks at the lower rate.task_id.
Definition fields
The response is the full workflow object (
wflo_ prefix).
task_id must exist in your organization, step names must be unique, and every {{ }} reference and if condition must point at a step that appears earlier in the array.
Tasks that declare tokenized input fields can’t be used in a workflow yet. Creating a workflow with one is rejected.
Running a workflow
session_id is not accepted on a step. All steps in a run execute in one session, so an agent that logged in during one step stays logged in for the next. The run’s session_id field reports which session that is.
Creating a run reserves that one session for the whole workflow, and it counts toward your organization’s session limit like any other. If you’re at the limit, the request fails with session_limit_exceeded rather than queuing a run that can’t start.
The response is a workflow run object (wrun_ prefix) with status queued. Execution is asynchronous: poll GET /v2/workflow-runs/{id} or subscribe to events for progress.
Idempotency-Key header to make run creation safe to retry. See Idempotency.
Reading a run
A finished run answers three questions.
This run comes from a version of the workflow with two more steps:
record_expense updates the booking’s cost in your expense platform, and save_confirmation downloads the confirmation. The price held, so rebook was skipped. record_expense then failed on bad expense platform credentials, which stopped the run before save_confirmation started.
skipped means the step’s if condition was false. queued on a finished run means the step was never reached, because an earlier step failed or the run was canceled. Both have no task runs and null output, so read the status to tell them apart.
Wiring outputs into inputs
Reference an earlier step’s output from insideinput with {{ steps.<name>.output.<field> }}. Deck resolves the reference against the upstream step’s actual output at run time.
input itself must be an object, so to forward an entire output, nest the reference under a key: "input": { "booking": "{{ steps.check_price.output }}" }. A reference embedded in a longer string is substituted as text.
Finding the paths
The upstream task’soutput_schema lists every field you can reference. Fetch it with GET /v2/tasks/{task_id}, or author the workflow in the Console, which autocompletes against the schema.
Missing fields
If the source doesn’t populate a field on the upstream task, the reference resolves tonull, and the downstream task run receives null for that field. Step input isn’t checked against the task’s input_schema before the run starts, so a required field arriving as null doesn’t fail the run up front. Write downstream tasks to tolerate a missing value, or gate the step with an if on the field.
Run-time overrides
A step can have aninput in its definition and still accept an input override on the run body. Deck shallow-merges the two: top-level keys from the override win, and keys absent from the override fall back to the definition, with references resolved.
Step types
Every step has atype and a name. Names must be lowercase identifiers matching [a-z_][a-z0-9_]*, since they appear inside references and conditions.
Task steps
Atask step wraps one task run.
Pin
credential_id in the definition when the step always uses the same credential. Leave it out when the credential varies per run and pass it on the run body instead.
Conditions
Add anif condition to any step to control whether it runs. If the expression is false, the step is marked skipped and the workflow continues to the next step.
output is null. Downstream references to it resolve to null.
Loops
Afor_each step runs an inner task step once per item in an array from an earlier step’s output. Iterations run sequentially in the same session, so the agent stays logged in between them.
Inside the inner step,
item refers to the current element. The inner step can carry its own if to skip individual items; that condition may read item, while the outer loop’s if cannot.
steps.process_refunds.output resolves to the ordered array of inner outputs, one per input item. An empty array completes the step immediately with an empty output. If items resolves to anything other than an array, the step fails with invalid_field_value and the workflow stops. Loops can’t nest: the inner step must be a task, and type may be omitted on the inner step.
Expression grammar
Conditions and references use a small expression language. It is not JavaScript, and there are no function calls or arithmetic.
A condition can be a field reference on its own, with no comparison. It passes when the field is
true, a non-zero number, a non-empty string or array, or any object. It fails when the field is false, zero, an empty string or array, null, or missing. Comparing values of different types is false rather than an error (!= is true), so a step never fails because a source omitted an optional field. Comparisons don’t chain: write a == b && b == c, not a == b == c. Deck rejects invalid syntax when you create or update the workflow.
Fetching a workflow run
Run fields
result aggregates every task run the workflow produced. It’s success only when all of them succeeded, failure if any ended in failure, and unknown when there were no failures but at least one task run finished unknown. Skipped steps produce no task runs and don’t count, so a run whose every step was skipped is success.
Step state
Each
task_runs entry is a summary of the underlying task run. For full details, including output, storage, and screenshots, fetch the task run itself with GET /v2/task-runs/{task_run_id}.
Because steps run one after another, the
created_at and runtime_ms values across entries give you the full timeline of the run from a single fetch. Gaps between entries are time spent waiting on an interaction or moving between steps.
Optional data
The default response returns the fields above. Use?include= to add optional data to each task_runs entry:
Comma-separated values are supported:
include parameter on task runs.
Listing workflow runs
workflow_id, the same way task runs relate to tasks.
Filters combine with AND, per the list endpoint conventions. List entries carry every run field and each step’s
status and output, but omit task_runs. Fetch a run individually for those.
Inspecting a workflow run’s task runs
Every task run a workflow produces carries aworkflow_run_id and the owning step name. Both are always present and null for standalone runs. To list a workflow run’s task runs directly instead of reading them out of each step:
step=<name> to narrow to one step, and combine with the existing task run filters. Use workflow_id instead to span every run of a workflow.
Interactions
If a step runs into MFA or other verification on the source, the workflow enterspaused and the step’s task run enters interaction_required. Find the task run in the step’s task_runs array and submit the response to its interaction endpoint:
Canceling a workflow run
canceling while the current step stops, then to canceled once it finishes. The in-flight task run is canceled at the same time; no separate call to the task run cancel endpoint is needed. A run can be canceled from queued, running, or paused. Canceling a run that already finished is rejected with invalid_field_value.
Timeouts
Each step is bounded by its task’s run timeout. A run that stays in flight for 24 hours fails with atimeout error on the step it was on, whatever the steps’ own limits.
Failure handling
A workflow run stops on the first step failure. The run endsfailed, steps that already completed keep their output, later steps stay queued, and the error is recorded on the run.
A workflow can’t continue past a failed step or retry it. Deck retries transient infrastructure failures inside the task run before it reports a failure.
To keep going when some items in a list fail, use the loop’s failure_behavior.
Continuing past failed items
Withfailure_behavior: "continue" on a loop, a failed item doesn’t stop the remaining items or the steps after the loop. The step finishes completed with null in the failed item’s output slot, and the run carries no top-level errors. The failed item’s own errors in task_runs is the record of what went wrong, and the run’s result is still failure because a task run failed.
Errors
When a run endsfailed, its top-level errors array holds standard Deck error objects with one extra field: step, naming the step that caused the failure. Workflows don’t introduce new error types, and field is present only when the error concerns a specific field, as on task runs. The same error, without step, appears on the failing step’s task_runs entry.
errors at GET /v2/task-runs/{id} is the authoritative record, along with its storage and screenshots.
Statuses
Workflow run statuses
Any state where the run is waiting on someone shows as
paused. Check the step for the specific reason. status is where the run is in its lifecycle and result is how it turned out, the same way task runs separate the two.
Step statuses
A step’sstatus is its task run’s status: queued, running, interaction_required, review_required, completed, failed, canceling, or canceled. See task run statuses. One value is workflow-only:
A loop’s
status summarizes its items: running while any item is in flight, failed when an item fails under failure_behavior: stop, and completed once every item finishes.
Managing workflows
Edits apply to future runs only. Runs already in flight continue against the definition they started with. Deleting a workflow doesn’t affect in-flight runs, but the definition can no longer be run.
POST /v2/workflows, PATCH, DELETE, and POST /v2/workflows/{id}/run all accept an Idempotency-Key header.
Running on a schedule
A trigger can target a workflow instead of a task. Each fire creates one workflow run per credential in scope, and that credential is used by every step the trigger doesn’t pin. Steps pinned in the trigger’ssteps config keep their own credential_id or source_id, which is how a scheduled workflow can log in with the fanned-out credential and then act on a second source. The trigger’s steps config has the same shape as the run body above, but it’s the same for every credential and every fire, so the first step’s input can’t vary per user. Later steps can, by referencing earlier outputs.
A trigger-created run carries the trigger_id, and GET /v2/workflow-runs?trigger_id=trg_... lists one trigger’s runs. The trigger’s skip_if reads the run’s result, so skip conditions behave the same whether the trigger targets a task or a workflow. Otherwise a triggered run is identical to one you create directly.
Events
Workflows emit into the existing events system on three channels. Lifecycle events track the definition:
Workflow run events fire once per status transition, so a subscriber sees every state the run passed through:
The payload carries the run’s
workflow_run_id, workflow_id, status, session_id, trigger_id, and result. result is only set on the terminal events. See the events reference for the full payload.
Task run events fire as normal. Every task step emits the standard task_run.* events, and events aren’t suppressed inside a workflow. Their payloads add workflow_run_id and the owning step name, plus trigger_id when the run was trigger-created, so existing handlers can attribute workflow-driven runs to the right step. A loop emits one stream per item.