Skip to main content
Run several tasks in sequence without orchestrating each step yourself. You define a workflow once, and Deck runs the chain from there. A workflow is an ordered array of steps. Each step references a task by 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.
The agent is determined by the task, so a step only needs a task_id.

Definition fields

The response is the full workflow object (wflo_ prefix).
Deck validates the definition on create: every 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

The run body carries per-step overrides. Each entry names a step from the definition and supplies the run-time values for it. Steps you don’t list run exactly as defined. 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.
Send an 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 inside input with {{ steps.<name>.output.<field> }}. Deck resolves the reference against the upstream step’s actual output at run time.
When a value is a single reference and nothing else, it keeps its JSON type, so an object stays an object and a number stays a number. 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’s output_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 to null, 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 an input 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.
Nested objects replace entirely. There’s no deep merge, so include every nested key you want to keep.

Step types

Every step has a type and a name. Names must be lowercase identifiers matching [a-z_][a-z0-9_]*, since they appear inside references and conditions.

Task steps

A task 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 an if 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.
There’s no branch step type. Express “A or B” as two steps with inverted conditions:
A skipped step produces no task run and its output is null. Downstream references to it resolve to null.

Loops

A for_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

Returns the current state of the run with every step’s status, output, and task runs.

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:
This mirrors the include parameter on task runs.

Listing workflow runs

Runs from every workflow are listed together, newest first. Scope to a single workflow with 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 a workflow_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:
Add 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 enters paused 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:
The request body and semantics match task run interactions. Once the task run accepts the input, the step resumes and the workflow continues. There’s no workflow-specific interaction endpoint. Inside a loop, each item is its own task run. When the current item needs input, the workflow pauses and later items wait until it resolves.

Canceling a workflow run

The run transitions to 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 a timeout 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 ends failed, 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

With failure_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 ends failed, 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.
The workflow-level entry is a summary. The task run’s own 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’s status 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’s steps 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.