Loopfour
API Reference

Workflows API

Create, manage, and execute workflow definitions

Workflows API

Manage workflow definitions including creation, updates, versioning, and execution.

List Workflows

Retrieve all workflows for your company with cursor-based pagination.

GET /api/v1/workflows

Query Parameters

ParameterTypeDefaultDescription
statusstring-Filter by status: draft, active, paused, archived
searchstring-Search by workflow name
limitnumber50Number of results (1-100)
cursorstring-Cursor for pagination

Response

{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Invoice Automation",
      "description": "Process incoming invoices",
      "status": "active",
      "version": 3,
      "trigger": {
        "type": "webhook",
        "provider": "stripe",
        "events": ["invoice.paid"]
      },
      "createdAt": "2024-01-15T10:00:00.000Z",
      "updatedAt": "2024-01-16T14:30:00.000Z"
    }
  ],
  "meta": {
    "cursor": "eyJ0cyI6MTcwNTMxMjAwMDAwMCwiaWQiOiI1NTBlODQwMC4uLiJ9",
    "hasMore": true
  }
}

Example

curl -X GET "https://workflow.loopfour.ai/api/v1/workflows?status=active&limit=10" \
  -H "x-api-key: YOUR_API_KEY"
const response = await fetch(
  'https://workflow.loopfour.ai/api/v1/workflows?status=active&limit=10',
  {
    headers: { 'x-api-key': process.env.API_KEY }
  }
);
const { data, meta } = await response.json();
import requests

response = requests.get(
    'https://workflow.loopfour.ai/api/v1/workflows',
    params={'status': 'active', 'limit': 10},
    headers={'x-api-key': api_key}
)
data = response.json()

Create Workflow

Create a new workflow definition. Workflows are created in draft status.

POST /api/v1/workflows

Request Body

FieldTypeRequiredDescription
namestringYesWorkflow name (1-255 chars)
descriptionstringNoHuman-readable description
triggerobjectYesTrigger configuration
stepsarrayYesArray of workflow steps
inputSchemaobjectNoJSON Schema for input validation
outputSchemaobjectNoJSON Schema for output
settingsobjectNoWorkflow settings

Example Request

{
  "name": "New Customer Onboarding",
  "description": "Send welcome email and create CRM contact",
  "trigger": {
    "type": "webhook",
    "provider": "stripe",
    "events": ["customer.created"]
  },
  "steps": [
    {
      "id": "send-welcome",
      "type": "email",
      "name": "Send Welcome Email",
      "config": {
        "action": "send",
        "to": "{{input.data.object.email}}",
        "subject": "Welcome to our platform!",
        "body": {
          "html": "<p>Hello {{input.data.object.name}},</p><p>Welcome aboard!</p>"
        }
      }
    },
    {
      "id": "create-contact",
      "type": "action",
      "name": "Create HubSpot Contact",
      "action": "hubspot.createContact",
      "config": {
        "email": "{{input.data.object.email}}",
        "properties": {
          "firstname": "{{input.data.object.name}}",
          "stripe_customer_id": "{{input.data.object.id}}"
        }
      }
    }
  ],
  "inputSchema": {
    "type": "object",
    "required": ["data"],
    "properties": {
      "data": {
        "type": "object",
        "properties": {
          "object": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "email": { "type": "string" },
              "name": { "type": "string" }
            }
          }
        }
      }
    }
  }
}

Response (201 Created)

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "New Customer Onboarding",
    "description": "Send welcome email and create CRM contact",
    "status": "draft",
    "version": 1,
    "trigger": { ... },
    "steps": [ ... ],
    "inputSchema": { ... },
    "outputSchema": null,
    "settings": null,
    "createdAt": "2024-01-15T10:00:00.000Z",
    "updatedAt": "2024-01-15T10:00:00.000Z"
  }
}

Get Workflow

Retrieve a specific workflow by ID.

GET /api/v1/workflows/:id

Path Parameters

ParameterTypeDescription
idstringWorkflow UUID

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Invoice Automation",
    "description": "Process incoming invoices",
    "status": "active",
    "version": 3,
    "trigger": {
      "type": "webhook",
      "provider": "stripe",
      "events": ["invoice.paid"]
    },
    "steps": [
      {
        "id": "notify-team",
        "type": "action",
        "name": "Notify Slack",
        "action": "slack.sendMessage",
        "config": {
          "channel": "#finance",
          "text": "Invoice {{input.data.object.number}} paid!"
        }
      }
    ],
    "inputSchema": null,
    "outputSchema": null,
    "settings": null,
    "createdAt": "2024-01-15T10:00:00.000Z",
    "updatedAt": "2024-01-16T14:30:00.000Z"
  }
}

Update Workflow

