big docs

Write an automation as steps

The dashboard builds automations as a canvas of connected bricks. That canvas is stored as a node graph, and for a long time the only way to make one programmatically was to hand-assemble it — inventing internal ids, wiring edges between them, numbering branch arms, and supplying canvas coordinates that the builder recomputes anyway.

A step document is the same automation written as a list of steps. It is what POST /api/automations and PUT /api/automations/:id/graph accept as document, what the CLI's rule:create sends, and what an AI assistant connected over MCP writes.

The shape

{
  "format": "big.automation/v1",
  "name": "Follow first → DM your link",
  "trigger": { "type": "dm", "keywords": ["VIP"] },
  "steps": [
    { "id": "ask", "dm": "Follow me and I'll send the link over. Reply once you have." },
    { "id": "check", "branch": {
        "max_attempts": 2,
        "arms": [
          { "when": "is_follower",    "goto": "link"  },
          { "when": "otherwise",      "goto": "nudge" },
          { "when": "loop_exhausted", "goto": "letgo" }
        ]
    }}
  ],
  "paths": {
    "link":  [{ "dm": "You're in. Here's the link: https://example.com" }],
    "nudge": [{ "dm": "I can't see the follow yet — reply again and I'll look.", "goto": "check" }],
    "letgo": [{ "dm": "All good, no hard feelings." }]
  }
}

That is the whole follower gate: ask, check, send or nudge, and give up gracefully.

Steps and paths

steps is the spine. Each step flows into the next one, so a plain keyword → DM automation is two steps and no wiring at all. The last step ends the conversation.

paths are named blocks, reached only by name — from a branch arm or an explicit goto. Each path runs top to bottom and then stops, unless its last step carries a goto.

A branch ends the spine. Everything after a branch is reached through its arms, never by falling through.

The split between the two is deliberate. With one flat list, a step that sends your link and a step that says "I can't see the follow yet" would be connected just because one was written above the other — and that automation would be accepted as valid. Naming the blocks means the document says where someone goes, rather than implying it from the order you happened to type things in.

Steps

A step does exactly one thing:

KeyWhat it does
dmSends a direct message. Add link to attach a URL.
branchWaits for their next reply and routes on it.

And takes these modifiers:

KeyWhereWhat it does
idrequired in stepsThe name other steps aim at. Not used in paths — the path's own name is its entry.
gotoany stepGo here instead of falling through.
choiceson a dmQuick-reply buttons: [{ "label": "✅ Done", "goto": "check" }]. A step with buttons waits for a tap.

Branch arms

Arms are tried in the order you write them:

whenMatches
contains_anyTheir reply contains any of values
equalsTheir reply is exactly value
is_followerThey follow your account — checked live, right then
loop_exhaustedThe re-check ran out of tries
otherwiseEverything else — every branch needs exactly one

max_attempts on a branch makes it a bounded loop: an arm can point back at the branch to re-check, and loop_exhausted is where someone lands when the tries run out. See Branch on replies and followers for what that feels like to the person on the other end.

When something is wrong

You get back the message the dashboard would show, naming your step:

branch node check closes a loop but its "after N tries" (loop_exhausted) edge doesn't leave it

The step names you choose are the names in the errors, so there is nothing to translate.

How many steps one can have

max_actions_per_run is optional and defaults to 20. It is also the node ceiling — an automation may not have more steps than the actions one run is allowed — and a document may not set it above 200.

Reading one back

GET /api/automations/:id returns the automation's document alongside its raw graph, so you can read one, change a message, and send it back.

Some automations can't be written as steps. Cards, carousels and media attachments have shapes the format doesn't cover, and a comment-triggered automation has no document at all — whatever its replies look like — because its trigger needs the ids of your own Instagram posts. Those come back with document: null, exact: false, and a documentReason saying which one it was. The graph is always there either way.

What it deliberately leaves out

Anything specific to one workspace: which Instagram account the automation runs on, which posts a comment trigger watches, which tags it applies. A document is meant to be saved in a file, shared, and run in someone else's workspace — so those stay as separate fields on the request.

Comment triggers, for the same reason: they need the ids of your own Instagram posts. Build those in the dashboard, or send a raw graph.

Next step

The REST API reference has the endpoints, and the CLI reference has rule:create, rule:graph and rule:view.