Back to Arky

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, and trigger from webhooks or schedules.

Key Concepts

Workflow Structure

A workflow consists of nodes and edges:

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

Node Types

| Type | Description | | --------------------- | ------------------------------------------------------------ | | trigger | Entry point - webhook or scheduled | | http | Make HTTP requests to external APIs | | send_email | Send a typed email through a configured mailbox and template | | deploy_webhook | Call a store build hook endpoint | | google_drive_upload | Upload content through a Google Drive workflow connection | | switch | Branch based on expressions | | transform | Transform data with JavaScript | | loop | Iterate over arrays |

Create Workflow

POST /v1/stores/{storeId}/workflows
SDK: sdk.automation.workflow.create()

Create a new automation workflow.

const workflow = await sdk.automation.workflow.create({
key: 'order-notification',
status: 'active',
nodes: {
  trigger: {
    type: 'trigger'
  },
  fetchOrder: {
    type: 'http',
    method: 'get',
    url: 'https://api.yourapp.com/orders/{{input.trigger.orderId}}',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY'
    },
    timeout_ms: 30000,
    delay_ms: 0,
    retries: 0,
    retry_delay_ms: 0
  },
  sendSlack: {
    type: 'http',
    method: 'post',
    url: 'https://hooks.slack.com/services/xxx',
    headers: { 'Content-Type': 'application/json' },
    body: {
      text: 'New order #{{input.fetchOrder.body.orderNumber}}, total {{input.fetchOrder.body.total}} minor units ({{input.fetchOrder.body.currency}})'
    },
    timeout_ms: 30000,
    delay_ms: 0,
    retries: 0,
    retry_delay_ms: 0
  }
},
edges: [
  { source: 'trigger', output: 'default', target: 'fetchOrder', back_edge: false },
  { source: 'fetchOrder', output: 'success', target: 'sendSlack', back_edge: false }
]
});

Parameters

Name Type Description
key required string Unique workflow identifier
status optional active | draft | archived Workflow status (default: active)
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)

Each edge has exactly source, target, output, and back_edge. Use default after a trigger, success or error after an HTTP/effect node, rule_0, rule_1, and so on plus fallback after a switch, and each or done after a loop. Set back_edge: false except for an explicit edge that returns a loop body to its loop node.

Get Workflow

GET /v1/stores/{storeId}/workflows/{id}
SDK: sdk.automation.workflow.get()

Retrieve a workflow by ID.

const workflow = await sdk.automation.workflow.get({
	id: "wf_abc123",
});

console.log(workflow.key, workflow.status);
console.log("Nodes:", Object.keys(workflow.nodes));

List Workflows

GET /v1/stores/{storeId}/workflows
SDK: sdk.automation.workflow.find()

List workflows with filtering and pagination.

const { items, cursor } = await sdk.automation.workflow.find({
	status: "active",
	query: "notification",
	limit: 20,
	cursor: null,
});

items.forEach((workflow) => {
	console.log(workflow.key, workflow.status);
});

Parameters

Name Type Description
ids optional string[] Filter by specific workflow IDs
status optional active | draft | archived Filter by workflow status
query optional string Search in workflow keys
limit optional number Items per page (max 100)
cursor optional string Pagination cursor
sort_field optional string Sort field (createdAt, key)
sort_direction optional asc | desc Sort direction

Update Workflow

PUT /v1/stores/{storeId}/workflows/{id}
SDK: sdk.automation.workflow.update()

Update an existing workflow.

const workflow = await sdk.automation.workflow.update({
	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",
			headers: {},
			timeout_ms: 30000,
			delay_ms: 0,
			retries: 0,
			retry_delay_ms: 0,
		},
	},
	edges: [
		{
			source: "trigger",
			output: "default",
			target: "notify",
			back_edge: false,
		},
	],
});

Delete Workflow

DELETE /v1/stores/{storeId}/workflows/{id}
SDK: sdk.automation.workflow.delete()

Delete a workflow.

const deleted = await sdk.automation.workflow.delete({
	id: "wf_abc123",
});

Trigger Workflow

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

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 execution = await sdk.automation.workflow.trigger({
secret: 'wh_secret_abc123xyz',
// Pass any data to the workflow
orderId: 'ord_123',
contact_email: 'contact@example.com',
items: [
  { product_id: 'prod_1', quantity: 2 }
]
});

Parameters

Name Type Description
secret required string Workflow webhook secret from the trigger URL
[key: string] optional unknown Additional JSON-object properties passed to the trigger
Tip

The request body must be a JSON object. A node connected directly to a trigger node named trigger reads orderId as input.trigger.orderId; any later node can use nodes.trigger.output.orderId after the trigger has completed.

List Executions

GET /v1/stores/{storeId}/workflows/{workflowId}/executions
SDK: sdk.automation.workflow.getExecutions()

List past executions of a workflow.

const { items, cursor } = await sdk.automation.workflow.getExecutions({
	workflow_id: "wf_abc123",
	limit: 20,
});

items.forEach((execution) => {
	console.log(execution.id, execution.status, execution.started_at);
});

Parameters

Name Type Description
workflow_id required string Workflow ID
limit optional number Items per page
cursor optional string Pagination cursor

Get Execution

GET /v1/stores/{storeId}/workflows/{workflowId}/executions/{executionId}
SDK: sdk.automation.workflow.getExecution()