Update a workflow. If the workflow is active, this increments the version and saves the previous version to history.

PUT /api/v1/workflows/:id

Request Body

FieldTypeRequiredDescription
namestringNoUpdated workflow name
descriptionstringNoUpdated description
triggerobjectNoUpdated trigger configuration
stepsarrayNoUpdated steps array
inputSchemaobjectNoUpdated input schema
outputSchemaobjectNoUpdated output schema
settingsobjectNoUpdated settings
changeSummarystringNoDescription of changes (stored in version history)

Example Request

{
  "name": "Invoice Automation v2",
  "steps": [
    {
      "id": "notify-team",
      "type": "action",
      "name": "Notify Slack",
      "action": "slack.sendMessage",
      "config": {
        "channel": "#finance",
        "text": "Invoice {{input.data.object.number}} paid for ${{input.data.object.amount_paid / 100}}!"
      }
    }
  ],
  "changeSummary": "Added amount to Slack notification"
}

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Invoice Automation v2",
    "status": "active",
    "version": 4,
    ...
  }
}

Save Canvas

Save the visual editor's layout and optionally replace its compiled steps. Requires authentication with workflows:write scope; the workflow must belong to your company.

PATCH /api/v1/workflows/:id/canvas
FieldTypeRequiredDescription
canvasStateobjectYesCanvas blocks, edges, Loop/Parallel membership, and optional viewport
stepsarrayNoCompiled steps; omission leaves saved steps unchanged
triggerConfigobjectNoReplacement trigger configuration; omission leaves it unchanged; null is rejected

Inactive workflows can save unfinished Loop bodies (config.steps: []) and static Parallel shells (config.branches: []). Static Parallel drafts must still use valid strategy, concurrency, and timeout values and cannot also specify forEach. Dynamic Parallel templates retain their existing validation. Unsupported Agent provider/model pairs are rejected in both steps and canvas metadata.

An active workflow must pass full step validation before its steps can be replaced. Empty shells cannot be activated or run; add and connect their child blocks and complete configuration first. The regular create/update workflow endpoints continue to require valid executable control-flow configuration.

Example: Save an empty Parallel draft

curl -X PATCH "https://workflow.loopfour.ai/api/v1/workflows/WORKFLOW_ID/canvas" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "canvasState": {
      "blocks": {
        "parallel-1": {
          "id": "parallel-1",
          "type": "parallel",
          "name": "Parallel",
          "position": { "x": 0, "y": 0 },
          "subBlocks": {},
          "outputs": {},
          "enabled": true,
          "horizontalHandles": true
        }
      },
      "edges": [],
      "loops": {},
      "parallels": {
        "parallel-1": { "id": "parallel-1", "nodes": ["parallel-1"], "enabled": true }
      }
    },
    "steps": [{
      "id": "parallel-1",
      "type": "parallel",
      "name": "Parallel",
      "config": { "branches": [], "strategy": "wait-all" }
    }]
  }'

A successful response returns 200 with data.id, data.canvasState, data.isVisual, and ISO timestamps lastSavedAt, lastModifiedAt, and updatedAt. Invalid input or incomplete steps on an active workflow return 400; a missing workflow returns 404. If the workflow's status, version, or steps change during a save that includes steps, the request returns 409; reload the workflow and reconcile your changes before retrying.

Delete Workflow

Soft delete (archive) a workflow. This also deletes any associated schedules.

DELETE /api/v1/workflows/:id

Response

{
  "success": true,
  "data": {
    "deleted": true
  }
}

Activate Workflow

Change workflow status to active. This enables the workflow to be triggered.

POST /api/v1/workflows/:id/activate

Status Transitions

FromTo Active
draftAllowed
pausedAllowed
activeNot allowed (already active)
archivedNot allowed

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "active"
  }
}

For schedule triggers, the response includes schedule information:

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "active",
    "schedule": {
      "id": "sch_xxx",
      "triggerScheduleId": "sched_xxx",
      "nextRunAt": "2024-01-16T09:00:00.000Z"
    }
  }
}

Pause Workflow

Pause an active workflow. Paused workflows won't be triggered.

POST /api/v1/workflows/:id/pause

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "paused"
  }
}

Run Workflow

Trigger a workflow execution via the API. Supports both async (default) and sync execution modes.

POST /api/v1/workflows/:id/run

Request Body

FieldTypeRequiredDescription
inputobjectNoInput data for the workflow
connectionIdstringNoSpecific connection to use
idempotencyKeystringNoPrevent duplicate runs (24h TTL)
modestringNoasync (default) or sync
maxExecutionTimeMsnumberNoMax wait time for sync mode (1000-300000ms)
onTimeoutstringNoSync timeout behavior: return_run_id, throw_error
onFailurestringNoSync failure behavior: throw_error, switch_to_async

