Skip to content

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:

ParamTypeRequiredDescription
methodstringYesHTTP method: GET, POST, PUT, PATCH, DELETE.
urlstringYesRequest URL.
headersmapNoHTTP headers as key-value pairs.
bodyanyNoRequest body. Objects are JSON-encoded.

Output:

FieldTypeDescription
statusnumberHTTP response status code.
headersmapResponse headers.
bodystringRaw response body as a string.
jsonanyParsed 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:

ParamTypeRequiredDescription
providerstringNoAI provider to use: openai (default) or bedrock.
modelstringYesModel name (e.g., gpt-4o, gpt-4o-mini, anthropic.claude-3-sonnet-20240229-v1:0).
promptstringYesThe user prompt to send.
regionstringNoAWS region for the Bedrock provider (e.g., us-east-1). Only used when provider is bedrock.
system_promptstringNoSystem message prepended to the conversation.
output_schemaobjectNoJSON Schema for structured output. When set, the model returns JSON conforming to this schema.
base_urlstringNoOverride the API base URL. Defaults to https://api.openai.com/v1. Use this for OpenAI-compatible providers like Azure, Ollama, or local models.
toolslistNoTool declarations for function calling. See Tool Use.
max_tool_roundsintegerNoMaximum number of LLM-tool interaction rounds. Default: 10 (from engine.default_max_tool_rounds).
max_tool_calls_per_roundintegerNoMaximum number of tool calls the LLM can make in a single round. Default: 10 (from engine.default_max_tool_calls_per_round).

Output:

FieldTypeDescription
textstringThe raw completion text returned by the model.
jsonanyIf the response is valid JSON (e.g., from structured output), the parsed object. Only present when the response parses as JSON.
tool_callslistTool 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_reasonstringWhy the model stopped generating. stop for normal text completion, tool_calls when the model requested tool invocations.
modelstringThe model name as reported by the API.
usage.prompt_tokensnumberNumber of tokens in the prompt.
usage.completion_tokensnumberNumber of tokens in the completion.
usage.total_tokensnumberTotal 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:

ParamTypeRequiredDescription
channelstringYesSlack channel — either a channel ID (e.g., C01234ABCDE) or a channel name with # prefix (e.g., #general). Channel IDs are preferred for reliability.
textstringYesMessage text. Supports Slack mrkdwn formatting.

Output:

FieldTypeDescription
okbooleantrue if the message was sent successfully.
tsstringSlack message timestamp. Use this to reference the message in follow-up API calls.
channelstringThe 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:

ParamTypeRequiredDescription
channelstringYesSlack channel ID (e.g., C01234ABCDE).
limitnumberNoMaximum number of messages to return. Default: 10.

Output:

FieldTypeDescription
okbooleantrue if the request was successful.
messageslistArray 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:

ParamTypeRequiredDescription
querystringYesSQL query to execute. Use $1, $2, etc. for parameterized values.
argslistNoOrdered list of values to substitute into the parameterized query.

Output (SELECT/WITH queries):

FieldTypeDescription
rowslistArray of row objects, each mapping column names to values. Empty array if no rows match.
row_countnumberNumber of rows returned.

Output (INSERT/UPDATE/DELETE statements):

FieldTypeDescription
rows_affectednumberNumber 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:

ParamTypeRequiredDescription
tostring or listYesRecipient email address(es). A single string or a list of strings.
fromstringYesSender email address.
subjectstringYesEmail subject line.
bodystringYesEmail body content.
htmlbooleanNoSet to true to send the body as HTML. Default: false (plaintext).
smtp_hoststringNoSMTP server hostname. Can also be provided via credential.
smtp_portstringNoSMTP server port. Default: 587. Can also be provided via credential.

Output:

FieldTypeDescription
sentbooleantrue if the email was sent successfully.
tostringComma-separated list of recipient addresses.
subjectstringThe 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:

ParamTypeRequiredDescription
folderstringNoFolder to read from (e.g., INBOX, Archive, [Gmail]/Sent Mail). Default: INBOX.
filterstringNoFilter messages by status: all, unseen, recent, flagged. Default: unseen.
limitnumberNoMaximum number of messages to return. Default: 10.
mark_seenbooleanNoMark retrieved messages as seen. Default: false.

Output:

FieldTypeDescription
message_countnumberNumber of messages returned.
messagesarrayArray 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:

ParamTypeRequiredDescription
uidnumberYesIMAP UID of the message.
source_folderstringNoSource folder (for reference). Default: INBOX.
target_folderstringYesDestination folder path (e.g., Archive, [Gmail]/All Mail).

Output:

FieldTypeDescription
movedbooleantrue if the move was successful.
uidnumberThe IMAP UID of the moved message.
target_folderstringThe 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:

ParamTypeRequiredDescription
uidnumberYesIMAP UID of the message.
folderstringNoFolder containing the message. Default: INBOX.

Output:

FieldTypeDescription
deletedbooleantrue if the deletion was successful.
uidnumberThe 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:

ParamTypeRequiredDescription
uidnumberYesIMAP UID of the message.
flagsarrayYesList of flag names to modify (e.g., ["flagged", "important"]).
actionstringYesadd to set flags, remove to unset flags.
folderstringNoFolder containing the message. Default: INBOX.

Standard IMAP Flags:

FlagDescription
seenMessage has been read.
flaggedMessage is flagged for follow-up.
answeredMessage has been replied to.
deletedMessage is marked for deletion.
draftMessage 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:

FieldTypeDescription
updatedbooleantrue if the flag operation was successful.
actionstringThe operation performed: add or remove.
uidnumberThe IMAP UID of the message.
flagsarrayThe 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:

ParamTypeRequiredDescription
bucketstringYesS3 bucket name.
keystringYesObject key (path) within the bucket.
contentstringYesObject content as a string.
content_typestringNoMIME type for the object. Default: application/octet-stream.

Output:

FieldTypeDescription
bucketstringThe bucket the object was uploaded to.
keystringThe object key.
sizenumberSize 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:

ParamTypeRequiredDescription
bucketstringYesS3 bucket name.
keystringYesObject key (path) within the bucket.

Output:

FieldTypeDescription
bucketstringThe bucket the object was downloaded from.
keystringThe object key.
contentstringObject content as a string.
sizenumberSize of the downloaded content in bytes.
content_typestringMIME 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:

ParamTypeRequiredDescription
bucketstringYesS3 bucket name.
prefixstringNoFilter results to keys that start with this prefix.

Output:

FieldTypeDescription
bucketstringThe bucket that was listed.
objectslistArray 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:

FieldRequiredDescription
access_keyYesAWS access key ID.
secret_keyYesAWS secret access key.
regionNoAWS region. Default: us-east-1.
endpointNoCustom 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:

ParamTypeRequiredDescription
workflowstringYesName of the child workflow to execute.
versionintegerNoSpecific version to run. Omit to use the latest applied version.
inputsmapNoInput parameters to pass to the child workflow.

Output:

FieldTypeDescription
execution_idstringThe child workflow’s execution ID.
statusstringFinal status of the child execution (completed or failed).
stepsmapMap 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:

ParamTypeRequiredDefaultDescription
imagestringYesContainer image (e.g., alpine:latest)
cmdarrayNoCommand and arguments
envobjectNoEnvironment variables
stdinstringNoData piped to container stdin
mountsarrayNoVolume/bind mounts (each with source, target, readonly)
networkstringNobridgeDocker network mode (bridge or none)
pullstringNomissingImage pull policy: always, missing, never
memorystringNoMemory limit (e.g., 512m, 1g)
cpusnumberNoCPU limit (e.g., 1.5)
removebooleanNotrueRemove container after completion

Output:

FieldTypeDescription
exit_codeintegerContainer exit code
stdoutstringContainer stdout (capped at 10MB)
stderrstringContainer 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:

ParamTypeRequiredDefaultDescription
languagestringNojavascriptScript language: javascript, typescript, or python.
scriptstringYesBrowser automation script. The global browser object is a Playwright Browser instance.
output_formatstringNotextOutput format: json or text. JSON output is automatically parsed.
envobjectNoEnvironment variables accessible in the script via process.env (JS/TS) or os.environ (Python).
pullstringNomissingImage pull policy: always, missing, never.
memorystringNo1gMemory limit (e.g., 512m, 1g).

Output:

FieldTypeDescription
exit_codeintegerScript exit code (0 = success).
stdoutstringScript stdout output (capped at 10MB).
stderrstringScript stderr output (capped at 10MB).
jsonanyParsed 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_modules beyond 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)