Automations

Scheduled and triggered workflows — wire any platform event to any API action. An automation is a graph of TRIGGER/FILTER/ACTION blocks joined by edges: discover the block catalog (GET /api/automations/actions), create the graph in DRAFT, test it synchronously, then enable it to arm.

13 endpoints·Scopes: automations:read, automations:write
GET/api/automations#

List automations

scope · automations:read

Lists automations the caller can see. Results are filtered by the caller's accessible organizations; many routes paginate via page and limit.

Scope: automations:read
Endpoint: GET /api/automations

Example request

curl "https://guliel.com/api/automations?organizationId=value" \
  -H "Authorization: Bearer $GULIEL_API_KEY"

Query parameters

  • organizationIdreq
    string
  • status
    enum<4>
    DRAFT · ENABLED · PAUSED · PAUSED_NO_ACCESS
  • pagereq
    integer
  • limitreq
    integer

Response (200/201)

  • automationsreq
    array<object>
    • idreq
      string
    • organizationIdreq
      string
    • ownerUserIdreq
      stringThe run-as identity: when the automation fires, actions execute under this user's current org permissions.
    • namereq
      string
    • descriptionreq
      string | null
    • statusreq
      enum<4>DRAFT = never armed; ENABLED = armed (fires on its trigger; the free tier allows 5 enabled per org); PAUSED = manually disarmed; PAUSED_NO_ACCESS = auto-paused because the owner lost the required org access.
      DRAFT · ENABLED · PAUSED · PAUSED_NO_ACCESS
    • createdAtreq
      string
    • updatedAtreq
      string
    • blocks
      array<object>
      • idreq
        stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
      • kindreq
        enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
        TRIGGER · FILTER · ACTION · CUSTOM
      • refreq
        string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
      • configreq
        objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
      • positionreq
        objectEditor canvas hint ({ x, y }). Ignored by the engine.
    • edges
      array<object>
      • idreq
        stringClient-chosen id, unique within the automation.
      • fromBlockIdreq
        stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
      • toBlockIdreq
        stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
      • condreq
        object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
      • modereq
        enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
        ONCE · FOR_EACH
      • orderreq
        integerDisplay ordering only — execution order is topological.
  • paginationreq
    object
    • pagereq
      integer
    • limitreq
      integer
    • totalreq
      integer
    • totalPagesreq
      integer

Error responses

400Validation
401Unauthenticated
403Forbidden
500Internal
POST/api/automations#

Create an automation

scope · automations:writerole · CONTRIBUTOR+

Creates a new automation in the target organization. Returns the created record on success.

An automation is a graph: blocks (nodes) joined by edges. One TRIGGER block starts the flow when its event fires; ACTION blocks do the work; optional FILTER blocks query org data in between. Discover the building blocks from GET /api/automations/actions: trigger event types (each with the payload fields available to {{trigger.*}} bindings), the action tool catalog (each with its input schema), and the filterable objects for FILTER specs.

Lifecycle. Created in DRAFT — it does not fire yet. Verify it synchronously with POST /api/automations/{id}/test, then arm it with POST /api/automations/{id}/enable (free tier: 5 enabled automations per org). Once enabled, it runs with the owner's permissions, resolved at fire time; an action the owner can't perform fails the run and auto-pauses the automation (PAUSED_NO_ACCESS).

Example — email on every paid invoice:

{
  "organizationId": "org_123",
  "name": "Email me when an invoice is paid",
  "blocks": [
    { "id": "t1", "kind": "TRIGGER", "ref": "invoice.paid" },
    {
      "id": "a1",
      "kind": "ACTION",
      "ref": "email.send",
      "config": {
        "input": {
          "to": "ops@example.com",
          "subject": "Invoice {{trigger.id}} paid",
          "body": "Amount: {{trigger.total}} {{trigger.currency}}"
        }
      }
    }
  ],
  "edges": [{ "id": "e1", "fromBlockId": "t1", "toBlockId": "a1" }]
}