Async Mode (Default)

Returns immediately with a run ID. Poll the runs API for status updates.

{
  "input": {
    "customerId": "cus_xxx",
    "amount": 9900
  }
}

Response (202 Accepted):

{
  "success": true,
  "data": {
    "run_id": "run_550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
    "mode": "async"
  }
}

Sync Mode

Waits for workflow completion and returns the result directly. Ideal for short-running workflows in request/response scenarios.

{
  "input": {
    "customerId": "cus_xxx",
    "amount": 9900
  },
  "mode": "sync",
  "maxExecutionTimeMs": 30000,
  "onTimeout": "return_run_id",
  "onFailure": "throw_error"
}

Sync Success Response (200):

{
  "success": true,
  "data": {
    "run_id": "run_550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
    "mode": "sync",
    "output": {
      "invoiceId": "inv_xxx",
      "status": "sent"
    },
    "duration_ms": 2500
  }
}

Sync Timeout Response (202):

When execution exceeds maxExecutionTimeMs with onTimeout: "return_run_id":

{
  "success": true,
  "data": {
    "run_id": "run_550e8400-e29b-41d4-a716-446655440000",
    "status": "running",
    "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
    "mode": "async",
    "timed_out": true,
    "message": "Execution in progress. Use GET /runs/:id to poll for results."
  }
}

Sync Failure Response (500):

When the run fails with onFailure: "throw_error", error.details carries the run and workflow ids. When the run failed at the platform level, it also carries triggerStatus and, when Trigger.dev reports one, triggerErrorCode (see Get Run):

{
  "success": false,
  "error": {
    "code": "TRIGGER_ERROR",
    "message": "trigger.dev internal error (TASK_PROCESS_OOM_KILLED)",
    "details": {
      "run_id": "run_550e8400-e29b-41d4-a716-446655440000",
      "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
      "triggerStatus": "CRASHED",
      "triggerErrorCode": "TASK_PROCESS_OOM_KILLED"
    },
    "requestId": "req_xxx"
  }
}

With onFailure: "switch_to_async", the same error object is returned under data.error with status 202.

Sync Mode Parameters

ParameterDefaultDescription
maxExecutionTimeMs30000Max wait time (1 second to 5 minutes)
onTimeoutreturn_run_idreturn_run_id returns gracefully, throw_error returns 408
onFailurethrow_errorthrow_error returns error, switch_to_async returns run ID

Sync mode is best for workflows completing under 30 seconds. For longer workflows, use async mode with polling or webhooks.

Idempotency

If the same idempotencyKey is used within 24 hours, the existing run is returned:

{
  "success": true,
  "data": {
    "run_id": "run_existing",
    "status": "completed",
    "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
    "message": "Existing run returned (idempotency key matched)"
  }
}

List Workflow Runs

Get runs for a specific workflow.

GET /api/v1/workflows/:id/runs

Query Parameters

ParameterTypeDefaultDescription
statusstring-Filter by pending, running, waiting_approval, completed, failed, suspended, or cancelled
limitnumber50Number of results (1-100)
cursorstring-Cursor for pagination

Response

{
  "success": true,
  "data": [
    {
      "id": "run_xxx",
      "workflowId": "550e8400-e29b-41d4-a716-446655440000",
      "status": "completed",
      "triggerType": "api",
      "startedAt": "2024-01-15T10:30:00.000Z",
      "completedAt": "2024-01-15T10:30:05.000Z",
      "durationMs": 5000,
      "error": null,
      "createdAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "meta": {
    "cursor": "eyJ0cyI6MTcwNTMxMjAwMDAwMCwiaWQiOiJydW5feHh4In0=",
    "hasMore": false
  }
}

Clone Workflow

Create a copy of an existing workflow.

POST /api/v1/workflows/:id/clone

Request Body

FieldTypeRequiredDescription
namestringYesName for the cloned workflow

Example Request

{
  "name": "Invoice Automation (Copy)"
}

Response (201 Created)

{
  "success": true,
  "data": {
    "id": "new-workflow-id",
    "name": "Invoice Automation (Copy)",
    "status": "draft",
    "version": 1,
    "clonedFrom": "550e8400-e29b-41d4-a716-446655440000",
    ...
  }
}

Version Management

List Versions

Get all versions of a workflow.

GET /api/v1/workflows/:id/versions

Response

{
  "success": true,
  "data": {
    "workflowId": "550e8400-e29b-41d4-a716-446655440000",
    "currentVersion": 4,
    "versions": [
      {
        "version": 4,
        "status": "current",
        "isCurrent": true
      },
      {
        "version": 3,
        "name": "Invoice Automation",
        "changeSummary": "Added error handling",
        "status": "archived",
        "createdAt": "2024-01-15T10:00:00.000Z",
        "isCurrent": false
      },
      {
        "version": 2,
        "name": "Invoice Automation",
        "changeSummary": "Added Slack notification",
        "status": "archived",
        "createdAt": "2024-01-14T10:00:00.000Z",
        "isCurrent": false
      }
    ]
  }
}

Get Specific Version

GET /api/v1/workflows/:id/versions/:version

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "version": 2,
    "name": "Invoice Automation",
    "description": "Process incoming invoices",
    "trigger": { ... },
    "steps": [ ... ],
    "changeSummary": "Added Slack notification",
    "status": "archived",
    "isCurrent": false,
    "createdAt": "2024-01-14T10:00:00.000Z"
  }
}

