Workflows

Automation workflows with DAG-based execution, scheduling, and webhooks

Workflows enable powerful automation with a DAG (Directed Acyclic Graph) execution model. Unlike simple sequential workflows, Arky workflows can run nodes in parallel, branch conditionally, loop over data, and trigger from webhooks or schedules.

Key Concepts

Workflow Structure

A workflow consists of nodes and edges:

  • Nodes: Individual operations (trigger, HTTP request, condition, loop, wait)
  • Edges: Connections between nodes that define execution flow
┌─────────┐     ┌─────────┐     ┌─────────┐
│ Trigger │────▶│  HTTP   │────▶│  HTTP   │
└─────────┘     │ (fetch) │     │ (notify)│
                └─────────┘     └─────────┘

Node Types

TypeDescription
triggerEntry point - webhook or scheduled
httpMake HTTP requests to external APIs
ifConditional branching based on expressions
loopIterate over arrays
waitPause execution for a duration

Create Workflow

POST /v1/businesses/{businessId}/workflows
SDK: sdk.workflow.createWorkflow()

Create a new automation workflow.

const result = await sdk.workflow.createWorkflow({
key: 'order-notification',
status: 'ACTIVE',
nodes: {
  trigger: {
    type: 'trigger',
    event: 'order.created'
  },
  fetchOrder: {
    type: 'http',
    method: 'GET',
    url: 'https://api.yourapp.com/orders/${trigger.data.orderId}',
    headers: {
      'Authorization': 'Bearer ${env.API_KEY}'
    }
  },
  sendSlack: {
    type: 'http',
    method: 'POST',
    url: 'https://hooks.slack.com/services/xxx',
    body: {
      text: 'New order #${fetchOrder.response.orderNumber} - $${fetchOrder.response.total}'
    }
  }
},
edges: [
  { id: 'e1', source: 'trigger', sourceOutput: 'default', target: 'fetchOrder' },
  { id: 'e2', source: 'fetchOrder', sourceOutput: 'default', target: 'sendSlack' }
]
});

Parameters

Name Type Description
key required string Unique workflow identifier
status optional ACTIVE | DRAFT | ARCHIVED Workflow status (default: DRAFT)
nodes required Record<string, WorkflowNode> Map of node IDs to node definitions
edges required WorkflowEdge[] Connections between nodes
schedule optional string Cron expression for scheduled execution (e.g., '0 9 * * *' for 9am daily)

Get Workflow

GET /v1/businesses/{businessId}/workflows/{id}
SDK: sdk.workflow.getWorkflow()

Retrieve a workflow by ID.

const result = await sdk.workflow.getWorkflow({
  id: 'wf_abc123'
});

if (result.ok) {
  const workflow = result.val;
  console.log(workflow.key, workflow.status);
  console.log('Nodes:', Object.keys(workflow.nodes));
}

List Workflows

GET /v1/businesses/{businessId}/workflows
SDK: sdk.workflow.getWorkflows()

List workflows with filtering and pagination.

const result = await sdk.workflow.getWorkflows({
  statuses: ['ACTIVE'],
  query: 'notification',
  limit: 20,
  cursor: null
});

if (result.ok) {
  const { items, cursor } = result.val;
  items.forEach(workflow => {
    console.log(workflow.key, workflow.status);
  });
}

Parameters

Name Type Description
ids optional string[] Filter by specific workflow IDs
statuses optional string[] Filter by status (ACTIVE, DRAFT, ARCHIVED)
query optional string Search in workflow keys
limit optional number Items per page (max 100)
cursor optional string Pagination cursor
sortField optional string Sort field (createdAt, key)
sortDirection optional asc | desc Sort direction

Update Workflow

PUT /v1/businesses/{businessId}/workflows/{id}
SDK: sdk.workflow.updateWorkflow()

Update an existing workflow.

const result = await sdk.workflow.updateWorkflow({
  id: 'wf_abc123',
  key: 'order-notification-v2',
  status: 'ACTIVE',
  nodes: {
    // Updated node definitions
    trigger: { type: 'trigger' },
    notify: {
      type: 'http',
      method: 'POST',
      url: 'https://api.example.com/notify'
    }
  },
  edges: [
    { id: 'e1', source: 'trigger', sourceOutput: 'default', target: 'notify' }
  ]
});

Delete Workflow

DELETE /v1/businesses/{businessId}/workflows/{id}
SDK: sdk.workflow.deleteWorkflow()

Delete a workflow.

const result = await sdk.workflow.deleteWorkflow({
  id: 'wf_abc123'
});

Trigger Workflow

POST /v1/workflows/trigger/{secret}
SDK: sdk.workflow.triggerWorkflow()

Trigger a workflow execution via webhook. This endpoint does not require authentication - the secret in the URL validates the request.

// The secret comes from your workflow's webhook URL
const result = await sdk.workflow.triggerWorkflow({
secret: 'wh_secret_abc123xyz',
// Pass any data to the workflow
orderId: 'ord_123',
customerEmail: '[email protected]',
items: [
  { productId: 'prod_1', quantity: 2 }
]
});

Parameters

