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.
- Create a free account at casc8.io/signup
- Click New Workflow on the dashboard
- Drag a Schedule Trigger or Webhook Trigger onto the canvas
- Add nodes — HTTP Request, AI, Code — and connect them
- Set the workflow to Active and it will run automatically
🐳 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 -d3. Open the app
Navigate to http://your-server-ip:3000 and create your admin account on first run.
SELF_HOSTED=true to disable public signup — only the first registered user becomes admin.Updating
docker compose pull && docker compose up -dCore 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
| State | Meaning |
|---|---|
success | All nodes completed without error |
failed | At least one node threw an error |
running | Currently in progress (streaming) |
pending | Queued, 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
| Token | JS variable | What it resolves to |
|---|---|---|
{{input.field}} | input.field | Field from the direct upstream node output |
{{data.field}} | data.field | Alias for input — same object, two names |
{{vars.myVar}} | vars.myVar | Value stored by a Set Variable node earlier in the run |
{{nodes.UUID.field}} | nodes['uuid'].field | Output of any upstream node by its UUID |
{{$secret.MY_KEY}} | N/A (injected) | Decrypted value from the Secrets Vault |
{{timestamp}} | timestamp | ISO 8601 string of when the execution started |
{{executionId}} | executionId | Unique 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 tagsinput.body.user, vars.plan, nodes[id].output.Transform node modes
| Mode | Description | Expression example |
|---|---|---|
passthrough | Passes input unchanged | (none) |
template | Renders {{}} template string | Hello {{input.name}} |
jsonpath | Extracts by dot-path | body.user.email |
js | Evaluates a JS expression | return 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
- Go to Settings → Secrets Vault
- Click Add secret
- Enter a name in
UPPER_SNAKE_CASE(e.g.MY_API_KEY), the value, and optional description - 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}}"}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.
trigger_manualFires the workflow on-demand via the dashboard or the REST API.
trigger_scheduleRuns the workflow on a cron expression, e.g. `0 9 * * 1` for every Monday at 9 AM.
trigger_webhookGenerates a unique URL. Any HTTP POST/GET to that URL fires the workflow with the full request as input.
http_requestMakes an outbound HTTP call. Supports GET/POST/PUT/PATCH/DELETE, custom headers, auth, and body templates.
ai_llmCalls an AI provider (OpenAI, Groq, Anthropic). Configure provider, model, system prompt, and user message.
codeRuns arbitrary JavaScript. Access upstream node outputs via `input`. Must return a value.
conditionEvaluates a boolean expression. Routes to the "true" or "false" output accordingly.
set_variableStores a value in workflow memory for later nodes to access.
data_transformMaps, filters, and reshapes JSON data using a JavaScript expression.
delayPauses the workflow for a configurable number of seconds before continuing.
send_emailSends a transactional email via the configured SMTP provider.
output_responseSets the HTTP response body when the workflow was triggered by a webhook.
Additional nodes (full reference)
| Type | Category | What it does |
|---|---|---|
action_google_sheets | Integration | Read/append/update/clear Google Sheets via API key. Operations: read, append, update, clear, metadata. |
action_airtable | Integration | List/get/create/update/delete Airtable records (auto-paginates). Operations: list, get, create, update, patch, delete, search. |
action_notion | Integration | Query, get, create, update Notion pages and databases. Operations: query_database, get_page, create_page, update_page. |
action_graphql | Integration | Execute a GraphQL query or mutation against any endpoint. Accepts query, variables, headers, auth_token. |
action_slack | Notification | Post a message (or color-coded attachment) to a Slack channel via Incoming Webhook URL. |
action_discord | Notification | Send content to a Discord channel via webhook URL. Supports username, avatar_url, and tts. |
action_email_send | Notification | Send transactional email via configured SMTP. Supports to/cc/bcc/reply_to, plain text and HTML body. |
action_webhook_notify | Notification | HTTP POST to any external URL. Thin wrapper around the HTTP node. |
action_json_extract | Data | Extract a value from JSON by dot-notation path (e.g. body.user.email). Optionally wraps result in output_field. |
action_template | Data | Render a Mustache-like template string with {{}} placeholders against the execution context. |
action_filter_array | Data | Filter an array using a JS boolean expression per item (item.active === true). |
action_merge | Data | Combine outputs from multiple upstream nodes into a single object keyed by node ID. |
action_loop | Data | Wrap an array into {items, total} metadata. Accepts an optional field name to extract the array. |
action_format_date | Data | Format a date: iso / locale / unix / date (YYYY-MM-DD) / time / custom pattern. |
action_switch | Logic | Route to different branches based on matched case value. Default branch handles unmatched input. |
action_error_handler | Logic | Catches errors from upstream nodes. Routes to success handle on pass, error handle on failure. |
action_set_variable | Storage | Store any value as a named workflow variable accessible via {{vars.name}} in any later node. |
action_get_variable | Storage | Retrieve a stored variable by name. |
action_database_query | Storage | Run raw SQL on the built-in SQLite database. Operations: query (SELECT), insert, update, delete. |
action_call_workflow | Developer | Invoke another Casc8 workflow as a sub-workflow, passing input and receiving its last node output. |
output_log | Output | Emit the current data to the execution console. Appears in the Logs tab of Execution History. |
output_response | Output | Sets the HTTP response body for webhook-triggered workflows. Downstream from the trigger node. |
output_notify | Output | Tags a field as the workflow result displayed in the dashboard run summary. |
output_format | Output | Apply a template string to format the final output before passing it to the response. |
note | Canvas | Sticky 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).
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.
Execute a Workflow
/api/workflows/{id}/executeManually triggers a workflow and waits for completion. Returns the execution result.
Path parameters
idstringrequiredThe 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
/api/workflows/{id}/webhookFires a workflow from any external service — no authentication required. The full request (method, headers, query, body) is passed as the trigger input.
# 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
/api/workflows/{id}/executeReturns 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
| Plan | Executions / month | Price | Support |
|---|---|---|---|
| Free | 100 | €0 | Community |
| Pro | 10,000 | €29 | Email & chat |
| Team | 100,000 | €99 | Dedicated |
| Self-Hosted | Unlimited | Free | Community |
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
| Variable | Required | Description |
|---|---|---|
SESSION_SECRET | Yes | Random 32+ character string for signing sessions |
SELF_HOSTED | Recommended | Set to true to disable public signup |
NODE_ENV | Yes | Set to production |
DATA_DIR | No | Path for SQLite file. Default: /data |
SMTP_HOST | No | SMTP server for email (e.g. smtp.resend.com) |
SMTP_PORT | No | 465 (direct TLS) or 587 (STARTTLS) |
SMTP_USER | No | SMTP username |
SMTP_PASS | No | SMTP password / API key |
SMTP_FROM | No | From address, e.g. Casc8 <noreply@yourdomain.com> |
SMTP_VERIFY_EMAIL | No | Set 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).dbReverse 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-recreatecasc8.db before updating to a new major version.Health check
curl http://localhost:3000/api/config
# Returns: {"selfHosted":true,"googleAuth":false,"appleAuth":false}