Compare Versions

Compare changes between two versions.

GET /api/v1/workflows/:id/versions/compare?from=2&to=4

Query Parameters

ParameterTypeRequiredDescription
fromnumberYesSource version number
tonumberYesTarget version number

Response

{
  "success": true,
  "data": {
    "fromVersion": 2,
    "toVersion": 4,
    "changes": {
      "name": {
        "from": "Invoice Automation",
        "to": "Invoice Automation v2",
        "changed": true
      },
      "description": {
        "from": "Process incoming invoices",
        "to": "Process incoming invoices",
        "changed": false
      },
      "trigger": {
        "changed": false
      },
      "steps": {
        "added": ["error-handler"],
        "removed": [],
        "modified": ["notify-team"]
      }
    }
  }
}

Rollback Version

Restore a workflow to a previous version. This creates a new version with the old content.

POST /api/v1/workflows/:id/versions/:version/rollback

Request Body

FieldTypeRequiredDescription
changeSummarystringNoReason for rollback

Example Request

{
  "changeSummary": "Rolling back due to bug in v4"
}

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "version": 5,
    "rolledBackFrom": 4,
    "rolledBackTo": 2,
    "name": "Invoice Automation",
    "status": "active",
    ...
  }
}

Schedule Management

Get Schedule

Get schedule information for a workflow with schedule trigger.

GET /api/v1/workflows/:id/schedule

Response

{
  "success": true,
  "data": {
    "id": "sch_xxx",
    "workflowId": "550e8400-e29b-41d4-a716-446655440000",
    "triggerScheduleId": "sched_xxx",
    "scheduleType": "cron",
    "cronExpression": "0 9 * * 1-5",
    "interval": null,
    "timezone": "America/New_York",
    "enabled": true,
    "nextRunAt": "2024-01-16T09:00:00.000Z",
    "lastRunAt": "2024-01-15T09:00:00.000Z",
    "lastRunId": "run_xxx",
    "runCount": 42,
    "consecutiveFailures": 0,
    "lastError": null,
    "createdAt": "2024-01-01T10:00:00.000Z",
    "updatedAt": "2024-01-15T09:00:05.000Z"
  }
}

Update Schedule

Modify schedule configuration.

PUT /api/v1/workflows/:id/schedule

Request Body

FieldTypeRequiredDescription
cronstringNoCron expression
intervalobjectNoInterval configuration
interval.valuenumberYesInterval value
interval.unitstringYesminutes, hours, days, weeks
timezonestringNoIANA timezone
enabledbooleanNoEnable/disable schedule

Example Request

{
  "cron": "0 10 * * 1-5",
  "timezone": "America/Los_Angeles",
  "enabled": true
}

Response

{
  "success": true,
  "data": {
    "updated": true,
    "scheduleId": "sch_xxx",
    "nextRunAt": "2024-01-16T10:00:00.000Z"
  }
}

Manual Schedule Trigger

Manually trigger a scheduled workflow (for testing).

POST /api/v1/workflows/:id/schedule/trigger

Response (202 Accepted)

{
  "success": true,
  "data": {
    "runId": "run_xxx",
    "status": "pending",
    "workflowId": "550e8400-e29b-41d4-a716-446655440000",
    "triggeredAt": "2024-01-15T10:30:00.000Z"
  }
}

Workflow Statuses

StatusDescription
draftInitial state, not yet activated
activeRunning and can be triggered
pausedTemporarily disabled
archivedSoft deleted

Error Responses

Workflow Not Found (404)

{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Workflow not found"
  }
}

Invalid Status Transition (400)

{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Cannot transition from 'archived' to 'active'",
    "details": {
      "currentStatus": "archived",
      "targetStatus": "active",
      "allowedTransitions": []
    }
  }
}

Validation Error (400)

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid workflow steps: Step 'send-email' references non-existent step 'process-data'"
  }
}

Workflow Not Active (400)

{
  "success": false,
  "error": {
    "code": "WORKFLOW_NOT_ACTIVE",
    "message": "Workflow must be active to run"
  }
}

On this page