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.