Skip to content

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

hello-world.yaml
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.

chained-requests.yaml
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.

conditional-workflow.yaml
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

fetch-and-summarize.yaml
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.

ai-structured-extraction.yaml
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

ai-tool-use.yaml
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.

multi-model-comparison.yaml
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.

data-pipeline.yaml
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.

db-to-slack.yaml
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.

slack-notification.yaml
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.

email-report.yaml
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.

s3-backup.yaml
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.

api-with-auth.yaml
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.

webhook-processor.yaml
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.

scheduled-health-check.yaml
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.

cron-and-webhook.yaml
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

parallel-fanout.yaml
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 }}