---
title: Agent Toolkit
description: The Notiflows agent toolkit (@notiflows/agent-toolkit) turns your published notiflows into typed, callable tools for your own AI agents — Vercel AI SDK, OpenAI function calling, and LangChain.
---

The agent toolkit (`@notiflows/agent-toolkit`) exposes your **notiflows as typed, callable tools** for AI agents. Where the [MCP server](/docs/ai/mcp) lets an editor *manage* notiflows, the toolkit lets your own agents *use* Notiflows at runtime — sending the right notification, to the right people, with a schema-safe payload, where every send is attributed and auditable.

Each **published** notiflow in your project becomes a typed tool (e.g. `trigger_welcome_series`), alongside a generic `trigger_notiflow` tool that takes the handle as an argument. The toolkit runs inside your agent, on your model — the account token authorizes API calls server-side and is never exposed to the LLM.

```bash
npm i @notiflows/agent-toolkit
```

## Create the toolkit

```ts
import { createNotiflowsToolkit } from "@notiflows/agent-toolkit";

const toolkit = createNotiflowsToolkit({
  accountToken: process.env.NOTIFLOWS_TOKEN!, // nf_at_...
  project: "acme",
});
```

| Option | Required | Description |
|---|---|---|
| `accountToken` | yes | Notiflows [account token](/docs/cli/account-tokens) (`nf_at_...`). |
| `project` | yes | Project slug. |
| `baseUrl` | no | Management API base (defaults to `https://api.notiflows.com/management/v1`). |
| `notiflows` | no | An allowlist of handles, or a `(notiflow) => boolean` predicate. Omit to expose all published notiflows. |

## Tool arguments

Every trigger tool takes the same arguments — provide **`recipients`** or a **`topic`**:

| Argument | Description |
|---|---|
| `recipients` | Users to notify (provide this *or* `topic`). Each recipient is `external_id` (required) plus optional `email`, `phone`, `first_name`, `last_name` — used to auto-create the user if they don't exist yet. |
| `topic` | Topic name — notifies all of its subscribers (provide this *or* `recipients`). |
| `data` | Template variables, available in the notification as `data.*`. |
| `actor` | The user who caused the action (`{ external_id }`), available as `actor.*`. |

The generic `trigger_notiflow` tool also takes `handle`, plus an optional `draft` (`"published"` — the default — or `"draft"` to test the current unpublished version).

## Vercel AI SDK

```ts
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { createNotiflowsToolkit } from "@notiflows/agent-toolkit";
import { toAISDKTools } from "@notiflows/agent-toolkit/ai-sdk";

const toolkit = createNotiflowsToolkit({
  accountToken: process.env.NOTIFLOWS_TOKEN!,
  project: "acme",
});

const tools = await toAISDKTools(await toolkit.getTools());

await generateText({
  model: openai("gpt-4o"),
  prompt: "Email jane@acme.com the welcome series; her id is user_42.",
  tools, // trigger_welcome_series, trigger_order_shipped, ... + trigger_notiflow
});
```

## OpenAI function calling

```ts
import OpenAI from "openai";
import { createNotiflowsToolkit, toOpenAITools } from "@notiflows/agent-toolkit";

const toolkit = createNotiflowsToolkit({ accountToken: process.env.NOTIFLOWS_TOKEN!, project: "acme" });
const { tools, execute } = toOpenAITools(await toolkit.getTools());

const completion = await new OpenAI().chat.completions.create({
  model: "gpt-4o",
  messages,
  tools,
});

for (const call of completion.choices[0].message.tool_calls ?? []) {
  const result = await execute(call.function); // { notiflow_run_id }
}
```

## LangChain

```ts
import { toLangChainTools } from "@notiflows/agent-toolkit/langchain";

const tools = await toLangChainTools(await toolkit.getTools()); // DynamicStructuredTool[]
```

## Framework-agnostic

`getTools()` returns plain tool descriptors you can wire into any framework:

```ts
const tools = await toolkit.getTools(); // [{ name, description, inputSchema, execute }]

await tools[0].execute({
  recipients: [{ external_id: "user_42" }],
  data: { plan: "pro" },
});
```

## Human-in-the-loop

`requireHumanInput` triggers an approval notiflow (typically an in-app channel) before an agent proceeds, then you wait for the human's response out-of-band:

```ts
const { notiflow_run_id } = await toolkit.requireHumanInput({
  notiflow: "agent-approval",
  recipients: [{ external_id: "ops_admin" }],
  message: "Agent wants to issue a $200 refund. Approve?",
  data: { amount: 200 },
});
// Wait for the response via webhook/poll on the delivery, then continue.
```

## Why it's safe to hand to an agent

- **Scoped** — the toolkit only exposes notiflows you allow (the `notiflows` allowlist or predicate), and only *published* ones.
- **Schema-safe** — each tool advertises a JSON Schema; recipients, topic, and data are validated by the API before anything sends.
- **Auditable** — every trigger goes through the Management API with your account token, so it shows up in the dashboard's **"Triggered by"** as the token.
- **Server-side only** — the account token is never exposed to the model.

## API surface

- `createNotiflowsToolkit(config)` → `NotiflowsToolkit`
- `toolkit.getTools({ perNotiflow?, includeGeneric? })` → `NotiflowTool[]`
- `toolkit.genericTool()` → the `trigger_notiflow` tool (handle as an argument; supports `draft`)
- `toolkit.listNotiflows()` → the published notiflows exposed as tools
- `toolkit.requireHumanInput(opts)` → trigger an approval notiflow
- Adapters: `toAISDKTools`, `toOpenAITools`, `toLangChainTools`

<Callout type="info">
The peer dependencies `ai`, `openai`, and `@langchain/core` are **optional** and loaded lazily — install only the adapter(s) you actually use.
</Callout>

## Related

- [MCP server](/docs/ai/mcp) — manage notiflows conversationally in an editor.
- [CLI](/docs/cli) — create and trigger notiflows from the terminal.
- [Agent skills](/docs/ai/skills) — teach your assistant the notiflow schemas and CLI.
- [llms.txt & docs for agents](/docs/ai/llms-txt) — machine-readable docs for grounding an assistant.
- [Notiflows for AI](/docs/ai) — the AI surfaces overview.
