Introduction

Casc8 is a visual workflow automation platform for developers and teams. Build multi-step automations by connecting nodes on a canvas — no infrastructure to manage on the cloud plan, or full control with a single Docker Compose file.

This documentation covers everything you need: from spinning up your first workflow to integrating with the REST API and self-hosting on your own servers.

Quick Start

☁️ Using the Cloud (SaaS)

The fastest way to start — no setup required.

  1. Create a free account at casc8.io/signup
  2. Click New Workflow on the dashboard
  3. Drag a Schedule Trigger or Webhook Trigger onto the canvas
  4. Add nodes — HTTP Request, AI, Code — and connect them
  5. Set the workflow to Active and it will run automatically
💡
The free plan includes 100 executions/month. Upgrade to Pro for 10,000.

🐳 Self-Hosting

One command to run Casc8 on your own server.

Prerequisites

  • Docker & Docker Compose (v2+)
  • Linux/macOS server with port 3000 accessible

1. Create docker-compose.yml

services:
  casc8:
    image: ghcr.io/casc8/casc8:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - casc8_data:/data
    environment:
      - NODE_ENV=production
      - SESSION_SECRET=your_random_32_char_secret_here
      - SELF_HOSTED=true
      # Optional SMTP for email notifications:
      # - SMTP_HOST=smtp.resend.com
      # - SMTP_PORT=465
      # - SMTP_USER=resend
      # - SMTP_PASS=re_xxxx
      # - SMTP_FROM=Casc8 <noreply@yourdomain.com>

volumes:
  casc8_data:

2. Start the container

docker compose up -d

3. Open the app

Navigate to http://your-server-ip:3000 and create your admin account on first run.

⚠️
Set SELF_HOSTED=true to disable public signup — only the first registered user becomes admin.

Updating

docker compose pull && docker compose up -d

Core Concepts

Workflows

A workflow is a directed graph of nodes. Each node performs one action and passes its output to connected downstream nodes. Workflows are triggered by a single trigger node.

Nodes

The building blocks of a workflow. Each node has a type, optional configuration, and produces an output that downstream nodes can reference.

Edges

Connections between nodes. Data flows along edges from the output of one node to the input of the next.

Executions

Every time a workflow runs, an execution record is created. You can inspect the input and output at every node in the execution log.

Referencing upstream data

In node configuration fields, use {{nodeid.output}} or {{nodeid.field}} to reference the output of any previous node. In Code nodes, access it via the input object.

Execution states

StateMeaning
successAll nodes completed without error
failedAt least one node threw an error
runningCurrently in progress (streaming)
pendingQueued, not yet started

Data & Template Reference

Casc8 uses a {{}} template syntax for injecting dynamic values into node configuration fields. The same values are available as plain JavaScript variables inside Code nodes.

Available context variables

TokenJS variableWhat it resolves to
{{input.field}}input.fieldField from the direct upstream node output
{{data.field}}data.fieldAlias for input — same object, two names
{{vars.myVar}}vars.myVarValue stored by a Set Variable node earlier in the run
{{nodes.UUID.field}}nodes['uuid'].fieldOutput of any upstream node by its UUID
{{$secret.MY_KEY}}N/A (injected)Decrypted value from the Secrets Vault
{{timestamp}}timestampISO 8601 string of when the execution started
{{executionId}}executionIdUnique ID of the current execution

Examples in node config fields

# HTTP body field
{"email": "{{input.body.user_email}}", "plan": "{{vars.selectedPlan}}"}

# Authorization header using a secret
Bearer {{$secret.STRIPE_SECRET_KEY}}

# Template node message
Hello {{input.name}}! Your order {{input.orderId}} has shipped.

# Reference a node output by UUID (copy from config panel)
{{nodes.a1b2c3d4-0000-0000-0000-000000000000.content}}

Nested path (dot notation)

body.user.email              → nested field
body.items[0].name           → first item in array
body.items.0.name            → same, dot notation
body.metadata.tags.1         → second element of tags
💡
In Code nodes do NOT use {{}} syntax — use plain JS: input.body.user, vars.plan, nodes[id].output.

Transform node modes

ModeDescriptionExpression example
passthroughPasses input unchanged(none)
templateRenders {{}} template stringHello {{input.name}}
jsonpathExtracts by dot-pathbody.user.email
jsEvaluates a JS expressionreturn input.items.map(i => i.id);

Secrets Vault

