curl --request GET \
--url https://openapi.enginy.ai/v1/workflows/blocks \
--header 'x-api-key: <api-key>'import requests
url = "https://openapi.enginy.ai/v1/workflows/blocks"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://openapi.enginy.ai/v1/workflows/blocks', 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/workflows/blocks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"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"
"net/http"
"io"
)
func main() {
url := "https://openapi.enginy.ai/v1/workflows/blocks"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://openapi.enginy.ai/v1/workflows/blocks")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://openapi.enginy.ai/v1/workflows/blocks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"status": "success",
"message": "<string>",
"data": {
"data": [
{
"id": "<string>",
"name": "<string>",
"category": "<string>",
"chainable": true,
"inputs": [
{
"key": "<string>",
"required": true,
"hasDefault": true
}
],
"outputs": "<string>",
"usedApis": [
"<string>"
],
"description": "<string>"
}
],
"meta": {
"total": 123
}
}
}Get workflow blocks
List the block types this workspace can use to build a workflow plan, the same steps the workflow builder’s step picker offers. Each block is a workflow step; use its id as a plan node’s blockId and configure its inputs (discover them with GET /v1/workflows/blocks/{blockId}). Blocks the workspace cannot use are left out: CRM blocks appear only when a CRM is available to the workspace, and Slack blocks only when Slack is connected. Generic CRM blocks are named after the connected CRM (e.g. “Export contacts to HubSpot”).
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].
Use importSources: [{ nodeId, previewId, maxCount }] to materialize source inputs from AI Finder previews server-side. The target BLOCK must use the resolved blockId from GET /v1/workflows/import-inputs; its inputs can be empty. Resolved source fields overwrite copied inputs, while extra inputs are preserved. References expire with the preview and are never persisted in the workflow.
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.
Required scope:
WORKFLOWS_READRate limit: 100 requests per minute
curl --request GET \
--url https://openapi.enginy.ai/v1/workflows/blocks \
--header 'x-api-key: <api-key>'import requests
url = "https://openapi.enginy.ai/v1/workflows/blocks"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://openapi.enginy.ai/v1/workflows/blocks', 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/workflows/blocks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"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"
"net/http"
"io"
)
func main() {
url := "https://openapi.enginy.ai/v1/workflows/blocks"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://openapi.enginy.ai/v1/workflows/blocks")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://openapi.enginy.ai/v1/workflows/blocks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"status": "success",
"message": "<string>",
"data": {
"data": [
{
"id": "<string>",
"name": "<string>",
"category": "<string>",
"chainable": true,
"inputs": [
{
"key": "<string>",
"required": true,
"hasDefault": true
}
],
"outputs": "<string>",
"usedApis": [
"<string>"
],
"description": "<string>"
}
],
"meta": {
"total": 123
}
}
}Authorizations
Query Parameters
Filter by block category (e.g. IMPORT, ENRICH, ACTIONS).
Case-insensitive match on block name/description.