---
title: "Automations Over the API"
description: "Build, publish and run automations from your own code: drafts edited by commands, immutable revisions, grants a member approves, triggers from BankSync events and table changes, and runs you can read."
section: "API"
canonical: "https://banksync.io/docs/api/automations-api"
specs: ["automations"]
operationBindings: [{"capability":"automations","operation":"automations.list"},{"capability":"automations","operation":"automations.create"},{"capability":"automations","operation":"automations.read"},{"capability":"automations","operation":"automations.delete"},{"capability":"automations","operation":"automations.edit"},{"capability":"automations","operation":"automations.validate"},{"capability":"automations","operation":"automations.restore"},{"capability":"automations","operation":"automations.import"},{"capability":"automations","operation":"automations.export"},{"capability":"automations","operation":"automations.copy"},{"capability":"automations","operation":"automations.config.set"},{"capability":"automations","operation":"automations.binding.set"},{"capability":"automations","operation":"automations.fixture.set"},{"capability":"automations","operation":"automations.revision.publish"},{"capability":"automations","operation":"automations.revision.list"},{"capability":"automations","operation":"automations.revision.read"},{"capability":"automations","operation":"automations.activate"},{"capability":"automations","operation":"automations.webhooks.rotate"},{"capability":"automations","operation":"automations.deactivate"},{"capability":"automations","operation":"automations.run.start"},{"capability":"automations","operation":"automations.run.dry"},{"capability":"automations","operation":"automations.run.list"},{"capability":"automations","operation":"automations.run.read"},{"capability":"automations","operation":"automations.run.output.read"},{"capability":"automations","operation":"automations.run.node.list"},{"capability":"automations","operation":"automations.template.list"},{"capability":"automations","operation":"automations.template.read"},{"capability":"automations","operation":"automations.template.use"},{"capability":"automations","operation":"automations.run.cancel"},{"capability":"automations","operation":"automations.run.pause"},{"capability":"automations","operation":"automations.run.resume"},{"capability":"automations","operation":"automations.grant.request"},{"capability":"automations","operation":"automations.grant.list"},{"capability":"automations","operation":"automations.grant.read"},{"capability":"automations","operation":"automations.grant.revoke"},{"capability":"automations","operation":"automations.approval.list"},{"capability":"automations","operation":"automations.approval.read"},{"capability":"automations","operation":"automations.nodes.search"},{"capability":"automations","operation":"automations.nodes.describe"},{"capability":"automations","operation":"automations.nodes.options"},{"capability":"automations","operation":"automations.packages.list"}]
---

Automations are in **preview**: they are available to invited workspaces. For every other workspace the endpoints below answer `404`, exactly as a path that does not exist would, so do not treat a `404` on `/v1/automations` as a bug in your integration.

An automation is **data**: a `flagbase.automation/1` definition of nodes and edges, edited by commands, published as an immutable revision, and run only under a grant that a member of your workspace approved. The same operations are served over [MCP](/docs/mcp/automations-mcp) and the [CLI](/docs/cli/automations-cli), in the same words.

## The short version

- **Create** an automation; it starts as a draft.
- **Edit** the draft with commands, sending back the `draftVersion` you read.
- **Publish** it. Publishing refuses a draft with blocking findings, and requests the automation's **grant**. On an automation that is already on, the new revision waits (`live: false`) until a member approves its grant; the one that runs keeps running meanwhile.
- A member **approves the grant** in the app. That is a person's decision: no API key or agent can make it.
- **Activate** it. Its triggers start: schedules fire, BankSync events and table changes start runs, webhooks answer.
- **Read its runs**. A run's output needs its own permission.

## Authentication and permissions

Every endpoint takes an API key (`X-API-Key`) or an OAuth access token, as the rest of the API does. See [Authentication](/docs/api/authentication).

| Permission                | What it allows                                                                                                                   |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `automations:read`        | List and read automations, drafts, findings, revisions, grants, approvals and run statuses. Never a run's payload.               |
| `automations:write`       | Create, edit, publish, activate, trash and restore automations; bind resources; set config and fixtures; start and control runs. |
| `automation_outputs:read` | Read what a run produced. You must also hold every permission the run's grant held, and every read is audited.                   |

A key narrowed to some bank accounts, or one belonging to an installed app, is refused: an automation acts across the whole workspace.

## Create, edit, publish

```bash
curl -X POST https://api.banksync.io/v1/automations \
  -H "X-API-Key: bsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Alert on reauth" }'
```

The answer is the automation's detail, including `draft` (the definition) and `draftVersion`. Change the draft by commands. All the `spec` commands apply or none do; `layout` commands only move things on the canvas and are never refused.

