curl --request PATCH \
--url https://openapi.enginy.ai/v1/workflow/{workflowId}/draft \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"ops": [
{}
],
"name": "<string>",
"trigger": {
"type": "MANUAL"
},
"maxCreditsPerRun": 123,
"folderId": 123,
"revision": "<string>"
}
'import requests
url = "https://openapi.enginy.ai/v1/workflow/{workflowId}/draft"
payload = {
"ops": [{}],
"name": "<string>",
"trigger": { "type": "MANUAL" },
"maxCreditsPerRun": 123,
"folderId": 123,
"revision": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
ops: [{}],
name: '<string>',
trigger: {type: 'MANUAL'},
maxCreditsPerRun: 123,
folderId: 123,
revision: '<string>'
})
};
fetch('https://openapi.enginy.ai/v1/workflow/{workflowId}/draft', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://openapi.enginy.ai/v1/workflow/{workflowId}/draft",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'ops' => [
[
]
],
'name' => '<string>',
'trigger' => [
'type' => 'MANUAL'
],
'maxCreditsPerRun' => 123,
'folderId' => 123,
'revision' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://openapi.enginy.ai/v1/workflow/{workflowId}/draft"
payload := strings.NewReader("{\n \"ops\": [\n {}\n ],\n \"name\": \"<string>\",\n \"trigger\": {\n \"type\": \"MANUAL\"\n },\n \"maxCreditsPerRun\": 123,\n \"folderId\": 123,\n \"revision\": \"<string>\"\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://openapi.enginy.ai/v1/workflow/{workflowId}/draft")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"ops\": [\n {}\n ],\n \"name\": \"<string>\",\n \"trigger\": {\n \"type\": \"MANUAL\"\n },\n \"maxCreditsPerRun\": 123,\n \"folderId\": 123,\n \"revision\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://openapi.enginy.ai/v1/workflow/{workflowId}/draft")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"ops\": [\n {}\n ],\n \"name\": \"<string>\",\n \"trigger\": {\n \"type\": \"MANUAL\"\n },\n \"maxCreditsPerRun\": 123,\n \"folderId\": 123,\n \"revision\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"message": "<string>",
"data": {
"id": 123,
"appUrl": "<string>",
"plan": {
"nodes": [
{
"id": "<string>",
"kind": "BLOCK",
"blockId": "<string>",
"inputs": {},
"next": [
"<string>"
]
}
],
"name": "<string>"
},
"folderId": 123,
"revision": "<string>",
"warnings": [
"<string>"
]
}
}Update workflow draft
Update a workflow draft. Provide either a full plan OR an ordered ops array (edit operations applied to the current draft plan) — not both. Optionally change name, trigger, or maxCreditsPerRun. Pass revision (from GET /v1/workflow/{workflowId}) for optimistic concurrency.
A workflow is a DAG of nodes described by the plan object: { name?, nodes: [] }.
Each node has a stable string id and a next array of node ids it points to (an empty string "" marks an unwired slot). There are three node kinds:
BLOCK— one workflow step.{ id, kind: "BLOCK", blockId, inputs: {}, next: [] }.blockIdcomes fromGET /v1/workflows/blocks;inputsare the block’s configurable inputs (discover them withGET /v1/workflows/blocks/{blockId}). A BLOCK has exactly onenextslot, except two-outlet blocks:WAIT_FOR_APPROVALhas[approvedTargetId, rejectedTargetId]andCHECK_LEAD_FOR_CHANGEShas[changedTargetId, unchangedTargetId]. Put configured values in a BLOCK’sinputs, never the input-catalog metadata. For a WEBHOOK trigger’s declared non-entity input, bind the matching field as{ "webhookInputKey": "<declared key>" }(for exampleinputs.companyPayload = { "webhookInputKey": "companyPayload" }). Entity inputs such asleadIdsandcompanyIdsuse"__MANUAL_INPUT"on the entry block instead.CONDITIONAL— branches the stream.{ id, kind: "CONDITIONAL", conditions: [], fallbackName?, next: [] }. Each condition is EITHER a field predicate{ field, operator, value? }(lead/company/AI-variable fields — discover them withGET /v1/workflows/condition-fields) OR a random-split branch{ type: "RANDOM_SPLIT", percentage }(deterministic A/B split; integer 1-100, percentages must sum to ≤ 100 across the node, the remainder falls through to the fallback).nexthas exactlyconditions.length + 1entries: one target per matched condition (in order) then the fallback target LAST.KING(shown as Prioritize by group) — groups records, sorts each group ascending by a formula field, and routes an exact number from every group separately from the remainder.{ id, kind: "KING", groupBy, orderBy, checkAnyDomain?, limit?, next: [] }.nextis[prioritizedTargetId, remainingTargetId].
The first node (the one no other node points to) should be a selector/import entry block. A plan may hold at most 60 nodes and no cycles. An empty plan (nodes: []) is valid for a brand-new draft.
Field/entity prefixes used inside conditions: company., anyLead., and webhookInput. for webhook-triggered workflows.
Responses include direct Enginy app URLs when available. The response includes an appUrl and the new revision. MCP agents should return those URLs to users whenever they are present in the response.
Required scope:
WORKFLOWS_WRITERate limit: 30 requests per minute
curl --request PATCH \
--url https://openapi.enginy.ai/v1/workflow/{workflowId}/draft \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"ops": [
{}
],
"name": "<string>",
"trigger": {
"type": "MANUAL"
},
"maxCreditsPerRun": 123,
"folderId": 123,
"revision": "<string>"
}
'import requests
url = "https://openapi.enginy.ai/v1/workflow/{workflowId}/draft"
payload = {
"ops": [{}],
"name": "<string>",
"trigger": { "type": "MANUAL" },
"maxCreditsPerRun": 123,
"folderId": 123,
"revision": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
ops: [{}],
name: '<string>',
trigger: {type: 'MANUAL'},
maxCreditsPerRun: 123,
folderId: 123,
revision: '<string>'
})
};
fetch('https://openapi.enginy.ai/v1/workflow/{workflowId}/draft', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://openapi.enginy.ai/v1/workflow/{workflowId}/draft",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'ops' => [
[
]
],
'name' => '<string>',
'trigger' => [
'type' => 'MANUAL'
],
'maxCreditsPerRun' => 123,
'folderId' => 123,
'revision' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://openapi.enginy.ai/v1/workflow/{workflowId}/draft"
payload := strings.NewReader("{\n \"ops\": [\n {}\n ],\n \"name\": \"<string>\",\n \"trigger\": {\n \"type\": \"MANUAL\"\n },\n \"maxCreditsPerRun\": 123,\n \"folderId\": 123,\n \"revision\": \"<string>\"\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://openapi.enginy.ai/v1/workflow/{workflowId}/draft")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"ops\": [\n {}\n ],\n \"name\": \"<string>\",\n \"trigger\": {\n \"type\": \"MANUAL\"\n },\n \"maxCreditsPerRun\": 123,\n \"folderId\": 123,\n \"revision\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://openapi.enginy.ai/v1/workflow/{workflowId}/draft")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"ops\": [\n {}\n ],\n \"name\": \"<string>\",\n \"trigger\": {\n \"type\": \"MANUAL\"\n },\n \"maxCreditsPerRun\": 123,\n \"folderId\": 123,\n \"revision\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"message": "<string>",
"data": {
"id": 123,
"appUrl": "<string>",
"plan": {
"nodes": [
{
"id": "<string>",
"kind": "BLOCK",
"blockId": "<string>",
"inputs": {},
"next": [
"<string>"
]
}
],
"name": "<string>"
},
"folderId": 123,
"revision": "<string>",
"warnings": [
"<string>"
]
}
}Authorizations
Path Parameters
Body
Replace the whole draft plan. Mutually exclusive with ops.
Show child attributes
Show child attributes
Ordered edit operations applied to the current draft plan. Mutually exclusive with plan.
1Show child attributes
Show child attributes
1 - 120Trigger configuration. SCHEDULE requires definition and startsAt; INTENT requires signal; WEBHOOK may declare inputs.
- WorkflowTrigger
- WorkflowTrigger
- WorkflowTrigger
- WorkflowTrigger
- WorkflowTrigger
Show child attributes
Show child attributes
Assign the workflow to a workflow folder (see GET /v1/workflows/folders), or null to remove it from any folder. Assignment is organizational only — a folderId-only update does not change the plan or advance the revision.
Optimistic concurrency token (the revision from GET /v1/workflow/{workflowId}). 409 on mismatch.