Developer section: these pages cover the S6S workflow engine for self-hosted installs and developers. On s6s.ai, API keys and MCP access are issued by S6S on request (contact us); on a self-hosted install, an admin creates them on the API Keys page. For the AI Visibility product, start at the AI Visibility docs.
Node Types
Every workflow is a series of steps connected in a flow. Each step has a node type that determines what it does. This page covers all 32 built-in node types.
In a Recipe, a step's action is the part after the dot (http, condition); triggers use trigger.type. In the tables below, required marks a field you must set and — means optional with no default. Defaults are the engine's; the canvas editor may prefill different values. Config objects are strict: an unknown key fails the deploy.
Triggers
Triggers define how a workflow starts. Every workflow needs exactly one trigger. See Triggers for authentication, timing and full examples.
trigger.webhook
Start a workflow when an HTTP request hits a specific path.
| Config | Type | Default | Description |
|---|---|---|---|
| path | string | required | URL path, e.g. /new-lead. Unique across the install. |
| method | "GET" | "POST" | POST | Stored with the trigger; the endpoint accepts both GET and POST. |
{ body, headers, query, method, path, receivedAt }https://s6s.ai/api/webhooks{path} — callers authenticate with the webhook secret from the deploy response (header, ?token=, HMAC signature or bearer token)."trigger": { "type": "webhook", "path": "/new-lead", "method": "POST" }trigger.schedule
Run a workflow on a recurring schedule using cron expressions.
| Config | Type | Default | Description |
|---|---|---|---|
| cron | string | required | Five-field cron expression (minute, hour, day, month, weekday). Stored in the IR as cronExpression. |
| timezone | string | UTC | IANA timezone name |
{ scheduledAt, fired_at }Both fields hold the same ISO timestamp, set when the scheduled run starts.
- */5 * * * * — every 5 minutes
- 0 9 * * 1-5 — weekdays at 9 AM
- 0 0 * * * — daily at midnight
- 0 0 1 * * — first of every month
"trigger": { "type": "schedule", "cron": "0 9 * * 1-5", "timezone": "Europe/London" }trigger.manual
Start a workflow on demand: "Run Now" in the app, or POST /api/v1/workflows/{id}/run with an API key. No configuration needed.
{ payload, triggeredAt, triggeredBy }payload — the input object from the run request body (or {} if none)
triggeredAt — ISO timestamp set when the run's trigger step executes
triggeredBy — always "api"
"trigger": { "type": "manual" }trigger.telegram
Start a workflow when a Telegram chat linked to your S6S account sends a text message to the S6S Telegram bot.
| Config | Type | Default | Description |
|---|---|---|---|
| credential | string | — | Optional label stored on the trigger. Message routing does not depend on it. |
{ chatId, text, from, chat, messageId, date, receivedAt }Each message starts a run of every enabled workflow you own with a telegram trigger. Bot commands such as /start and /help are handled by the bot instead. Deploying starts one run with empty trigger data unless you send "autoRun": false.
"trigger": { "type": "telegram" }Actions
Actions do work — call APIs, send messages, transform data, run code. A step that needs a credential uses the connected account named by its credential field, or any connected account for the provider when it is "default" or omitted.
action.http
Make an HTTP request to a public URL.
| Config | Type | Default | Description |
|---|---|---|---|
| url | string | required | Full URL. Private and internal addresses are blocked. |
| method | string | POST | GET, POST, PUT, PATCH or DELETE |
| headers | object | — | Request headers (string values). No Content-Type is added for you. |
| body | string | object | — | Request body; objects are sent as JSON text. Ignored for GET. |
| timeoutMs | number | 30000 | Request timeout, 100–300000 ms |
{ status, statusCode, statusText, body }JSON responses are parsed into body; other text is cut at 4096 characters. Binary responses (images, video, audio, PDF, archives) are stored as a temporary file and return fileUrl, fileName, mimeType, sizeBytes and expiresAt instead of body. A non-2xx response fails the step with HTTP_<status>. An optional credential is injected as authentication.
{
"ref": "create_contact",
"action": "http",
"config": {
"url": "https://api.example.com/contacts",
"method": "POST",
"headers": { "Content-Type": "application/json" },
"body": { "email": "{{trigger.output.body.email}}" },
"timeoutMs": 10000
}
}action.transform
Reshape data between steps using a JSON template with expressions.
| Config | Type | Default | Description |
|---|---|---|---|
| template | object | required | JSON template with {{expression}} placeholders |
Output: The template with every expression resolved. An expression that points at an object or array is inserted as JSON text.
{
"ref": "format",
"action": "transform",
"config": {
"template": {
"message": "Hello, {{trigger.output.body.name}}!",
"received": "{{trigger.output.receivedAt}}"
}
}
}action.code
Run JavaScript in a sandbox and return the result.
| Config | Type | Default | Description |
|---|---|---|---|
| expression | string | required | A single expression, or statements with return. await is allowed. |
| language | string | javascript | Only javascript |
The code can read steps (earlier outputs by ref), trigger (the trigger output) and input (this step's config), and can call fetch on public addresses. It has no access to Node.js modules and times out after 30 seconds. Output: a returned object or array as-is; other values as { value }.
{
"ref": "count_orders",
"action": "code",
"config": {
"expression": "const orders = trigger.body.orders || []; return { count: orders.length };"
}
}action.filter
Filter, map, find or reduce an array.
| Config | Type | Default | Description |
|---|---|---|---|
| arrayExpression | string | required | Expression that resolves to an array, e.g. {{fetch.output.body.items}} |
| condition | string | required | JavaScript expression using item and index (and acc for reduce) |
| mode | string | filter | filter, map, find or reduce |
| reduceInitial | any | — | Starting value for reduce |
Output: an array result as { result, count }; an object result as-is; any other result as { result }. Up to 100,000 items.
{
"ref": "big_orders",
"action": "filter",
"config": {
"arrayExpression": "{{trigger.output.body.orders}}",
"condition": "item.total > 100",
"mode": "filter"
}
}action.email
Send a plain-text email via SMTP, Resend, SendGrid or Postmark.
| Config | Type | Default | Description |
|---|---|---|---|
| to | string | required | Recipient email |
| subject | string | required | Email subject |
| bodyTemplate | string | required | Email body as plain text (supports expressions) |
| provider | string | — | smtp, resend, sendgrid or postmark. When omitted: SMTP if the credential has a host, Resend if it has an api_key. |
| from | string | — | Sender address. Defaults to the credential's from, user or username field. |
{ messageId, accepted, rejected, provider }That is the SMTP output; the other providers return { messageId, provider, status }.
Requires credential: smtp
action.slack
Post a message to a Slack channel.
| Config | Type | Default | Description |
|---|---|---|---|
| channel | string | required | Channel name or ID |
| textTemplate | string | required | Message text (supports expressions) |
{ channel, ts }Requires credential: slack
action.discord
Send a message to a Discord channel.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | send_message | Only send_message |
| channelId | string | required | Discord channel ID |
| content | string | required | Message content, up to 2000 characters |
| username | string | — | Passed to Discord as username |
| avatarUrl | string | — | A URL, passed to Discord as avatar_url |
{ messageId, channelId }Requires credential: discord
action.telegram
Send messages, photos or documents, forward a message, or read updates with a Telegram bot.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | send_message | send_message, send_photo, send_document, get_updates, forward_message |
| chatId | string | — | Target chat. Falls back to the chat_id stored on the credential. |
| text | string | — | Message text (required for send_message) |
| parseMode | string | — | HTML, MarkdownV2 or Markdown |
| photoUrl | string | — | Photo to send (required for send_photo) |
| documentUrl | string | — | Document to send (send_document) |
| caption | string | — | Caption for a photo or document |
| offset | number | — | Update offset (get_updates) |
| limit | number | — | Updates to fetch, 1–100 (get_updates) |
| fromChatId | string | — | Source chat (forward_message) |
| messageId | number | — | Message to forward (forward_message) |
{ messageId, chatId, action, chunks }That is the send_message output. send_photo, send_document and forward_message return { messageId, chatId, action } (forward_message adds fromChatId); get_updates returns { updates, updateCount, action }.
Requires credential: telegram
Auto-chunking: With send_message, text longer than 4096 characters is split, at a newline where possible, and sent as several messages.
action.imap
Fetch, search, or read mailbox content over IMAP.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | fetch_recent | fetch_recent, search, read_message |
| query | string | — | Search query for search mode |
| messageId | string | — | Message identifier (required for read_message) |
| maxResults | number | 20 | Maximum messages to return, 1–100 |
| since | string | — | Relative time filter like 1h or 7d |
| folder | string | INBOX | Mailbox folder |
| markAsRead | boolean | false | Mark fetched messages as read |
{ messages, resultCount } or { message }Requires credential: imap
action.rss
Fetch and parse an RSS or Atom feed.
| Config | Type | Default | Description |
|---|---|---|---|
| feedUrl | string | required | Feed URL |
| maxItems | number | 10 | Maximum items to return, 1–100 |
{ feedTitle, itemCount, items: [{ title, link, description, pubDate }] }action.twitter
Post a tweet or search tweets.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | post_tweet | post_tweet or search |
| text | string | — | Tweet text, up to 280 characters (required for post_tweet) |
| query | string | — | Search query (required for search) |
| maxResults | number | 10 | Search results, 1–100 |
{ tweetId, text } or { resultCount, tweets }Requires credential: twitter
action.google_sheets
Append rows, read rows, or update cells in Google Sheets.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | append_row | append_row, read_rows, update_cell |
| spreadsheetId | string | required | Spreadsheet ID from the URL |
| range | string | required | A1 range, e.g. Sheet1!A:C |
| values | array | — | Rows as arrays of strings, numbers or booleans (required for append_row) |
{ rowCount, values } / { updatedRows, updatedRange } / { updatedRange, updatedCells }For read_rows, append_row and update_cell respectively.
Requires credential: google
action.google_drive
List, upload, or download files in Google Drive.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | list | list, upload, download, download_media |
| folderId | string | — | Folder to list or upload into |
| fileName | string | — | Name for upload (required for upload) |
| fileId | string | — | File to download |
| content | string | — | File content for upload |
| mimeType | string | application/octet-stream | MIME type for upload |
| maxResults | number | 20 | Files to list, 1–100 |
Output: list { fileCount, files }; upload { fileId, name, mimeType }; download returns the file as text { contentLength, content, wasTruncated } (up to 65,536 characters); download_media stores binary content as a temporary file { fileUrl, fileName, mimeType, sizeBytes, expiresAt }.
Requires credential: google
action.gmail
Search, read, or send email via the Gmail API.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | search_messages | search_messages, read_message, send_message |
| query | string | — | Gmail search query (required for search_messages) |
| messageId | string | — | Message ID (required for read_message) |
| maxResults | number | 10 | Max results for search, 1–100 |
| to | string | — | Recipient (required for send_message) |
| subject | string | — | Email subject (required for send_message) |
| body | string | — | Email body (required for send_message) |
| bodyType | string | text | text or html |
{ messages, resultCount, account } / { message } / { messageId, threadId }Requires credential: google
action.database
Run a SQL query against a connected PostgreSQL database.
| Config | Type | Default | Description |
|---|---|---|---|
| query | string | required | SQL query; use $1, $2 … for parameters |
| params | array | — | Parameter values (string, number, boolean or null) |
| operation | string | select | select, insert, update, delete or raw. A label: select marks the step as a read; the SQL runs as written. |
{ rows, rowCount, operation }Requires credential: database
action.ai
Send a prompt to the LLM configured for the install (OpenAI, Anthropic, Google or OpenRouter), optionally as a tool-using agent.
| Config | Type | Default | Description |
|---|---|---|---|
| prompt | string | required | The prompt (supports expressions) |
| systemPrompt | string | — | System instructions |
| responseFormat | string | text | text or json. With json, the reply is also parsed into json. |
| model | string | — | Model name. Defaults to the install's model; a name that doesn't fit the configured provider is ignored. |
| temperature | number | 0.2 | 0–2. Defaults to the install setting, else 0.2. |
| maxTokens | number | 4096 | 1–128000. Defaults to the install setting, else 4096. |
| outputSchema | object | — | JSON Schema for structured output. Can't be combined with tools. |
| tools | array | — | Up to 20 tools: { name, description, parameters }. A tool with no built-in handler returns its arguments as the result, which is useful for structured extraction. |
| agentMode | boolean | false | Adds the http_request tool (public URLs only) and loops: the model calls tools until it answers or hits maxIterations. |
| maxIterations | number | 10 | Agent loop limit, 1–50. At the limit, one last call without tools produces the answer. |
| memoryEnabled | boolean | false | Adds memory_read, memory_write and memory_search tools. Memory belongs to the workflow and persists across runs. |
| skills | string[] | — | Up to 5 skill slugs (yours or public). Each skill adds its instructions to the system prompt and its tools. |
| workflowTools | array | — | Up to 5 { slug, name, description }. Each becomes a tool named s6s_wf_<slug> that runs that workflow with the tool call's arguments as input and waits up to 15 seconds for its result. |
| images | string[] | — | Up to 10 image URLs or data: URIs sent with the prompt |
| streaming | boolean | false | Accepted; it does not change the step output. |
| timeoutMs | number | 120000 | Per-request timeout, 1000–300000 ms |
{ text, model, promptTokens, completionTokens }Extra output fields: json (responseFormat json or outputSchema), schemaValid (with outputSchema), toolCalls (every tool call with its arguments and result), toolCall (outside agent mode, when there was exactly one call), and totalIterations (agent mode). The step needs no credential; it uses the install's LLM settings.
{
"ref": "research",
"action": "ai",
"config": {
"systemPrompt": "You are a careful research assistant.",
"prompt": "Find the pricing page of {{trigger.output.payload.domain}} and list its plans as JSON.",
"responseFormat": "json",
"agentMode": true,
"maxIterations": 8,
"memoryEnabled": true
}
}{
"ref": "classify",
"action": "ai",
"config": {
"prompt": "Classify this support message: {{trigger.output.body.text}}",
"outputSchema": {
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["billing", "bug", "other"] },
"urgent": { "type": "boolean" }
},
"required": ["category", "urgent"]
}
}
}action.mcp_tool
Call one tool on a remote MCP server.
| Config | Type | Default | Description |
|---|---|---|---|
| serverUrl | string | required | MCP server URL. Private and internal addresses are blocked. |
| toolName | string | required | Tool to call |
| arguments | object | — | Tool arguments (supports expressions) |
| transport | string | sse | streamable-http: Streamable HTTP only. sse: tries Streamable HTTP first and uses the older HTTP+SSE transport when the server refuses that request with a 4xx. |
| timeoutMs | number | 30000 | Request timeout, 1000–120000 ms |
{ toolName, result, isError, serverUrl, transport }S6S sends initialize and then tools/call, without authentication headers. Replies may be JSON or an event stream. With the older transport, S6S opens the event stream at serverUrl and posts to the endpoint it names, which must be on the same origin. transport in the output says which one ran. result is the tool's text output, parsed as JSON when possible. A JSON-RPC error fails the step with MCP_TOOL_ERROR.
{
"ref": "lookup_issue",
"action": "mcp_tool",
"config": {
"serverUrl": "https://mcp.example.com/mcp",
"toolName": "get_issue",
"arguments": { "id": "{{trigger.output.payload.issue_id}}" },
"timeoutMs": 20000
}
}action.rag
Search a knowledge base with a query embedding.
| Config | Type | Default | Description |
|---|---|---|---|
| query | string | required | Search text (supports expressions) |
| knowledgeBaseId | string | required | Knowledge base to search |
| topK | number | 5 | Chunks to return, 1–50 |
| minScore | number | 0.5 | Minimum similarity, 0–1 |
| includeMetadata | boolean | true | Include chunk metadata |
Status: the schema and the deploy API accept this node, but no vector store is connected in this release. The step embeds the query with the install's LLM key and then fails with RAG_NO_VECTOR_STORE (or RAG_NO_API_KEY when no key is set).
action.buffer
Create, update, delete and publish posts through Buffer, and list channels, queued and sent posts.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | create_post | create_post, list_channels, get_queue, get_sent, share_now, delete_post, update_post |
| text | string | — | Post text (required for create_post; also update_post) |
| mediaUrl | string | — | URL of an image or video to attach |
| channelIds | array | — | Buffer channel IDs to publish to (create_post) |
| scheduledAt | string | — | ISO timestamp to schedule the post |
| shareMode | string | — | queue, now or schedule. Without it: schedule when scheduledAt is set, else queue. |
| postType | string | — | post, story or reel (Instagram and Facebook channels) |
| shorten | boolean | true | Shorten links in post text |
| profileId | string | — | Profile/channel ID (get_queue, get_sent) |
| updateId | string | — | Buffer update ID (share_now, delete_post, update_post) |
| count | number | — | Results per page, 1–100 (get_queue, get_sent) |
| page | number | — | Page number for pagination |
| since | string | — | Unix timestamp — only return updates after this time |
Output: create_post { postIds, postId, count, scheduledAt, errors? }; list_channels { channels, channelCount }; get_queue and get_sent { updates, updateCount, total }; share_now { success, updateId }; delete_post { deleted, updateId }; update_post { postId, status, text, scheduledAt }.
Requires credential: buffer
action.youtube
Upload videos, list channel videos, get video details, and update video metadata via the YouTube Data API.
| Config | Type | Default | Description |
|---|---|---|---|
| action | string | upload_video | upload_video, list_videos, get_video, update_video |
| videoUrl | string | — | URL of the video to upload (required for upload_video) |
| title | string | — | Video title (required for upload_video) |
| description | string | — | Video description |
| tags | array | — | Video tags |
| privacyStatus | string | private | public, unlisted, or private |
| categoryId | string | 22 | YouTube video category ID |
| videoId | string | — | Video ID (required for get_video / update_video) |
| maxResults | number | 10 | Max videos to return, 1–50 (list_videos) |
Output: list_videos { videos, resultCount }; the other actions { videoId, url, status, title }, plus channelId for upload_video and get_video.
Requires credential: google
action.webhook_response
Record a status code, headers and body as the step output.
| Config | Type | Default | Description |
|---|---|---|---|
| statusCode | number | 200 | HTTP status code, 100–599 |
| headers | object | — | Header values (strings) |
| body | string | object | — | Response body; objects are stored as JSON text |
{ statusCode, headers, body }This does not change the HTTP reply to the webhook caller. That reply is sent as soon as the run is created and contains the run ID and trigger data.
action.execute_workflow
Start another S6S workflow by slug.
| Config | Type | Default | Description |
|---|---|---|---|
| slug | string | required | Target workflow slug |
| input | object | — | Input for the child run (its trigger payload) |
| waitForCompletion | boolean | true | Wait for the child run to finish |
| maxWaitMs | number | 300000 | How long to wait, 1000–600000 ms |
{ runId, status, workflowSlug, result }result holds the child run and its steps. Without waiting, the output is { triggered, runId, workflowSlug }. A failed or cancelled child run fails this step; so does running out of wait time (EXECUTE_WORKFLOW_TIMEOUT).
{
"ref": "run_child",
"action": "execute_workflow",
"config": {
"slug": "enrich-lead",
"input": { "email": "{{trigger.output.body.email}}" },
"waitForCompletion": true,
"maxWaitMs": 120000
}
}action.stop_and_error
Fail the step, and so the run, with a custom message. Useful for guardrails.
| Config | Type | Default | Description |
|---|---|---|---|
| message | string | required | Error message; the step fails with STOP_AND_ERROR: <message> |
{ message }Usually combined with run_if so it only fires when a check fails.
Logic
Logic nodes control execution flow — branching, routing, timing, and joining parallel steps.
logic.condition
Branch execution on a comparison. In a Recipe, place it first in a flow tuple: [condition, true_branch, false_branch].
| Config | Type | Default | Description |
|---|---|---|---|
| expression | string | required | Left-hand value (supports expressions) |
| operator | string | required | Comparison operator (see below) |
| value | string | number | boolean | array | — | Right-hand value |
eqneqgtgteltltecontainsnot_containsexistsnot_existsmatchesineq, neq, contains and not_contains compare as text. gt, gte, lt and lte compare as numbers (an empty value counts as 0) and are false if either side isn't numeric. matches takes a regular expression; in takes an array or a comma-separated list. exists is true when the field the expression names is set (an empty string, 0 and false count as set) and false when it is missing or null; not_exists is the opposite. For a template that mixes text or several fields, exists means its text is not empty.
{ conditionResult, resolvedValue, operator, compareValue, skippedNodeIds }Raw IR also accepts a compound form, { logic: "and" | "or", conditions }: and needs every condition to hold, or any one, and a condition can itself be compound (up to 10 levels). Its output has logic in place of operator and compareValue, and resolvedValue reads compound(and) or compound(or). Recipes take one comparison per condition step.
{
"name": "Check Order Size",
"trigger": { "type": "manual" },
"steps": [
{
"ref": "is_large",
"action": "condition",
"config": {
"expression": "{{trigger.output.payload.total}}",
"operator": "gt",
"value": 1000
}
},
{
"ref": "large_order",
"action": "transform",
"config": { "template": { "size": "large" } }
},
{
"ref": "small_order",
"action": "transform",
"config": { "template": { "size": "small" } }
}
],
"flow": [["is_large", "large_order", "small_order"]]
}logic.switch
Route to one of several steps based on an expression's value.
| Config | Type | Default | Description |
|---|---|---|---|
| expression | string | required | Value to switch on |
| cases | array | required | At least one { value, target_ref } in a Recipe ({ value, target } with a node ID in the IR) |
| defaultTarget | string | — | Step ref to run if no case matches |
{ resolvedValue, matchedTarget, skippedNodeIds }Values are compared as text. The matching step runs; the other targets and the steps that only follow them are skipped. With no match and no default, all targets are skipped. Put the targets after the switch in the flow, usually as one parallel group.
{
"name": "Route Ticket",
"trigger": { "type": "manual" },
"steps": [
{
"ref": "route",
"action": "switch",
"config": {
"expression": "{{trigger.output.payload.priority}}",
"cases": [
{ "value": "high", "target_ref": "page_oncall" },
{ "value": "low", "target_ref": "log_ticket" }
],
"defaultTarget": "log_ticket"
}
},
{
"ref": "page_oncall",
"action": "transform",
"config": { "template": { "action": "page" } }
},
{
"ref": "log_ticket",
"action": "transform",
"config": { "template": { "action": "log" } }
}
],
"flow": ["route", ["page_oncall", "log_ticket"]]
}logic.loop
Turn an expression into an array of items, capped at maxIterations.
| Config | Type | Default | Description |
|---|---|---|---|
| arrayExpression | string | required | Expression resolving to an array, e.g. {{fetch.output.body.items}}. A non-array value becomes a one-item array. |
| itemVariable | string | item | Name returned in the output |
| maxIterations | number | 100 | Maximum items kept, 1–1000 |
{ items, iterations, itemVariable }The loop does not run later steps once per item. To process each item, use action.filter (map mode) or action.code.
logic.wait
Pause execution for a specified duration.
| Config | Type | Default | Description |
|---|---|---|---|
| seconds | integer | required | Wait time in seconds (1 or more) |
{ waited, seconds, actualMs }logic.merge
Combine outputs from parallel steps into a single object. Place it right after a parallel group in the flow array.
| Config | Type | Default | Description |
|---|---|---|---|
| mode | string | wait_all | Merge strategy; only wait_all exists, and Recipes always use it |
{ "<step_ref>": <that step's output>, ... }{
"name": "Fetch Two Sources",
"trigger": { "type": "manual" },
"steps": [
{
"ref": "branch_a",
"action": "http",
"config": { "url": "https://api.example.com/a", "method": "GET" }
},
{
"ref": "branch_b",
"action": "http",
"config": { "url": "https://api.example.com/b", "method": "GET" }
},
{
"ref": "combine",
"action": "merge",
"config": {}
},
{
"ref": "send_result",
"action": "transform",
"config": {
"template": {
"a_status": "{{combine.output.branch_a.status}}",
"b_status": "{{combine.output.branch_b.status}}"
}
}
}
],
"flow": [["branch_a", "branch_b"], "combine", "send_result"]
}combine waits for both parallel steps and outputs their results keyed by ref.
Conditional Execution
Any step can include a run_if field next to its config (not inside it). The engine resolves it at runtime: a truthy result runs the step, a falsy one skips it with status skipped. Skipped steps do not fail the run. Put comparison operators outside the braces.
{
"ref": "send_alert",
"action": "telegram",
"config": {
"action": "send_message",
"chatId": "123456",
"text": "Alert: amount is {{trigger.output.payload.amount}}"
},
"credential": "default",
"run_if": "{{trigger.output.payload.amount}} > 1000"
}See Recipe Format for the full comparison rules.
Dynamic Integrations
Beyond the built-in action types, S6S supports a provider.action syntax for other APIs, such as Notion, Pinterest, HubSpot or Stripe. These steps compile to action.http nodes.
| Action | Description |
|---|---|
| notion.create_page | Create a page in Notion |
| pinterest.create_pin | Create a pin on Pinterest |
| hubspot.create_contact | Create a contact in HubSpot |
| stripe.create_invoice | Create a Stripe invoice |
S6S resolves the provider and action from its store of learned integrations (learning unknown ones at deploy time when the learning module and an LLM are available), builds the request, and injects the step's credential. Failed calls are retried only under the step's retry policy. Learn more about dynamic integrations.
Expressions
Use {{expression}} syntax in any config string to reference data from the trigger and earlier steps:
| Expression | Description |
|---|---|
| {{trigger.output.body.name}} | Webhook request body field |
| {{format.output.message}} | An earlier step's output by ref ({{steps.format.output.message}} also works) |
| {{fetch.output.body.items.0}} | Array element by index (dot notation; brackets are not supported) |
| {{run.id}} | Current run ID |
Expressions are resolved just before each step runs. See Expressions for every context and output shape.
Need help choosing node types?
The Recipe Format guide shows how steps, flow and error handling fit together.