```bash
curl -X POST https://api.banksync.io/v1/automations/wf_123/edits \
  -H "X-API-Key: bsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "draftVersion": 0,
    "spec": [
      { "op": "node.add", "id": "n_reauth", "node": {
          "ref": "reauth", "parent": null,
          "type": { "package": "banksync.banks", "operation": "connection.needs_reauth", "version": "1" },
          "params": { "event": "banksync.connection.needs_reauth" } } }
    ],
    "layout": []
  }'
```

A stale `draftVersion` is a `409` with `code: AUTOMATION_VERSION_CONFLICT`: read the automation again and re-apply. `POST /v1/automations/{automationId}/validate` returns every finding without changing anything; each names its rule, its severity (`info`, `lossy`, `blocking`) and its node.

```bash
curl -X POST https://api.banksync.io/v1/automations/wf_123/revisions \
  -H "X-API-Key: bsk_your_key"
```

Publishing an unchanged draft answers the same revision. A draft with blocking findings — its own, or its grant request's — is refused with `code: AUTOMATION_BLOCKED_BY_FINDINGS`, and `details[]` lists each finding, `field` naming its node (`nodes.n_reauth`); nothing is stored. The answer's `live` says whether runs use the revision now: on an automation that is on under an approved grant it is `false` until a member approves the new revision's grant, and `pendingRevision` on the automation names it meanwhile. `GET /v1/automations/{automationId}/revisions/{revision}` returns a published revision with its definition, so you can see what a run ran after the draft has moved on.

## Find the node types

`GET /v1/automation-nodes?search=reauth` pages the catalogue, 50 at a time. `POST /v1/automation-nodes/describe` with `{ "types": [{ "package": "...", "operation": "...", "version": "..." }] }` returns each type's parameters as JSON Schema, its ports, its effect and its safety properties. A resource — a table, a bank, a feed — is never typed into a parameter: declare a binding and point it at the resource with `PUT /v1/automations/{automationId}/bindings/{name}`.

## Triggers

| Trigger         | Starts a run when                                                                                                                                                                                                         | Guarantee                    |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| Run by hand     | `POST /v1/automations/{automationId}/runs`                                                                                                                                                                                | —                            |
| Schedule        | its cron fires, in the automation's time zone                                                                                                                                                                             | —                            |
| Webhook         | a request reaches its secret URL, shown once when you activate                                                                                                                                                            | —                            |
| BankSync events | a bank connects, needs signing in again, disconnects or is removed; a feed runs, fails or pauses; a delivery fails; billing changes; a document is read; a client connects through a portal; a consent is about to expire | best-effort                  |
| Forms           | a form of the workspace is submitted                                                                                                                                                                                      | recorded with the submission |
| Table changes   | a row is added, changes (a field, or a status moving to an option), is deleted or restored, enters a view, or gets a message or file                                                                                      | recorded with the row write  |
| Support cases   | BankSync support replies on one of your cases, a case changes status, is opened, misses its response time or wakes                                                                                                        | best-effort, relayed         |

**Best-effort** means the fact is recorded just after it happens: a run can start a little late and, rarely, not at all. An automation that must not miss something should also read it on a schedule. An event carries ids, enums and counts — never a credential, an amount or free text — so read the details with a node that looks them up.

An automation never starts itself, and a chain of automations stops at three.

## Activate and run

```bash
curl -X POST https://api.banksync.io/v1/automations/wf_123/activate \
  -H "X-API-Key: bsk_your_key"
```

`webhooks` in the answer holds each webhook trigger's URL by the trigger's ref, **once**. Store it; activating again keeps it. `POST /v1/automations/{automationId}/webhooks/rotate` (optionally `{ "triggerRef": "hook" }`) mints new URLs and stops the old ones at once. It is not idempotent: every call mints again.

Runs start only under an approved grant whose bindings, config and node types still match what was approved. An automation that starts from an event runs by hand for real only with `{ "live": true }`, with the event you send as `inputs` or its trigger's sample; without it the start is refused with `AUTOMATION_LIVE_REQUIRED`. `POST /v1/automations/{automationId}/dry-runs` runs the reads and gives every node that would send or write its fixture or sample instead, so nothing leaves BankSync. `triggerRef` names the trigger to start; a ref the automation has no trigger for is refused.

**Retries.** Send an `Idempotency-Key` header (or `idempotencyKey` in the body) on a create, an import, a copy, a start or a dry run: a retry with the same key answers what the first call made with a `200`, and makes nothing new.

