Connector Reference
Connectors define the actions a step can perform. Actions use a connector/action naming convention. For AI tool use (function calling), see Tool Use.
http/request
Makes an HTTP request.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
method | string | Yes | HTTP method: GET, POST, PUT, PATCH, DELETE. |
url | string | Yes | Request URL. |
headers | map | No | HTTP headers as key-value pairs. |
body | any | No | Request body. Objects are JSON-encoded. |
Output:
| Field | Type | Description |
|---|---|---|
status | number | HTTP response status code. |
headers | map | Response headers. |
body | string | Raw response body as a string. |
json | any | Parsed response body. Only present when the response is valid JSON. |
Example:
- name: create-item
action: http/request
params:
method: POST
url: https://api.example.com/items
headers:
Authorization: "Bearer {{ env.API_TOKEN }}"
Content-Type: application/json
body:
name: "New Item"
quantity: 5
ai/completion
Sends a prompt to an OpenAI-compatible chat completion API and returns the result. Requires a credential with an API key — see the Secrets Guide for setup.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
provider | string | No | AI provider to use: openai (default) or bedrock. |
model | string | Yes | Model name (e.g., gpt-4o, gpt-4o-mini, anthropic.claude-3-sonnet-20240229-v1:0). |
prompt | string | Yes | The user prompt to send. |
region | string | No | AWS region for the Bedrock provider (e.g., us-east-1). Only used when provider is bedrock. |
system_prompt | string | No | System message prepended to the conversation. |
output_schema | object | No | JSON Schema for structured output. When set, the model returns JSON conforming to this schema. |
base_url | string | No | Override the API base URL. Defaults to https://api.openai.com/v1. Use this for OpenAI-compatible providers like Azure, Ollama, or local models. |
tools | list | No | Tool declarations for function calling. See Tool Use. |
max_tool_rounds | integer | No | Maximum number of LLM-tool interaction rounds. Default: 10 (from engine.default_max_tool_rounds). |
max_tool_calls_per_round | integer | No | Maximum number of tool calls the LLM can make in a single round. Default: 10 (from engine.default_max_tool_calls_per_round). |
Output:
| Field | Type | Description |
|---|---|---|
text | string | The raw completion text returned by the model. |
json | any | If the response is valid JSON (e.g., from structured output), the parsed object. Only present when the response parses as JSON. |
tool_calls | list | Tool invocations requested by the model. Each item has id, type, and function (with name and arguments). Only present when the model requests tool calls in the final response. |
finish_reason | string | Why the model stopped generating. stop for normal text completion, tool_calls when the model requested tool invocations. |
model | string | The model name as reported by the API. |
usage.prompt_tokens | number | Number of tokens in the prompt. |
usage.completion_tokens | number | Number of tokens in the completion. |
usage.total_tokens | number | Total tokens used. |
Example — basic completion:
- name: summarize
action: ai/completion
credential: my-openai
params:
model: gpt-4o
prompt: "Summarize this in 3 bullet points: {{ steps.fetch-data.output.body }}"
Example — with system prompt and structured output:
- name: extract-entities
action: ai/completion
credential: my-openai
timeout: 60s
params:
model: gpt-4o
system_prompt: "You are a data extraction assistant. Always respond with valid JSON."
prompt: "Extract all person names and companies from: {{ steps.fetch-data.output.body }}"
output_schema:
type: object
properties:
people:
type: array
items:
type: string
companies:
type: array
items:
type: string
required:
- people
- companies
additionalProperties: false
The structured output is available as steps.extract-entities.output.json.people and steps.extract-entities.output.json.companies in subsequent steps.
Example — custom base URL (Ollama):
- name: local-completion
action: ai/completion
params:
model: llama3
base_url: http://localhost:11434/v1
prompt: "Explain this error: {{ steps.fetch-logs.output.body }}"
Example — AWS Bedrock:
- name: summarize
action: ai/completion
credential: aws-bedrock-creds
params:
provider: bedrock
model: anthropic.claude-3-sonnet-20240229-v1:0
region: us-east-1
prompt: "Summarize: {{ steps.fetch.output.body }}"
When running on AWS infrastructure with an IAM role attached (IRSA, instance profile, etc.), the credential field can be omitted — the Bedrock provider uses the standard AWS credential chain automatically.
Authentication: The AI connector reads the credential’s api_key field (or token or key as fallbacks) and sends it as a Bearer token. If the credential includes an org_id field, it is sent as the OpenAI-Organization header. See the Secrets Guide for how to create an openai-type credential.
slack/send
Sends a message to a Slack channel via the chat.postMessage API. Requires a credential with a Slack Bot User OAuth Token.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
channel | string | Yes | Slack channel — either a channel ID (e.g., C01234ABCDE) or a channel name with # prefix (e.g., #general). Channel IDs are preferred for reliability. |
text | string | Yes | Message text. Supports Slack mrkdwn formatting. |
Output:
| Field | Type | Description |
|---|---|---|
ok | boolean | true if the message was sent successfully. |
ts | string | Slack message timestamp. Use this to reference the message in follow-up API calls. |
channel | string | The channel ID where the message was posted. |
Example:
- name: notify-team
action: slack/send
credential: slack-bot
params:
channel: "C01234ABCDE"
text: "Deployment complete: {{ steps.deploy.output.body }}"
Authentication: The Slack connector reads the credential’s token field and sends it as a Bearer token. Create a credential of type bearer with a token field containing your Slack Bot User OAuth Token:
mantle secrets create --name slack-bot --type bearer --field token=xoxb-your-bot-token
slack/history
Reads recent messages from a Slack channel via the conversations.history API.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
channel | string | Yes | Slack channel ID (e.g., C01234ABCDE). |
limit | number | No | Maximum number of messages to return. Default: 10. |
Output:
| Field | Type | Description |
|---|---|---|
ok | boolean | true if the request was successful. |
messages | list | Array of message objects. Each message contains fields like text, user, ts, and type. |
Example:
- name: read-channel
action: slack/history
credential: slack-bot
params:
channel: "C01234ABCDE"
limit: 5
- name: summarize-messages
action: ai/completion
credential: my-openai
params:
model: gpt-4o
prompt: "Summarize these Slack messages: {{ steps['read-channel'].output.messages }}"
postgres/query
Executes a parameterized SQL query against an external Postgres database. The connector opens a connection per step execution and closes it afterward. Supports both read queries (SELECT, WITH) and write statements (INSERT, UPDATE, DELETE).
Params:
| Param | Type | Required | Description |
|---|---|---|---|
query | string | Yes | SQL query to execute. Use $1, $2, etc. for parameterized values. |
args | list | No | Ordered list of values to substitute into the parameterized query. |
Output (SELECT/WITH queries):
| Field | Type | Description |
|---|---|---|
rows | list | Array of row objects, each mapping column names to values. Empty array if no rows match. |
row_count | number | Number of rows returned. |
Output (INSERT/UPDATE/DELETE statements):
| Field | Type | Description |
|---|---|---|
rows_affected | number | Number of rows affected by the statement. |
Example — read query:
- name: fetch-users
action: postgres/query
credential: my-database
params:
query: "SELECT id, email FROM users WHERE active = $1 LIMIT $2"
args:
- true
- 100
Example — write statement:
- name: update-status
action: postgres/query
credential: my-database
params:
query: "UPDATE orders SET status = $1 WHERE id = $2"
args:
- "shipped"
- "{{ steps['create-order'].output.json.order_id }}"
Authentication: The Postgres connector reads the database connection URL from the credential’s url field (or key as a fallback). Create a credential with the full Postgres connection string:
mantle secrets create --name my-database --type generic --field url=postgres://user:pass@host:5432/dbname?sslmode=require
email/send
Sends an email via SMTP. Supports plaintext and HTML content.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
to | string or list | Yes | Recipient email address(es). A single string or a list of strings. |
from | string | Yes | Sender email address. |
subject | string | Yes | Email subject line. |
body | string | Yes | Email body content. |
html | boolean | No | Set to true to send the body as HTML. Default: false (plaintext). |
smtp_host | string | No | SMTP server hostname. Can also be provided via credential. |
smtp_port | string | No | SMTP server port. Default: 587. Can also be provided via credential. |
Output:
| Field | Type | Description |
|---|---|---|
sent | boolean | true if the email was sent successfully. |
to | string | Comma-separated list of recipient addresses. |
subject | string | The subject line that was sent. |
Example:
- name: send-report
action: email/send
credential: smtp-creds
params:
to:
- "[email protected]"
- "[email protected]"
from: "[email protected]"
subject: "Daily Report — {{ steps.generate.output.json.date }}"
body: "{{ steps.generate.output.json.html_report }}"
html: true
Authentication: The email connector reads username, password, host, and port from the credential. If host or port are not in the credential, they fall back to the smtp_host and smtp_port params. Create a basic credential with SMTP fields:
mantle secrets create --name smtp-creds --type basic \
--field username=apikey \
--field password=SG.your-sendgrid-key \
--field host=smtp.sendgrid.net \
--field port=587
TLS: The email connector enforces TLS for all SMTP connections. Port 465 uses implicit TLS (SMTPS); all other ports use STARTTLS and fail if the server does not support TLS. Plaintext SMTP is not supported.
email/receive
Reads messages from an email mailbox. Supports filtering by folder and read status.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
folder | string | No | Folder to read from (e.g., INBOX, Archive, [Gmail]/Sent Mail). Default: INBOX. |
filter | string | No | Filter messages by status: all, unseen, recent, flagged. Default: unseen. |
limit | number | No | Maximum number of messages to return. Default: 10. |
mark_seen | boolean | No | Mark retrieved messages as seen. Default: false. |
Output:
| Field | Type | Description |
|---|---|---|
message_count | number | Number of messages returned. |
messages | array | Array of message objects. Each message contains: message_id (string), from (string), to (string), cc (string), subject (string), body (string), date (RFC 3339 timestamp), headers (map), flags (array of strings), uid (number, IMAP UID). |
Authentication: Credentials are provided via the step-level credential field. The email connector reads username, password, host, and port from the credential (IMAP-compatible).
Example:
- name: read-inbox
action: email/receive
credential: company-inbox
params:
folder: INBOX
filter: unseen
limit: 20
mark_seen: true
email/move
Moves an email message to a different folder.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
uid | number | Yes | IMAP UID of the message. |
source_folder | string | No | Source folder (for reference). Default: INBOX. |
target_folder | string | Yes | Destination folder path (e.g., Archive, [Gmail]/All Mail). |
Output:
| Field | Type | Description |
|---|---|---|
moved | boolean | true if the move was successful. |
uid | number | The IMAP UID of the moved message. |
target_folder | string | The folder the message was moved to. |
Authentication: Credentials are provided via the step-level credential field.
Note: Gmail’s “archive” action is implemented as a move to [Gmail]/All Mail.
Example:
- name: archive-message
action: email/move
credential: company-inbox
params:
uid: "{{ trigger.uid }}"
source_folder: INBOX
target_folder: Archive
email/delete
Deletes an email message.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
uid | number | Yes | IMAP UID of the message. |
folder | string | No | Folder containing the message. Default: INBOX. |
Output:
| Field | Type | Description |
|---|---|---|
deleted | boolean | true if the deletion was successful. |
uid | number | The IMAP UID of the deleted message. |
Authentication: Credentials are provided via the step-level credential field.
Example:
- name: delete-spam
action: email/delete
credential: company-inbox
params:
uid: "{{ trigger.uid }}"
folder: INBOX
email/flag
Adds or removes flags (labels) on an email message.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
uid | number | Yes | IMAP UID of the message. |
flags | array | Yes | List of flag names to modify (e.g., ["flagged", "important"]). |
action | string | Yes | add to set flags, remove to unset flags. |
folder | string | No | Folder containing the message. Default: INBOX. |
Standard IMAP Flags:
| Flag | Description |
|---|---|
seen | Message has been read. |
flagged | Message is flagged for follow-up. |
answered | Message has been replied to. |
deleted | Message is marked for deletion. |
draft | Message is a draft. |
Custom Keywords: Most email providers support custom flag names beyond the standard set. These are often used as tags or labels (e.g., important, urgent, client-xyz).
Output:
| Field | Type | Description |
|---|---|---|
updated | boolean | true if the flag operation was successful. |
action | string | The operation performed: add or remove. |
uid | number | The IMAP UID of the message. |
flags | array | The flags that were modified. |
Authentication: Credentials are provided via the step-level credential field.
Example:
- name: flag-important
action: email/flag
credential: company-inbox
params:
uid: "{{ trigger.uid }}"
flags: ["flagged", "important"]
action: add
s3/put
Uploads an object to an S3-compatible storage bucket.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
bucket | string | Yes | S3 bucket name. |
key | string | Yes | Object key (path) within the bucket. |
content | string | Yes | Object content as a string. |
content_type | string | No | MIME type for the object. Default: application/octet-stream. |
Output:
| Field | Type | Description |
|---|---|---|
bucket | string | The bucket the object was uploaded to. |
key | string | The object key. |
size | number | Size of the uploaded content in bytes. |
Example:
- name: upload-report
action: s3/put
credential: aws-s3
params:
bucket: "my-reports"
key: "reports/{{ steps.generate.output.json.date }}.json"
content: "{{ steps.generate.output.json.report }}"
content_type: "application/json"
s3/get
Downloads an object from an S3-compatible storage bucket.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
bucket | string | Yes | S3 bucket name. |
key | string | Yes | Object key (path) within the bucket. |
Output:
| Field | Type | Description |
|---|---|---|
bucket | string | The bucket the object was downloaded from. |
key | string | The object key. |
content | string | Object content as a string. |
size | number | Size of the downloaded content in bytes. |
content_type | string | MIME type of the object as reported by S3. |
Example:
- name: download-config
action: s3/get
credential: aws-s3
params:
bucket: "my-configs"
key: "app/config.json"
s3/list
Lists objects in an S3-compatible storage bucket, with optional prefix filtering.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
bucket | string | Yes | S3 bucket name. |
prefix | string | No | Filter results to keys that start with this prefix. |
Output:
| Field | Type | Description |
|---|---|---|
bucket | string | The bucket that was listed. |
objects | list | Array of objects. Each object has key (string), size (number), and last_modified (string, RFC 3339). |
Example:
- name: list-reports
action: s3/list
credential: aws-s3
params:
bucket: "my-reports"
prefix: "reports/2026/"
S3 Authentication
All S3 connectors (s3/put, s3/get, s3/list) read the following fields from the credential:
| Field | Required | Description |
|---|---|---|
access_key | Yes | AWS access key ID. |
secret_key | Yes | AWS secret access key. |
region | No | AWS region. Default: us-east-1. |
endpoint | No | Custom S3 endpoint URL. Use this for S3-compatible services like MinIO, DigitalOcean Spaces, or Backblaze B2. |
Create a credential for S3:
mantle secrets create --name aws-s3 --type generic \
--field access_key=AKIA... \
--field secret_key=wJalr... \
--field region=us-west-2
For S3-compatible services, add an endpoint field:
mantle secrets create --name minio --type generic \
--field access_key=minioadmin \
--field secret_key=minioadmin \
--field endpoint=http://localhost:9000
workflow/run
Invokes another workflow as a child execution. The child workflow runs synchronously within the parent step, with full checkpoint-and-resume support. If the parent crashes and recovers, the child execution is reused rather than re-executed.
Params:
| Param | Type | Required | Description |
|---|---|---|---|
workflow | string | Yes | Name of the child workflow to execute. |
version | integer | No | Specific version to run. Omit to use the latest applied version. |
inputs | map | No | Input parameters to pass to the child workflow. |
Output:
| Field | Type | Description |
|---|---|---|
execution_id | string | The child workflow’s execution ID. |
status | string | Final status of the child execution (completed or failed). |
steps | map | Map of child step names to their results. Each entry has an output field (and optionally error). |
Accessing child results in CEL:
Child step outputs are nested under the parent step’s output:
steps['my-step'].output.steps['child-step'].output.field
Depth limiting: Workflow nesting depth is configurable via engine.max_workflow_depth (default: 10). Exceeding this limit returns an error.
Checkpoint recovery: If the parent workflow crashes mid-execution, the child execution record is preserved in the database. On resume, the engine detects the existing child and reuses its result instead of creating a duplicate.
Cancellation: Running mantle cancel on a parent execution cascades cancellation to all child executions.
Example — parent workflow invoking a reusable child:
name: order-pipeline
description: Process an order using a reusable validation workflow
steps:
- name: validate
action: workflow/run
params:
workflow: validate-order
inputs:
order_id: "{{ inputs.order_id }}"
- name: notify
action: slack/send
credential: slack-bot
params:
channel: "#orders"
text: "Order validated: {{ steps.validate.output.steps['check-inventory'].output.available }}"
Example — child workflow (validate-order):
name: validate-order
description: Validate an order's inventory and pricing
inputs:
order_id:
type: string
steps:
- name: check-inventory
action: http/request
params:
method: GET
url: "https://api.example.com/inventory/{{ inputs.order_id }}"
- name: check-pricing
action: http/request
params:
method: GET
url: "https://api.example.com/pricing/{{ inputs.order_id }}"
docker/run
Runs a Docker container to completion and captures its output. The container is created, started, waited on, and optionally removed. Non-zero exit codes do not constitute a step failure — use if conditions to branch on exit code.
Params:
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
image | string | Yes | — | Container image (e.g., alpine:latest) |
cmd | array | No | — | Command and arguments |
env | object | No | — | Environment variables |
stdin | string | No | — | Data piped to container stdin |
mounts | array | No | — | Volume/bind mounts (each with source, target, readonly) |
network | string | No | bridge | Docker network mode (bridge or none) |
pull | string | No | missing | Image pull policy: always, missing, never |
memory | string | No | — | Memory limit (e.g., 512m, 1g) |
cpus | number | No | — | CPU limit (e.g., 1.5) |
remove | boolean | No | true | Remove container after completion |
Output:
| Field | Type | Description |
|---|---|---|
exit_code | integer | Container exit code |
stdout | string | Container stdout (capped at 10MB) |
stderr | string | Container stderr (capped at 10MB) |
Authentication: The Docker connector uses a docker credential type for daemon access. All fields are optional — an empty credential connects to the local Docker socket. For private images, use registry_credential with a basic credential type. Note that registry_credential is a step-level field (alongside credential), not a param.
Security: Containers run with all Linux capabilities dropped (CAP_DROP ALL), no-new-privileges, and a PID limit. Only bridge and none network modes are permitted.
Example:
- name: process-data
action: docker/run
credential: my-docker
registry_credential: my-registry
timeout: "2m"
params:
image: myorg/processor:latest
cmd: ["process", "--format", "json"]
stdin: "{{ steps['fetch-data'].output.body }}"
memory: "512m"
cpus: 1.0
browser/run
Runs browser automation scripts (JavaScript, TypeScript, or Python) using Playwright. Scripts run in a containerized browser environment and can interact with web pages, perform DOM queries, take screenshots, and generate structured output.
Params:
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
language | string | No | javascript | Script language: javascript, typescript, or python. |
script | string | Yes | — | Browser automation script. The global browser object is a Playwright Browser instance. |
output_format | string | No | text | Output format: json or text. JSON output is automatically parsed. |
env | object | No | — | Environment variables accessible in the script via process.env (JS/TS) or os.environ (Python). |
pull | string | No | missing | Image pull policy: always, missing, never. |
memory | string | No | 1g | Memory limit (e.g., 512m, 1g). |
Output:
| Field | Type | Description |
|---|---|---|
exit_code | integer | Script exit code (0 = success). |
stdout | string | Script stdout output (capped at 10MB). |
stderr | string | Script stderr output (capped at 10MB). |
json | any | Parsed JSON output. Only present when output_format: json and stdout is valid JSON. |
Container Images:
- JavaScript/TypeScript:
mcr.microsoft.com/playwright:v1.52.0-noble - Python:
mcr.microsoft.com/playwright/python:v1.52.0-noble
Artifacts: Scripts can write files to /mantle/artifacts/ directory for screenshots, PDFs, HAR files, and other outputs. Declare artifacts in the step to register them with the execution.
Credentials: Secrets are injected as environment variables via the env param. Access them in scripts using process.env.VAR_NAME (JS/TS) or os.environ['VAR_NAME'] (Python).
Security: Containers run with all Linux capabilities dropped (CAP_DROP ALL), no-new-privileges, and a PID limit. Same security hardening as docker/run.
:::caution[Script Injection Risk]
The script field is concatenated directly into a Playwright wrapper template. If the script content is derived from untrusted input (e.g., script: "{{ trigger.body }}"), an attacker could inject arbitrary code that executes inside the container. The container sandbox limits blast radius, but injected code can still make network requests, access environment variables, and consume resources.
Best practice: Never interpolate untrusted input directly into the script field. Pass untrusted data via env variables and access them through process.env (JS/TS) or os.environ (Python), which treats them as string values rather than executable code.
:::
Example — JavaScript with login and screenshot:
- name: scrape-portal
action: browser/run
timeout: "2m"
params:
language: javascript
output_format: json
env:
USERNAME: "{{ inputs.username }}"
PASSWORD: "{{ inputs.password }}"
script: |
const page = await browser.newPage();
await page.goto('https://portal.example.com/login');
await page.fill('#username', process.env.USERNAME);
await page.fill('#password', process.env.PASSWORD);
await page.click('#login-button');
await page.waitForSelector('.dashboard');
const data = await page.evaluate(() => {
const rows = document.querySelectorAll('.data-table tr');
return Array.from(rows).map(row => ({
name: row.querySelector('.name')?.textContent,
value: row.querySelector('.value')?.textContent,
}));
});
await page.screenshot({ path: '/mantle/artifacts/dashboard.png' });
console.log(JSON.stringify({ records: data, count: data.length }));
artifacts:
- path: dashboard.png
name: dashboard-screenshot
Example — TypeScript with form submission:
- name: submit-form
action: browser/run
timeout: "2m"
params:
language: typescript
output_format: json
script: |
const page = await browser.newPage();
await page.goto('https://portal.example.com/form');
await page.fill('#email', '[email protected]');
await page.fill('#message', 'Automated submission');
await page.click('#submit-button');
await page.waitForSelector('.success-message');
const confirmationId = await page.textContent('.confirmation-id');
console.log(JSON.stringify({ submitted: true, confirmation_id: confirmationId }));
:::caution[TypeScript Limitations]
TypeScript support uses Node.js --experimental-strip-types, which only strips type annotations. The following TypeScript features are not supported:
- Enums (
enum Direction { Up, Down }) - Namespaces (
namespace Foo { }) - Decorators (
@decorator) import =/export =syntax- npm imports — the container does not have
node_modulesbeyond Playwright itself
Use TypeScript for type annotations only. If you need advanced TypeScript features, use JavaScript instead. :::
Example — Python with PDF generation:
- name: generate-pdf
action: browser/run
timeout: "2m"
params:
language: python
output_format: json
script: |
import os
import json
page.goto('https://example.com/report')
page.pdf(path='/mantle/artifacts/report.pdf')
file_size = os.path.getsize('/mantle/artifacts/report.pdf')
print(json.dumps({'generated': True, 'size_bytes': file_size}))
artifacts:
- path: report.pdf
name: generated-report
Playwright API Reference:
Within browser/run scripts, use the standard Playwright API:
- Page navigation:
page.goto(url),page.goBack(),page.goForward(),page.reload() - Interactions:
page.fill(selector, text),page.click(selector),page.selectOption(selector, value),page.press(key) - Waiting:
page.waitForSelector(selector),page.waitForNavigation(),page.waitForFunction(fn) - DOM queries:
page.textContent(selector),page.getAttribute(selector, name),page.evaluate(fn) - Screenshots/exports:
page.screenshot(options),page.pdf(options),page.recordHar(path)