Bindings. Action inputs, filter predicate values, and edge-condition operands may embed {{…}} paths resolved at run time: {{trigger.*}} (the event payload), {{blocks.<blockId>.*}} (an upstream block's output), {{item.*}} (the current element, under a FOR_EACH edge), and {{context.now}} / {{context.today}} / {{context.orgId}}. A whole-string binding ("{{trigger.total}}") keeps the value's native type; inline bindings interpolate into the string.

Example — monthly digest of overdue receivables, one webhook call per document. The FILTER block's config.spec selects and shapes rows; the FOR_EACH edge fans the list out, one ACTION step per element:

{
  "organizationId": "org_123",
  "name": "Monthly overdue digest",
  "blocks": [
    { "id": "t1", "kind": "TRIGGER", "ref": "schedule.monthly" },
    {
      "id": "f1",
      "kind": "FILTER",
      "config": {
        "spec": {
          "root": "Document",
          "predicates": [
            { "object": "Document", "field": "status", "op": "ne", "value": "PAID" },
            { "object": "Document", "field": "direction", "op": "eq", "value": "OUTGOING" },
            { "object": "Document", "field": "dueDate", "op": "lt", "value": "{{context.today}}" }
          ],
          "extract": [
            { "as": "documentId", "path": "Document.id" },
            { "as": "number", "path": "Document.invoiceNumber" },
            { "as": "amountDue", "path": "Document.total" }
          ],
          "shape": "objects"
        }
      }
    },
    {
      "id": "a1",
      "kind": "ACTION",
      "ref": "webhook.post",
      "config": {
        "input": {
          "url": "https://example.com/hooks/overdue",
          "body": { "id": "{{item.documentId}}", "number": "{{item.number}}", "due": "{{item.amountDue}}" }
        }
      }
    }
  ],
  "edges": [
    { "id": "e1", "fromBlockId": "t1", "toBlockId": "f1" },
    { "id": "e2", "fromBlockId": "f1", "toBlockId": "a1", "mode": "FOR_EACH" }
  ]
}

Validation. At least one TRIGGER block; every edge must reference blocks in the set; self-loops and cycles are rejected (the graph must be a DAG). Block/edge ids are client-chosen and must be unique within the automation.

Scope: automations:write
Min role: CONTRIBUTOR or higher
Endpoint: POST /api/automations

Example request

curl -X POST "https://guliel.com/api/automations" \
  -H "Authorization: Bearer $GULIEL_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "organizationId": "abc123",
  "name": "Sample",
  "blocks": [],
  "edges": []
}'

Request body

  • organizationIdreq
    string
  • namereq
    string
  • description
    string
  • blocksreq
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
    • kindreq
      enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
      TRIGGER · FILTER · ACTION · CUSTOM
    • ref
      string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
    • config
      objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
    • position
      objectEditor canvas hint ({ x, y }). Ignored by the engine.
  • edgesreq
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation.
    • fromBlockIdreq
      stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
    • toBlockIdreq
      stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
    • cond
      object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
    • mode
      enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
      ONCE · FOR_EACH
    • order
      integerDisplay ordering only — execution order is topological.

Response (200/201)

  • idreq
    string
  • organizationIdreq
    string
  • ownerUserIdreq
    stringThe run-as identity: when the automation fires, actions execute under this user's current org permissions.
  • namereq
    string
  • descriptionreq
    string | null
  • statusreq
    enum<4>DRAFT = never armed; ENABLED = armed (fires on its trigger; the free tier allows 5 enabled per org); PAUSED = manually disarmed; PAUSED_NO_ACCESS = auto-paused because the owner lost the required org access.
    DRAFT · ENABLED · PAUSED · PAUSED_NO_ACCESS
  • createdAtreq
    string
  • updatedAtreq
    string
  • blocks
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
    • kindreq
      enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
      TRIGGER · FILTER · ACTION · CUSTOM
    • refreq
      string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
    • configreq
      objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
    • positionreq
      objectEditor canvas hint ({ x, y }). Ignored by the engine.
  • edges
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation.
    • fromBlockIdreq
      stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
    • toBlockIdreq
      stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
    • condreq
      object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
    • modereq
      enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
      ONCE · FOR_EACH
    • orderreq
      integerDisplay ordering only — execution order is topological.

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal
DELETE/api/automations/{id}#

