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": { },
"outputKey": "result_name"
}
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 |
outputKey |
string | No | Custom key name for storing this step's output in context |
Step Types
Primitives (built-in, no registration required):
| Type | Reference |
|---|---|
matcher |
Matcher |
loop |
Loop — foreach or react mode |
PbotApproval |
Approval |
PbotForm |
Form |
custom-table |
Custom Table |
Registered actions — use the action_name you registered as the type:
{ "type": "fetch_customer", "properties": { "customer_id": "@input.id" } }
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 — each step's output is added for subsequent steps to reference.
| 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 (object, array, number). {{template}} performs string interpolation — it converts the value to a string and embeds 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": "react",
"objective": "Review {{unmatched_items.length}} unmatched items",
"tools": [
{ "type": "action", "name": "search_purchase_orders" },
{ "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": ["invoice_id"],
"values": ["@input.invoice_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"
}
}
}
/workflowsCreate the workflow definition.
:::api POST /workflows/:id/execute
Execute with an input payload. The @input.* paths resolve to the data you send.
:::