Examples
Ready-to-use workflow definitions covering common patterns. Copy any example, adjust the credentials
and URLs, and mantle apply it.
Getting Started
hello-world.yaml
Fetch a random fact from a public API — the simplest possible Mantle workflow
name: hello-world
description: Fetch a random fact from a public API — the simplest possible Mantle workflow
steps:
- name: fetch
action: http/request
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts/1" chained-requests.yaml
Fetch a user from a public API, then fetch their posts using the user's ID. Demonstrates CEL data passing between steps via steps.<name>.output.
name: chained-requests
description: >
Fetch a user from a public API, then fetch their posts using the user's ID.
Demonstrates CEL data passing between steps via steps.<name>.output.
steps:
- name: get-user
action: http/request
params:
method: GET
url: "https://jsonplaceholder.typicode.com/users/1"
- name: get-user-posts
action: http/request
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts?userId={{ steps['get-user'].output.json.id }}" conditional-workflow.yaml
Fetch todos for a user, then conditionally post a summary only if there are incomplete todos. Demonstrates conditional execution with if: and retry policies.
name: conditional-workflow
description: >
Fetch todos for a user, then conditionally post a summary only if there are
incomplete todos. Demonstrates conditional execution with if: and retry policies.
inputs:
user_id:
type: string
description: JSONPlaceholder user ID (1-10)
steps:
- name: get-todos
action: http/request
timeout: "10s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "https://jsonplaceholder.typicode.com/todos?userId={{ inputs.user_id }}"
- name: post-summary
action: http/request
if: "steps['get-todos'].output.status == 200"
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Todo summary"
body: "Fetched todos for user {{ inputs.user_id }}"
userId: "{{ inputs.user_id }}" AI & LLM
fetch-and-summarize.yaml
Fetch data from an API and summarize it with an LLM
name: fetch-and-summarize
description: Fetch data from an API and summarize it with an LLM
inputs:
url:
type: string
description: URL to fetch
steps:
- name: fetch-data
action: http/request
params:
method: GET
url: "{{ inputs.url }}"
- name: summarize
action: ai/completion
credential: my-openai-key
params:
model: gpt-4o
prompt: "Summarize this data in 2-3 sentences: {{ steps['fetch-data'].output.body }}"
system_prompt: "You are a concise data summarizer."
output_schema:
type: object
properties:
summary:
type: string
key_points:
type: array
items:
type: string
required:
- summary
- key_points
additionalProperties: false
- name: post-result
action: http/request
if: "size(steps.summarize.output.json.key_points) > 0"
params:
method: POST
url: https://jsonplaceholder.typicode.com/posts
body:
title: "Summary"
body: "{{ steps.summarize.output.json.summary }}" ai-structured-extraction.yaml
Fetch a webpage and use an LLM with output_schema to extract structured data (title, author, key topics). Demonstrates enforcing JSON structure from AI output.
name: ai-structured-extraction
description: >
Fetch a webpage and use an LLM with output_schema to extract structured data
(title, author, key topics). Demonstrates enforcing JSON structure from AI output.
inputs:
url:
type: string
description: URL of the page to fetch and extract data from
steps:
- name: fetch-page
action: http/request
timeout: "15s"
retry:
max_attempts: 2
backoff: exponential
params:
method: GET
url: "{{ inputs.url }}"
- name: extract-metadata
action: ai/completion
credential: openai
params:
model: gpt-4o
system_prompt: >
You are a structured data extraction engine. Given raw page content,
extract the requested fields accurately. If a field cannot be determined,
use null or an empty value as appropriate.
prompt: >
Extract the following metadata from this page content:
{{ steps['fetch-page'].output.body }}
output_schema:
type: object
properties:
title:
type: string
author:
type: string
key_topics:
type: array
items:
type: string
required:
- title
- author
- key_topics
additionalProperties: false
- name: post-extracted-data
action: http/request
if: "steps['extract-metadata'].output.json.title != ''"
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "{{ steps['extract-metadata'].output.json.title }}"
author: "{{ steps['extract-metadata'].output.json.author }}"
body: "Extracted structured metadata from {{ inputs.url }}" ai-tool-use.yaml
AI agent that searches the web and summarizes findings
name: research-assistant
description: AI agent that searches the web and summarizes findings
inputs:
- name: topic
type: string
description: Research topic
steps:
- name: research
action: ai/completion
credential: openai-key
params:
model: gpt-4o
system_prompt: |
You are a research assistant. Use the search tool to find information,
then synthesize your findings into a concise summary.
prompt: "Research this topic: {{ inputs.topic }}"
max_rounds: 5
tools:
- name: web_search
description: Search the web for information on a topic
action: http/request
input_schema:
type: object
properties:
query:
type: string
description: Search query
required: [query]
params:
method: GET
url: "https://api.duckduckgo.com/"
headers:
Accept: application/json
query_params:
q: "{{ inputs.tool_input.query }}"
format: json multi-model-comparison.yaml
Send the same prompt to two different OpenAI models and post both results to a webhook. Demonstrates multiple AI steps sharing a single credential.
name: multi-model-comparison
description: >
Send the same prompt to two different OpenAI models and post both results
to a webhook. Demonstrates multiple AI steps sharing a single credential.
inputs:
topic:
type: string
description: Topic to generate explanations about
steps:
- name: fast-model
action: ai/completion
credential: openai
timeout: "30s"
params:
model: gpt-4o-mini
system_prompt: "You are a concise technical writer."
prompt: "Explain {{ inputs.topic }} in 2-3 sentences for a developer audience."
- name: advanced-model
action: ai/completion
credential: openai
timeout: "60s"
params:
model: gpt-4o
system_prompt: "You are a concise technical writer."
prompt: "Explain {{ inputs.topic }} in 2-3 sentences for a developer audience."
- name: post-comparison
action: http/request
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Model comparison: {{ inputs.topic }}"
fast_model_response: "{{ steps['fast-model'].output.text }}"
advanced_model_response: "{{ steps['advanced-model'].output.text }}" Data Pipelines
data-pipeline.yaml
A four-step data pipeline that fetches raw data from a source URL, transforms it with an AI model using structured output, validates the result, and conditionally posts the final output. Demonstrates chaining HTTP and AI steps, structured extraction, credentials, retry policies, and conditional execution.
name: data-pipeline
description: >
A four-step data pipeline that fetches raw data from a source URL,
transforms it with an AI model using structured output, validates the
result, and conditionally posts the final output. Demonstrates chaining
HTTP and AI steps, structured extraction, credentials, retry policies,
and conditional execution.
inputs:
source_url:
type: string
description: URL to fetch raw data from
steps:
- name: fetch-source
action: http/request
timeout: "15s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "{{ inputs.source_url }}"
headers:
Accept: "application/json"
- name: transform
action: ai/completion
credential: openai
timeout: "60s"
params:
model: gpt-4o
system_prompt: >
You are a data transformation engine. Given raw JSON data, extract and
normalize the key fields into a clean structured format.
prompt: "Transform this data into a normalized record: {{ steps['fetch-source'].output.body }}"
output_schema:
type: object
properties:
title:
type: string
summary:
type: string
tags:
type: array
items:
type: string
confidence:
type: number
required:
- title
- summary
- tags
- confidence
additionalProperties: false
- name: validate
action: http/request
timeout: "10s"
retry:
max_attempts: 2
backoff: fixed
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Validation check"
body: "{{ steps.transform.output.json.summary }}"
tags: "{{ steps.transform.output.json.tags }}"
- name: publish-result
action: http/request
if: "steps.validate.output.status == 201"
timeout: "10s"
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "{{ steps.transform.output.json.title }}"
body: "{{ steps.transform.output.json.summary }}"
source: "{{ inputs.source_url }}"
confidence: "{{ steps.transform.output.json.confidence }}" db-to-slack.yaml
Query a Postgres database for recent records, summarize the results with an AI model, and post the summary to Slack. The quintessential cross-connector workflow demonstrating three connectors chained together: Postgres, AI, and Slack.
name: db-to-slack
description: >
Query a Postgres database for recent records, summarize the results with an
AI model, and post the summary to Slack. The quintessential cross-connector
workflow demonstrating three connectors chained together: Postgres, AI, and
Slack.
steps:
- name: query-recent-orders
action: postgres/query
credential: my-db
timeout: "15s"
retry:
max_attempts: 2
backoff: fixed
params:
query: "SELECT id, customer, total, created_at FROM orders WHERE created_at > now() - interval '24 hours' ORDER BY created_at DESC LIMIT 50"
- name: summarize-results
action: ai/completion
credential: openai
timeout: "60s"
params:
model: gpt-4o-mini
system_prompt: >
You are a concise business analyst. Given a set of recent order records,
produce a brief summary with total revenue, order count, and notable
trends.
prompt: "Summarize these recent orders: {{ steps['query-recent-orders'].output.json }}"
output_schema:
type: object
properties:
order_count:
type: number
total_revenue:
type: number
summary:
type: string
highlights:
type: array
items:
type: string
required:
- order_count
- total_revenue
- summary
- highlights
additionalProperties: false
- name: post-to-slack
action: slack/send
credential: slack-token
timeout: "10s"
if: "steps['summarize-results'].output.json.order_count > 0"
params:
channel: "#daily-orders"
text: "Daily Orders Report — {{ steps['summarize-results'].output.json.order_count }} orders, ${{ steps['summarize-results'].output.json.total_revenue }} revenue. {{ steps['summarize-results'].output.json.summary }}" Integrations
slack-notification.yaml
Fetch data from a public API and send the result as a Slack notification. Demonstrates the Slack connector with CEL expressions referencing a prior step's output.
name: slack-notification
description: >
Fetch data from a public API and send the result as a Slack notification.
Demonstrates the Slack connector with CEL expressions referencing a prior
step's output.
steps:
- name: fetch-data
action: http/request
timeout: "10s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts/1"
headers:
Accept: "application/json"
- name: notify-slack
action: slack/send
credential: slack-token
timeout: "10s"
params:
channel: "#engineering-alerts"
text: "New post fetched: {{ steps['fetch-data'].output.json.title }}" email-report.yaml
Fetch data from a public API and email a summary report to a specified recipient. Demonstrates the email connector with workflow inputs and credential-based SMTP authentication.
name: email-report
description: >
Fetch data from a public API and email a summary report to a specified
recipient. Demonstrates the email connector with workflow inputs and
credential-based SMTP authentication.
inputs:
recipient:
type: string
description: Email address to send the report to
steps:
- name: fetch-data
action: http/request
timeout: "10s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Accept: "application/json"
- name: send-email
action: email/send
credential: email-creds
timeout: "15s"
params:
to: "{{ inputs.recipient }}"
subject: "Daily Data Report"
body: "Fetched data summary — total items returned: {{ size(steps['fetch-data'].output.json) }}. First item title: {{ steps['fetch-data'].output.json[0].title }}" s3-backup.yaml
Fetch data from an API on a daily schedule and store it in S3 for archival. Demonstrates the S3 connector with cron-triggered backups and credential-based AWS authentication.
name: s3-backup
description: >
Fetch data from an API on a daily schedule and store it in S3 for archival.
Demonstrates the S3 connector with cron-triggered backups and credential-based
AWS authentication.
triggers:
- type: cron
schedule: "0 2 * * *"
steps:
- name: fetch-data
action: http/request
timeout: "30s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Accept: "application/json"
- name: upload-to-s3
action: s3/put
credential: aws-creds
timeout: "30s"
retry:
max_attempts: 2
backoff: exponential
params:
bucket: "mantle-backups"
key: "daily-export/posts-{{ steps['fetch-data'].output.json[0].id }}.json"
content_type: "application/json"
body: "{{ steps['fetch-data'].output.body }}" api-with-auth.yaml
Call an authenticated API using a stored credential for Bearer token auth, then post the processed result. Demonstrates credential-based HTTP authentication without AI.
name: api-with-auth
description: >
Call an authenticated API using a stored credential for Bearer token auth,
then post the processed result. Demonstrates credential-based HTTP
authentication without AI.
steps:
- name: fetch-protected-resource
action: http/request
credential: my-api-token
timeout: "10s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts/1"
headers:
Accept: "application/json"
- name: post-processed-result
action: http/request
if: "steps['fetch-protected-resource'].output.status == 200"
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Processed: {{ steps['fetch-protected-resource'].output.json.title }}"
body: "{{ steps['fetch-protected-resource'].output.json.body }}"
source_id: "{{ steps['fetch-protected-resource'].output.json.id }}" Triggers & Server
webhook-processor.yaml
Process incoming webhook payloads. Triggered by HTTP POST to /hooks/process, the workflow echoes the payload to an external API and conditionally logs the result based on status code. Demonstrates webhook triggers and trigger.payload access in CEL expressions.
name: webhook-processor
description: >
Process incoming webhook payloads. Triggered by HTTP POST to /hooks/process,
the workflow echoes the payload to an external API and conditionally logs
the result based on status code. Demonstrates webhook triggers and
trigger.payload access in CEL expressions.
triggers:
- type: webhook
path: "/hooks/process"
steps:
- name: echo-payload
action: http/request
timeout: "10s"
retry:
max_attempts: 3
backoff: exponential
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Webhook payload received"
body: "{{ inputs.trigger.payload }}"
source_path: "{{ inputs.trigger.path }}"
- name: log-success
action: http/request
if: "steps['echo-payload'].output.status == 201"
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Webhook processed successfully"
body: "Echo returned status 201"
echo_id: "{{ steps['echo-payload'].output.json.id }}" scheduled-health-check.yaml
Run every 5 minutes via cron to check whether a service is healthy. If the health endpoint returns a non-200 status, post an alert to a notification webhook. Demonstrates cron triggers, retry policies, and conditional alerting.
name: scheduled-health-check
description: >
Run every 5 minutes via cron to check whether a service is healthy.
If the health endpoint returns a non-200 status, post an alert to a
notification webhook. Demonstrates cron triggers, retry policies, and
conditional alerting.
triggers:
- type: cron
schedule: "*/5 * * * *"
steps:
- name: check-health
action: http/request
timeout: "15s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts/1"
headers:
Accept: "application/json"
- name: send-alert
action: http/request
if: "steps['check-health'].output.status != 200"
timeout: "10s"
retry:
max_attempts: 2
backoff: fixed
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Health check failed"
body: "Service returned status {{ steps['check-health'].output.status }}"
severity: "critical" cron-and-webhook.yaml
A daily report workflow that runs on a weekday schedule (9 AM Mon-Fri) and can also be triggered on-demand via webhook. Demonstrates multiple triggers on a single workflow — a common pattern for reports that need both scheduled and manual execution.
name: cron-and-webhook
description: >
A daily report workflow that runs on a weekday schedule (9 AM Mon-Fri)
and can also be triggered on-demand via webhook. Demonstrates multiple
triggers on a single workflow — a common pattern for reports that need
both scheduled and manual execution.
triggers:
- type: cron
schedule: "0 9 * * 1-5"
- type: webhook
path: "/hooks/report"
steps:
- name: fetch-data
action: http/request
timeout: "15s"
retry:
max_attempts: 3
backoff: exponential
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Accept: "application/json"
- name: summarize
action: ai/completion
credential: openai
timeout: "60s"
params:
model: gpt-4o-mini
system_prompt: >
You are a concise report generator. Given a list of items, produce
a brief executive summary with key highlights.
prompt: "Generate a daily summary report from this data: {{ steps['fetch-data'].output.body }}"
output_schema:
type: object
properties:
summary:
type: string
highlights:
type: array
items:
type: string
item_count:
type: number
required:
- summary
- highlights
- item_count
additionalProperties: false
- name: post-report
action: http/request
if: "size(steps.summarize.output.json.highlights) > 0"
timeout: "10s"
params:
method: POST
url: "https://jsonplaceholder.typicode.com/posts"
headers:
Content-Type: "application/json"
body:
title: "Daily Report"
body: "{{ steps.summarize.output.json.summary }}"
item_count: "{{ steps.summarize.output.json.item_count }}" Parallel Execution
parallel-fanout.yaml
Demonstrates parallel step execution with depends_on
name: parallel-processing
description: Demonstrates parallel step execution with depends_on
steps:
- name: fetch-data
action: http/request
params:
method: GET
url: "https://jsonplaceholder.typicode.com/posts/1"
- name: analyze-sentiment
action: ai/completion
credential: openai-key
depends_on: [fetch-data]
params:
model: gpt-4o-mini
prompt: "Analyze the sentiment of: {{ steps.fetch-data.output.json.body }}"
- name: extract-keywords
action: ai/completion
credential: openai-key
depends_on: [fetch-data]
params:
model: gpt-4o-mini
prompt: "Extract 5 keywords from: {{ steps.fetch-data.output.json.body }}"
- name: summarize
action: ai/completion
credential: openai-key
depends_on: [analyze-sentiment, extract-keywords]
params:
model: gpt-4o-mini
prompt: |
Combine these analyses into a brief report:
Sentiment: {{ steps.analyze-sentiment.output.text }}
Keywords: {{ steps.extract-keywords.output.text }}