Delete an automation

scope · automations:write

Deletes the automation. Some endpoints hard-delete the row, others soft-revoke (set revokedAt). Check the response shape.

Scope: automations:write
Endpoint: DELETE /api/automations/{id}

Example request

curl -X DELETE "https://guliel.com/api/automations/abc123?id=value" \
  -H "Authorization: Bearer $GULIEL_API_KEY"

Query parameters

  • idreq
    string

Response (200/201)

  • successreq
    boolean

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal
GET/api/automations/{id}#

Get an automation (with its graph)

scope · automations:read

Fetches a single automation by id. Returns 404 if the automation isn't in the caller's accessible organizations.

Scope: automations:read
Endpoint: GET /api/automations/{id}

Example request

curl "https://guliel.com/api/automations/abc123?id=value" \
  -H "Authorization: Bearer $GULIEL_API_KEY"

Query parameters

  • idreq
    string

Response (200/201)

  • idreq
    string
  • organizationIdreq
    string
  • ownerUserIdreq
    stringThe run-as identity: when the automation fires, actions execute under this user's current org permissions.
  • namereq
    string
  • descriptionreq
    string | null
  • statusreq
    enum<4>DRAFT = never armed; ENABLED = armed (fires on its trigger; the free tier allows 5 enabled per org); PAUSED = manually disarmed; PAUSED_NO_ACCESS = auto-paused because the owner lost the required org access.
    DRAFT · ENABLED · PAUSED · PAUSED_NO_ACCESS
  • createdAtreq
    string
  • updatedAtreq
    string
  • blocks
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
    • kindreq
      enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
      TRIGGER · FILTER · ACTION · CUSTOM
    • refreq
      string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
    • configreq
      objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
    • positionreq
      objectEditor canvas hint ({ x, y }). Ignored by the engine.
  • edges
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation.
    • fromBlockIdreq
      stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
    • toBlockIdreq
      stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
    • condreq
      object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
    • modereq
      enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
      ONCE · FOR_EACH
    • orderreq
      integerDisplay ordering only — execution order is topological.

Error responses

400Validation
401Unauthenticated
403Forbidden
500Internal
PATCH/api/automations/{id}#

Update an automation (rename / edit its graph)

scope · automations:write

Updates an existing automation. Only the fields present in the request body are changed; omitted fields stay as-is.

Scope: automations:write
Endpoint: PATCH /api/automations/{id}

Example request

curl -X PATCH "https://guliel.com/api/automations/abc123" \
  -H "Authorization: Bearer $GULIEL_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "id": "abc123"
}'

Request body

  • idreq
    string
  • name
    string
  • description
    string | null
  • blocks
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
    • kindreq
      enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
      TRIGGER · FILTER · ACTION · CUSTOM
    • ref
      string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
    • config
      objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
    • position
      objectEditor canvas hint ({ x, y }). Ignored by the engine.
  • edges
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation.
    • fromBlockIdreq
      stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
    • toBlockIdreq
      stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
    • cond
      object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
    • mode
      enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
      ONCE · FOR_EACH
    • order
      integerDisplay ordering only — execution order is topological.

Response (200/201)

  • idreq
    string
  • organizationIdreq
    string
  • ownerUserIdreq
    stringThe run-as identity: when the automation fires, actions execute under this user's current org permissions.
  • namereq
    string
  • descriptionreq
    string | null
  • statusreq
    enum<4>DRAFT = never armed; ENABLED = armed (fires on its trigger; the free tier allows 5 enabled per org); PAUSED = manually disarmed; PAUSED_NO_ACCESS = auto-paused because the owner lost the required org access.
    DRAFT · ENABLED · PAUSED · PAUSED_NO_ACCESS
  • createdAtreq
    string
  • updatedAtreq
    string
  • blocks
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
    • kindreq
      enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
      TRIGGER · FILTER · ACTION · CUSTOM
    • refreq
      string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
    • configreq
      objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
    • positionreq
      objectEditor canvas hint ({ x, y }). Ignored by the engine.
  • edges
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation.
    • fromBlockIdreq
      stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
    • toBlockIdreq
      stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
    • condreq
      object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
    • modereq
      enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
      ONCE · FOR_EACH
    • orderreq
      integerDisplay ordering only — execution order is topological.

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal
POST/api/automations/{id}/disable#