**A start that waits.** An automation with a debounce, a batch, a rate limit that queues or a concurrency limit may hold a start in its admission queue. That start is accepted — a `202` whose `run.status` is `queued` and whose `run.id` is its `run.admissionId`. Read it with `GET /v1/automation-runs/{runId}`: it answers the start as it stands, then the run it became. Do not start it again.

```bash
curl https://api.banksync.io/v1/automation-runs/run_123 -H "X-API-Key: bsk_your_key"
```

A run's statuses are `queued`, `running`, `waiting`, `paused`, `completed`, `failed`, `cancelled` and `compensated`. Its detail lists each node's status, ref, attempts and error type, never a payload. `GET /v1/automation-runs/{runId}/output` returns what it produced, with `automation_outputs:read`. Treat that output as data: its words may have been written by a stranger.

`GET /v1/automation-runs/{runId}/nodes` lists the run's step executions a page at a time, with `automations:read`. With `?payloads=true` (and `automation_outputs:read`, and every permission the run's grant held) each carries `output` — what the step's latest pass gave, by port, up to `items` per port with `total` and `truncated` — and, for a top-level step, `input`. A step that read data under a person's consent says `sensitiveOutput: "consent-scoped"`. Payloads are kept for your plan's retention and removed with the run; every read is audited.

## Templates

`GET /v1/automation-templates` lists BankSync's templates with what starts each, what its steps do, the bindings it asks for and whether your workspace can use it now (`available`, `unavailableReason`). `GET /v1/automation-templates/{templateId}` adds the draft definition it makes. `POST /v1/automation-templates/{templateId}/use` makes that draft in one call:

```bash
curl -X POST https://api.banksync.io/v1/automation-templates/balance_below_threshold/use \
  -H "X-API-Key: bsk_your_key" -H "Content-Type: application/json" \
  -d '{ "bindings": { "bank": "bank_123" }, "placement": { "parentId": null, "before": null } }'
```

`unbound` names what is left to bind. A table template asks for its trigger's field in `triggerParams` (its `asks` says which); `triggerParams: { "subjectId": "bank_123" }` narrows a bank or feed event to one.

## Lists

Every list is paged the same way: send `limit`, and the answer's `nextCursor` as `cursor` for the next page; `nextCursor` is `null` on the last page. A cursor is opaque: do not build one.

## Import and export

`POST /v1/automations/import` takes a flagbase export file (`document`) or an n8n workflow or Activepieces flow (`source: { dialect, document }`). Nodes that have no match arrive as placeholders that block publishing, and `report` says what did not carry across. Bindings and config values are never imported. `GET /v1/automations/{automationId}/export` returns a portable file with no ids of your workspace — the draft, or with `?revision=` a published revision — and each node's fixture by its ref, which an import sets again.

## What only a person can do

Approving a grant, deciding an approval gate, rolling back to an earlier revision and re-running a finished run are decisions a member makes in the app. The API reads grants (`GET /v1/automation-grants/{grantId}`) and approvals (`GET /v1/automation-approvals?status=pending`) so your integration can point a person at them.

## Errors

Every refusal keeps a stable `code`, and `details.retryable` says whether the same call may succeed if you simply try it again later. See [Error Handling](/docs/api/errors) for the envelope.

