Flows API
List, create and edit flows, start a manual run, and read run history and step detail.
Flows are visual workflows: a trigger followed by conditions and actions. This API lists, creates and edits flows, starts a manual run, and reads run history, which is the way to wire AxisIQ into a deployment pipeline or check that scheduled flows are healthy.
Endpoints
| Method | Path | Permission | What it does |
|---|---|---|---|
GET |
/orgs/{orgId}/flows |
flows:read |
List flows |
POST |
/orgs/{orgId}/flows |
flows:create |
Create a flow (starts disabled) |
GET |
/orgs/{orgId}/flows/{flowId} |
flows:read |
Get a flow |
PUT |
/orgs/{orgId}/flows/{flowId} |
flows:update |
Replace a flow's definition |
DELETE |
/orgs/{orgId}/flows/{flowId} |
flows:delete |
Delete a flow |
POST |
/orgs/{orgId}/flows/{flowId}/run |
flows:run |
Start a manual run |
GET |
/orgs/{orgId}/flows/{flowId}/runs |
flows:read |
Recent runs of one flow |
GET |
/orgs/{orgId}/flows/{flowId}/runs/{runId} |
flows:read |
One run with every step |
GET |
/orgs/{orgId}/flows/runs |
flows:read |
Runs across all flows |
GET |
/orgs/{orgId}/flows/overview |
flows:read |
Counts and trends for the Flows home |
POST |
/orgs/{orgId}/flows/generate |
flows:create |
Draft a flow from a description (not saved) |
Important: A flow runs with the permissions of whoever last changed its definition (nodes, connections, trigger or variables). That is a person or a service account. Enabling, renaming or moving blocks does not change it. A flow can never do something its last editor could not do, and a key that can edit flows can therefore only authorise actions the key itself holds.
The flow object
{
"id": "0190f0cc-…",
"name": "Alert on large invoices",
"description": "",
"enabled": true,
"trigger_kind": "record_event",
"trigger_type": "invoices",
"trigger_event": "created",
"next_run_at": null,
"last_run_at": "2026-10-04T09:30:02Z",
"nodes": [
{ "id": "t", "type": "trigger.record_event", "config": { "object_type": "invoices", "event": "created" }, "position": { "x": 0, "y": 0 } },
{ "id": "c", "type": "logic.condition", "config": { "match": "all", "clauses": [ … ] }, "position": { "x": 240, "y": 0 } }
],
"edges": [ { "id": "e1", "source": "t", "source_handle": "out", "target": "c" } ],
"context": { "threshold": "100000" },
"created_at": "2026-10-01T08:00:00Z",
"updated_at": "2026-10-04T09:00:00Z"
}
trigger_kind is record_event, schedule, manual or calendar. The trigger_*, cron_expr and next_run_at fields are derived from the trigger block; you do not send them.
Blocks (nodes[].type)
A flow has exactly one trigger block and no loops back (it is a directed acyclic graph). source_handle on an edge says which output it leaves from.
| Type | Output handles | config |
|---|---|---|
trigger.record_event |
out |
object_type, event (created, updated or deleted) |
trigger.schedule |
out |
cron (a cron expression, evaluated in UTC) |
trigger.manual |
out |
none. Run it through the run route |
trigger.calendar_event |
out |
calendar_key (blank for any calendar), event (created, updated, cancelled or starting), minutes_before (for starting) |
logic.condition |
true, false |
match (all or any), clauses |
logic.filter |
out |
list, match, clauses |
logic.reduce |
out |
list, op (sum, avg, min, max, count, join), field |
logic.for_each |
loop, done |
list |
action.create_record |
out |
object_type, values (field key to text) |
action.update_record |
out |
object_type, record_id, values |
action.run_function |
out |
function_id, input (key to text) |
action.http_request |
out |
method (GET, POST, PUT, PATCH, DELETE), url, headers, body |
action.send_message |
out |
connection_id, template_id, to, record_type, record_id, to_field, subject, body_text, body_html, vars |
action.create_event |
out |
calendar_key, title, description, location, start, end, duration_minutes, all_day, attendees, record_type, record_id |
Text values in config can contain {{…}} references to the trigger and to earlier blocks, for example {{trigger.record.number}} (a field of the record that fired the flow), {{trigger.record_id}}, {{trigger.input.name}} for a manual run, or {{context.threshold}} for the flow's context variables. The exact shapes are easiest to learn by building a flow in the editor and reading it back with GET /flows/{flowId}.
Outgoing HTTP calls from a flow only reach public internet addresses over http or https, with a 10 second timeout and a 1 MB response limit. A non-2xx answer from the other side is data, not a failure.
POST /orgs/{orgId}/flows
Create a flow. New flows start disabled, so you can build the graph step by step. Permission: flows:create.
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | Yes | |
description |
string | No | |
enabled |
boolean | No | A flow can only be enabled when it is complete and valid |
nodes |
array | No | See above |
edges |
array | No | |
context |
object | No | Named text variables the blocks can reference |
Response 201 with the flow object. Errors: 400 VALIDATION_ERROR with a message naming the problem (unknown block type, no trigger, more than one trigger, a loop, a bad connection handle).
PUT /orgs/{orgId}/flows/{flowId}
Same body as create. It replaces the definition. Permission: flows:update. Response 200 with the flow object.
DELETE /orgs/{orgId}/flows/{flowId}
Returns 204 No Content.
POST /orgs/{orgId}/flows/{flowId}/run
Start a manual run. Permission: flows:run.
| Field | Type | Required | Notes |
|---|---|---|---|
input |
any JSON | No | Available to blocks as trigger.input |
curl -s -X POST https://axisiq.co/api/v1/orgs/$ORG/flows/$FLOW/run \
-H "Authorization: Bearer $AXIS_KEY" -H "Content-Type: application/json" \
-d '{"input": {"invoice": "INV-1001"}}'
{ "data": { "queued": true } }
The run happens in the background, so 202 means accepted, not finished. Poll the runs list for the result. A manual run executes even when the flow is disabled, which makes it a safe way to test. Runs are delivered at least once, so make actions safe to repeat.
Errors: 404 NOT_FOUND (no such flow), 403 FORBIDDEN.
The response status is 202.
GET /orgs/{orgId}/flows/{flowId}/runs
Recent runs of one flow, newest first. Returns an array of run objects (up to 50).
GET /orgs/{orgId}/flows/runs
Runs across every flow, newest first. Cursor paging.
| Parameter | Notes |
|---|---|
status |
ok or failed |
flow |
A flow id |
from |
RFC 3339 time or YYYY-MM-DD: only runs at or after this |
limit |
Page size |
cursor |
The next_cursor of the previous page |
{
"data": {
"runs": [
{
"id": "0190f0dd-…",
"flow_id": "0190f0cc-…",
"flow_name": "Alert on large invoices",
"status": "failed",
"error": "update_record: record not found",
"trigger": { "kind": "record_event", "event": "created", "object_type": "invoices", "record_id": "0190f0aa-…" },
"started_at": "2026-10-04T09:30:02Z",
"finished_at": "2026-10-04T09:30:02Z"
}
],
"next_cursor": "0190f0dc-…"
}
}
next_cursor is null on the last page.
GET /orgs/{orgId}/flows/{flowId}/runs/{runId}
One run including every step:
{
"data": {
"id": "0190f0dd-…",
"flow_id": "0190f0cc-…",
"status": "ok",
"started_at": "2026-10-04T09:30:02Z",
"finished_at": "2026-10-04T09:30:03Z",
"steps": [
{ "node_id": "t", "node_type": "trigger.record_event", "status": "ok", "duration_ms": 0, "started_at": "2026-10-04T09:30:02Z" },
{ "node_id": "a1", "node_type": "action.create_record", "status": "ok", "output": { "id": "0190f0de-…" }, "duration_ms": 41, "started_at": "2026-10-04T09:30:02Z" }
]
}
}
A run's status is ok, failed or running. A step's is ok or failed, with error on a failed step.
GET /orgs/{orgId}/flows/overview
Counts for a dashboard: total, enabled, by_trigger, runs_24h and runs_7d (each {ok, failed}), a daily series, the failing and busiest flows, and upcoming scheduled runs.
POST /orgs/{orgId}/flows/generate
Body {"prompt": "…"}. Returns a proposed name, nodes, edges and context that you can review and save with a normal create. Nothing is saved. Answers 503 AGENT_DISABLED when AI drafting is unavailable.