Disable (disarm) an automation

scope · automations:write

Creates a new disable attached to the given automation.

Scope: automations:write
Endpoint: POST /api/automations/{id}/disable

Example request

curl -X POST "https://guliel.com/api/automations/abc123/disable" \
  -H "Authorization: Bearer $GULIEL_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "id": "abc123"
}'

Request body

  • idreq
    string

Response (200/201)

  • idreq
    string
  • organizationIdreq
    string
  • ownerUserIdreq
    stringThe run-as identity: when the automation fires, actions execute under this user's current org permissions.
  • namereq
    string
  • descriptionreq
    string | null
  • statusreq
    enum<4>DRAFT = never armed; ENABLED = armed (fires on its trigger; the free tier allows 5 enabled per org); PAUSED = manually disarmed; PAUSED_NO_ACCESS = auto-paused because the owner lost the required org access.
    DRAFT · ENABLED · PAUSED · PAUSED_NO_ACCESS
  • createdAtreq
    string
  • updatedAtreq
    string
  • blocks
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
    • kindreq
      enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
      TRIGGER · FILTER · ACTION · CUSTOM
    • refreq
      string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
    • configreq
      objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
    • positionreq
      objectEditor canvas hint ({ x, y }). Ignored by the engine.
  • edges
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation.
    • fromBlockIdreq
      stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
    • toBlockIdreq
      stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
    • condreq
      object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
    • modereq
      enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
      ONCE · FOR_EACH
    • orderreq
      integerDisplay ordering only — execution order is topological.

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal
POST/api/automations/{id}/enable#

Enable (arm) an automation

scope · automations:write

Creates a new enable attached to the given automation.

Scope: automations:write
Endpoint: POST /api/automations/{id}/enable

Example request

curl -X POST "https://guliel.com/api/automations/abc123/enable" \
  -H "Authorization: Bearer $GULIEL_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "id": "abc123"
}'

Request body

  • idreq
    string

Response (200/201)

  • idreq
    string
  • organizationIdreq
    string
  • ownerUserIdreq
    stringThe run-as identity: when the automation fires, actions execute under this user's current org permissions.
  • namereq
    string
  • descriptionreq
    string | null
  • statusreq
    enum<4>DRAFT = never armed; ENABLED = armed (fires on its trigger; the free tier allows 5 enabled per org); PAUSED = manually disarmed; PAUSED_NO_ACCESS = auto-paused because the owner lost the required org access.
    DRAFT · ENABLED · PAUSED · PAUSED_NO_ACCESS
  • createdAtreq
    string
  • updatedAtreq
    string
  • blocks
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation. Edges reference blocks by this id, and downstream blocks read this block's output via {{blocks.<id>.…}} bindings.
    • kindreq
      enum<4>TRIGGER starts the flow when its event fires (every automation needs at least one); FILTER queries org data and outputs a shaped list/value; ACTION invokes a tool; CUSTOM is reserved and not executable in v1.
      TRIGGER · FILTER · ACTION · CUSTOM
    • refreq
      string | nullWhat the block invokes. TRIGGER: an event type from the catalog, e.g. "invoice.paid" or "schedule.daily" (GET /api/automations/actions lists them). ACTION: an MCP tool name, e.g. "invoices.send", or engine-native "webhook.post" / "email.send". FILTER: omit — the query lives in config.spec.
    • configreq
      objectKind-specific configuration. ACTION: { input: { … } } — the arguments for the tool named by ref, matching that tool's inputSchema from the catalog; values may embed {{…}} bindings ({{trigger.*}}, {{blocks.<id>.*}}, {{item.*}} under a FOR_EACH edge, {{context.now}}, {{context.today}}, {{context.orgId}}) — a whole-string binding like "{{trigger.total}}" keeps the value's native type, inline bindings interpolate into the string. FILTER: { spec: { root, follows?, predicates?, aggregateBy?, extract, shape? } } over the served filter catalog. TRIGGER: ignored.
    • positionreq
      objectEditor canvas hint ({ x, y }). Ignored by the engine.
  • edges
    array<object>
    • idreq
      stringClient-chosen id, unique within the automation.
    • fromBlockIdreq
      stringId of the source block. The graph must be a DAG — self-loops and cycles are rejected.
    • toBlockIdreq
      stringId of the target block. The target runs only after the source step succeeds and the cond (if any) passes.
    • condreq
      object | nullOptional guard — null/omitted means the edge is always followed. Leaf: { left, op, right? } with op one of eq | ne | gt | gte | lt | lte | in | nin | contains | exists | empty; compose with { all: […] }, { any: […] }, { not: … }. Operands may be literals or {{…}} bindings; comparisons are type-light (100 equals "100").
    • modereq
      enum<2>ONCE (default) passes the source output downstream as-is; FOR_EACH fans a list-valued source output out, running the target once per element (exposed as {{item.*}}) — a non-list source falls back to a single run.
      ONCE · FOR_EACH
    • orderreq
      integerDisplay ordering only — execution order is topological.

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal
GET/api/automations/{id}/runs#

