Automation & Playbooks
Olyron CRM ships two complementary automation layers: a visual workflow builder that runs node-by-node against a CRM record, and a set of scheduled cron jobs that keep cadences, SLAs, next-best-action enrollment, and A/B tests moving without anyone clicking a button. This page covers what each layer actually does today, the exact API surface behind the builder, and how the background jobs are secured and scheduled.
#How automation is structured
Automation in Olyron CRM is split across two systems. The first is a per-record workflow engine you build visually and run on demand against a lead, contact, or deal. The second is a fleet of scheduled jobs (Vercel Cron) that operate tenant-wide on a fixed timetable. Understanding which layer you need saves a lot of debugging.
| Layer | What it runs on | How it fires | Where it lives |
|---|---|---|---|
| Visual workflows | A single CRM record (lead / contact / deal / task) | Manual execute or dry-run via the API | /dashboard/workflows/[id]/builder |
| Cadences | Contacts/leads enrolled in a sequence | Cron every 15-30 min | /api/cron/advance-cadences, /api/cron/run-cadences |
| SLA escalations | Open sla_events rows past their due time | Cron every 15 min | /api/cron/run-sla-escalations |
| Agent Trees | A product's enrollment/eligibility pipeline | Programmatic execution runs | /api/agent-trees |
tenant_id. Builder API calls resolve the caller's tenant via requireTenantUser and reject any workflow that does not belong to that tenant with a 404 - so automations never leak across organizations.#Building a workflow
Open a workflow in the visual builder at /dashboard/workflows/[id]/builder. The left rail (the Node Palette) groups the building blocks into Triggers, Logic, Actions, Advanced, and Flow. You add nodes by clicking them onto the canvas, wire them together with edges, then Save and Test.
- 1Start with a TriggerEvery workflow needs exactly one Trigger node - execution always begins there. A workflow with no trigger fails with the error "Workflow has no trigger node".
- 2Add Logic and Action nodesDrop in Condition, Split, or Delay for branching, then Action nodes (Send Email, Send SMS, Update Field, Assign, Tag, Move Stage, and more) for side effects.
- 3Wire the edgesConnect node outputs to inputs. A Condition node exposes
trueandfalseoutput ports; the engine follows only the matching branch at run time. - 4SaveSaving bulk-upserts nodes and edges. The builder PUTs the full node set, and any node no longer on the canvas is deleted server-side.
- 5Test with a dry runUse the Test action to run the workflow in dry-run mode. Dry runs report every node as "would execute" without writing any data or recording an execution.
#Node reference
Each node has a node_type and a free-form config object. The engine in src/lib/crm/workflowActions.ts maps a node type to a concrete CRM mutation. These are the node types the executor understands today:
trigger / endflow- Entry and terminal markers.
endstops that branch; the engine simply acknowledges both. conditionlogic- Evaluates
config.fieldagainstconfig.valueusing an operator (eq, neq, contains, gt, lt, gte, lte, empty, not_empty). Supportscustom_fields.<key>paths. Routes to the true or false edge. delaylogic- Durable pause: execution status becomes
waitingwithresume_atin variables;/api/cron/resume-workflowscontinues the graph after the configured duration (seconds/minutes/hours/days). email / sms / notificationaction- Queues an entry in
crm_activities(type email/sms/notification) linked to the record. Email requires a resolvable recipient (record email orconfig.custom_email). field_updateaction- Updates
config.fieldtoconfig.valueon the record's module table (crm_leads / crm_contacts / crm_deals / crm_tasks). create_recordaction- Creates a task, note, or module record.
config.fields_json(string or object) supplies the field values; defaults to a task. assignaction- Sets
assigned_to(orowner_idon accounts). Useconfig.strategy = specific_userwithconfig.user_id, otherwise assigns the triggering user. tagaction- Adds or removes a tag stored in the record's
custom_fields.tagsarray. Useconfig.operation = removeto strip a tag. score_updateaction- Leads only. Adjusts
custom_fields.scorebyconfig.points;config.operationmay be set, add (default), or subtract. move_stageaction- Deals: sets
stage_id. Leads: setsstatusfromconfig.stage/config.status. webhookadvanced- Calls an external
config.url(default POST) with a JSON body containing tenant_id, record_id, module, and variables. Custom headers viaconfig.headers. approvaladvanced- Creates a high-priority approval task in
crm_tasks. Note: execution continues past the node - it does not pause the workflow.
delay and approval do not halt execution - there is no durable timer or human-in-the-loop pause yet. Workflows also run on demand against a supplied record; there is no automatic event trigger (e.g. "when a lead is created") wired into the execute endpoint today.#Executing a workflow via the API
The builder Test button and any programmatic caller both hit POST /api/crm/workflows/[id]/execute. The engine loads the workflow's nodes and edges, starts at the trigger, and walks the graph breadth-first (capped at 100 steps) executing each node's action. Pass dry_run: true to preview without writing.
curl -X POST https://core.olyron.com/api/crm/workflows/WORKFLOW_ID/execute \
-H "Content-Type: application/json" \
-d '{
"dry_run": false,
"module": "leads",
"record_id": "00000000-0000-0000-0000-000000000000"
}'{
"success": true,
"dry_run": false,
"preview_only": false,
"steps_executed": 3,
"steps": [
{
"node_id": "node-abc",
"node_type": "trigger",
"label": "Trigger",
"status": "executed",
"message": "trigger acknowledged",
"timestamp": "2026-07-10T14:00:00.000Z"
},
{
"node_id": "node-def",
"node_type": "condition",
"label": "Is qualified?",
"status": "executed",
"message": "Condition status eq qualified -> true",
"branch": "true"
}
]
}dry_runboolean- Defaults to false. When true, nothing is written and no execution row is recorded.
record_iduuid (optional)- The CRM record to run against. Its fields become the
variablescontext (email, phone, first_name, last_name, plus all columns). modulestring (optional)- One of leads, contacts, or deals - determines which table the record is loaded from and which foreign key activities are linked to.
crm_workflow_executions with the full step log, steps_executed count, and a status of completed or failed. Dry runs are explicitly excluded so previews never pollute your history.#Scheduled automation (cron)
Beyond on-demand workflows, Olyron CRM runs a set of background jobs registered in vercel.json under crons. These handle the recurring operational automation - advancing cadences, breaching SLAs, enrolling stuck leads, and promoting A/B winners. Each job is a route under /api/cron/*.
| Endpoint | Schedule | What it does |
|---|---|---|
| /api/cron/advance-cadences | */15 * * * * | Ticks active cadences and sends the next due step (limit 500). |
| /api/cron/run-cadences | */30 * * * * | Runs due cadence steps via the cadence runner. |
| /api/cron/run-sla-escalations | */15 * * * * | Marks sla_events past due_at as breached. |
| /api/cron/nba-autoenroll | */15 * * * * | Auto-enrolls stuck leads into next-best-action cadences. |
| /api/cron/email-ab-winner | 0 * * * * | Re-scores running email A/B tests and promotes a winner. |
| /api/cron/nba-snapshot | 0 1 * * * | Nightly next-best-action snapshot. |
| /api/cron/expire-quotes | 0 2 * * * | Expires quotes past their validity date. |
| /api/cron/crm-sftp-sync | */30 * * * * | Pulls/pushes CRM data over configured SFTP connections. |
Additional social jobs (sync-social-metrics, refresh-social-tokens, publish-social) and tenant-validation round out the schedule. All of them accept both GET (Vercel Cron's default verb) and POST for manual or end-to-end triggering.
#Securing and triggering cron jobs
Every cron route is gated by a single shared secret, the CRON_SECRET environment variable. The route reads the Authorization header and requires an exact Bearer <CRON_SECRET> match; otherwise it returns 401. Some routes (e.g. run-cadences) go further and return 503 "Not configured" when the secret is missing entirely, so a misconfigured deploy fails closed rather than running open.
curl -X POST https://core.olyron.com/api/cron/advance-cadences \
-H "Authorization: Bearer $CRON_SECRET"CRON_SECRETenv var- Shared bearer token for every /api/cron/* route. Required - routes reject requests without a matching header.
NEXT_PUBLIC_SUPABASE_URLenv var- Supabase project URL used by service-role cron jobs (SLA, A/B winner, NBA autoenroll).
SUPABASE_SERVICE_ROLE_KEYenv var- Service-role key that lets cron jobs operate across tenants. Jobs return 500/503 if it is missing.
#Agent Trees (AI orchestration)
Agent Trees are an AI-native orchestration layer for product enrollment and eligibility. A tree is an ordered collection of agent nodes attached to a product; each node has an agentType, an executionOrder, a config, and per-node controls like requiresHuman, haltOnFailure, and timeoutMs. Only one tree can be active per product at a time.
curl -X POST https://core.olyron.com/api/agent-trees \
-H "Content-Type: application/json" \
-d '{
"productId": "PRODUCT_ID",
"name": "Medicare eligibility pipeline"
}'
# List a product's trees
curl "https://core.olyron.com/api/agent-trees?productId=PRODUCT_ID"Unlike the visual workflow builder, agent trees carry rich execution context - an AgentExecutionContext includes a run id, the triggering user, tenant, product/plan ids, and a userContext (age, household size, state, income, etc.) used for eligibility. Node configs can set a confidenceThreshold for auto-approval, letting low-confidence steps route to a human while confident ones proceed automatically.