Frontend Integration
Automation — Frontend Integration
For web (React/Next.js) and mobile developers building the workflow builder. After this page you can list, compose, save, and activate a workflow without asking a backend engineer.
Every endpoint authenticates with the admin login JWT (not an API key):
Authorization: Bearer <jwt>Required permission
Workflow endpoints are gated by the admin permission system (see Authorization):
| To… | You need the route |
|---|---|
| List / read / preview / stats / templates | workflow.read |
| Create a workflow / use a template | workflow.create |
| Update, activate/pause, retry, approve/reject | workflow.update |
| Delete | workflow.delete |
The /workflow/meta/* metadata endpoints require only a valid admin login (no
workflow.* route) so the builder can populate pickers before the user has any
create rights.
The flow
1. Metadata GET /workflow/meta/triggers, /actions, /operators
2. Object model GET /workflow/meta/objects/:objectSlug/fields → build field pickers
3. Variables GET /workflow/meta/variables/:triggerType → {{token}} palette
4. Save draft POST /workflow → is_active:false
5. Validate POST /workflow/meta/validate-session-link → can it activate?
6. Activate PUT /workflow/:slug/toggle
7. Monitor GET /workflow/:slug/executions · WS /workflow/eventsHow a config is shaped (slug-first)
A workflow is trigger_type + trigger_config, an optional filter_config,
action_type + action_config, and an optional steps[] array. All references
are slugs — object_slug, session_slug, field_slug, status_field_slug —
never numeric ids. Example create body:
{
"name": "Welcome new leads on WhatsApp",
"trigger_type": "record_created",
"trigger_config": { "object_slug": "leads" },
"filter_config": {
"condition_groups": [
{ "conditions": [
{ "field_slug": "source", "operator": "equals", "value": "website" }
] }
]
},
"action_type": "send_message",
"action_config": {
"session_slug": "sales-wa",
"phone_field_slug": "phone",
"message_template": "Hi {{name}} 👋 thanks for reaching out!",
"link_to_record": true
}
}filter_config is OR between condition_groups, AND within each group's
conditions. Operators per field type come from GET /workflow/meta/operators.
Activate = validate first
A new workflow is created paused. PUT /workflow/:slug/toggle flips
is_active. Activation runs a gate: a message-triggered workflow that reads or
writes a CRM record needs its messaging session linked to an object, otherwise it
returns 400 with code: "SESSION_NOT_LINKED_TO_OBJECT" and
unlinkedSessionSlugs[]. Call POST /workflow/meta/validate-session-link with the
in-progress draft to show that banner before the user hits Activate (same gate,
no persistence). Deactivating is always allowed.
Media attachments
For a send_message action in mode: "media", upload files first via
POST /workflow/attachments/upload (multipart, field files). It returns
{ url, storage_key, media_type, ... } — stash url on
action_config.attachments[].
Execution progress (WebSocket)
Connect Socket.IO to the /workflow/events namespace, passing the JWT in the
handshake. You join your admin:{id} and tenant:{db} rooms and receive live run
events:
import { io } from 'socket.io-client';
const socket = io(`${WS_HOST}/workflow/events`, {
auth: { token: jwt },
query: { tenantDb }, // or send x-tenant-database header
});
socket.on('connected', () => {});
socket.on('workflow:execution.completed', (e) => bump(e.workflowId));
socket.on('workflow:execution.failed', (e) => showError(e));
socket.on('workflow:execution.skipped', (e) => {});
socket.on('workflow:stats.updated', (e) => refreshStats(e.workflowId));Full event list: Events.
UI concerns
- Actions are async — an execution is queued, not run inline. Show run status
from
GET /workflow/:slug/executions(or the WS event), not the save response. - Dry-run —
POST /workflow/:slug/testruns the config against sample data with no side effects;GET /workflow/:slug/previewrenders a human summary andGET /workflow/:slug/impactestimates how often it would have fired. - Errors — see Troubleshooting for 400 / 403 causes.