Workflow DSL

Every Hyphen workflow is a JSON document with a defined structure. This page is the complete specification.

Workflow Structure

json
{
  "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:

json
{
  "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:

json
{ "type": "fetch_customer", "properties": { "customer_id": "@input.id" } }

OAuth business tools — pre-registered actions available after OAuth setup:

json
{ "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:

json
{
  "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).

json
{
  "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:

json
{
  "schedule": {
    "every": "1d",
    "at": "02:00",
    "timezone": "America/New_York"
  }
}

→ Full reference: Scheduling


Complete Example

A production workflow combining all DSL features:

json
{
  "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"
    }
  }
}
POST/workflows

Create the workflow definition.

:::api POST /workflows/:id/execute Execute with an input payload. The @input.* paths resolve to the data you send. :::