Skip to content
GuideAPIANT.aiv2

Conditions, loops and parallel branches

View .md

How APIANT automations branch with conditions, repeat steps with for-each loops, run branches in parallel, group steps, and stop processing a record.

Conditions, loops and parallel branches are engine steps that hold other steps inside them. You describe the logic in plain English, such as "if the contact already exists, update it, otherwise create it", and Claude or the assistant builds the structure. None of these steps counts a task; the actions inside them do.

Conditions

A condition step runs its then steps when a test is true and its else steps when it is false. The test is an expression written without {{ }} braces:

json
{
  "step_id": "contact_exists",
  "type": "condition",
  "name": "Contact already exists?",
  "if": "len(steps.find_contact.output.records) > 0",
  "then": [ { "step_id": "update_contact", "type": "assembly" } ],
  "else": [ { "step_id": "create_contact", "type": "assembly" } ]
}

A condition has exactly two branches. There is no switch step; for more than two outcomes, put a condition inside the else branch of another.

Writing the test

TestExample
Compare a valuetrigger.amount > 100
Match texttrigger['Plan'] == "premium"
Check a field is emptysteps.lookup.output.id == ""
Check a list has itemslen(steps.list_rows.output.records) > 0
Combine teststrigger.amount > 100 && trigger.country == "US"
Check whether a step failedsteps.lookup_client.output.error == nil

Field names with spaces use brackets, as in mappings: trigger['Field Name']. Wrapping the whole test in {{ }} is tolerated.

These values count as false: empty text, 0, false, an empty list, an empty object, and a missing value. Everything else counts as true.

When the data is missing

Apps often leave a field out of a response instead of returning it empty. A condition treats a missing field the same way as an empty one for equality and length:

Test on a missing fieldResult
contact.phone == ""true
contact.phone != ""false
len(contact.tags) == 0true
contact.score > 5The whole test is treated as false and the else branch runs
rows[0].status == "ok" when rows is emptyTreated as false, and else runs
rows[5] when rows has three itemsThe step fails

Test for "nothing came back" explicitly with len(...) == 0 when the automation should behave differently in that case.

What a condition outputs

A condition's output field branch is then, else, or none. none means the test was false and the condition has no else branch. To check which way a condition went, test branch == "then", since branch == "else" misses the none case.

Loops

A for-each step runs its do steps once for every item in a list. items is an expression that must produce a list:

json
{
  "step_id": "each_line_item",
  "type": "for_each",
  "name": "Create a line for each order item",
  "items": "trigger.body.line_items",
  "max_iterations": 500,
  "do": [
    {
      "step_id": "create_line",
      "type": "assembly",
      "settings": {
        "SKU": "{{ loop.item.sku }}",
        "Quantity": "{{ loop.item.qty }}",
        "Line Number": "{{ loop.index + 1 }}"
      }
    }
  ]
}

Inside the loop body:

ValueContains
loop.itemThe current item
loop.indexThe position, starting at 0
loop.first, loop.lasttrue on the first or last pass
loop.lengthHow many passes the loop runs, after any cap

Iterations run one after another, in list order. If items is not a list, the step fails.

Limits and stopping early

  • max_iterations caps the number of passes. Leaving it out means no cap.
  • A break loop step inside the body stops the loop. No further items are processed, and the output from the pass that broke is kept.

What a loop outputs

FieldMeaning
iterationsHow many passes ran
totalHow many items the list had, before any cap
truncatedtrue if max_iterations dropped items
broketrue if a break loop step stopped the loop
resultsOne entry per pass that ran, in order: that pass's last step output

Check truncated when a list can exceed the cap. The run still succeeds when items are dropped, so the dropped items are otherwise invisible.

To collect values across passes for use after the loop, use the Variables app: Append to array inside the loop, Get after it.

Parallel branches

A parallel step runs several branches of steps at the same time:

waitBehavior
all (default)Waits for every branch. If any branch fails, the step fails.
anyFinishes when the first branch succeeds. The other branches are cancelled, and the step waits for them to stop before continuing, so a cancelled branch cannot start another write. The step fails only if no branch succeeds.

Each branch starts from the data available before the parallel step. A step in one branch cannot read a step in another branch; a later step after the parallel step can read any branch that completed. Steps that pause the run, such as Snooze or waiting for child automations, are refused inside a parallel branch.

Groups

A group draws a labelled box around a run of steps on the diagram, and can be collapsed. It has no effect on how the steps run: the grouped steps execute in place as if the group were not there.

The diagram of the Docs demo: Order intake automation: a webhook trigger, a script step, a condition with FALSE and TRUE branches, an Array Iterator looping over trigger.body.items with a body step, and a second condition whose TRUE branch snoozes the run.

The diagram shows each condition's branches and each loop's body steps nested under the step that holds them.

Stopping a record

The Flow Control app has two actions that stop the current record without running its remaining steps:

ActionRecord ends asUse
Halt data row processingSuccessSkip a record on purpose, such as a test contact
Halt data row processing with errorError, with your messageStop a record that should be investigated. The message can trigger an alert.

For a polling trigger that returned several records, processing continues with the next record. The remaining steps are skipped at every nesting level, and error handlers do not run for a success halt.

There is no step that stops every record in a run at once.

Running automations one at a time

The Flow Control action Serialize automation execution makes runs that share a group name wait for each other. A run holds the group from that step until the run ends. A run that parks, for example on a snooze, releases the group and takes it again when it resumes.

  • Place the step first in the automation so the whole run is covered.
  • The scope is My account (default) or Linked accounts.
  • A run waits up to 30 minutes for the group. If the wait runs out, the step fails.
  • A run holds the group for at most 60 minutes; a longer run loses the guarantee for its remainder.

Troubleshooting

SymptomLikely causeWhat to do
A condition always takes elseThe test compares a missing field with > or <, or the field name is wrongAsk Claude to compare the test with the step output recorded in the run, and check field names and brackets
A step after a condition reads blankThe step it reads is in the branch that did not runTest the condition's branch, or store the value in a variable in both branches
A loop processed fewer items than expectedmax_iterations capped itCheck truncated and total in the loop's output
A parallel branch reads blank from a sibling branchBranches cannot see each otherRun those steps in sequence instead

Next steps

Related docs

Last updated September 15, 2026