Workflow API Documentation
A Workflow in ScriptRun is a visual representation of a process or automation. It consists of interconnected nodes (steps, triggers, actions, outputs) and edges (connections between nodes), allowing you to design and execute complex logic flows.
Key Concepts
- Workflow: A schema consisting of nodes and edges that defines the sequence of actions.
- Node: A step in the process (e.g., trigger, LLM request, data processing, output). Each node has a unique UUID identifier.
- Edge: Connects two nodes, defining the direction of the flow.
- Data: The structure describing all nodes, edges, the start node, and viewport parameters.
- WorkflowRun: A single execution instance of a workflow. Has its own UUID, status, and result.
Workflow Data Structure Example
The example below is based on a real workflow: Trigger → If/Else → two LLM branches.
{
"id": 65,
"project": 12,
"title": "Text Classification Workflow",
"data": {
"edges": [
{
"id": "xy-edge__052af1cb-86fd-443a-bc25-626c365025db-0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8",
"type": "custom-edge",
"style": { "stroke": "#76A9FA", "strokeWidth": 2 },
"source": "052af1cb-86fd-443a-bc25-626c365025db",
"target": "0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8",
"animated": false,
"markerEnd": "edge-target",
"markerStart": "edge-source"
},
{
"id": "xy-edge__0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8condition-true-58a35684-7d25-4dff-b012-609eab065751",
"type": "custom-edge",
"style": { "stroke": "#76A9FA", "strokeWidth": 2 },
"source": "0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8",
"target": "58a35684-7d25-4dff-b012-609eab065751",
"animated": false,
"markerEnd": "edge-target",
"markerStart": "edge-source",
"sourceHandle": "condition-true"
},
{
"id": "xy-edge__0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8condition-false-8f6bd336-3d7c-41fe-b94c-d47974c1e056",
"type": "custom-edge",
"style": { "stroke": "#76A9FA", "strokeWidth": 2 },
"source": "0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8",
"target": "8f6bd336-3d7c-41fe-b94c-d47974c1e056",
"animated": false,
"markerEnd": "edge-target",
"markerStart": "edge-source",
"sourceHandle": "condition-false"
}
],
"nodes": [
{
"id": "052af1cb-86fd-443a-bc25-626c365025db",
"type": "trigger-node",
"position": { "x": 250, "y": 0 },
"data": {
"id": "052af1cb-86fd-443a-bc25-626c365025db",
"name": "Trigger",
"label": "Trigger",
"version": "0.1",
"input_fields": [
{
"id": "category",
"title": "Category",
"type": "text",
"input_requirements": true,
"placeholder": "Enter category",
"tooltip": "",
"default_value": "",
"options": []
}
],
"next_nodes": ["0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8"]
}
},
{
"id": "0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8",
"type": "if-else-node",
"position": { "x": 550, "y": 0 },
"data": {
"id": "0a4c9bdb-0378-4426-9e4d-0ba5c6e3e5a8",
"name": "If",
"label": "If",
"version": "0.1",
"condition": {
"left": "{{ input.category }}",
"right": "positive",
"operator": "=="
},
"true_node": "58a35684-7d25-4dff-b012-609eab065751",
"false_node": "8f6bd336-3d7c-41fe-b94c-d47974c1e056",
"next_nodes": [
"58a35684-7d25-4dff-b012-609eab065751",
"8f6bd336-3d7c-41fe-b94c-d47974c1e056"
]
}
},
{
"id": "58a35684-7d25-4dff-b012-609eab065751",
"type": "basic-node",
"position": { "x": 850, "y": -160 },
"data": {
"id": "58a35684-7d25-4dff-b012-609eab065751",
"name": "LLM OpenAI",
"label": "LLMOpenAI",
"version": "0.1",
"messages": [
{
"role": "system",
"message": "You are a helpful assistant. Reply briefly."
},
{
"role": "user",
"message": "The sentiment is positive. Provide a short encouraging response."
}
],
"settings": {
"model_name": "gpt-4o",
"temperature": 0.7
},
"next_nodes": []
}
},
{
"id": "8f6bd336-3d7c-41fe-b94c-d47974c1e056",
"type": "basic-node",
"position": { "x": 850, "y": 136 },
"data": {
"id": "8f6bd336-3d7c-41fe-b94c-d47974c1e056",
"name": "LLM OpenAI",
"label": "LLMOpenAI 1",
"version": "0.1",
"messages": [
{
"role": "system",
"message": "You are a helpful assistant. Reply briefly."
},
{
"role": "user",
"message": "The sentiment is negative. Provide a short empathetic response."
}
],
"settings": {
"model_name": "gpt-4o",
"temperature": 0.7
},
"next_nodes": []
}
}
],
"viewport": { "x": 434, "y": 557, "zoom": 1.0 },
"start_node": "052af1cb-86fd-443a-bc25-626c365025db"
}
}
How to read this example:
start_node— UUID of the first node to execute (Trigger).edges— connections between nodes.source→target. Forif-else-node, thesourceHandlefield indicates which branch (condition-true/condition-false).nodes[].type— node type:trigger-node,if-else-node,basic-node,output-node.nodes[].data.input_fields— fields declared in the Trigger node. These map to{{ input.<field_id> }}variables throughout the workflow.nodes[].data.condition— forif-else-node: comparesleftandrightusingoperator. Supports==,!=,>,<,>=,<=.nodes[].data.messages— for LLM nodes: the prompt messages array (system + user roles).nodes[].data.next_nodes— list of downstream node UUIDs.
API Endpoints
Create a Workflow
import requests
url = "https://scriptrun.ai/api/v1/workflows/"
headers = {"X-Api-Key": "<Your API Key>"}
data = {
"project": 307,
"title": "New workflow",
"data": {} # see structure above
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl -X POST "https://scriptrun.ai/api/v1/workflows/" \
-H "X-Api-Key: <Your API Key>" \
-H "Content-Type: application/json" \
-d '{
"project": 307,
"title": "New workflow",
"data": {}
}'
POST /api/v1/workflows/
Summary: Creates a new workflow.
Request Body:
| Name | Type | Required | Description |
|---|---|---|---|
project | integer | Yes | Project ID to which the workflow belongs. |
title | string | Yes | Name/title of the workflow. |
data | object | Yes | Workflow structure (nodes, edges, viewport) |
Responses:
- 201: Workflow successfully created.
- 400: Invalid input or missing required parameters.
Retrieve Workflows
url = "https://scriptrun.ai/api/v1/workflows/"
headers = {"X-Api-Key": "<Your API Key>"}
params = {"project": 307}
response = requests.get(url, headers=headers, params=params)
print(response.json())
curl -X GET "https://scriptrun.ai/api/v1/workflows/?project=307" \
-H "X-Api-Key: <Your API Key>"
GET /api/v1/workflows/
Summary: Retrieves a list of workflows, optionally filtered by project.
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project | query | integer | No | Filter workflows by project ID. |
title | query | string | No | Filter by workflow title (case-insensitive). |
page | query | integer | No | Page number for pagination. |
Responses:
- 200: Returns a paginated list of workflows.
Retrieve a Specific Workflow
url = "https://scriptrun.ai/api/v1/workflows/6/"
headers = {"X-Api-Key": "<Your API Key>"}
response = requests.get(url, headers=headers)
print(response.json())
curl -X GET "https://scriptrun.ai/api/v1/workflows/6/" \
-H "X-Api-Key: <Your API Key>"
GET /api/v1/workflows/{id}/
Summary: Retrieves the details of a specific workflow by its ID.
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | ID of the workflow to fetch. |
Responses:
- 200: Returns the workflow details.
- 404: Workflow not found.
Update a Workflow
url = "https://scriptrun.ai/api/v1/workflows/6/"
headers = {"X-Api-Key": "<Your API Key>"}
data = {
"title": "Updated Workflow Title",
"data": {}
}
response = requests.patch(url, headers=headers, json=data)
print(response.json())
curl -X PATCH "https://scriptrun.ai/api/v1/workflows/6/" \
-H "X-Api-Key: <Your API Key>" \
-H "Content-Type: application/json" \
-d '{
"title": "Updated Workflow Title",
"data": {}
}'
PATCH /api/v1/workflows/{id}/
Summary: Updates an existing workflow.
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | ID of the workflow to update. |
Request Body:
| Name | Type | Required | Description |
|---|---|---|---|
title | string | No | Updated workflow title. |
data | object | No | Updated workflow structure. |
Responses:
- 200: Workflow successfully updated.
- 404: Workflow not found.
Delete a Workflow
url = "https://scriptrun.ai/api/v1/workflows/6/"
headers = {"X-Api-Key": "<Your API Key>"}
response = requests.delete(url, headers=headers)
print(response.status_code)
curl -X DELETE "https://scriptrun.ai/api/v1/workflows/6/" \
-H "X-Api-Key: <Your API Key>"
DELETE /api/v1/workflows/{id}/
Summary: Deletes a specific workflow by its ID.
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | ID of the workflow to delete. |
Responses:
- 204: Workflow successfully deleted.
- 404: Workflow not found.
Additional Workflow Endpoints
Get Workflow Input Fields
GET /api/v1/workflows/{id}/input-fields/
Summary: Returns the list of input fields defined in the workflow's Trigger node. Use this to discover what parameters the workflow expects before running it.
import requests
url = "https://scriptrun.ai/api/v1/workflows/271/input-fields/"
headers = {"X-Api-Key": "<Your API Key>"}
response = requests.get(url, headers=headers)
print(response.json())
curl -X GET "https://scriptrun.ai/api/v1/workflows/271/input-fields/" \
-H "X-Api-Key: <Your API Key>"
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | ID of the workflow. |
Response (200):
Returns an array of input field objects:
| Field | Type | Description |
|---|---|---|
id | string | Field identifier (used as key in input data). |
title | string | Human-readable field label. |
type | string | Field type (e.g., text, select, number). |
input_requirements | boolean | Whether the field is required. |
placeholder | string | Placeholder text for the field. |
tooltip | string | Helper text shown to the user. |
default_value | string | Default value if none is provided. |
options | array | Available options for select type fields. |
Example Response:
[
{
"id": "user_name",
"title": "User Name",
"type": "text",
"input_requirements": true,
"placeholder": "Enter your name",
"tooltip": "Full name of the user",
"default_value": "",
"options": []
},
{
"id": "language",
"title": "Language",
"type": "select",
"input_requirements": false,
"placeholder": "",
"tooltip": "Output language",
"default_value": "en",
"options": [
{"title": "English", "value": "en", "is_default": true},
{"title": "Russian", "value": "ru", "is_default": false}
]
}
]
Check Workflow Structure
GET /api/v1/workflows/{id}/check_workflow/
Summary: Validates the structure of a saved workflow.
import requests
url = "https://scriptrun.ai/api/v1/workflows/6/check_workflow/"
headers = {"X-Api-Key": "<Your API Key>"}
response = requests.get(url, headers=headers)
print(response.json())
curl -X GET "https://scriptrun.ai/api/v1/workflows/6/check_workflow/" \
-H "X-Api-Key: <Your API Key>"
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | ID of the workflow. |
Responses:
- 200: Validation result returned.
- 404: Workflow not found.
Get Node Schemas for Frontend
GET /api/v1/workflows/get_nodes/
Summary: Retrieves the available node schemas for rendering and configuration in the frontend.
import requests
url = "https://scriptrun.ai/api/v1/workflows/get_nodes/"
headers = {"X-Api-Key": "<Your API Key>"}
response = requests.get(url, headers=headers)
print(response.json())
curl -X GET "https://scriptrun.ai/api/v1/workflows/get_nodes/" \
-H "X-Api-Key: <Your API Key>"
Running a Workflow
POST /api/v1/workflows/{id}/run/
Summary: Starts the execution of a specific workflow. Optionally pass input data to parameterize the run.
Request Format: Two Modes
The /run/ endpoint behaves differently depending on the authentication method used. Both modes are officially supported. There is no planned deprecation of either format.
Mode 1 — API Key (X-Api-Key header): flat body (canonical for API integrations)
When authenticating with an API key, send the input data directly as the request body — either as a raw JSON string or plain text. Do not wrap in input_data.
This is the recommended format for all API integrations.
import requests
url = "https://scriptrun.ai/api/v1/workflows/6/run/"
headers = {
"X-Api-Key": "<Your API Key>",
"Content-Type": "application/json",
}
# Send input fields directly as JSON body
body = '{"user_name": "John Doe", "email": "john@example.com"}'
response = requests.post(url, headers=headers, data=body)
print(response.json())
curl -X POST "https://scriptrun.ai/api/v1/workflows/6/run/" \
-H "X-Api-Key: <Your API Key>" \
-H "Content-Type: application/json" \
-d '{"user_name": "John Doe", "email": "john@example.com"}'
Plain text is also accepted:
curl -X POST "https://scriptrun.ai/api/v1/workflows/6/run/" \
-H "X-Api-Key: <Your API Key>" \
-H "Content-Type: text/plain" \
-d "Hello, process this text"
Mode 2 — JWT (frontend / session): input_data wrapper
When authenticating with JWT (frontend sessions), wrap the input inside input_data:
import requests
url = "https://scriptrun.ai/api/v1/workflows/6/run/"
headers = {"Authorization": "Bearer <JWT Token>"}
data = {
"input_data": {
"user_name": "John Doe",
"email": "john@example.com"
}
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
Summary of request format by auth mode:
| Auth method | Request body format | Content-Type |
|---|---|---|
X-Api-Key header | Raw JSON string or plain text (flat) | application/json or text/plain |
| JWT Bearer token | {"input_data": { ... }} | application/json |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | ID of the workflow to run. |
Response (200)
The endpoint returns HTTP 200 with a JSON body in the following format:
{
"data": {
"id": "0781b175-2135-45ec-9063-ad3e0d929adc",
"status": "created"
},
"status": 201
}
| Field | Type | Description |
|---|---|---|
status | integer | HTTP status code returned by the execution service. 201 = accepted and started. |
data | object | Execution service response body. Contains data.id — the WorkflowRun UUID needed for polling. |
data.id (UUID string) is the WorkflowRun identifier. Save it immediately — you will use it to poll for the result via GET /api/v1/workflows/workflow_result/?id=<uuid>.
The id field is a UUID string (e.g., "0781b175-2135-45ec-9063-ad3e0d929adc"), not an integer. Do not treat it as an integer or compare with workflow IDs.
Error response (workflow structure invalid):
When the workflow has structural errors, the endpoint still returns HTTP 200 but with an error payload:
{
"data": { "errors": ["Node X is missing required field Y"] },
"status": 400
}
Check response["status"] (not HTTP status) to distinguish success from error:
status == 201— workflow run was accepted and is executingstatus == 400orstatus == 422— workflow structure error, run was not started
Polling pattern
import time
import requests
API_KEY = "<Your API Key>"
WORKFLOW_ID = 6
# 1. Start the run
run_resp = requests.post(
f"https://scriptrun.ai/api/v1/workflows/{WORKFLOW_ID}/run/",
headers={"X-Api-Key": API_KEY, "Content-Type": "application/json"},
data='{"user_name": "John Doe"}',
)
run_body = run_resp.json()
if run_body.get("status") != 201:
raise RuntimeError(f"Workflow failed to start: {run_body}")
run_id = run_body["data"]["id"] # UUID string
# 2. Poll until complete
TERMINAL_STATUSES = {"succeed", "failed"}
for _ in range(60):
result_resp = requests.get(
"https://scriptrun.ai/api/v1/workflows/workflow_result/",
headers={"X-Api-Key": API_KEY},
params={"id": run_id},
)
result = result_resp.json()
executor_data = result.get("data", {})
current_status = executor_data.get("status")
if current_status in TERMINAL_STATUSES:
print("Done:", executor_data)
break
time.sleep(3)
else:
print("Timed out")
Get Workflow Run Result
GET /api/v1/workflows/workflow_result/
Summary: Retrieves the current status and result of a workflow run by its UUID. Use this endpoint for polling after calling /run/.
import requests
url = "https://scriptrun.ai/api/v1/workflows/workflow_result/"
headers = {"X-Api-Key": "<Your API Key>"}
params = {"id": "0781b175-2135-45ec-9063-ad3e0d929adc"}
response = requests.get(url, headers=headers, params=params)
print(response.json())
curl -X GET "https://scriptrun.ai/api/v1/workflows/workflow_result/?id=0781b175-2135-45ec-9063-ad3e0d929adc" \
-H "X-Api-Key: <Your API Key>"
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | query | string | Yes | UUID of the workflow run (from /run/ response data.id). |
Full Response Schema
The endpoint returns HTTP 200 with the following structure:
{
"data": {
"id": "0781b175-2135-45ec-9063-ad3e0d929adc",
"status": "succeed",
"data": {
"93e9822b-63a4-4553-adb4-79029db3c09b": {
"id": "93e9822b-63a4-4553-adb4-79029db3c09b",
"status": "succeed",
"result": {
"content": "Hi! How can I help you today?"
}
},
"a7bf1234-0000-0000-0000-000000000001": {
"id": "a7bf1234-0000-0000-0000-000000000001",
"status": "succeed",
"result": {
"content": "{\"key\": \"value\"}"
}
}
},
"metrics": {
"total_cost": 0.0032
}
},
"status": 200
}
Top-level fields:
| Field | Type | Description |
|---|---|---|
status | integer | HTTP status from executor. 200 = response delivered. |
data | object | Executor response body. See nested fields below. |
data object fields:
| Field | Type | Description |
|---|---|---|
id | string | UUID of the workflow run (same as the query parameter). |
status | string | Overall run status. See allowed values below. |
data | object | Per-node results, keyed by node UUID (not node name). |
metrics | object | Optional. Contains total_cost (float, USD) if available. |
Allowed status Values
| Value | Meaning |
|---|---|
"in queue" | Run is queued, execution has not started yet. |
"in progress" | Execution is active. |
"succeed" | Execution completed successfully. Results are ready. |
"failed" | Execution completed with an error. |
"not found" | No run found for this UUID. May be expired or invalid. |
Terminal statuses (stop polling): "succeed", "failed".
Non-terminal statuses (continue polling): "in queue", "in progress".
Per-Node Result Object (data.data.<node-uuid>)
Each node UUID maps to:
| Field | Type | Description |
|---|---|---|
id | string | Node UUID (same as the key). |
status | string | Node-level status. Same values as run-level status. |
result | object | Node output. Present only when node status is succeed. |
result object:
| Field | Type | Description |
|---|---|---|
content | string | Node output content. See result.content formats below. |
Node Order
data.data is a JSON object keyed by node UUID, not an array. Node order is not guaranteed — do not rely on insertion order. To identify specific nodes, use their UUID (obtainable from GET /api/v1/workflows/{id}/ — data.nodes[].id).
Determining the Final Result
To reliably find the workflow's final output:
- Check that overall
data.status == "succeed". - Identify the output node(s) by type (
"output-node") from the workflow'sdata.nodesarray. - Look up those node UUIDs in
data.dataand read theirresult.content.
Do not assume the last node in data.data is the output — iteration order is not stable.
Error Responses
| HTTP Status | Meaning |
|---|---|
400 | Missing id parameter, or workflow run not found / no access. |
401 | Authentication credentials not provided. |
result.content Formats
The result.content field is always a string, but its semantic content varies:
| Format | Example | How to handle |
|---|---|---|
| Plain text | "Hello, world!" | Use directly. |
| JSON string | "{\"key\": \"value\"}" | Parse with json.loads(). |
| Markdown JSON code fence | ```json\n{"key": "value"}\n``` | Strip the fence, then parse JSON. |
import json
import re
def parse_content(content: str):
"""Parse result.content regardless of format."""
# Strip markdown code fence if present
fence_match = re.match(r"```(?:json)?\s*([\s\S]*?)\s*```", content.strip())
if fence_match:
content = fence_match.group(1)
try:
return json.loads(content)
except (json.JSONDecodeError, ValueError):
return content # plain text
The markdown code fence wrapping (```json ... ```) is a known quirk produced by certain LLM nodes. The API does not currently normalize this on the server side. Use the parser above on the client side.
Workflow Run History
GET /api/v1/workflows/{id}/history/
Summary: Retrieves the paginated run history for a specific workflow.
import requests
url = "https://scriptrun.ai/api/v1/workflows/6/history/"
headers = {"X-Api-Key": "<Your API Key>"}
params = {"page_size": 10, "ordering": "-created_at"}
response = requests.get(url, headers=headers, params=params)
print(response.json())
curl -X GET "https://scriptrun.ai/api/v1/workflows/6/history/?page_size=10&ordering=-created_at" \
-H "X-Api-Key: <Your API Key>"
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | ID of the workflow. |
ordering | query | string | No | Sort field. Options: created_at, -created_at, cost, -cost. |
page | query | integer | No | Page number. |
page_size | query | integer | No | Number of items per page. |
Responses:
- 200: Returns a paginated list of workflow run history items.
- 404: Workflow not found.
Execution Update Webhook (internal)
POST /api/v1/workflows/execution-update/{workflow_run_id}/
This endpoint is called by the ScriptRun execution service — not by external API clients. It delivers live status updates and the final result of a workflow run to the Django backend.
Understanding this endpoint helps explain the temporal behavior described below.
Authentication: This endpoint uses an internal service API key (X-Service-Api-Key header). Requests from unauthorized sources are rejected with 403.
Path Parameter:
| Name | Type | Description |
|---|---|---|
workflow_run_id | string | UUID of the workflow run. |
Request Body:
{
"type": "workflow_execution_progress_change",
"payload": {
"id": "0781b175-2135-45ec-9063-ad3e0d929adc",
"status": "succeed",
"data": {
"93e9822b-63a4-4553-adb4-79029db3c09b": {
"id": "93e9822b-63a4-4553-adb4-79029db3c09b",
"status": "succeed",
"result": {
"content": "Hi! How can I help you today?"
}
}
}
}
}
Guaranteed fields in payload:
| Field | Type | Description |
|---|---|---|
id | string | Workflow run UUID. |
status | string | Overall run status: "succeed", "failed", "in progress", "in queue". |
data | object | Per-node results, keyed by node UUID. |
Response:
{ "status": "created", "detail": { "msg": "ok" } }
Temporal Semantics: Polling After Status Update
There is a known timing window between when the execution service marks a run as complete and when GET /workflow_result/ returns fully consistent data.
What this means:
- The run status may transition to
"succeed"or"failed"in the backend database slightly before the result data is fully persisted. - If you poll
workflow_resultimmediately after detecting a"succeed"status,data.datamay be partially populated or empty.
Recommended retry strategy:
- Poll
workflow_resultevery 3–5 seconds while status is non-terminal. - When status becomes
"succeed"or"failed", verify thatdata.datais non-empty and contains the expected output nodes. - If
data.datais empty or missing expected nodes despite a terminal status, wait 2–3 seconds and retry (up to 3 retries). - Only finalize your result after both conditions are true: terminal status AND non-empty
data.data.
import time
import requests
def poll_workflow_result(run_id: str, api_key: str, timeout_seconds: int = 180):
headers = {"X-Api-Key": api_key}
url = "https://scriptrun.ai/api/v1/workflows/workflow_result/"
terminal = {"succeed", "failed"}
deadline = time.time() + timeout_seconds
while time.time() < deadline:
resp = requests.get(url, headers=headers, params={"id": run_id})
body = resp.json()
data = body.get("data", {})
status = data.get("status")
if status in terminal:
node_results = data.get("data", {})
if node_results:
return data # fully consistent result
# Terminal but data not yet available — brief consistency window
time.sleep(2)
continue
time.sleep(3)
raise TimeoutError(f"Workflow run {run_id} did not complete within {timeout_seconds}s")
Workflow Data Structure
- nodes: List of nodes, each containing:
id: Unique node UUID string (e.g."3d63f846-0f6a-478c-b91c-5921259429c1").type: Node type (e.g.,"trigger-node","basic-node","output-node").data: Node parameters (name, settings, schema, messages, etc.).position,measured,selected, etc. — visualization parameters.
- edges: List of connections between nodes, each containing:
id: Unique edge identifier.source: Source node UUID.target: Target node UUID.type,style,markerEnd,markerStart, etc. — visualization parameters.
- viewport: Viewport parameters (x, y, zoom).
- start_node: UUID of the start node (usually the first action or trigger).
Summary
- A Workflow is a visual process schema consisting of nodes and edges.
- The API allows you to create, retrieve, update, and delete workflows.
- Use
GET /api/v1/workflows/{id}/input-fields/to discover what inputs a workflow expects. - Use
POST /api/v1/workflows/{id}/run/to execute a workflow. Saveresponse["data"]["id"]— this is the UUID for polling. - Use
GET /api/v1/workflows/workflow_result/?id=<run_uuid>to poll for results. Poll untildata.statusis"succeed"or"failed". - Use
GET /api/v1/workflows/{id}/history/to view past runs.
Working with Input Data
Overview
When running a workflow via API key, send input data directly in the request body (flat format). Access it in nodes using {{ input.* }} variables.
Key Features:
- ✅ Supports valid JSON objects (flat or nested)
- ✅ Supports JSON arrays
- ✅ Supports plain text (automatically wrapped)
- ✅ Access data in nodes using
{{ input.field_name }}syntax - ✅ Supports nested objects and arrays
Example 1: Object Input Data
import requests
url = "https://scriptrun.ai/api/v1/workflows/6/run/"
headers = {"X-Api-Key": "<Your API Key>", "Content-Type": "application/json"}
import json
body = json.dumps({
"user_name": "John Doe",
"email": "john@example.com",
"age": 30,
"preferences": {
"theme": "dark",
"notifications": True
},
"tags": ["admin", "developer"]
})
response = requests.post(url, headers=headers, data=body)
print(response.json())
curl -X POST "https://scriptrun.ai/api/v1/workflows/6/run/" \
-H "X-Api-Key: <Your API Key>" \
-H "Content-Type: application/json" \
-d '{
"user_name": "John Doe",
"email": "john@example.com",
"age": 30,
"preferences": {
"theme": "dark",
"notifications": true
},
"tags": ["admin", "developer"]
}'
Accessing in nodes:
{{ input.user_name }} → "John Doe"
{{ input.email }} → "john@example.com"
{{ input.age }} → 30
{{ input.preferences.theme }} → "dark"
{{ input.tags[0] }} → "admin"
{{ input.tags[1] }} → "developer"
Example 2: Array Input Data
curl -X POST "https://scriptrun.ai/api/v1/workflows/6/run/" \
-H "X-Api-Key: <Your API Key>" \
-H "Content-Type: application/json" \
-d '{
"data": [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"}
]
}'
Accessing in nodes:
{{ input.data[0].name }} → "Alice"
{{ input.data[1].role }} → "user"
{{ input.data[0].id }} → 1
Example 3: Plain Text Input
curl -X POST "https://scriptrun.ai/api/v1/workflows/6/run/" \
-H "X-Api-Key: <Your API Key>" \
-H "Content-Type: text/plain" \
-d "Hello, this is a simple text input"
Accessing in nodes:
{{ input.__raw_input__ }} → "Hello, this is a simple text input"
Plain text input is automatically wrapped as {"__raw_input__": "..."}. Access via {{ input.__raw_input__ }}.
Adding Variables in the Interface
To add a variable in the workflow node editor in the ScriptRun interface, use double curly braces {{ }}:
Input data variables (values passed when running the workflow via API):
{{ input.field_name }}
{{ input.nested.field }}
{{ input.array[0].field }}
{{ input.__raw_input__ }}
Node output variables (results from other nodes):
Each node has a UUID (visible in the workflow data.nodes[].id). Reference another node's output using:
{{ nodeId.result.content }}
Where nodeId is the UUID of the upstream node. For example:
{{ 93e9822b-63a4-4553-adb4-79029db3c09b.result.content }}
Structure of node output variables:
When the input for a node comes from another node via the API (e.g., when building dynamic chains), the output data from each node is available at:
{{ <nodeId>.result.content }} → string output of that node
{{ <nodeId>.result.<field> }} → other fields in the node result
This is the same structure that appears in workflow_result response under data.data.<nodeId>.result.
Using Input Data in Nodes
In HTTP Node
{
"url": "https://api.example.com/users",
"method": "POST",
"headers": {
"Authorization": "Bearer {{ input.api_token }}",
"Content-Type": "application/json"
},
"body": {
"name": "{{ input.user_name }}",
"email": "{{ input.email }}",
"age": "{{ input.age }}"
}
}
In LLM Node
{
"messages": [
{
"role": "system",
"message": "You are a helpful assistant"
},
{
"role": "user",
"message": "Create a welcome message for {{ input.user_name }} with email {{ input.email }}"
}
]
}
Complex Example: Nested Structures
import json
import requests
body = json.dumps({
"company": {
"name": "Acme Inc",
"departments": [
{
"name": "IT",
"employees": [
{"name": "Alice", "role": "Developer"},
{"name": "Bob", "role": "DevOps"}
]
},
{
"name": "HR",
"employees": [
{"name": "Charlie", "role": "Manager"}
]
}
]
}
})
response = requests.post(
"https://scriptrun.ai/api/v1/workflows/6/run/",
headers={"X-Api-Key": "<Your API Key>", "Content-Type": "application/json"},
data=body,
)
Accessing nested data in nodes:
{{ input.company.name }} → "Acme Inc"
{{ input.company.departments[0].name }} → "IT"
{{ input.company.departments[0].employees[0].name }} → "Alice"
{{ input.company.departments[0].employees[1].role }} → "DevOps"
{{ input.company.departments[1].employees[0].name }} → "Charlie"
Variable Syntax
ScriptRun supports flexible variable syntax for accessing input data:
| Syntax | Description | Example |
|---|---|---|
{{ input.field }} | Access object field | {{ input.user_name }} |
{{ input.nested.field }} | Access nested field | {{ input.preferences.theme }} |
{{ input[0] }} | Access array element by index | {{ input[0] }} |
{{ input.users[0].name }} | Access field in array element | {{ input.users[0].name }} |
{{ input.tags[1] }} | Access nested array element | {{ input.tags[1] }} |
{{ nodeId.result.content }} | Access another node's output | {{ 93e9822b-....result.content }} |
Both array notations are equivalent:
- Dot notation:
{{ input.tags.0 }} - Bracket notation:
{{ input.tags[0] }}
Error Handling
Invalid JSON (API key mode):
- Plain text or invalid JSON is automatically wrapped in
{"__raw_input__": "text"} - Access it via
{{ input.__raw_input__ }}
Missing Fields:
- If a field doesn't exist, it returns an empty value
- Example:
{{ input.nonexistent_field }}→{}
Empty Input:
- If no input is provided, it defaults to
{}
Best Practices
- Use descriptive field names:
user_nameinstead ofn - Validate required fields: Use Trigger node with required input fields
- Keep structure consistent: Use the same data structure for similar workflows
- Test with sample data: Verify your variables work before running in production
- Handle
result.contentdefensively: Always parse it — it may be plain text, JSON, or markdown-wrapped JSON (see result.content formats) - Implement retry with backoff: When polling, use 3–5 second intervals and handle the brief consistency window after terminal status
Responses
- 200: Workflow execution started successfully (check inner
statusfield) - 400: Invalid input data or workflow structure
- 404: Workflow not found