Build with the API
Create and run workflows over the REST API, the same workflows the dashboard builds. The rules an integration or an AI agent needs to get it right.
Everything the visual builder does, the API does too. You build a workflow as a graph of nodes, join them with edges, test it, then publish it. The dashboard and the API read and write the same workflow, so a graph the API creates opens in the canvas, and a workflow a person drew can be edited by the API.
Base URL https://omnidim.io/api/v1 · All routes live under /api/v1/workflows
and /api/v1/workflow-node-types.
The tools, in the order you use them
listWorkflowNodeTypeswithq(for examplewhatsapp) to find node types.getWorkflowNodeTypefor the fields one type needs. Ask fordetail=fullonly when you want the examples.listWorkflowFieldOptionsfor any field of typeref(a number, an integration, a template, an agent). Never guess an id. If it returnsno_options, ask the user for the value.createWorkflowwith aname. It starts with onetriggernode. You may pass a complete firstgraphinstead, in the shapegetWorkflowreturns underdraft.editWorkflowwith therevisionfrom your last read and a list ofops.validateWorkflow, then fix what it reports with anothereditWorkflow.testWorkflowonly after the user agrees (see Test runs are real).publishWorkflow, thenactivateWorkflowordeactivateWorkflow.
Draft and published
- You always edit the draft. A published workflow is never changed in place.
- Real events and webhooks always run the published version. A test runs the draft.
publishWorkflowcopies the draft to the live version and leaves a fresh draft for the next round of edits. It refuses whilevalidateWorkflowhas errors.deactivateWorkflowpauses the live version.activateWorkflowresumes it.
Connecting nodes
An edge runs from one node's output to the next node. Plain nodes have one
output, default. Branching nodes have named outputs, and you must say which.
getWorkflowNodeType lists each type's outputs. A summary:
| Node | Outputs |
|---|---|
conditional | True, False |
switch_condition | each case id, plus else |
time_condition | Yes, No |
photo_analyzer | pass, fail |
voice_ai | Completed, Not Completed |
zoho_bigin | Created, Found, Ambiguous, Failed, Success |
- One output can feed several nodes. They all run.
add_nodewithafterandhandleplaces and connects a node in one step.- Every node except
notesmust be reachable from the trigger, or publish is refused.
Ops
| Op | Fields |
|---|---|
add_node | id, type, config?, label?, position?, after?, handle? |
update_node | id, config (merged; null removes a key), label? |
move_node | id, position {x, y} |
delete_node | id (its edges go too) |
connect | from, to, handle? (default default) |
disconnect | from, to, handle? |
A batch applies completely or not at all. A failing op returns 422 invalid_op
with its op_index, and nothing is saved.
Passing data between nodes
{{ context.<node id>.<field> }} reads an earlier node's output. The engine
also provides {{ context.webhook }} (the trigger payload) and
{{ context.execution_link }}. Only nodes that run earlier have output.
Node ids appear in these references, so pick readable ones: check_interest,
wa_followup, notify_sales. Use lowercase letters, digits and underscores,
starting with a letter. These names are reserved: webhook, execution_id,
execution_link, get, items, keys, values.
Node rules worth knowing
coderuns a sandboxed Python snippet and must assign its output to aresultvariable. A dict inresultbecomes the node's output. Without it, the node has no output.sub_workflowstarts another of your workflows and continues at once. The target must be published and active. A paused target fails the run.wait,wait_for_time,voice_aiandsend_emailpause the run and resume it later. There is no end node: a branch ends at a node with no outgoing edge, and the run finishes when every branch has ended.notesnever connects and never runs. Add one that says what the workflow is for, so the next person to open the canvas understands it.
Credentials
Never put secrets in config when an integration exists. Pick integrations,
numbers and templates from listWorkflowFieldOptions. Secret fields (a Slack
webhook URL, API headers and body params) read back as ********. Send
******** back unchanged and the stored value is kept. If the user has no
matching integration, tell them to connect one in the dashboard first.
Test runs are real
Errors you will see
| Error | Meaning | What to do |
|---|---|---|
409 revision_conflict | The draft changed since you read it, often the user saved in the dashboard | getWorkflow, then retry with the new revision |
422 validation_failed with not_found | An id is not in this workspace | Pick from listWorkflowFieldOptions |
422 validation_failed on publish | Missing fields, an unconnected node, or a bad handle | Fix each one, then publish again |
422 invalid_op | An op is malformed or names a missing node | Fix the op at op_index |
422 validation_failed with templated_ref | An id field holds a {{ }} template | Put a literal id from listWorkflowFieldOptions |
400 no_options | The field holds an id from another system | Ask the user for the value |
Worked example
"After a call, if the lead is interested, send our follow-up WhatsApp template and tell #sales in Slack."
Create
createWorkflow { "name": "Interested lead follow-up" } gives you a trigger
node and a revision.
Find the ids
listWorkflowFieldOptions for whatsapp.phone_number_id, then for
whatsapp.template_id with depends set to that number. Confirm both with the
user.
Build the graph
One editWorkflow batch, using the revision from step 1:
{
"revision": "3f9a1c07be2d",
"ops": [
{"op": "add_node", "id": "check_interest", "type": "conditional", "after": "trigger",
"config": {"conditions": [{"variable": "{{ context.trigger.extracted_variables.interested }}", "operator": "==", "value": "yes"}]}},
{"op": "add_node", "id": "wa_followup", "type": "whatsapp", "after": "check_interest", "handle": "True",
"config": {"phone_number_id": 8265, "message_type": "template", "template_id": 3676, "to_number": "{{ context.trigger.customer_number }}"}},
{"op": "add_node", "id": "notify_sales", "type": "slack", "after": "wa_followup",
"config": {"slack_webhook_url": "<from the user>", "message": "Follow-up sent to {{ context.trigger.customer_number }}"}},
{"op": "add_node", "id": "about", "type": "notes",
"config": {"note_text": "Follows up interested leads after a call."}}
]
}The phone_number_id and template_id are literal ids from step 2. The
to_number and message are free text, so they take {{ }}.
Validate, publish, attach
validateWorkflow, fix anything reported, then publishWorkflow. Tell the
user to point the agent's post-call action at this workflow.
