---
title: "Use CrewAI with FlowRunner"
description: "Code for wiring a CrewAI crew to FlowRunner: send human feedback to Slack or email and resume the Flow, start crews from business apps, and give agents flows as tools."
url: https://flowrunner.ai/alternatives/crewai/with-flowrunner
date_published: 2026-11-02
date_modified: 2026-11-03T09:28:56-08:00
---

[Alternatives](https://flowrunner.ai/alternatives) / [CrewAI](https://flowrunner.ai/alternatives/crewai) / Use with FlowRunner

![CrewAI logo](https://flowrunner.ai/assets/alternatives/crewai.svg)

# Use CrewAI with FlowRunner

Code for wiring a CrewAI crew to FlowRunner: send human feedback to Slack or email and resume the Flow, start crews from business apps, and give agents flows as tools.

2026-11-02 Updated November 3, 2026 10 min

Your crews stay in Python, where they run today. This page wires them to FlowRunner in four ways: a person reviews the crew’s output in Slack or by email and the Flow resumes, AMP’s human input webhook lands in a flow, a business event kicks off a crew, and an agent uses a flow as a tool.

You don’t need to learn the flow editor for any of it. FlowRunner Prompt to Flow builds the FlowRunner side from a prompt, and the code below is the CrewAI side. Every sample reads the workspace id and key from environment variables (`FLOWRUNNER_WORKSPACE_ID`, `FLOWRUNNER_KEY`); both are in Workspace Settings, under Credentials.

## 1\. Connect FlowRunner Prompt to Flow

[FlowRunner Prompt to Flow](https://docs.flowrunner.ai/build/prompt-to-flow.html) is an MCP server you add to the AI tool you already use. You connect it once and sign in with your FlowRunner account. The server URL is the same for every tool: `https://app.flowrunner.ai/mcp`.

Claude (web and desktop)

1.  In Claude, open **Settings** > **Connectors**.
2.  Click **\+ Add**, then **Add custom connector**.
3.  Enter `FlowRunner` as the name and `https://app.flowrunner.ai/mcp` as the server URL. If Claude offers sign-in choices, pick **Sign in now** and **Register automatically**, and leave the client ID and secret empty.
4.  Click **Add**, then **Connect**, and approve the FlowRunner sign-in page that opens.
5.  In a chat, click **+** > **Connectors** and check that FlowRunner is on.

On Claude's Team or Enterprise plan, an Owner adds FlowRunner once for everyone under **Organization settings** > **Connectors**, with the same URL. Each member then finds it under **Settings** > **Connectors** and clicks **Connect**.

Claude Code

1.  In your project folder, add the server. Add `--scope user` to use it in every project.
    
    ```
    claude mcp add --transport http flowrunner https://app.flowrunner.ai/mcp
    ```
    
2.  Sign in, and approve the FlowRunner sign-in page that opens in your browser.
    
    ```
    claude mcp login flowrunner
    ```
    
3.  Check the connection with `claude mcp list`. The `flowrunner` line ends in `✔ Connected`.
    

ChatGPT

1.  On the web, open [ChatGPT Plugins](https://chatgpt.com/plugins). ChatGPT connects to MCP servers on chatgpt.com only.
2.  Click **Add**, then **Add custom MCP server**.
3.  Enter `FlowRunner` as the name and, under **Connection**, `https://app.flowrunner.ai/mcp` as the server URL.
4.  Choose **OAuth**, select **I understand and want to continue**, then **Create as a plugin**.
5.  Install the plugin and approve the FlowRunner sign-in page.
6.  In a chat, type `@` and pick FlowRunner.

Whether you can add a custom MCP server depends on your ChatGPT plan and, in a Business, Enterprise or Edu workspace, on your admin's settings.

Codex

1.  Add these lines to `~/.codex/config.toml`, creating the file if it doesn't exist.
    
    ```
    [mcp_servers.flowrunner]
    url = "https://app.flowrunner.ai/mcp"
    ```
    
2.  Sign in, and approve the FlowRunner sign-in page that opens in your browser.
    
    ```
    codex mcp login flowrunner
    ```
    
3.  Check the connection with `codex mcp list`. The `flowrunner` row shows `OAuth` in the **Auth** column.
    

Cursor

1.  Create `~/.cursor/mcp.json` to use FlowRunner in every project, or `.cursor/mcp.json` in a project folder for that project only. If the file already exists, add the `flowrunner` entry inside its `mcpServers`.
    
    ```
    {
      "mcpServers": {
        "flowrunner": { "url": "https://app.flowrunner.ai/mcp" }
      }
    }
    ```
    
2.  Open **Customize** in the sidebar, then **MCPs**, and follow the sign-in prompt for `flowrunner`.
    

VS Code (GitHub Copilot)

1.  Open the Command Palette, run **MCP: Add Server**, choose **HTTP**, enter `https://app.flowrunner.ai/mcp`, and name it `flowrunner`.
2.  Open Copilot Chat in agent mode and ask "List my FlowRunner workspaces". The first time, VS Code asks you to sign in: allow it and approve the FlowRunner sign-in page.
3.  Copilot lists your workspaces. FlowRunner is connected.

On the FlowRunner sign-in page, check the account under **Logged in as**, tick **I recognize and trust this URL**, and click **Authorize**. If you don't have a FlowRunner account yet, the [Free plan](https://flowrunner.ai/pricing) needs no card, and every new account starts with 14 days of Professional. The [setup guide in the docs](https://docs.flowrunner.ai/build/prompt-to-flow.html) has every tool step by step.

## Pattern: your Flow asks a person, FlowRunner gets the answer

CrewAI’s `@human_feedback` pauses a Flow and hands the request to a provider. The default provider reads the console. A custom one can hand it to FlowRunner, which reaches the reviewer and calls you back with the answer.

**Build the review flow.** Paste a request like this into your AI tool:

> Using the FlowRunner tools, build a flow called “Crew Review”. My Python service calls it through the API without waiting for an answer. It sends `flowId`, `step`, `message`, and `draft`. Post the message and the draft to #content-review in Slack with Approve and Reject buttons and a box for a comment. Wait for the answer, however long it takes. Then POST JSON to `https://agents.example.com/crew-feedback` with `flowId`, `approved` (true or false), `comment`, and who answered. Send the header `X-Review-Secret` on that request, with its value from an API KEY placeholder called `reviewSecret`. I’ll pick the key myself. Retry that call on 429 and 5XX up to 3 times with exponential backoff, and if it still fails, post the error in #content-review. Ask me before you make it live, and tell me exactly what my service sends and what my endpoint receives.

Save the secret first, on the Custom tab of [API Keys](https://docs.flowrunner.ai/platform/api-keys.html) under **Connections**. The flow reads it through a [placeholder](https://docs.flowrunner.ai/learn/concepts/placeholders.html) of type API KEY: you pick the saved key in the flow’s **Settings** > **Placeholder Data**, so the secret stays out of the chat and out of the blocks.

The AI builds the flow, gives you a link to connect Slack if it isn’t connected yet, and asks before making it live. Read the field names from its answer before you write the code, because the AI picks them where your prompt doesn’t.

**Hand the request to the flow.** The provider starts the flow with the non-blocking Call Flow API, which answers at once with an `executionId`, then raises `HumanFeedbackPending` so CrewAI saves the Flow’s state:

```
import os
import requests
from crewai.flow import (
    Flow, start, listen, human_feedback,
    HumanFeedbackProvider, HumanFeedbackPending, PendingFeedbackContext,
)

FR_ACTIVATE = (
    "https://api.flowrunner.ai/api/v1/"
    f"{os.environ['FLOWRUNNER_WORKSPACE_ID']}/flows/<flow-id>/activate"
)

class FlowRunnerReview(HumanFeedbackProvider):
    def request_feedback(self, context: PendingFeedbackContext, flow) -> str:
        r = requests.post(
            FR_ACTIVATE,
            headers={"FR-Access-Key": os.environ["FLOWRUNNER_KEY"]},
            json={
                "flowId": context.flow_id,
                "step": context.method_name,
                "message": context.message,
                "draft": str(context.method_output),
            },
            timeout=10,
        )
        r.raise_for_status()
        raise HumanFeedbackPending(
            context=context,
            callback_info={"flowrunner_execution": r.json()["executionId"]},
        )

class ReplyFlow(Flow):
    @start()
    @human_feedback(
        message="Approve this customer reply, or say what to change.",
        emit=["approved", "rejected"],
        llm=os.environ["FEEDBACK_MODEL"],
        provider=FlowRunnerReview(),
    )
    def draft_reply(self):
        return "Hi Dana, your replacement ships today..."

    @listen("approved")
    def send_reply(self):
        ...

    @listen("rejected")
    def revise_reply(self):
        ...
```

`kickoff()` returns the `HumanFeedbackPending` object instead of raising it, so the caller can tell the Flow is waiting.

**Resume when the answer arrives.** The flow posts the decision to your endpoint. Check the shared secret, make sure each Flow resumes only once, then hand CrewAI the feedback in words so `emit` can route it:

```
import hmac
import os
from fastapi import FastAPI, Header, HTTPException

app = FastAPI()

@app.post("/crew-feedback")
async def crew_feedback(body: dict, x_review_secret: str = Header(...)):
    if not hmac.compare_digest(x_review_secret, os.environ["REVIEW_SECRET"]):
        raise HTTPException(status_code=401)
    if not claim_once(body["flowId"]):  # your store: returns False if already resumed
        return {"ok": True}  # a retried delivery

    words = "Approved." if body["approved"] else "Rejected."
    feedback = f"{words} {body.get('comment') or ''}".strip()

    flow = ReplyFlow.from_pending(body["flowId"])
    await flow.resume_async(feedback)
    return {"ok": True}
```

Use `resume()` instead of `resume_async()` outside an event loop. The FlowRunner run ends when your endpoint answers, so the record on the flow’s Instances tab shows the request, the reviewer’s answer, and the call back to you. It can wait up to 30 days on Free, Starter, and Growth, and up to a year on Professional and above, and it counts as one execution either way.

## Pattern: AMP’s human input webhook lands in a flow

On CrewAI AMP, the pause is already there: pass `humanInputWebhook` at kickoff and the execution waits for human input. Point that webhook at a FlowRunner flow, and the flow does the reaching and the resuming.

**Build the flow.** Ask for one that starts from a webhook:

> Using the FlowRunner tools, build a flow called “AMP Review”. It starts with a webhook: CrewAI AMP posts to it when a crew task needs human input, with the execution ID, the task ID, and the task output. Post the task output to #crew-review in Slack with Approve and Reject buttons and a box for a comment. When someone answers, POST to my crew’s resume endpoint, `<crew-base-url>/resume`, with an `Authorization` header of `Bearer` followed by an API KEY placeholder called `crewToken`, and a JSON body with `execution_id`, `task_id`, `human_feedback` (the comment, or “Approved” when it’s empty), and `is_approve`. Retry on 429 and 5XX up to 3 times. Ask me before making it live, and tell me which block holds the webhook address.

Before the first real run, open the External Callback block in the flow editor and turn on Learning Mode, then kick off one crew. FlowRunner records the payload AMP sends, so the flow reads AMP’s real field names instead of guessed ones.

**Kick off with the flow as the webhook.** Copy the block’s Callback URL into your server’s config. It contains your workspace key, so treat it like one:

```
import os
import requests

r = requests.post(
    f"{os.environ['CREW_BASE_URL']}/kickoff",
    headers={"Authorization": f"Bearer {os.environ['CREW_TOKEN']}"},
    json={
        "inputs": {"topic": "Q3 renewal risk"},
        "humanInputWebhook": {"url": os.environ["FLOWRUNNER_AMP_REVIEW_URL"]},
    },
    timeout=30,
)
r.raise_for_status()
```

Add the `authentication` object to `humanInputWebhook` if your AMP setup asks for it; the Callback URL itself is what FlowRunner checks. If you also use `taskWebhookUrl`, `stepWebhookUrl`, or `crewWebhookUrl`, add them to the flow’s resume call too: CrewAI’s docs note that webhook settings aren’t carried over from kickoff. When the reviewer rejects, AMP has the crew retry with their comment.

## Pattern: a business event starts your crew

CrewAI AMP has triggers for Gmail, Google Calendar, Google Drive, Outlook, OneDrive, Microsoft Teams, HubSpot, Salesforce, Slack, and Zapier. For anything else, or for a crew you host yourself, a FlowRunner flow can start it:

> Using the FlowRunner tools, build a flow called “New Ticket to Crew”. When a new ticket is created in Zendesk, POST the ticket’s subject, description, requester email, and priority to `https://agents.example.com/crews/triage` with the header `X-Crew-Key`, its value from an API KEY placeholder called `crewKey`. Retry on 429 and 5XX up to 4 times with exponential backoff. If it still fails, email me the ticket id and the error. Ask before making it live.

Swap Zendesk for any of FlowRunner’s 2,100+ integrations, or for a schedule (every weekday at 9 am, or a cron expression). On your side, the endpoint calls `kickoff()` with the fields it receives. If the crew runs on AMP, point the flow at AMP’s `/kickoff` endpoint with your bearer token instead.

## Pattern: your crew uses a flow as a tool

When an agent needs something from a business app, a flow can do it and return the answer in the same request. Wrap the blocking Call Flow API in a CrewAI tool:

```
import os
import requests
from crewai.tools import tool

FR_BLOCKING = (
    "https://api.flowrunner.ai/api/v1/"
    f"{os.environ['FLOWRUNNER_WORKSPACE_ID']}/flows/<flow-id>/activate-blocking"
)

@tool("Look up customer")
def look_up_customer(email: str) -> str:
    """Return the customer's plan, open invoices, and account owner from the CRM."""
    r = requests.post(
        FR_BLOCKING,
        params={"waitResponseTimeoutSeconds": 30},
        headers={"FR-Access-Key": os.environ["FLOWRUNNER_KEY"]},
        json={"email": email},
        timeout=40,
    )
    r.raise_for_status()
    return r.text
```

The flow ends with a Return Result block, and that value is the response body. Ask for it with a prompt that names the inputs and the exact answer you want back, and that says your program calls it through the API and waits. The blocking call waits up to 300 seconds. If the timeout runs out, the API answers with an error and the run keeps going, so don’t retry blindly: a retry starts a second run. Anything that waits on a person belongs in the non-blocking pattern above.

## Optional: turn a crew into a flow

FlowRunner Prompt to Flow can also read agent code and build a flow that does the same job with FlowRunner’s AI Agent blocks, on your own model keys. It has done this with the Agents SDK code that OpenAI Agent Builder exports ([six real conversions](https://flowrunner.ai/blog/openai-agent-builder-to-flowrunner-conversions)). For a crew, paste the agents, tasks, and Flow, and ask it to list anything it can’t map one to one before it builds. This is an option for a crew the rest of the team needs to read and change on a canvas. For everything else, keep the crew in Python and use the patterns above.

Questions about wiring CrewAI to FlowRunner? [Contact us](https://flowrunner.ai/contact).

## Frequently asked questions

### Where do the FlowRunner workspace id and key come from?

Both are in Workspace Settings, under General, in the Credentials section. Keep the key on your server, in an environment variable, and send it in the FR-Access-Key header. Never put it in client code.

### What happens if nobody answers the review?

The FlowRunner run keeps waiting, up to 30 days on Free, Starter, and Growth, and up to a year on Professional and above. Your CrewAI Flow stays paused in its persistence layer until your resume endpoint is called. If you want a reminder or a default answer after a set time, say so in the prompt that builds the flow.

### Does the review flow work with the open source framework and AMP?

Yes, with different wiring. On the open source framework, your HumanFeedbackProvider starts the flow and the flow calls your resume endpoint. On AMP, the humanInputWebhook points at the flow and the flow calls AMP's /resume endpoint.

### How many executions does one review use?

One. The whole FlowRunner run, from the request to the answer being sent back, counts as one execution however long it waits.

### Can FlowRunner Prompt to Flow convert my crew into a flow?

It can read crew code and build a flow that does the same work with FlowRunner's AI Agent blocks. That's an option, not a requirement. Most teams keep the crew in Python and let FlowRunner run the triggers, reviews, and record around it.

## Keep reading

-   [CrewAI and FlowRunner](https://flowrunner.ai/alternatives/crewai)

[All alternatives](https://flowrunner.ai/alternatives)

By [Mark Piller](https://flowrunner.ai/about#mark-piller), Founder of FlowRunner [Editorial policy](https://flowrunner.ai/editorial-policy)

---
Markdown version of https://flowrunner.ai/alternatives/crewai/with-flowrunner. Site index: https://flowrunner.ai/llms.txt
