Skip to main content

Workflows

Workflows are trigger-driven automations that take a list of domains and run them through a configurable action graph. Triggers fire on events like a new intent alert, a watchlist change, an audience match, an inbound webhook, or a schedule. Actions cover the common moves — enroll into a sequence, add to a list, score, enrich, call an external API, branch on a condition, fan out into sub-workflows.

tip

Workflows require the Starter plan or higher. Workflow plan limits: Starter 5 / Pro 25 / Business 100 / Enterprise unlimited.

Anatomy

Every workflow has four parts:

FieldWhat it does
trigger_typeWhen the workflow fires. One of intent_alert, audience_match, watchlist_change, new_domain, schedule, webhook, manual.
trigger_configConfiguration specific to the trigger (e.g. {alert_id: 42} for intent_alert, {cron: "0 9 * * 1"} for schedule).
conditionsOptional pre-filter applied to the candidate domains before any action runs. Array of {field, op, value} rules.
actionsThe action graph: an ordered list of {type, config} objects. Branches via parallel_split / merge_branches / conditional_branch.

List Workflows

Endpoint

GET /v1/workflows

Example Request

curl -X GET \
-H "Authorization: Bearer cdb_your_api_key_here" \
"https://api.crondb.com/v1/workflows"

Response

{
"workflows": [
{
"id": 17,
"name": "Notify Slack when fintech intent fires",
"description": "When a fintech intent alert matches, post the top 5 to #sales-leads.",
"trigger_type": "intent_alert",
"trigger_config": { "alert_id": 42 },
"conditions": [
{ "field": "industry", "op": "equals", "value": "fintech" }
],
"actions": [
{ "type": "filter_domains", "config": { "limit": 5 } },
{ "type": "http_request",
"config": {
"method": "POST",
"url": "https://hooks.slack.com/services/...",
"body": { "text": "Top 5 fintech: {{domains}}" }
}
}
],
"is_active": true,
"last_run_at": "2026-05-05T13:14:00Z",
"run_count": 28,
"created_at": "2026-04-12T11:00:00Z",
"updated_at": "2026-05-04T08:22:18Z"
}
],
"count": 1,
"limit": 25,
"remaining": 24
}

limit and remaining reflect plan-tier workflow quotas, not pagination.


Create Workflow

Endpoint

POST /v1/workflows

Body Parameters

ParameterTypeRequiredDescription
namestringYes1–200 chars.
descriptionstringNoUp to 2000 chars.
trigger_typestringYesintent_alert, audience_match, watchlist_change, new_domain, schedule, webhook, or manual.
trigger_configobjectSometimesRequired for triggers that need a target (alert_id, audience_id, etc.).
conditionsarrayNoUp to 50 condition objects.
actionsarrayYesUp to 100 action objects.
execution_limitsobjectNo{max_domains_per_run, max_runtime_seconds, ...} — overrides plan defaults.
canvas_dataobjectNoVisual editor coordinates. Free-form JSON, not interpreted by the engine.

When trigger_type is webhook, the response includes a freshly-generated webhook_url and webhook_token — point your external system at the URL.

Example Request

curl -X POST \
-H "Authorization: Bearer cdb_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Hot fintech to Slack",
"trigger_type": "intent_alert",
"trigger_config": { "alert_id": 42 },
"conditions": [{"field": "industry", "op": "equals", "value": "fintech"}],
"actions": [
{"type": "filter_domains", "config": {"limit": 5}},
{"type": "http_request", "config": {
"method": "POST",
"url": "https://hooks.slack.com/services/T00000/B00000/XXXXXXXX",
"body": {"text": "Top 5 fintech leads: {{domains}}"}
}}
]
}' \
"https://api.crondb.com/v1/workflows"

Get / Update / Delete Workflow

MethodPathNotes
GET/v1/workflows/{id}Returns the workflow (with webhook URL when applicable).
PUT/v1/workflows/{id}All fields optional. Each save creates a new entry in the version history (queryable at GET /workflows/{id}/versions).
DELETE/v1/workflows/{id}Cascades to runs.

Test (Dry Run)

Evaluate the trigger and conditions against sample domains and return what would execute, without actually running the action graph or charging budget.

POST /v1/workflows/{id}/test

Rate-limited to 10 calls per workflow per minute.

{
"workflow": { /* workflow object */ },
"test_results": {
"sample_domains_checked": 10,
"domains_after_conditions": 4,
"sample_filtered": [
{"domain": "stripe.com", "industry": "fintech", "intent_score": 91}
],
"actions_that_would_execute": [
{"type": "filter_domains", "config": {"limit": 5}},
{"type": "http_request", "config": {"method": "POST", "url": "..."}}
]
}
}