Name Type Description
secret required string Workflow webhook secret from the trigger URL
[key: string] optional any Any additional data to pass to the workflow
Tip

The trigger data is available in your workflow nodes as trigger.data. For example, if you pass orderId, access it as ${trigger.data.orderId}.

Node Types Reference

Trigger Node

The entry point for workflow execution. Every workflow must have exactly one trigger node.

{
  type: 'trigger',
  event: 'order.created' // Optional: listen for specific business events
}

HTTP Node

Make HTTP requests to external APIs. Supports template variables from previous nodes.

{
  type: 'http',
  method: 'POST', // GET, POST, PUT, PATCH, DELETE
  url: 'https://api.example.com/endpoint',
  headers: {
    'Authorization': 'Bearer ${env.API_KEY}',
    'Content-Type': 'application/json'
  },
  body: {
    userId: '${trigger.data.userId}',
    message: 'Hello from Arky!'
  },
  timeoutMs: 30000 // Optional timeout in milliseconds
}

Available Variables:

  • ${trigger.data.*} - Data passed to the trigger
  • ${env.*} - Environment variables configured in your business
  • ${previousNodeId.response.*} - Response from a previous HTTP node

If Node

Conditional branching based on JavaScript expressions.

{
  type: 'if',
  condition: 'trigger.data.amount > 100'
}

If nodes have two outputs:

  • true - Executed when condition is truthy
  • false - Executed when condition is falsy
edges: [
  { id: 'e1', source: 'checkAmount', sourceOutput: 'true', target: 'sendHighValueAlert' },
  { id: 'e2', source: 'checkAmount', sourceOutput: 'false', target: 'sendStandardNotification' }
]

Loop Node

Iterate over arrays. Each iteration runs the connected nodes.

{
  type: 'loop',
  array: 'trigger.data.items' // Path to array
}

Inside loop iterations, access the current item via ${loop.item} and index via ${loop.index}.

Wait Node

Pause execution for a specified duration.

{
  type: 'wait',
  duration: '5m' // Supports: 30s, 5m, 2h, 1d
}

Scheduled Workflows

Run workflows on a schedule using cron expressions.

const result = await sdk.workflow.createWorkflow({
  key: 'daily-report',
  status: 'ACTIVE',
  schedule: '0 9 * * *', // Every day at 9:00 AM UTC
  nodes: {
    trigger: { type: 'trigger' },
    generateReport: {
      type: 'http',
      method: 'POST',
      url: 'https://api.yourapp.com/reports/generate'
    },
    sendEmail: {
      type: 'http',
      method: 'POST',
      url: 'https://api.yourapp.com/email/send',
      body: {
        to: '[email protected]',
        subject: 'Daily Report',
        body: '${generateReport.response.reportUrl}'
      }
    }
  },
  edges: [
    { id: 'e1', source: 'trigger', sourceOutput: 'default', target: 'generateReport' },
    { id: 'e2', source: 'generateReport', sourceOutput: 'default', target: 'sendEmail' }
  ]
});

Common Cron Expressions:

ExpressionDescription
0 9 * * *Every day at 9:00 AM
0 */2 * * *Every 2 hours
0 9 * * 1Every Monday at 9:00 AM
0 0 1 * *First day of every month
*/15 * * * *Every 15 minutes

Complete Example: Order Processing

const workflow = await sdk.workflow.createWorkflow({
  key: 'process-new-order',
  status: 'ACTIVE',
  nodes: {
    // Entry point
    trigger: {
      type: 'trigger',
      event: 'order.created'
    },

    // Check order value
    checkValue: {
      type: 'if',
      condition: 'trigger.data.total > 10000' // Over $100
    },

    // High value: notify sales team
    notifySales: {
      type: 'http',
      method: 'POST',
      url: 'https://hooks.slack.com/services/sales-channel',
      body: {
        text: '🎉 High-value order! $${trigger.data.total / 100} from ${trigger.data.customerEmail}'
      }
    },

    // Always: update inventory
    updateInventory: {
      type: 'http',
      method: 'POST',
      url: 'https://api.yourapp.com/inventory/decrement',
      body: {
        items: '${trigger.data.items}'
      }
    },

    // Send confirmation email
    sendConfirmation: {
      type: 'http',
      method: 'POST',
      url: 'https://api.yourapp.com/email/order-confirmation',
      body: {
        orderId: '${trigger.data.orderId}',
        email: '${trigger.data.customerEmail}'
      }
    }
  },
  edges: [
    // Trigger -> Check Value
    { id: 'e1', source: 'trigger', sourceOutput: 'default', target: 'checkValue' },

    // High value -> Notify Sales
    { id: 'e2', source: 'checkValue', sourceOutput: 'true', target: 'notifySales' },

    // Both paths -> Update Inventory (runs in parallel with notification)
    { id: 'e3', source: 'trigger', sourceOutput: 'default', target: 'updateInventory' },

    // After inventory -> Send Confirmation
    { id: 'e4', source: 'updateInventory', sourceOutput: 'default', target: 'sendConfirmation' }
  ]
});
Tip

Workflows execute nodes in parallel when possible. In the example above, checkValue and updateInventory run simultaneously since they both connect directly to the trigger.