Workflow DSL
Every Hyphen workflow is a JSON document with a defined structure. This page is the complete specification.
Workflow Structure
{
"name": "workflow_name",
"definition": {
"condition": { },
"actions": [ ],
"schedule": { }
}
}
Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique identifier within the organization |
definition |
object | Yes | The workflow specification |
definition.condition |
object | No | Top-level gate: if false, workflow ends with condition_not_met |
definition.actions |
array | Yes | Ordered array of steps to execute |
definition.schedule |
object | No | Scheduling configuration (see Scheduling) |
Step Structure
Each element in the actions array is a step:
{
"type": "step_type",
"properties": { },
"filter": { },
"onFalse": { }
}
Step Fields
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Primitive name (matcher, loop, PbotApproval, PbotForm, custom-table) or registered action name |
properties |
object | Yes | Step-specific configuration: varies by type |
filter |
object | No | Condition evaluated before execution. If false, step is skipped (or onFalse runs) |
filter.condition |
object | No | The condition expression (see Conditional Logic) |
onFalse |
object | No | Alternative step to execute when filter evaluates to false. Same structure as a step |
Step Types
Primitives (built in, no registration required):
| Type | Reference |
|---|---|
matcher |
Matcher |
loop |
Loop, in foreach or react mode |
PbotApproval |
Approval |
PbotForm |
Form |
custom-table |
Custom Table |
Registered actions: use the action_name you registered as the type. Step-specific options stay under properties:
{
"type": "summarize_document",
"properties": { "outputKey": "summary" }
}
OAuth business tools: pre-registered actions available after OAuth setup:
{ "type": "gmail_send", "properties": { "__oauth_account__": "ops@company.com", "to": "@input.email", "subject": "Update" } }
→ See Actions for registration details
Context Resolution: @path
The @ prefix references values from the execution context. Context accumulates as steps execute, and each step's output is available to later steps.
| Path | Description | Example |
|---|---|---|
@input.field |
Workflow input payload | @input.customer_id |
@input.field.nested |
Nested input fields | @input.invoice.amount |
@outputKey.field |
Step output by outputKey |
@matched.0.invoice_id |
@item |
Current item in a foreach loop | @item.email |
@item.field |
Field on current loop item | @item.amount |
@__run_id |
Current run ID | |
@__step |
Current step index | |
@now |
Current timestamp | |
@__approved |
Boolean from PbotApproval | |
doc:doc_xxx |
Uploaded document content (Document Storage) | doc:doc_a1b2c3d4e5f6 |
doc:doc_xxx@N |
Specific document version | doc:doc_a1b2c3d4e5f6@2 |
Array indexing is supported: @matched.0.invoice_id references the first matched record's invoice_id.
→ Full deep dive: Context Resolution
Template Syntax: {{ }}
Double braces perform string interpolation inside property values. Use for constructing dynamic strings:
{
"subject": "Invoice {{input.invoice_id}} requires review",
"body": "Dear {{input.vendor_name}},\n\nYour invoice for ${{input.amount}} is pending."
}
@path returns the actual value, such as an object, array, or number. {{template}} performs string interpolation, converting the value to a string and embedding it in surrounding text. Use @path for data references in properties. Use {{ }} when building human-readable strings.
Condition Expressions
Conditions are used in definition.condition (top-level gate) and filter.condition (step-level branching).
{
"condition": {
"and": [
{ "greaterThan": ["@input.amount", 1000] },
{ "equal": ["@input.status", "pending"] }
]
}
}
→ Complete operator reference: Conditional Logic
Schedule Configuration
Optional: makes the workflow run on a recurring basis:
{
"schedule": {
"every": "1d",
"at": "02:00",
"timezone": "America/New_York"
}
}
→ Full reference: Scheduling
Complete Example
A production workflow combining all DSL features:
{
"name": "daily_invoice_reconciliation",
"definition": {
"condition": {
"and": [
{ "equal": ["@input.type", "invoice"] },
{ "greaterThan": ["@input.items.length", 0] }
]
},
"actions": [
{
"type": "matcher",
"properties": {
"left": "@input.items",
"right": "@input.purchase_orders",
"matchOn": ["po_number", "amount"],
"tolerance": 50,
"outputMatched": "matched",
"outputUnmatchedLeft": "unmatched_items"
}
},
{
"type": "loop",
"filter": {
"condition": { "greaterThan": ["@unmatched_items.length", 0] }
},
"properties": {
"mode": "foreach",
"items_path": "@unmatched_items",
"item_variable_name": "item",
"actions_to_execute": [
{
"type": "custom-table",
"properties": {
"table": "purchase_order_cases",
"operation": "upsert",
"key_fields": ["run_id", "invoice_id"],
"keys": ["run_id", "invoice_id", "po_number", "amount", "status"],
"values": ["@__run_id", "@item.invoice_id", "@item.po_number", "@item.amount", "pending_review"]
}
}
],
"failure_strategy": "fail_fast",
"collect_results": false,
"result_key": "case_writes"
}
},
{
"type": "loop",
"filter": {
"condition": { "greaterThan": ["@unmatched_items.length", 0] }
},
"properties": {
"mode": "react",
"objective": "Read the current run's unmatched purchase-order cases with the supplied tool. Explain each exception and recommend the next action.",
"tools": [
{ "type": "action", "name": "read_unmatched_purchase_order_cases" },
{ "type": "action", "name": "slack_post" }
],
"max_iterations": 15,
"on_stuck": { "iterations": 3, "action": "escalate" },
"result_key": "review_result"
}
},
{
"type": "custom-table",
"properties": {
"table": "processing_log",
"operation": "write",
"keys": ["run_id"],
"values": ["@__run_id"],
"fields": {
"matched_count": "@matched.length",
"unmatched_count": "@unmatched_items.length",
"review_result": "@review_result",
"processed_at": "@now"
}
}
}
],
"schedule": {
"every": "1d",
"at": "02:00",
"timezone": "America/New_York"
}
}
}
This example assumes purchase_order_cases and processing_log already exist. The read_unmatched_purchase_order_cases action is a custom-table read bound to purchase_order_cases with where: { "run_id": "@__run_id" }.
/workflowsCreate the workflow definition.
:::api POST /workflows/:id/execute
Execute with an input payload. The @input.* paths resolve to the data you send.
:::