Write your first Flower workflow

This guide provides a step-by-step explanation of how to define an API workflow to achieve real-world goals using the Flower specification. For a complete reference of all properties, see Flower support.

How to design your workflows #

Before writing a single line of YAML, take the time to define your use cases. The first question you should ask yourself is: what tasks most of my users (either humans or agents) want to achieve using my APIs, or my UI? It could be looking at analytics, checking the gross revenue for the last 30 days, finding a product in a catalog, …

Once that question is behind you, think about:

If that’s the first time you design workflows for an MCP server, start simple: focus on the two or three workflows that will be the most useful to your users. Ideally, these first workflows are read-only.

The closer your workflows stay to real user needs, the more effectively AI tools will use them, and the less guesswork they’ll need.

Generate a Flower document using AI #

We recommend following this getting started to get a hand on the specification, but you can also generate a Flower document using an LLM. To do so:

Learn the basics #

Set the global structure #

Create a new YAML file and add the following root properties:

flower: "0.1"
id: weather 
title: Get the weather for a specific city 
description: >
  Fetches the current weather for any city in the world.
flows:
  - ...

Define your first flow #

A flow is a sequence of API calls (or steps in Flower). Replace the placeholder - ... from above with your first flow. Try to keep it simple for your first document, so you easily get the full picture.

flows:
  - id: weather_current
    description: >
      Get the current temperature and rain for a specific city.
      Returns temperature in Celsius and current rain in mm.
    inputs:
      properties:
        city:
          type: string
          description: The name of the city (e.g. Paris, Tokyo)
      required:
        - city
    steps:
      - ...

A precise description makes a big difference. Vague descriptions lead AI tools to call the wrong flow or pass incorrect inputs.

Add a step #

Steps are where the HTTP calls happen. Each step makes one request and optionally extracts values from the response. A flow is usually made of multiple steps, but we’ll only add one for now to focus on its content. Replace the placeholder - ... from above with your first step:

flows:
  - id: weather_current
    ...
    steps:
      - id: get_lat_lon
        request:
          method: GET
          url: https://nominatim.openstreetmap.org/search
          headers:
            Authorization: "Bearer $secrets.my-api-key"
          query:
            q: $inputs.city
            format: jsonv2
        outputs:
          lat: $response.body.0.lat
          lon: $response.body.0.lon

A few things to notice:

Define global outputs #

Flows return data to the API tool through a top-level outputs map. Add one at the end of your flow:

flows:
  - id: weather_current
    steps:
      - id: get_lat_lon
        ...
    outputs:
      lat: $steps.get_lat_lon.outputs.lat
      lon: $steps.get_lat_lon.outputs.lon

$steps.get_lat_lon.outputs.lat follows the pattern $steps.<step_id>.outputs.<key>.

Even if the API returns more information, only values declared as outputs are returned to the AI tool.

Congratulations! You have your first valid, deployable Flower document. It calls one API and returns two values. Let’s make it more useful and robust.

Define a reliable, multi-step workflow #

Chain steps together #

Workflows power comes from passing data between steps to achieve a real business goal. Reference a previous step’s outputs using $steps.<step_id>.outputs.<key> in the next step’s request.

Add a second step that takes the coordinates from get_lat_lon and calls the weather API:

    steps:
      - id: get_lat_lon
        ...
        outputs:
          lat: $response.body.0.lat
          lon: $response.body.0.lon
      - id: get_weather_current
        request:
          method: GET
          url: https://api.open-meteo.com/v1/forecast
          query:
            latitude: $steps.get_lat_lon.outputs.lat
            longitude: $steps.get_lat_lon.outputs.lon
            current: temperature_2m,wind_speed_10m,rain
            timezone: Europe/Berlin
        outputs:
          temperature: $response.body.current.temperature_2m
          rain: $response.body.current.rain
    outputs:
      temperature: $steps.get_weather_current.outputs.temperature
      rain: $steps.get_weather_current.outputs.rain

Steps run sequentially. Each step can reference the outputs of any step that ran before it.

Add conditions and handle errors #

By default, a step completes and the flow moves on. actions let you change that behavior based on the response. They are evaluated in order: the first matching one is executed.

    steps:
      - id: get_lat_lon
        ...
        actions:
          - when: "$statusCode == 404"
            do: end
          - when: "$statusCode == 429"
            do: retry
            wait: 5
            max_retry: 3
          - do: next

Let’s break down the actions:

You can have conditions on response body values, not just status codes:

actions:
  - when: "$response.body.error == true"
    do: end
  - do: next
Action What it does
end Terminates the flow and returns collected outputs so far.
retry Re-runs the same step. wait (seconds) and max_retry are optional.
next Continues to the following step.
goto Jumps to a specific step by its id.

The last action in the list is typically a catch-all with no when clause.

Authenticate with protected APIs #

It’s not the case in our example, but if the API you’re calling requires authentication, create a secret on Bump.sh to store the key securely.

Reference it with the $secrets.{name} expression. You can also use $currentUser.token to directly retrieve the authenticated user’s token.

For example, if the API expects the key in a request header, add it in the step request.headers:

flows:
  - id: weather_current
    steps:
      - id: fetch_weather
        request:
          method: GET
          url: https://api.example.com/weather
          headers:
            Authorization: "Bearer $secrets.my-api-key"

The secret is injected at runtime and never sent to the AI tool.

Complete example #

Here is the full workflow built step by step in this guide:

flower: "0.1"
id: weather
title: Get the weather for a specific city
description: >
  Fetches the current weather for any city in the world.

flows:
  - id: weather_current
    description: >
      Get the current temperature and rain for a specific city.
      Returns temperature in Celsius and rain in mm.
    inputs:
      properties:
        city:
          type: string
          description: The name of the city (e.g. Paris, Tokyo)
      required:
        - city

    steps:
      - id: get_lat_lon
        request:
          method: GET
          url: https://nominatim.openstreetmap.org/search
          query:
            q: $inputs.city
            format: jsonv2
        outputs:
          lat: $response.body.0.lat
          lon: $response.body.0.lon
        actions:
          - when: "$statusCode == 404"
            do: end
          - do: next

      - id: get_weather_current
        request:
          method: GET
          url: https://api.open-meteo.com/v1/forecast
          query:
            latitude: $steps.get_lat_lon.outputs.lat
            longitude: $steps.get_lat_lon.outputs.lon
            current: temperature_2m,wind_speed_10m,rain
            timezone: Europe/Berlin
        outputs:
          temperature: $response.body.current.temperature_2m
          rain: $response.body.current.rain
        actions:
          - when: "$statusCode == 429"
            do: retry
            wait: 5
            max_retry: 3
          - do: next

    outputs:
      temperature: $steps.get_weather_current.outputs.temperature
      rain: $steps.get_weather_current.outputs.rain

Next steps #