List an automation's runs (history)

scope · automations:read

Returns every runs row associated with the given automation.

Scope: automations:read
Endpoint: GET /api/automations/{id}/runs

Example request

curl "https://guliel.com/api/automations/abc123/runs?id=value" \
  -H "Authorization: Bearer $GULIEL_API_KEY"

Query parameters

  • idreq
    string
  • pagereq
    integer
  • limitreq
    integer

Response (200/201)

  • runsreq
    array<object>
    • idreq
      string
    • automationIdreq
      string
    • organizationIdreq
      string
    • triggerEventIdreq
      string | nullThe outbox event that fired this run; null for a manual test run.
    • inputreq
      objectThe trigger payload the run was seeded with — what {{trigger.*}} resolved against.
    • statusreq
      enum<4>FAILED pauses at the first failed step; a FAILED run can be resumed via replay.
      RUNNING · SUCCEEDED · FAILED · CANCELLED
    • errorreq
      string | null
    • startedAtreq
      string
    • finishedAtreq
      string | null
    • steps
      array<object>
      • idreq
        string
      • blockIdreq
        string | nullThe block this step executed; null if that block was later edited out of the graph.
      • statusreq
        enum<5>SKIPPED = no incoming edge had a succeeded source with a passing cond.
        PENDING · RUNNING · SUCCEEDED · FAILED · SKIPPED
      • inputreq
        objectThe block's resolved input (bindings already substituted).
      • outputreq
        any | nullThe block's output — what downstream {{blocks.<id>.…}} bindings read.
      • errorreq
        string | null
      • attemptreq
        integer1-based; transient action failures are retried.
      • latencyMsreq
        integer | null
      • startedAtreq
        string | null
      • finishedAtreq
        string | null
  • paginationreq
    object
    • pagereq
      integer
    • limitreq
      integer
    • totalreq
      integer
    • totalPagesreq
      integer

Error responses

400Validation
401Unauthenticated
403Forbidden
500Internal
GET/api/automations/{id}/runs/{runId}#

Get a run with its step timeline

scope · automations:read

Returns every runs row associated with the given automation.

Scope: automations:read
Endpoint: GET /api/automations/{id}/runs/{runId}

Example request

curl "https://guliel.com/api/automations/abc123/runs/abc123?id=value" \
  -H "Authorization: Bearer $GULIEL_API_KEY"

Query parameters

  • idreq
    string
  • runIdreq
    string