The Secrets Vault stores sensitive values (API keys, tokens, passwords) encrypted with AES-256-GCM. Secrets are never persisted in the workflow JSON graph — they are decrypted at execution time only.

Creating a secret

  1. Go to Settings → Secrets Vault
  2. Click Add secret
  3. Enter a name in UPPER_SNAKE_CASE (e.g. MY_API_KEY), the value, and optional description
  4. Click Save secret

Using a secret in a workflow

# HTTP node — Authorization header
Bearer {{$secret.OPENAI_API_KEY}}

# Slack webhook URL field
https://hooks.slack.com/services/{{$secret.SLACK_WEBHOOK}}

# HTTP body — mix secrets and dynamic data
{"api_key": "{{$secret.STRIPE_KEY}}", "customer": "{{input.customerId}}"}
⚠️
Secrets are encrypted using SESSION_SECRET as the key material. Rotate SESSION_SECRET carefully — existing secrets will need to be re-saved.

Secrets vs. API Keys

API Keys (Settings → API Keys) are provider records linked to AI nodes via a dropdown. Secrets are raw key-value pairs for any integration — HTTP headers, webhook URLs, passwords, etc.

Node Reference

Casc8 ships with 30+ built-in node types across 7 categories. All nodes receive the upstream output as input and produce an output passed to downstream nodes.

Manual Trigger
trigger_manual

Fires the workflow on-demand via the dashboard or the REST API.

⏱️
Schedule Trigger
trigger_schedule

Runs the workflow on a cron expression, e.g. `0 9 * * 1` for every Monday at 9 AM.

🪝
Webhook Trigger
trigger_webhook

Generates a unique URL. Any HTTP POST/GET to that URL fires the workflow with the full request as input.

🌐
HTTP Request
http_request

Makes an outbound HTTP call. Supports GET/POST/PUT/PATCH/DELETE, custom headers, auth, and body templates.

🤖
AI / LLM
ai_llm

Calls an AI provider (OpenAI, Groq, Anthropic). Configure provider, model, system prompt, and user message.

⚙️
Code (JS)
code

Runs arbitrary JavaScript. Access upstream node outputs via `input`. Must return a value.

🔀
Condition
condition

Evaluates a boolean expression. Routes to the "true" or "false" output accordingly.

📝
Set Variable
set_variable

Stores a value in workflow memory for later nodes to access.

🔄
Transform
data_transform

Maps, filters, and reshapes JSON data using a JavaScript expression.

Delay
delay

Pauses the workflow for a configurable number of seconds before continuing.

📨
Send Email
send_email

Sends a transactional email via the configured SMTP provider.

📤
Response
output_response

Sets the HTTP response body when the workflow was triggered by a webhook.

Additional nodes (full reference)

TypeCategoryWhat it does
action_google_sheetsIntegrationRead/append/update/clear Google Sheets via API key. Operations: read, append, update, clear, metadata.
action_airtableIntegrationList/get/create/update/delete Airtable records (auto-paginates). Operations: list, get, create, update, patch, delete, search.
action_notionIntegrationQuery, get, create, update Notion pages and databases. Operations: query_database, get_page, create_page, update_page.
action_graphqlIntegrationExecute a GraphQL query or mutation against any endpoint. Accepts query, variables, headers, auth_token.
action_slackNotificationPost a message (or color-coded attachment) to a Slack channel via Incoming Webhook URL.
action_discordNotificationSend content to a Discord channel via webhook URL. Supports username, avatar_url, and tts.
action_email_sendNotificationSend transactional email via configured SMTP. Supports to/cc/bcc/reply_to, plain text and HTML body.
action_webhook_notifyNotificationHTTP POST to any external URL. Thin wrapper around the HTTP node.
action_json_extractDataExtract a value from JSON by dot-notation path (e.g. body.user.email). Optionally wraps result in output_field.
action_templateDataRender a Mustache-like template string with {{}} placeholders against the execution context.
action_filter_arrayDataFilter an array using a JS boolean expression per item (item.active === true).
action_mergeDataCombine outputs from multiple upstream nodes into a single object keyed by node ID.
action_loopDataWrap an array into {items, total} metadata. Accepts an optional field name to extract the array.
action_format_dateDataFormat a date: iso / locale / unix / date (YYYY-MM-DD) / time / custom pattern.
action_switchLogicRoute to different branches based on matched case value. Default branch handles unmatched input.
action_error_handlerLogicCatches errors from upstream nodes. Routes to success handle on pass, error handle on failure.
action_set_variableStorageStore any value as a named workflow variable accessible via {{vars.name}} in any later node.
action_get_variableStorageRetrieve a stored variable by name.
action_database_queryStorageRun raw SQL on the built-in SQLite database. Operations: query (SELECT), insert, update, delete.
action_call_workflowDeveloperInvoke another Casc8 workflow as a sub-workflow, passing input and receiving its last node output.
output_logOutputEmit the current data to the execution console. Appears in the Logs tab of Execution History.
output_responseOutputSets the HTTP response body for webhook-triggered workflows. Downstream from the trigger node.
output_notifyOutputTags a field as the workflow result displayed in the dashboard run summary.
output_formatOutputApply a template string to format the final output before passing it to the response.
noteCanvasSticky note — purely visual annotation; not executed, not connected to edges.

