← Files CorezoidARCHIVED FILE
docs/node-structures.md
19.5 KB · Oct 10, 2026 · 06:12 UTC
# Node JSON Structures Reference
Complete JSON structures for all node types used when modifying Corezoid processes.
---
## Node ID Rules
- All node IDs must be **24-character hexadecimal strings**: `^[0-9a-f]{24}$`
- Must be **unique** within the process
- Use descriptive names only in custom string IDs (when the process doesn't enforce hex format)
---
## Start Node (`obj_type: 1`)
```json
{
"id": "507f1f77bcf86cd799439011",
"obj_type": 1,
"condition": {
"logics": [
{
"type": "go",
"to_node_id": "<next_node_id>"
}
],
"semaphors": []
},
"title": "Start",
"description": "",
"x": 600,
"y": 100,
"extra": "{\"modeForm\":\"collapse\",\"icon\":\"\"}",
"options": null
}
```
---
## Code Node (`obj_type: 0`, `type: "api_code"`)
```json
{
"id": "507f1f77bcf86cd799439012",
"obj_type": 0,
"condition": {
"logics": [
{
"type": "api_code",
"lang": "js",
"src": "data.result = data.input * 2;",
"err_node_id": "<error_node_id>"
},
{
"type": "go",
"to_node_id": "<next_node_id>"
}
],
"semaphors": []
},
"title": "Transform Data",
"description": "Describe what this code does",
"x": 500,
"y": 300,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- `lang` must be `"js"` (JavaScript) or `"erl"` (Erlang)
- `err_node_id` is **required** — every Code node must have its own dedicated error node
- Modify the `data` object directly: `data.myParam = value;`
- Use `try/catch` inside `src` for internal error handling
- `console.log()` has no effect — use `data._ = []; data._.push("log")` for debugging
---
## API Call Node (`obj_type: 0`, `type: "api"`)
```json
{
"id": "507f1f77bcf86cd799439018",
"obj_type": 0,
"condition": {
"logics": [
{
"type": "api",
"is_migrate": true,
"rfc_format": true,
"format": "",
"content_type": "application/json",
"method": "POST",
"url": "{{env_var[@api-url]}}/endpoint",
"extra": {
"param1": "{{input_value}}",
"param2": "42"
},
"extra_type": {
"param1": "string",
"param2": "number"
},
"extra_headers": {},
"cert_pem": "",
"max_threads": 5,
"send_sys": true,
"debug_info": false,
"err_node_id": "<error_node_id>",
"customize_response": true,
"response": {
"header": "{{resp_header}}",
"body": "{{resp_body}}"
},
"response_type": {
"header": "object",
"body": "object"
},
"version": 2
},
{
"type": "go",
"to_node_id": "<next_node_id>"
}
],
"semaphors": [
{
"type": "time",
"value": 30,
"dimension": "sec",
"to_node_id": "<timeout_error_node_id>"
}
]
},
"title": "Call External API",
"description": "Sends POST request to external API with prepared parameters",
"x": 500,
"y": 300,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- All fields are required — do not omit any field shown above
- `err_node_id` is **required**
- The `semaphors` timeout is **mandatory** — without it, tasks hang forever if the API is unresponsive
- **Response variable gotcha:** the body is stored under the **second key** in `response`, not under `body`
- With `"body": "{{resp_body}}"` → access as `{{resp_body.field}}` (not `{{body.field}}`)
- With `"data": "{{resp_data}}"` → access as `{{resp_data.field}}`
- POST body fields go in `extra` / `extra_type`
- For GET requests: set `"method": "GET"` and use empty `"extra": {}, "extra_type": {}`
- Never hardcode URLs, tokens, or IDs — use `{{env_var[@name]}}`
---
## Set Parameters Node (`obj_type: 0`, `type: "set_param"`)
```json
{
"id": "507f1f77bcf86cd799439019",
"obj_type": 0,
"condition": {
"logics": [
{
"type": "set_param",
"extra": {
"output_var": "{{response.field}}",
"full_name": "{{first_name}}_{{last_name}}",
"total": "$.math({{price}}*{{qty}})",
"timestamp": "$.date()"
},
"extra_type": {
"output_var": "string",
"full_name": "string",
"total": "number",
"timestamp": "string"
},
"err_node_id": "<error_node_id>"
},
{
"type": "go",
"to_node_id": "<next_node_id>"
}
],
"semaphors": []
},
"title": "Prepare Parameters",
"description": "Extracts and transforms fields from the API response",
"x": 500,
"y": 300,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- Prefer `set_param` over `api_code` (Code node) for simple transformations — it is native and has zero overhead
- `err_node_id` is **required**
- Built-in functions:
- `$.math({{a}}+{{b}})` — arithmetic (exactly 2 operands; nest for more complex expressions)
- `$.date()` — current Unix timestamp
- `$.random()` — random value
- `$.md5_hex()`, `$.sha256_hex()` — hashing
- String concatenation: `"full": "{{first}}_{{last}}"`
- Dynamic key access: `"value": "{{response.{{currency_code}}}}"`
- Use `api_code` (Code node) only when you need: array indexing, `.length`, regex, JSON parsing, complex conditionals
---
## Async Call (Fire-and-Forget) Node (`obj_type: 0`, `type: "api_copy"`)
```json
{
"id": "507f1f77bcf86cd799439020",
"obj_type": 0,
"condition": {
"logics": [
{
"type": "api_copy",
"conv_id": "@process-alias",
"ref": "{{unique_ref}}",
"mode": "create",
"is_sync": false,
"data": {
"field1": "{{value1}}",
"field2": "{{value2}}"
},
"data_type": {
"field1": "string",
"field2": "string"
},
"err_node_id": "<error_node_id>"
},
{
"type": "go",
"to_node_id": "<next_node_id>"
}
],
"semaphors": []
},
"title": "Notify Analytics",
"description": "Fire-and-forget: sends event to analytics process without waiting for result",
"x": 500,
"y": 500,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- Does NOT wait for the target process to finish — continues immediately after sending
- Use for async fan-out, logging, notifications, background processing
- Use `api_rpc` instead when you need the output from the called process
- `conv_id`: use `@alias` (preferred) or numeric process ID
- `data` / `data_type` instead of `extra` / `extra_type` (unlike `api_rpc`)
- `err_node_id` is **required**
---
## Condition Node (`obj_type: 0`, `type: "go_if_const"`)
```json
{
"id": "507f1f77bcf86cd799439021",
"obj_type": 0,
"condition": {
"logics": [
{
"type": "go_if_const",
"to_node_id": "<success_node_id>",
"conditions": [
{"param": "status", "const": "active", "fun": "eq", "cast": "string"},
{"param": "age", "const": "18", "fun": "ge", "cast": "number"}
]
},
{
"type": "go_if_const",
"to_node_id": "<pending_node_id>",
"conditions": [
{"param": "status", "const": "pending", "fun": "eq", "cast": "string"}
]
},
{
"type": "go",
"to_node_id": "<default_node_id>"
}
],
"semaphors": []
},
"title": "Check Status",
"description": "Routes task based on status and age: active+adult → success, pending → pending handler, else → default",
"x": 500,
"y": 300,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- Multiple `conditions` items within one `go_if_const` = **AND** logic (all must match)
- Multiple `go_if_const` entries in a node = **OR / if-else-if** (first match wins)
- Must end with a fallback `go` as the last logic entry
- `fun` values: `eq`, `ne`, `not_eq`, `gt`, `lt`, `ge`, `le`, `regex`
- `cast` values: `"string"`, `"number"`, `"boolean"`
- `param`, `const`, `fun`, `cast` are on `conditions` items, NOT on the `go_if_const` object
- `obj_type: 0` is for **business-flow** conditions reached via `go`/`go_if_const`.
A condition that is an `err_node_id` target (e.g. the retry/fatal switch of an
error cluster) is an **escalation** and must be `obj_type: 3` — see the
Escalation Node section.
- A condition node contains **only** `go_if_const` logics plus the fallback `go`.
Never mix an action logic (`set_param`, `api_code`, `api`, …) with `go_if_const`
in one node — that is the legacy "old format": the UI shows
*"Convert process to new format"* on open and force-splits the node in two.
Author it split from the start: action node + `go` → separate condition node.
---
## Call a Process Node (`obj_type: 0` or Stub-enabled `obj_type: 4`, `type: "api_rpc"`)
```json
{
"id": "507f1f77bcf86cd799439013",
"obj_type": 0,
"condition": {
"logics": [
{
"type": "api_rpc",
"conv_id": 9876543,
"err_node_id": "<error_node_id>",
"extra": {
"param1": "value1",
"param2": "42",
"param3": "{\"name\":\"John\",\"age\":30}"
},
"extra_type": {
"param1": "string",
"param2": "number",
"param3": "object"
},
"group": "all"
},
{
"type": "go",
"to_node_id": "<next_node_id>"
}
],
"semaphors": []
},
"title": "Call Payment Process",
"description": "Calls the payment validation process with customer_id and amount",
"x": 500,
"y": 500,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- `type` must be `"api_rpc"` — NOT `"call_process"`
- `conv_id`: use `@alias` (preferred) or numeric process ID (can also be `"{{param}}"` for dynamic)
- `extra` and `extra_type` are **required** (use `{}` if no params needed)
- Keys in `extra` and `extra_type` must **match exactly**
- Object values in `extra` must be stringified: `"{\"key\":\"value\"}"`
- `group: "all"` sends all current task parameters to the called process;
`group: ""` sends only fields listed in `extra` / `extra_type`.
- `err_node_id` is **required**
- Stub/mock replies configured in the UI are stored under `condition.stub`.
Stub Mode is active only when the Call a Process node is saved as
`obj_type: 4`; with `obj_type: 0`, the runtime calls the real target process.
Stub conditions are evaluated against the parameters sent to the called
process, so use `group: "all"` or explicitly map the fields through `extra`.
Stub branches return `api_rpc_reply`; optional `go_if_const` branches inside
the stub do not use `to_node_id` because they select a mock reply, not a graph
route.
- Treat active Stub Mode as a temporary development/integration mock. It
bypasses the real called process. `push-process` shows it as a warning on a
resolved mutable non-production-like stage, but blocks it on immutable,
production-like, or unknown stages unless `allow_active_stub_mode=true` is
passed after explicit confirmation.
---
## Reply to Process Node (`obj_type: 0` in business flow / `obj_type: 3` as err target, `type: "api_rpc_reply"`)
A success reply reached via `go` is a normal `obj_type: 0` node. An error reply
that is an `err_node_id` target is an escalation and must be `obj_type: 3`
(see the Escalation Node section — an `obj_type: 0` err target triggers the
UI's "Convert process to new format").
### Success reply
```json
{
"id": "507f1f77bcf86cd799439014",
"obj_type": 0,
"condition": {
"logics": [
{
"type": "api_rpc_reply",
"mode": "key_value",
"res_data": {
"result": "success",
"payload": "{{response_data}}"
},
"res_data_type": {
"result": "string",
"payload": "object"
},
"throw_exception": false
},
{
"type": "go",
"to_node_id": "<final_node_id>"
}
],
"semaphors": []
},
"title": "Reply Success",
"description": "Returns successful result to calling process",
"x": 500,
"y": 700,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
### Error reply
```json
{
"id": "507f1f77bcf86cd799439015",
"obj_type": 3,
"condition": {
"logics": [
{
"type": "api_rpc_reply",
"mode": "key_value",
"res_data": {
"result": "error",
"error_message": "{{error_message}}"
},
"res_data_type": {
"result": "string",
"error_message": "string"
},
"throw_exception": true,
"exception_reason": "Describe why this error is thrown"
},
{
"type": "go",
"to_node_id": "<error_end_node_id>"
}
],
"semaphors": []
},
"title": "Reply Error",
"description": "Returns error to calling process",
"x": 800,
"y": 700,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- `mode` must be `"key_value"` (object) or `"keys"` (array) — must be consistent with `res_data` format
- `res_data` and `res_data_type` keys must **match exactly**
- `throw_exception: true` routes the caller to its `err_node_id` path
- `exception_reason` — include on error replies to describe the failure cause
- Object values must be properly stringified with escaped quotes
---
## Escalation Node (`obj_type: 3`)
Escalation nodes handle errors that require active logic: replying to the calling process,
conditional retry routing, or performing any action before terminating.
```json
{
"id": "507f1f77bcf86cd799439022",
"obj_type": 3,
"condition": {
"logics": [
{
"type": "api_rpc_reply",
"mode": "key_value",
"res_data": {
"description": "{{__conveyor_api_return_description__}}"
},
"res_data_type": {
"description": "string"
},
"throw_exception": true,
"exception_reason": "API call failed or timed out"
},
{
"type": "go",
"to_node_id": "<error_final_node_id>"
}
],
"semaphors": []
},
"title": "Error Handler",
"description": "Returns error reply to calling process when API call fails",
"x": 800,
"y": 300,
"extra": "{\"modeForm\":\"expand\",\"icon\":\"\"}",
"options": null
}
```
**Rules:**
- `obj_type: 3` marks this node as an escalation node
- Must always end with `go` routing to a Final Error node (`obj_type: 2`)
- `{{__conveyor_api_return_description__}}` is the system variable that captures the error message from the failed node
- API Call nodes also need a separate timeout escalation node via `semaphors`
**When to use an escalation node vs. a direct final node:**
Use an escalation node (`obj_type: 3`) only when the error path needs actual logic:
- Replying to the calling process (`api_rpc_reply` with `throw_exception: true`)
- Conditional routing based on error type (`go_if_const` on `__conveyor_*_return_type_tag__`)
- Any other action before terminating
Wire `err_node_id` **directly to a Final Error node** (`obj_type: 2`) when the error path
simply needs to terminate with no additional logic:
```
[Functional Node] --err_node_id--> [Final Error obj_type:2] ✓ no logic needed
[Functional Node] --err_node_id--> [Escalation obj_type:3] ✓ reply / routing needed
└──go──> [Final Error obj_type:2]
[API Call Node] --semaphor-----> [Timeout Escalation obj_type:3] --go--> [Timeout Final obj_type:2]
```
**An `err_node_id` (or a count semaphor's `esc_node_id`) must never point at an
`obj_type: 0` node.** The *direct* escalation target — the error Reply, or the
retry/fatal Condition an error escalates into — carries `obj_type: 3`. Nodes the
cluster then reaches through ordinary routing (the retry **Delay** reached from
that Condition via `go_if_const`) are not escalation targets and stay
`obj_type: 0`, like any business-flow node reached via `go`. An `obj_type: 0`
escalation target is the legacy "old format": the process still deploys, but the
UI shows *"Convert process to new format"* on open and rewrites the node to
`obj_type: 3` itself. `lint-process` flags this as an OLD FORMAT NODE.
**Anti-pattern — passthrough escalation (flagged by `lint-process`):**
An escalation node with only a bare `go` and no action logic is pointless clutter.
Replace it with a direct `err_node_id` pointer to the final node:
```json
// ✗ wrong — empty escalation node, no logic inside
{
"id": "...",
"obj_type": 3,
"condition": {
"logics": [{"type": "go", "to_node_id": "<final_error_id>"}],
"semaphors": []
}
}
// ✓ correct — err_node_id points straight at the final error node
"err_node_id": "<final_error_id>"
```
---
## End Node / Final Node (`obj_type: 2`)
### Success end
```json
{
"id": "507f1f77bcf86cd799439016",
"obj_type": 2,
"condition": {
"logics": [],
"semaphors": []
},
"title": "Process Completed",
"description": "",
"x": 600,
"y": 900,
"extra": "{\"modeForm\":\"collapse\",\"icon\":\"success\"}",
"options": "{\"save_task\":true}"
}
```
### Error end
```json
{
"id": "507f1f77bcf86cd799439017",
"obj_type": 2,
"condition": {
"logics": [],
"semaphors": []
},
"title": "Process Failed",
"description": "",
"x": 800,
"y": 900,
"extra": "{\"modeForm\":\"collapse\",\"icon\":\"error\"}",
"options": "{\"save_task\":true}"
}
```
---
## extra / extra_type Rules
These rules apply to **all** nodes that use `extra`/`extra_type` or `res_data`/`res_data_type`:
| Value type | `extra` value | `extra_type` value |
|------------|---------------|--------------------|
| String | `"hello"` | `"string"` |
| Number | `"42"` | `"number"` |
| Boolean | `"true"` | `"boolean"` |
| Object | `"{\"key\":\"val\"}"` | `"object"` |
| Array | `"[1,2,3]"` | `"array"` |
| Dynamic ref | `"{{param_name}}"` | `"string"` |
| Dynamic object | `"{\"id\":\"{{user_id}}\"}"` | `"object"` |
**Critical:** Object and array values must always be stringified JSON with escaped quotes. Never use raw JSON objects as values in `extra`.
---
## Node Positioning Guidelines
Nodes are arranged in vertical lanes:
| Lane | x position | Contents |
|------|-----------|---------|
| Main flow (happy path) | x ≈ 1000 | Start, logic nodes, Reply Success, Final Success |
| Timeout handlers | x ≈ 740–800 | Timeout Escalation, Timeout Final |
| Error handlers | x ≈ 1150–1264 | Error Escalation, Error Final |
- Vertical step: 160–200px between nodes in the same lane
- Start and Final nodes: `x = center_of_lane + 100` (offset due to center pivot point)
- Expand logic nodes (`modeForm: "expand"`), collapse Start and Final (`modeForm: "collapse"`)
- No overlapping nodes — each needs its own `y` position
Example coordinates for a typical process with API call:
```
Start: x=1100, y=100
Code/Prepare node: x=1000, y=300
API Call node: x=1000, y=500 → err_node_id → Error Escalation
Reply Success: x=1000, y=700
Final (success): x=1100, y=900
Timeout Escalation: x=800, y=500 (semaphor target of API Call)
Timeout Final: x=800, y=700
Error Escalation: x=1264, y=500 (err_node_id target of API Call)
Error Final: x=1264, y=700
```
For simpler processes without timeout handling:
```
Start: x=600, y=100
Code/Logic node: x=500, y=300
Call Process node: x=500, y=500
Reply Success: x=500, y=700
Final (success): x=600, y=900
Error node: x=800, y=300 (right of its parent)
Reply Error: x=800, y=500
Final (error): x=900, y=700
```
SHA-256: 71ef477342cd6e77129c7e71f0338959203d8432cfbc76379802cbfa60837641