Retrieve a single execution including node-level state and any errors.

const execution = await sdk.automation.workflow.getExecution({
	workflow_id: "wf_abc123",
	execution_id: "exec_xyz789",
});

console.log(execution.status); // pending | running | completed | failed | cancelled
console.log(execution.results); // Record<nodeId, NodeResult>

Parameters

Name Type Description
workflow_id required string Workflow ID
execution_id required string Execution ID

Node Types Reference

Trigger Node

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

{
  type: 'trigger',
  delay_ms: 0
}

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 YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: {
    userId: '{{input.trigger.userId}}',
    message: 'Hello from Arky!'
  },
  timeout_ms: 30000,
  delay_ms: 0,
  retries: 0,
  retry_delay_ms: 0
}

Available Variables:

  • {{input.sourceNodeId.*}} - Output from a node connected directly to this node
  • {{nodes.nodeId.output.*}} - Output from any completed node

Switch Node

Conditional branching based on JavaScript expressions.

{
  type: 'switch',
  rules: [{ condition: 'input.trigger.amount > 100' }]
}

Switch nodes use one output per rule plus a fallback output:

  • rule_0 - Executed when the first rule matches
  • fallback - Executed when no rule matches
edges: [
	{
		source: "checkAmount",
		output: "rule_0",
		target: "sendHighValueAlert",
		back_edge: false,
	},
	{
		source: "checkAmount",
		output: "fallback",
		target: "sendStandardNotification",
		back_edge: false,
	},
];

Delays And Read-only Retries

HTTP and deploy webhook nodes support delay_ms. Retry settings apply only to read-only HTTP GET nodes. Mutating HTTP methods, deploy webhooks, uploads, and other external effects make one durably claimed provider attempt; an ambiguous result becomes terminal unknown instead of being sent again.

{
  type: 'http',
  method: 'get',
  url: 'https://api.example.com/jobs/latest',
  headers: {},
  timeout_ms: 30000,
  delay_ms: 300000,
  retries: 2,
  retry_delay_ms: 10000
}

Scheduled Workflows

Run workflows on a schedule using cron expressions.

const workflow = await sdk.automation.workflow.create({
	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",
			headers: {},
			timeout_ms: 30000,
			delay_ms: 0,
			retries: 0,
			retry_delay_ms: 0,
		},
		sendEmail: {
			type: "http",
			method: "post",
			url: "https://api.yourapp.com/email/send",
			body: {
				to: "team@company.com",
				subject: "Daily Report",
				body: "{{input.generateReport.body.reportUrl}}",
			},
			headers: {},
			timeout_ms: 30000,
			delay_ms: 0,
			retries: 0,
			retry_delay_ms: 0,
		},
	},
	edges: [
		{
			source: "trigger",
			output: "default",
			target: "generateReport",
			back_edge: false,
		},
		{
			source: "generateReport",
			output: "success",
			target: "sendEmail",
			back_edge: false,
		},
	],
});

Common Cron Expressions:

| Expression | Description | | -------------- | ------------------------ | | 0 9 * * * | Every day at 9:00 AM | | 0 */2 * * * | Every 2 hours | | 0 9 * * 1 | Every 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.automation.workflow.create({
	key: "process-new-order",
	status: "active",
	nodes: {
		// Entry point
		trigger: {
			type: "trigger",
		},

		// Check order value
		checkValue: {
			type: "switch",
			rules: [{ condition: "input.trigger.total > 10000" }], // Over 10,000 minor units
		},

		// High value: notify sales team
		notifySales: {
			type: "http",
			method: "post",
			url: "https://hooks.slack.com/services/sales-channel",
			body: {
				text: "🎉 High-value order! {{nodes.trigger.output.total}} minor units ({{nodes.trigger.output.currency}}) from {{nodes.trigger.output.contactEmail}}",
			},
			headers: {},
			timeout_ms: 30000,
			delay_ms: 0,
			retries: 0,
			retry_delay_ms: 0,
		},

		// Always: update inventory
		updateInventory: {
			type: "http",
			method: "post",
			url: "https://api.yourapp.com/inventory/decrement",
			body: {
				items: "{{input.trigger.items}}",
			},
			headers: {},
			timeout_ms: 30000,
			delay_ms: 0,
			retries: 0,
			retry_delay_ms: 0,
		},

		// Send confirmation email
		sendConfirmation: {
			type: "http",
			method: "post",
			url: "https://api.yourapp.com/email/order-confirmation",
			body: {
				orderId: "{{nodes.trigger.output.orderId}}",
				email: "{{nodes.trigger.output.contactEmail}}",
			},
			headers: {},
			timeout_ms: 30000,
			delay_ms: 0,
			retries: 0,
			retry_delay_ms: 0,
		},
	},
	edges: [
		// Trigger -> Check Value
		{
			source: "trigger",
			output: "default",
			target: "checkValue",
			back_edge: false,
		},

		// High value -> Notify Sales
		{
			source: "checkValue",
			output: "rule_0",
			target: "notifySales",
			back_edge: false,
		},

		// Both paths -> Update Inventory (runs in parallel with notification)
		{
			source: "trigger",
			output: "default",
			target: "updateInventory",
			back_edge: false,
		},

		// After inventory -> Send Confirmation
		{
			source: "updateInventory",
			output: "success",
			target: "sendConfirmation",
			back_edge: false,
		},
	],
});
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.