Code Node Cookbook

The Code node runs async JavaScript in a sandboxed context. It receives input, vars, nodes, timestamp, executionId, and console. You must return a value. Timeout defaults to 5 s (max 30 s).

ℹ️
Use console.log(...) inside Code nodes — output appears in the Execution Console Logs tab.

1 — Transform / reshape data

const users = input.body.data;
return users.map(u => ({
  id: u.id,
  name: u.first_name + ' ' + u.last_name,
  email: u.email.toLowerCase(),
  isAdmin: u.role === 'admin',
}));

2 — Filter and count

const active = input.filter(u => u.status === 'active' && u.plan === 'premium');
console.log('Found ' + active.length + ' qualifying users');
return { users: active, count: active.length };

3 — Date math

const expiresAt = new Date(input.subscription.expires_at);
const daysLeft = Math.ceil((expiresAt - new Date()) / 86400000);
return { ...input, daysLeft, isExpiringSoon: daysLeft <= 7 && daysLeft >= 0 };

4 — Prepare a branching verdict for Condition node

const score = input.riskScore ?? 0;
return {
  ...input,
  verdict: score > 80 ? 'block' : score > 50 ? 'review' : 'allow',
};
// Downstream Condition: input.verdict === 'block'

5 — Build a Slack attachment payload

const ok = input.status === 'success';
return {
  text: (ok ? '✅' : '❌') + ' Deployment ' + input.service + ' ' + input.status,
  username: 'Casc8 Bot',
  icon_emoji: ':rocket:',
  color: ok ? '#10b981' : '#ef4444',
};

6 — Access upstream nodes by UUID

// Hover a node on canvas → config panel header shows the UUID
const httpOut = nodes['abc12345-dead-beef-cafe-000000000000'];
const aiOut   = nodes['def67890-1234-5678-abcd-000000000001'];
return {
  httpData: httpOut?.body?.data,
  aiContent: aiOut?.content,
};

7 — Use stored variables

// Set Variable node earlier: variable_name=userPlan, value={{input.plan}}
const plan  = vars.userPlan;            // 'free' | 'pro' | 'team'
const limit = plan === 'pro' ? 10000 : 100;
return { ...input, limit, planName: plan };

8 — Conditional HTTP call inside Code

// Note: fetch is available. Use this only when the HTTP node config isn't flexible enough.
if (!input.email) return { skipped: true };

const res = await fetch('https://api.example.com/notify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ to: input.email, message: 'Hello!' }),
});
const data = await res.json();
return { notified: res.ok, response: data };

REST API

All API endpoints are available at https://casc8.io/api (or your self-hosted URL). Authenticated endpoints require a valid session cookie — obtain one by logging in via the UI or using the session token from your account settings.

ℹ️
The API is rate-limited. Webhook endpoints allow 60 requests/min per IP. Execute endpoints respect your plan's monthly execution quota.

Execute a Workflow

POST/api/workflows/{id}/execute

Manually triggers a workflow and waits for completion. Returns the execution result.

Path parameters

idstringrequired

The workflow ID from the dashboard URL.

Request body

Optional. Any JSON object — passed as input to the trigger node.

curl -X POST https://casc8.io/api/workflows/wf_abc123/execute \
  -H "Content-Type: application/json" \
  -H "Cookie: casc8_session=<your_session>" \
  -d '{"userId": "123", "action": "send_report"}'

Response

{
  "executionId": "exec_xyz789",
  "status": "success",
  "output": { "emailSent": true },
  "durationMs": 1240
}