Response (200/201)

  • idreq
    string
  • automationIdreq
    string
  • organizationIdreq
    string
  • triggerEventIdreq
    string | nullThe outbox event that fired this run; null for a manual test run.
  • inputreq
    objectThe trigger payload the run was seeded with — what {{trigger.*}} resolved against.
  • statusreq
    enum<4>FAILED pauses at the first failed step; a FAILED run can be resumed via replay.
    RUNNING · SUCCEEDED · FAILED · CANCELLED
  • errorreq
    string | null
  • startedAtreq
    string
  • finishedAtreq
    string | null
  • steps
    array<object>
    • idreq
      string
    • blockIdreq
      string | nullThe block this step executed; null if that block was later edited out of the graph.
    • statusreq
      enum<5>SKIPPED = no incoming edge had a succeeded source with a passing cond.
      PENDING · RUNNING · SUCCEEDED · FAILED · SKIPPED
    • inputreq
      objectThe block's resolved input (bindings already substituted).
    • outputreq
      any | nullThe block's output — what downstream {{blocks.<id>.…}} bindings read.
    • errorreq
      string | null
    • attemptreq
      integer1-based; transient action failures are retried.
    • latencyMsreq
      integer | null
    • startedAtreq
      string | null
    • finishedAtreq
      string | null

Error responses

400Validation
401Unauthenticated
403Forbidden
500Internal
POST/api/automations/{id}/runs/{runId}/replay#

Replay a failed run (resume from the failed step)

scope · automations:writerole · CONTRIBUTOR+

Creates a new runs/replay attached to the given automation.

Scope: automations:write
Min role: CONTRIBUTOR or higher
Endpoint: POST /api/automations/{id}/runs/{runId}/replay

Example request

curl -X POST "https://guliel.com/api/automations/abc123/runs/abc123/replay" \
  -H "Authorization: Bearer $GULIEL_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "id": "abc123",
  "runId": "abc123"
}'

Request body

  • idreq
    string
  • runIdreq
    string

Response (200/201)

  • idreq
    string
  • automationIdreq
    string
  • organizationIdreq
    string
  • triggerEventIdreq
    string | nullThe outbox event that fired this run; null for a manual test run.
  • inputreq
    objectThe trigger payload the run was seeded with — what {{trigger.*}} resolved against.
  • statusreq
    enum<4>FAILED pauses at the first failed step; a FAILED run can be resumed via replay.
    RUNNING · SUCCEEDED · FAILED · CANCELLED
  • errorreq
    string | null
  • startedAtreq
    string
  • finishedAtreq
    string | null
  • steps
    array<object>
    • idreq
      string
    • blockIdreq
      string | nullThe block this step executed; null if that block was later edited out of the graph.
    • statusreq
      enum<5>SKIPPED = no incoming edge had a succeeded source with a passing cond.
      PENDING · RUNNING · SUCCEEDED · FAILED · SKIPPED
    • inputreq
      objectThe block's resolved input (bindings already substituted).
    • outputreq
      any | nullThe block's output — what downstream {{blocks.<id>.…}} bindings read.
    • errorreq
      string | null
    • attemptreq
      integer1-based; transient action failures are retried.
    • latencyMsreq
      integer | null
    • startedAtreq
      string | null
    • finishedAtreq
      string | null

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal
POST/api/automations/{id}/test#

Test-run an automation with a sample payload (owner or MANAGER)

scope · automations:writerole · CONTRIBUTOR+

Creates a new test attached to the given automation.

Runs the flow inline in this request (unlike a real fire, which goes through the event queue) and returns the run with its step timeline immediately. The run executes under the automation owner's permissions — real fire-time authorization — which is why only the owner or a MANAGER may call it. Actions really execute; there is no dry-run mode. Shape payload like the trigger's payloadSchema from GET /api/automations/actions so {{trigger.*}} bindings resolve.

Scope: automations:write
Min role: CONTRIBUTOR or higher
Endpoint: POST /api/automations/{id}/test

Example request

curl -X POST "https://guliel.com/api/automations/abc123/test" \
  -H "Authorization: Bearer $GULIEL_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "id": "abc123"
}'

Request body

  • idreq
    string
  • payload
    objectSample trigger payload — the value {{trigger.*}} bindings resolve against. Shape it like the trigger's payloadSchema from automations.catalog. Defaults to {}.