Execute Now

Run a workflow immediately against the trigger's natural domain source. Unlike /test, this charges budget and writes a WorkflowRun row. Use for manual reruns or manual-trigger workflows.

POST /v1/workflows/{id}/execute?activate=false

Set activate=true to additionally flip is_active=true if the workflow was paused.


List Runs

GET /v1/workflows/{id}/runs?limit=20&offset=0

Returns recent execution history with status (success, error, partial), trigger event, domain count, action results, and start/end timestamps. Each run row carries actions_executed — a JSONB array with one entry per action including its status, result, and any captured fields. Bearer tokens used in http_request headers are redacted before persisting.

Stream a single run

GET /v1/workflows/{id}/runs/{run_id}/stream

Server-Sent Events stream that emits action-by-action progress as the run executes. Each event is a JSON payload with the action type, status, and incremental result. Useful for live UIs.


Action graph reference

Every action object has the shape {type, config, retry?}. Common types:

TypeWhat it does
enroll_in_sequence{sequence_id} — Enroll the current domain set into a sequence.
add_to_list{list_id} — Push to a saved lead list.
score_domains{rule_id} — Apply a scoring rule and stamp the score onto each domain.
enrich{fields: ["dns","ssl","whois","tech_stack"]} — Fan out enrichment.
filter_domains{op, field, value, limit} — Narrow the domain list.
data_transform{operation, ...}map_fields, sort, dedup, limit, slice.
http_requestCall any external API. See below.
conditional_branch{branches: [{when, then}, ...]} — Route to different sub-actions.
parallel_split / merge_branchesRun sub-graphs in parallel, then re-merge.
cross_run_memory{op: "store"|"recall", key, value} — Persist state across runs.
wait_for_approval{slack_url|email, message, expires_in_hours} — Pause until a human clicks Approve/Reject.
auto_reply_templateAuto-reply to inbox threads using a stored template.
schedule_workflowTrigger another workflow now or at a future time.
sub_workflow{workflow_id} — Inline another workflow's actions. Cycle detection guards against A→B→A.

http_request action

The most flexible action — call any external API and capture the response.

{
"type": "http_request",
"config": {
"method": "POST",
"url": "https://api.example.com/v1/leads/{{domain}}",
"headers": { "Authorization": "Bearer {{ai_memory_recall:example_token}}" },
"body": { "domain": "{{domain}}", "score": "{{intent_score}}" },
"timeout_seconds": 30,
"capture_field": "data.id",
"iterate_into_domains": false
},
"retry": { "attempts": 2, "backoff_seconds": 1.0 }
}
Config fieldDescription
methodGET, POST, PUT, PATCH, or DELETE.
urlTarget URL. Supports {{variable}} template tokens, including {{ai_memory_recall:KEY}}.
headersObject of header name → value. Authorization headers are redacted in actions_executed audit trail.
bodyJSON body for POST/PUT/PATCH. Templated.
timeout_secondsDefault 30.
capture_fieldDot-path into the response JSON. The captured value is exposed to downstream actions as {{previous_result}}.
iterate_into_domainsWhen capture_field resolves to a list, replace the working domains set with those items so downstream actions fan out per item. Strings become {"domain": str}; objects pass through unchanged. The n8n "Item Lists" pattern.

SSRF guard

The http_request action blocks targets in private IP ranges, link-local + loopback, IPv6 unique-local, and known cloud metadata endpoints (169.254.169.254 and friends). Calls to those targets return a Blocked by SSRF guard error without making the network request.

Per-action retry

Add a retry block at the action level (sibling of config) to retry transient failures with exponential backoff:

"retry": { "attempts": 2, "backoff_seconds": 0.5 }
FieldRangeDescription
attempts0–5Number of additional attempts after the first failure.
backoff_seconds0–30Base delay. The engine doubles the wait between attempts (backoff × 2^(n-1)).

Set attempts: 0 (or omit retry) to keep the previous "fail fast" behavior. The retry_failed flag at the workflow level keeps working as a global "rerun the entire workflow once" knob; the per-action retry is independent and stacks on top.


Webhook trigger

Workflows with trigger_type: "webhook" get an auto-generated public URL. POST any JSON to it to fire the workflow.

POST https://api.crondb.com/v1/workflows/incoming/{token}

The token IS the auth — keep it secret. Rate-limited to 30 calls per token per minute.

curl -X POST \
-H "Content-Type: application/json" \
-d '{"domains": ["acme.com", "globex.com"], "source": "my-crm"}' \
"https://api.crondb.com/v1/workflows/incoming/abc123def456..."