Error — quota exceeded

HTTP 402
{
  "error": "Monthly execution limit reached (100/100). Upgrade your plan.",
  "quota": { "used": 100, "limit": 100, "plan": "free" }
}

Webhook Trigger

POSTGETPUTPATCH/api/workflows/{id}/webhook

Fires a workflow from any external service — no authentication required. The full request (method, headers, query, body) is passed as the trigger input.

💡
Find the exact webhook URL for a workflow by opening it in the editor and clicking the Webhook Trigger node — the URL is shown in the config panel.
# POST with JSON body
curl -X POST https://casc8.io/api/workflows/wf_abc123/webhook \
  -H "Content-Type: application/json" \
  -d '{"event": "payment.success", "amount": 49.99}'

# GET with query parameters
curl "https://casc8.io/api/workflows/wf_abc123/webhook?user=456&action=sync"

Trigger input shape

{
  "method": "POST",
  "path": "/api/workflows/wf_abc123/webhook",
  "query": { "user": "456" },
  "headers": { "content-type": "application/json" },
  "body": { "event": "payment.success" },
  "timestamp": "2026-04-19T12:00:00.000Z"
}

Response

{
  "executionId": "exec_xyz789",
  "status": "success",
  "output": null,
  "durationMs": 340
}

If the workflow contains a Response node, its value becomes the HTTP response body.

Execution History

GET/api/workflows/{id}/execute

Returns the last 50 executions for a workflow, newest first.

curl https://casc8.io/api/workflows/wf_abc123/execute \
  -H "Cookie: casc8_session=<your_session>"
[
  {
    "id": "exec_xyz789",
    "workflow_id": "wf_abc123",
    "status": "success",
    "trigger_type": "webhook",
    "started_at": "2026-04-19T12:00:00.000Z",
    "duration_ms": 340,
    "output": { "emailSent": true },
    "error": null
  },
  ...
]

Plans & Billing

PlanExecutions / monthPriceSupport
Free100€0Community
Pro10,000€29Email & chat
Team100,000€99Dedicated
Self-HostedUnlimitedFreeCommunity
ℹ️
Execution counters reset on the 1st of each month at midnight UTC. When a subscription renews, the counter is reset automatically via the Stripe invoice.paid webhook.

Upgrading

Go to Settings → Billing in the dashboard and click Upgrade. You'll be redirected to Stripe Checkout. Once payment completes, your plan updates immediately.

Payment failure

If a renewal payment fails, your account is set to past_due status and a banner is shown in the dashboard. Update your payment method in Stripe — the next retry will restore your account automatically.

Cancellation

Cancel any time from Settings → Billing. Your plan stays active until the end of the billing period.

Self-Hosting Guide

Run Casc8 on any server that supports Docker. All data is stored in a single SQLite file mounted at /data/casc8.db.

Environment variables

VariableRequiredDescription
SESSION_SECRETYesRandom 32+ character string for signing sessions
SELF_HOSTEDRecommendedSet to true to disable public signup
NODE_ENVYesSet to production
DATA_DIRNoPath for SQLite file. Default: /data
SMTP_HOSTNoSMTP server for email (e.g. smtp.resend.com)
SMTP_PORTNo465 (direct TLS) or 587 (STARTTLS)
SMTP_USERNoSMTP username
SMTP_PASSNoSMTP password / API key
SMTP_FROMNoFrom address, e.g. Casc8 <noreply@yourdomain.com>
SMTP_VERIFY_EMAILNoSet to true to require email confirmation on signup

Backups

SQLite is a single file. Back it up by copying /data/casc8.db. The Docker volume casc8_data persists across container restarts and updates.

# Manual backup
docker cp casc8:/data/casc8.db ./casc8-backup-$(date +%Y%m%d).db

# Cron backup (daily at 2 AM)
0 2 * * * docker cp casc8:/data/casc8.db /backups/casc8-$(date +\%Y\%m\%d).db

Reverse proxy (nginx / Caddy)

Run Casc8 behind a reverse proxy for SSL. Example with Caddy (auto-HTTPS):

yourdomain.com {
    reverse_proxy localhost:3000
    encode gzip
}

Updating

docker compose pull && docker compose up -d --force-recreate
⚠️
Always back up casc8.db before updating to a new major version.

Health check

curl http://localhost:3000/api/config
# Returns: {"selfHosted":true,"googleAuth":false,"appleAuth":false}