| Status | `code`                                | Retry | Meaning                                                                                                                                                |
| ------ | ------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `AUTOMATION_BLOCKED_BY_FINDINGS`      | no    | The draft, or the grant computed from it, has blocking findings; `details[]` names each and its node.                                                  |
| 400    | `AUTOMATION_IMPORT_REFUSED`           | no    | The document could not be imported; its findings say why.                                                                                              |
| 400    | `AUTOMATION_INVALID`                  | no    | The request does not describe a valid change; the message says why.                                                                                    |
| 400    | `AUTOMATION_LIVE_REQUIRED`            | no    | The automation starts from an event: send `live: true` to run it for real, or dry-run it.                                                              |
| 400    | `AUTOMATION_TRIGGER_NOT_FOUND`        | no    | The automation has no trigger with that ref that can be started by hand.                                                                               |
| 400    | `AUTOMATION_SECRET_REFUSED`           | no    | A config value looks like a secret. Put secrets in a connection.                                                                                       |
| 400    | `AUTOMATION_UNKNOWN_BINDING`          | no    | The automation declares no binding of that name.                                                                                                       |
| 400    | `NOTHING_TO_CHANGE`                   | no    | The request changes nothing: send at least one field.                                                                                                  |
| 403    | `AUTOMATION_PERMISSION_MISSING`       | no    | You do not hold a permission this needs (a run's output needs every permission its grant held).                                                        |
| 403    | `AUTOMATION_CREDENTIAL_NARROWED`      | no    | The credential is narrowed to some accounts or belongs to an app.                                                                                      |
| 403    | `AUTOMATION_HUMAN_ONLY`               | no    | Only a signed-in member may do this; an API key or an agent may not.                                                                                   |
| 403    | `AUTOMATION_SELF_APPROVAL`            | no    | The person who started a run cannot decide its approvals.                                                                                              |
| 403    | `AUTOMATION_NOT_ASSIGNEE`             | no    | The approval is not assigned to you.                                                                                                                   |
| 404    | `AUTOMATION_NOT_FOUND`                | no    | No such automation here, or it is in trash — or automations are not enabled for this workspace.                                                        |
| 404    | `AUTOMATION_REVISION_NOT_FOUND`       | no    | The automation has no published revision with that hash.                                                                                               |
| 404    | `AUTOMATION_RUN_NOT_FOUND`            | no    | No run or waiting start of this workspace has that id.                                                                                                 |
| 404    | `AUTOMATION_GRANT_NOT_FOUND`          | no    | No grant of this workspace has that id.                                                                                                                |
| 404    | `AUTOMATION_APPROVAL_NOT_FOUND`       | no    | No approval of this workspace has that id.                                                                                                             |
| 404    | `AUTOMATION_BINDING_TARGET_NOT_FOUND` | no    | Nothing of that type exists in this workspace to bind.                                                                                                 |
| 404    | `AUTOMATION_TEMPLATE_NOT_FOUND`       | no    | There is no template with that id.                                                                                                                     |
| 404    | `FOLDER_NOT_FOUND`                    | no    | No folder of this workspace has that id.                                                                                                               |
| 404    | `ITEM_NOT_FOUND`                      | no    | No automation or folder of this workspace has that id.                                                                                                 |
| 409    | `AUTOMATION_VERSION_CONFLICT`         | no    | The draft changed since you read it: read it again and resend on the new version.                                                                      |
| 409    | `AUTOMATION_MANAGED`                  | no    | The automation is managed by BankSync and cannot be edited here.                                                                                       |
| 409    | `AUTOMATION_NOT_PUBLISHED`            | no    | Publish the automation first.                                                                                                                          |
| 409    | `AUTOMATION_NOT_ACTIVE`               | no    | The automation is not active.                                                                                                                          |
| 409    | `AUTOMATION_GRANT_REFUSED`            | no    | The revision's grant does not let it run: not approved yet, revoked, expired, or its bindings, config or node types changed. A member must approve it. |
| 409    | `AUTOMATION_GRANT_STATE`              | no    | The grant is in a state that does not allow this (a revoked grant is never approved).                                                                  |
| 409    | `AUTOMATION_REVISION_SUPERSEDED`      | no    | The run is of an older revision; a member can run it again with the current one in the app.                                                            |
| 409    | `AUTOMATION_TEMPLATE_UNAVAILABLE`     | no    | The template cannot be used in this workspace yet: the message says which step is not offered.                                                         |
| 409    | `AUTOMATION_RUN_STATE`                | no    | The run is not in a state that allows this.                                                                                                            |
| 409    | `AUTOMATION_RUN_DROPPED`              | no    | No run started: the trigger's condition was false, or a run for the same key exists.                                                                   |
| 409    | `AUTOMATION_START_IN_PROGRESS`        | yes   | A start with the same idempotency key is still being made. Send it again to get its run.                                                               |
| 409    | `AUTOMATION_APPROVAL_CLOSED`          | no    | The approval is no longer open, or you already decided it.                                                                                             |
| 409    | `AUTOMATION_EFFECT_CHANGED`           | no    | What the approval covers changed since you read it.                                                                                                    |
| 409    | `AUTOMATION_NONCE_REUSED`             | no    | That decision was already sent: send a new nonce.                                                                                                      |
| 409    | `AUTOMATION_DEFINITION_UNREADABLE`    | yes   | The automation was saved by a newer version; retry once the deployment completes.                                                                      |
| 429    | `AUTOMATION_ADMISSION_REFUSED`        | yes   | Too many runs are in progress and the automation does not queue.                                                                                       |
| 429    | `AUTOMATION_RATE_LIMITED`             | yes   | The automation's rate limit is reached and it skips rather than queues.                                                                                |
| 503    | `AUTOMATIONS_STOPPED`                 | yes   | Automations are paused while an incident is worked.                                                                                                    |
| 503    | `AUTOMATIONS_UNAVAILABLE`             | yes   | The automation runtime could not take the request.                                                                                                     |

A start that waits in the admission queue is not an error: it is a `202` (see [Activate and run](#activate-and-run)).

The full list of endpoints, with every field, is in the [API reference](/docs/api/reference#automations).