The body is passed as trigger_data. Any domains key is extracted and used as the action input — accept either an array of strings or an array of objects ({domain, industry, ...}). When domains is omitted, the engine seeds with a sample query against the domain pool.

Response:

{
"status": "accepted",
"workflow_id": 17,
"run_id": 1042,
"run_status": "success"
}

Token management

MethodPathDescription
GET/v1/workflows/{id}/webhook-tokenReveal the token + URL + trigger count.
POST/v1/workflows/{id}/webhook-token/regenerateRotate. The old token immediately stops working.

Approvals

Pause a workflow mid-run until a human approves or rejects. Use the wait_for_approval action to send a Slack/email message with an Approve/Reject link, then resume execution with the decision available as {{trigger.approval_decision}} on the spawned follow-up run.

Approval landing

GET /v1/workflows/approvals/{token}

Public endpoint (no auth — the token is the secret). Renders an HTML page with Approve / Reject buttons that POST back to the same path. Use this URL in the Slack/email body of wait_for_approval.

Record decision

POST /v1/workflows/approvals/{token}

Accepts both JSON ({"decision": "approved"|"rejected"}) and form-encoded (decision=approved). On approved or rejected, the engine dispatches a NEW workflow run with trigger_event=approval_received carrying the decision — branch on it via conditional_branch on {{trigger.approval_decision}}.

Returns {status: "approved"|"rejected"|"already_decided"|"expired", ...}. Approvals expire automatically (default 24h, configurable in the action's expires_in_hours).


Budget

Cap monthly LLM spend per workflow. The engine debits each AI call (via ai_extract, ai_write, auto_reply_template, etc.) and auto-pauses the workflow when the cap is hit.

MethodPathDescription
GET/v1/workflows/{id}/budgetCurrent cap + month-to-date spend. Returns configured: false if no cap set.
PUT/v1/workflows/{id}/budgetSet or update. Body: {"monthly_cap_usd": 25.00, "auto_pause_on_exceed": true}.
POST/v1/workflows/{id}/budget/resetZero current month spend (manual override of the auto-pause).
DELETE/v1/workflows/{id}/budgetRemove the cap entirely.

Setting monthly_cap_usd: 0 disables the guard — the engine treats 0 as "no cap." Spend resets automatically at the start of every month; the workflow_maintenance_worker does the rollover.


Saved Connections

Reuse API endpoints across many http_request actions without re-typing the URL/auth in each one.

MethodPathDescription
GET/v1/connectionsList saved connections.
POST/v1/connectionsCreate. Body: {name, base_url, headers, auth_type, description}. Max 20 per user.
PUT/v1/connections/{id}Update any field.
DELETE/v1/connections/{id}Delete. Doesn't break workflows that referenced it — they fall back to the inline URL.

auth_type is one of none, bearer, basic, api_key. The actual secret lives encrypted in the headers blob, not on the row itself.


Versioning

Every PUT /v1/workflows/{id} snapshots the previous state to workflow_versions. Use these endpoints to inspect history or roll back:

MethodPath
GET/v1/workflows/{id}/versions
GET/v1/workflows/{id}/versions/{version_id}
POST/v1/workflows/{id}/versions/{version_id}/rollback

Templates & Marketplace

Pre-built workflows you can install with one click. Discoverable at GET /v1/workflows/templates (filterable by detail=summary|full). Install with POST /v1/workflows/install-template (body: {template_id}). Browse community-published workflows at GET /v1/workflows/marketplace. See Workflow Templates for the dashboard guide.


Notes

  • Workflow execution is in-process async. A redeploy mid-run will lose any actively-executing workflow; the workflow_run_queue table is the foundation for durable execution but the broader migration is on the roadmap.
  • All AI calls inside actions are logged to the AI cost dashboard with action_type of the originating action (e.g. workflow:ai_extract, workflow:auto_reply_template).
  • The actions_executed JSONB on each WorkflowRun is your audit trail — it captures every action's input, output, status, and timing. Headers in http_request are redacted to keep tokens out of the audit log.
  • A workflow_maintenance_worker runs hourly: prunes runs older than 90 days (configurable), resets monthly budgets, expires ai_memory keys past their TTL.

Next Steps

  • Sequences — Most common downstream action: enroll a workflow's domain set into an outreach sequence.
  • Webhooks — Outbound webhooks complement workflows. Workflows fire actions; outbound webhooks notify your stack of CronDB-side events.
  • Workflow Builder — Visual editor walkthrough.