Response (200/201)

  • idreq
    string
  • automationIdreq
    string
  • organizationIdreq
    string
  • triggerEventIdreq
    string | nullThe outbox event that fired this run; null for a manual test run.
  • inputreq
    objectThe trigger payload the run was seeded with — what {{trigger.*}} resolved against.
  • statusreq
    enum<4>FAILED pauses at the first failed step; a FAILED run can be resumed via replay.
    RUNNING · SUCCEEDED · FAILED · CANCELLED
  • errorreq
    string | null
  • startedAtreq
    string
  • finishedAtreq
    string | null
  • steps
    array<object>
    • idreq
      string
    • blockIdreq
      string | nullThe block this step executed; null if that block was later edited out of the graph.
    • statusreq
      enum<5>SKIPPED = no incoming edge had a succeeded source with a passing cond.
      PENDING · RUNNING · SUCCEEDED · FAILED · SKIPPED
    • inputreq
      objectThe block's resolved input (bindings already substituted).
    • outputreq
      any | nullThe block's output — what downstream {{blocks.<id>.…}} bindings read.
    • errorreq
      string | null
    • attemptreq
      integer1-based; transient action failures are retried.
    • latencyMsreq
      integer | null
    • startedAtreq
      string | null
    • finishedAtreq
      string | null

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal
GET/api/automations/actions#

The block catalog for the automation editor (triggers + actions)

scope · automations:read

Returns the actions data for the automations feature.

The discovery endpoint for authoring automations: consult it before POST /api/automations. triggers lists every event type a TRIGGER block may reference, with the payload schema {{trigger.*}} bindings resolve against. actions lists every tool an ACTION block may invoke — the platform's full MCP tool catalog plus the engine-native webhook.post and email.send — each carrying the input schema its config.input must satisfy and the output schema downstream bindings can read. filterCatalog lists the objects/fields/morphisms a FILTER block's config.spec may query.

Scope: automations:read
Endpoint: GET /api/automations/actions

Example request

curl "https://guliel.com/api/automations/actions" \
  -H "Authorization: Bearer $GULIEL_API_KEY"

Response (200/201)

  • triggersreq
    array<object>Event types usable as a TRIGGER block's ref.
    • typereq
      stringEvent type — use as a TRIGGER block's ref (e.g. "invoice.paid", "schedule.daily").
    • labelreq
      string
    • groupreq
      string
    • payloadSchemareq
      object | nullJSON Schema for the trigger's event payload — the fields {{trigger.*}} bindings can reference.
  • actionsreq
    array<object>Tools usable as an ACTION block's ref.
    • namereq
      stringTool name — use as an ACTION block's ref (e.g. "invoices.send", "webhook.post").
    • descriptionreq
      string
    • featurereq
      string
    • verbreq
      string
    • kindreq
      enum<2>system = an API route invoked as a tool; engine = built into the worker.
      system · engine
    • inputSchemareq
      object | nullJSON Schema (draft-7) for the action's input — the shape an ACTION block's config.input must satisfy.
    • outputSchemareq
      object | nullJSON Schema for the action's output (what downstream {{blocks.<id>.…}} bindings can read), or null for opaque/streamed results.
  • filterCatalogreq
    objectObjects a FILTER block's config.spec may range over: object name → readable fields (with primitive types) + follow morphisms (name → target object).

Error responses

400Validation
401Unauthenticated
403Forbidden
500Internal
POST/api/automations/filter/preview#

Preview a filter spec — match count + sample rows

scope · automations:read

Performs the filter/preview operation for the automations feature.

Scope: automations:read
Endpoint: POST /api/automations/filter/preview

Example request

curl -X POST "https://guliel.com/api/automations/filter/preview" \
  -H "Authorization: Bearer $GULIEL_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "organizationId": "abc123",
  "spec": {}
}'

Request body

  • organizationIdreq
    string
  • specreq
    objectA FilterSpec: { root, follows?, predicates?, aggregateBy?, extract, shape? }. root names a filterCatalog object; predicates are { object, field, op, value } with op one of eq|ne|gt|gte|lt|lte|in|nin|contains; extract entries are { as, path: "Object.field", fn?: "sum"|"count" }; shape is "objects" (default) | "scalars" | "scalar".

Response (200/201)

  • countreq
    integerNumber of matching rows (capped at 1000).
  • samplereq
    array<any>First 5 shaped results (empty for shape: "scalar").
  • scalar
    anyThe single value when the spec's shape is "scalar".

Error responses

400Validation
401Unauthenticated
403Forbidden
429RateLimit
500Internal