---
title: "Use the OpenAI Agents SDK with FlowRunner"
description: "Code for sending OpenAI Agents SDK approvals to people in Slack or email through FlowRunner, resuming the run safely, and giving agents flows as tools."
url: https://flowrunner.ai/alternatives/openai-agents-sdk/with-flowrunner
date_published: 2026-11-02
date_modified: 2026-11-03T09:28:56-08:00
---

[Alternatives](https://flowrunner.ai/alternatives) / [OpenAI Agents SDK](https://flowrunner.ai/alternatives/openai-agents-sdk) / Use with FlowRunner

# Use the OpenAI Agents SDK with FlowRunner

Code for sending OpenAI Agents SDK approvals to people in Slack or email through FlowRunner, resuming the run safely, and giving agents flows as tools.

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

Your agents stay in your app, in Python or TypeScript. This page wires them to FlowRunner: an approval goes to a person in Slack or by email and the run resumes when they answer, a flow serves as a function tool, and a business event starts the agent.

The FlowRunner side is built from a prompt with FlowRunner Prompt to Flow, so you don’t need to learn the flow editor. The Python samples read the workspace id and key from `FLOWRUNNER_WORKSPACE_ID` and `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 agent asks a person, FlowRunner gets the answer

The SDK pauses the run before a tool that needs approval. Your app saves the paused state and hands the request to a FlowRunner flow. The flow reaches the approver, waits, and posts the decision back. Your app checks it and resumes.

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

> Using the FlowRunner tools, build a flow called “Agent Approval”. My app calls it through the API without waiting for an answer. It sends `requestId`, `summary`, and `calls`, a list where each item has `tool` and `arguments`. Post the summary and the calls to #approvals in Slack with Approve and Reject buttons and a box for a reason. Wait for the answer, however long it takes. Then POST JSON to `https://agents.example.com/approvals` with `requestId`, `approved` (true or false), `reason`, and who answered. Send the header `X-Approval-Secret` on that request, with its value from an API KEY placeholder called `approvalSecret`. 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 #approvals. Ask me before you make it live, and tell me exactly what my app 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.

Swap Slack for email, WhatsApp, phone, or a Form if that’s where your approvers are. The AI builds the flow, sends you a link to connect Slack if it isn’t connected, and asks before making it live. It picks field names where the prompt doesn’t, so read them from its answer.

**Pause and hand off.** When the run stops on `interruptions`, save the state server-side under a new request id, then start the flow. The non-blocking call answers at once with an `executionId`:

```
import os
import uuid
import requests
from agents import Agent, Runner, RunState
from agents.decorators import tool

FR_BASE = f"https://api.flowrunner.ai/api/v1/{os.environ['FLOWRUNNER_WORKSPACE_ID']}"
FR_HEADERS = {"FR-Access-Key": os.environ["FLOWRUNNER_KEY"]}

@tool(needs_approval=True)
async def issue_refund(order_id: str, amount: float) -> str:
    """Refund an order."""
    # One key per logical refund, so a replayed approval can't refund twice.
    return payments.refund(order_id, amount, idempotency_key=f"refund-{order_id}")

agent = Agent(
    name="Support agent",
    instructions="Resolve tickets. Refunds need approval.",
    tools=[issue_refund],
)

async def handle_ticket(ticket_text: str) -> str:
    result = await Runner.run(agent, ticket_text)
    if not result.interruptions:
        return result.final_output

    request_id = str(uuid.uuid4())
    pending.save(request_id, result.to_state().to_json())  # your database, server-side

    r = requests.post(
        f"{FR_BASE}/flows/<flow-id>/activate",
        headers=FR_HEADERS,
        json={
            "requestId": request_id,
            "summary": ticket_text[:500],
            "calls": [
                {"tool": item.name, "arguments": item.arguments}
                for item in result.interruptions
            ],
        },
        timeout=10,
    )
    r.raise_for_status()
    return "Waiting for approval"
```

**Resume when the answer arrives.** The flow posts the decision to your endpoint. This is where the checks from OpenAI’s guide live: verify the caller, claim the request exactly once, then restore the state and apply the decision:

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

app = FastAPI()

@app.post("/approvals")
async def approval_answer(body: dict, x_approval_secret: str = Header(...)):
    if not hmac.compare_digest(x_approval_secret, os.environ["APPROVAL_SECRET"]):
        raise HTTPException(status_code=401)

    stored = pending.claim(body["requestId"])  # atomic: returns None if already claimed
    if stored is None:
        return {"ok": True}  # already handled: a retried delivery

    state = await RunState.from_json(agent, stored)
    for item in state.get_interruptions():
        if body["approved"]:
            state.approve(item)
        else:
            state.reject(item, rejection_message=body.get("reason") or "Rejected by the approver.")

    result = await Runner.run(agent, state)
    if result.interruptions:
        ...  # another tool needs approval: save and hand off again
    return {"ok": True}
```

What FlowRunner did: put the request in front of the approver, waited for them, kept a record of who answered and when on the flow’s Instances tab, and delivered the answer with retries. What your app still does, as OpenAI’s guide says it must: keep the state on the server, check the callback, claim each request atomically, and pass idempotency keys to tools with side effects.

The FlowRunner run can wait up to 30 days on Free, Starter, and Growth, and up to a year on Professional and above. However long it waits, it’s one execution.

## Pattern: a flow as a function tool

When an agent needs data or an action from a business app, a flow can do it with FlowRunner’s integrations and return the answer in the same request. Build it with a prompt that says your program calls it through the API and waits, names the inputs, and spells out the answer you want back:

> Using the FlowRunner tools, build a flow called “Order Lookup”. My program calls it through the API and waits for the answer. It gets `orderId`. Look the order up in Shopify and return `status`, `total`, `items` (name and quantity), and `refundable` (true when it was delivered less than 30 days ago). If the order doesn’t exist, return `found: false`. When it’s built, make it live and tell me exactly what my program sends and gets back.

Then wrap the blocking Call Flow API in a tool:

```
@tool
def order_lookup(order_id: str) -> str:
    """Look up an order's status, total, items, and whether it can be refunded."""
    r = requests.post(
        f"{FR_BASE}/flows/<flow-id>/activate-blocking",
        params={"waitResponseTimeoutSeconds": 30},
        headers=FR_HEADERS,
        json={"orderId": order_id},
        timeout=40,
    )
    r.raise_for_status()
    return r.text
```

The response body is whatever the flow’s Return Result block returns. The blocking call waits up to 300 seconds. If that runs out, the API answers with an error and the run keeps going, so a blind retry starts a second run. For anything that waits on a person, use the approval pattern above.

## Pattern: FlowRunner calls your agent

The SDK has no inbound triggers, so something has to start the agent. A flow can:

> Using the FlowRunner tools, build a flow called “Inbound Support Email”. When a new email arrives in the support@ Gmail inbox, POST its sender, subject, and body to `https://agents.example.com/tickets` with the header `X-Agent-Key`, its value from an API KEY placeholder called `agentKey`. Retry on 429 and 5XX up to 4 times with exponential backoff. If it still fails, forward the email to me with the error. Ask before making it live.

Gmail is one of FlowRunner’s 2,100+ integrations; a new ticket, a form submission, a CRM change, or a schedule (every weekday at 9 am, or a cron expression) works the same way. Your endpoint calls `Runner.run` with what it receives.

If your service exposes its tools over a public MCP server, you can also register that server in FlowRunner (Agent tools & Knowledge > MCP Servers) and attach its tools to a FlowRunner AI Agent, so flows other people build can call the same tools. See [MCP servers in the docs](https://docs.flowrunner.ai/platform/mcp-servers.html).

## Optional: turn agent code into a flow

FlowRunner Prompt to Flow can read Agents SDK code and build a flow that does the same work with FlowRunner’s AI Agent blocks, on your own model keys. We did this with six of OpenAI’s own Agent Builder templates, whose export is Agents SDK code, and wrote up what came across and what didn’t: [six real conversions](https://flowrunner.ai/blog/openai-agent-builder-to-flowrunner-conversions). The approval stubs in those exports became real approval steps.

That’s worth doing when the people who own the process need to read and change it on a canvas, or when you’re migrating off Agent Builder anyway ([how to migrate from Agent Builder](https://flowrunner.ai/alternatives/openai-agent-builder/migrate)). If your agent is part of your product’s code, keep it there and use the patterns above.

Questions about wiring OpenAI Agents SDK 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 browser code.

### Why not use the blocking call and wait for the approval?

The blocking call waits at most 300 seconds, and approvals take longer than that. Use the non-blocking call, which returns an executionId at once, and have the flow post the decision to your endpoint when it arrives.

### What if the approver never answers?

The FlowRunner run waits up to 30 days on Free, Starter, and Growth, and up to a year on Professional and above. If you want a reminder or an automatic rejection after a set time, put it in the prompt that builds the flow, and your endpoint handles the rejection like any other.

### Can one approval cover several tool calls?

Yes. The sample sends every pending interruption in one request and applies the approver's decision to all of them. If each call needs its own decision, send the list and have the flow return one decision per item.

### Does this work with the TypeScript SDK?

Yes. Use needsApproval on the tool, store result.state.toString(), restore with RunState.fromString(agent, serialized), and call state.approve or state.reject. The FlowRunner calls are the same HTTP requests.

## Keep reading

-   [OpenAI Agents SDK and FlowRunner](https://flowrunner.ai/alternatives/openai-agents-sdk)
-   [FlowRunner vs OpenAI Agent Builder](https://flowrunner.ai/alternatives/openai-agent-builder)

[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/openai-agents-sdk/with-flowrunner. Site index: https://flowrunner.ai/llms.txt
