Documentation

Flow builder

The shape of an automation flow and how to create, update, and execute one.

A flow is a JSON document with three lists: triggers, steps, and an optional settings block. The runner walks the steps by nextStepId. Conditions can fork to multiple branches; actions can pause and resume via the queue. This page is the canonical reference for the data shape and the CRUD endpoints.

The flow document

Every automation is wrapped in BaseModel\<T\>. The data payload is the workflow itself:

interface AutomationWorkflow {
  id: string;
  orgId: string;
  name: string;
  description?: string;
  status: 'draft' | 'active' | 'inactive' | 'archived';
  steps: AutomationStep[];
  triggers: AutomationTrigger[];
  settings?: WorkflowSettings;
  errorHandling?: ErrorHandlingConfig;
  createdBy: string;
}

interface AutomationStep {
  id: string;
  name: string;
  enabled: boolean;
  type: 'trigger' | 'action' | 'condition' | 'wait';
  config: Record<string, any>;
  nextStepId?: string;
  description?: string;
}

interface AutomationTrigger {
  id: string;
  type: string;            // e.g. 'contact_created', 'database_change'
  name: string;
  config: Record<string, any>;
  enabled: boolean;
  automationId: string;
}

A trigger fires → the runner builds an AutomationContext (orgId, executionId, variables, metadata) → it walks steps from the first one whose ID matches the trigger's resolved nextStepId, or the first step in the array.

Step config keys

Each step's config carries the type-specific parameters. The step-processor.service.ts reads config.actionType (or conditionType) to look up the registered handler.

{
  "id": "step-2",
  "type": "action",
  "name": "Send welcome email",
  "enabled": true,
  "config": {
    "actionType": "send_notification",
    "deliveryType": "email",
    "to": "{{contact.email}}",
    "subject": "Welcome, {{contact.firstName}}",
    "templateId": "welcome-template"
  },
  "nextStepId": "step-3"
}

{{...}} placeholders are resolved against context.variables. Triggers inject contact, event, and trigger data into variables before the first step runs. See the per-trigger payload shapes in triggers reference.

Create a flow

POST/automationJWT

Pass the wrapped BaseModel. The repository service generates pk/sk for you.

curl -X POST https://appengine.appmint.io/automation \
  -H "Authorization: Bearer $JWT" \
  -H "orgid: $ORG" \
  -H "Content-Type: application/json" \
  -d @flow.json

The response returns the saved automation with sk — that's the automationId for every other endpoint.

Activate it

POST/automation/start/:automationIdJWT

start flips data.status to active and registers all enabled triggers in the trigger registry so live events can match them. Without this call, triggers never fire — even if enabled is true.

curl -X POST https://appengine.appmint.io/automation/start/$AUTOMATION_ID \
  -H "Authorization: Bearer $JWT" \
  -H "orgid: $ORG" \
  -H "Content-Type: application/json" \
  -d '{"startedBy": "[email protected]"}'

To stop: POST /automation/stop/:automationId. That deregisters triggers and cancels pending queue jobs.

Run it manually

POST/automation/:automationId/executeJWT

Forces a single execution outside the trigger system. Good for ad-hoc runs and back-fills. Pass any variables your steps will read.

curl -X POST https://appengine.appmint.io/automation/$AUTOMATION_ID/execute \
  -H "Authorization: Bearer $JWT" \
  -H "orgid: $ORG" \
  -H "Content-Type: application/json" \
  -d '{
    "executionId": "manual-001",
    "variables": { "contact": { "id": "c-123", "email": "[email protected]" } },
    "startStepId": "s1",
    "executedBy": "[email protected]"
  }'
FieldTypeDescription
executionIdstring

Optional. Auto-generated if omitted.

variablesobject

Seeds context.variables. Step {{...}} placeholders resolve against this.

startStepIdstring

Skip past triggers and start at this step.

executedBystring

Recorded in execution metadata.

The response is an ExecutionResult with success, executionId, completedSteps, logs. The runner walks the graph synchronously until a step returns pauseExecution: true (delays, waits-for-reply). Paused steps schedule a resume job in the automation queue and the response returns immediately.

Update or delete

PUT/automation/:automationIdJWT
Body is the full BaseModel. Updating an active automation does not auto-restart triggers — call start again if triggers changed.

DELETE/automation/:automationIdJWT
Stops first if status === 'active', then soft-deletes. Pass { "deletedBy": "[email protected]", "reason": "obsolete" }.

Settings worth knowing

interface WorkflowSettings {
  maxExecutionTime?: number;            // minutes
  retryAttempts?: number;
  retryDelay?: number;                  // seconds
  timezone?: string;
  businessHours?: BusinessHours;        // skip steps outside business hours
  circularPrevention?: CircularPreventionSettings;
}

circularPrevention is on by default. It blocks an automation from triggering itself, caps chain depth at 5, and skips re-runs against the same record within a 1 s cooldown. Bump maxDepth only when you have a deliberate fan-out chain.

Step IDs are load-bearing

Branching uses nextStepId, thenStepId, elseStepId, etc. Renaming a step ID without updating every reference will break the workflow at runtime — the runner will simply terminate when it can't find the next step.