> ## Documentation Index
> Fetch the complete documentation index at: https://docs.panoflow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect AI agents to PanoFlow over MCP

> Connect an AI agent to one PanoFlow agent account through the MCP server: OAuth 2.1 sign-in, the seven agent scopes, the tool list, guardrails and errors.

The PanoFlow MCP server lets an AI agent use **one agent account** of a business, inside guardrails that people in the business set. The agent can check its balance, read its transactions, save recipients, pay them, and create invoices. Each tool is one call to the public API, made as the agent, with the same inputs and outputs.

## Connect

| | |
| - | - |
| Server URL | `https://mcp.panoflow.io` |
| Transport | Streamable HTTP (MCP spec 2025-06-18). `POST` for JSON-RPC requests, optional `GET` for the server-to-client SSE stream. No stdio server is offered. |
| Auth | OAuth 2.1 with PKCE. Your MCP client needs no API key. |

Sandbox uses the same URL. A grant on a sandbox agent gets sandbox tokens, and the token decides whether the agent reaches sandbox or live. Sandbox calls never reach a real partner.

## Sign in

PanoFlow is the OAuth 2.1 authorization server. MCP clients that support OAuth discover and run this flow on their own:

**1. Discover**

A call without a token gets `401` with `WWW-Authenticate: Bearer resource_metadata="https://mcp.panoflow.io/.well-known/oauth-protected-resource"`. That document (RFC 9728) points to the authorization server, whose metadata (RFC 8414) is at `https://api.panoflow.io/.well-known/oauth-authorization-server`.

**2. Register**

The client registers itself with dynamic client registration (RFC 7591) at `POST https://api.panoflow.io/v1/oauth/register`. Only public clients are accepted (`token_endpoint_auth_method: none`), and redirect URIs must be `https://` or `http://localhost` / `127.0.0.1`.

**3. Get consent**

The client starts an authorization code flow with PKCE (S256 only) and `resource=https://mcp.panoflow.io`. In the browser, at `https://app.panoflow.io/oauth/authorize`, a person whose role can manage agents picks the business and one agent account, reviews what the client will be able to do, and confirms with their two-factor code. This creates a grant (`grt_...`), shown in the app as a connected app. Paused agents can't be picked.

**4. Get tokens**

The client exchanges the code and refreshes tokens at `POST https://api.panoflow.io/v1/oauth/token`, and revokes them at `POST https://api.panoflow.io/v1/oauth/revoke`.

### Scopes

Agents can hold only these seven scopes:

`accounts:read`, `transfers:read`, `transfers:write`, `recipients:read`, `recipients:write`, `collections:read`, `collections:write`

A grant holds the scopes the client requested that the agent is also allowed. They are checked again against the agent's current allowed scopes on every call. Payroll, team, API key and agent management, and anything that changes limits, can never be granted to an agent. A token used on any operation outside its scopes gets `403 scope_missing`.

### Token lifetimes

| Token | Lifetime |
| - | - |
| Authorization code | 60 seconds, single use. Reusing it revokes the grant. |
| Access token | 15 minutes (`expires_in: 900`), bound to the grant and to `resource` |
| Refresh token | Rotates on every use. Reusing an old one revokes the grant. |
| Grant | Expires 90 days after the last refresh, and renews while in use |

Tokens stop working early when the owner pauses the agent (every token of every grant of that agent is revoked, and each app must sign in again after the agent is turned back on), when someone disconnects the app, when the client revokes its refresh token, or when the grant expires. An expired or revoked token gets `401` with `WWW-Authenticate: Bearer error="invalid_token"`; refresh it or sign in again.

## Tools

| Tool | What it does | Same as | Scope |
| - | - | - | - |
| `get_balance` | The agent's own account and balances. Takes no arguments. | `GET /v1/accounts/{account_id}` | `accounts:read` |
| `list_transactions` | Money in and out of the agent's account, newest first, paginated | `GET /v1/transactions` | `accounts:read` |
| `create_recipient` | Saves a Nigerian bank account after the bank confirms its name | `POST /v1/recipients` | `recipients:write` |
| `pay` | Pays a saved recipient from the agent's account over NIP, inside its guardrails | `POST /v1/transfers` | `transfers:write` |
| `get_payment` | Current state of a payment the agent made, including one held for approval | `GET /v1/transfers/{transfer_id}` | `transfers:read` |
| `create_invoice` | Creates an invoice paid into the agent's own account | `POST /v1/collections/invoices` | `collections:write` |
| `list_invoices` | Invoices that settle into the agent's account, newest first | `GET /v1/collections/invoices` | `collections:read` |

Tool inputs use the REST operation's parameter and field names. The server fills in the agent's own account where the REST operation takes one, and `pay` always makes a `payout` to a saved `recipient_id` with a `reason` (it can't pay a raw bank account or move money between accounts). Tool results (`structuredContent`) are the REST response bodies unchanged: `snake_case`, `Money` objects in kobo, RFC 3339 times, and the list envelope for list tools.

`pay` requires `idempotency_key`; `create_recipient` and `create_invoice` accept it. It is sent as the `Idempotency-Key` header, stored for 7 days, and you reuse it on retries. See [Idempotency](https://docs.panoflow.io/idempotency.md).

## Guardrails

People in the business set an agent's guardrails in the web app. No tool can change limits, allowlists, scopes or ask-first rules, approve a payment, pause or resume an agent, or touch any account other than the agent's own.

For `pay`, **limits block and ask-first holds**:

* **Blocked:** a payment over the per-payment or monthly limit (`limit_exceeded`), over the daily limit (`daily_limit_exceeded`), or to a recipient outside the allowlist when ask-first for new recipients is off (`recipient_not_allowed`) is refused as a tool error and nothing is created. PanoFlow sends `agent.payment.blocked`.
* **Held:** a payment over the agent's ask-first amount, outside business hours (08:00 to 18:00 Lagos time, Monday to Saturday), or to a recipient outside the allowlist when ask-first for new recipients is on, becomes a transfer in `pending_approval` with an `agent_payment` approval, and PanoFlow sends `agent.payment.held`. The money is reserved and counts toward the daily limit. A person approves or rejects it in the web app. Rejected payments become `rejected`; payments nobody decides within 30 minutes become `canceled` with `status_reason: approval_expired`. The agent polls `get_payment` to find out.

Other checks return ordinary errors: a paused agent gets `agent_paused`, an amount in another currency gets `currency_mismatch`, and an available balance below the amount plus fee gets `insufficient_balance`.

## Results and errors

* Money-moving results (`pay`, `get_payment` and each `list_transactions` row) carry `response_code` and `response_message`: `09` held or processing, `01` bank status not confirmed yet, `00` completed, `21` when a person rejected it or the approval expired, or the bank's NIP code (or PanoFlow's) when it failed. See [Payment statuses](https://docs.panoflow.io/payment-statuses.md).
* A failed tool call returns `isError: true` with `structuredContent` set to the PanoFlow [error object](https://docs.panoflow.io/errors.md) (`error.type`, `error.code`, `error.response_code`, `error.message`, `error.param`, `error.request_id`, `error.details`).
* Protocol problems, such as an unknown tool or malformed JSON-RPC, are JSON-RPC errors, not tool errors.

## Rate limits

Each agent has its own rate limit, tighter than an API key's. Over it, a tool returns `rate_limited` with `details.retry_after` in seconds.

## Activity log

Every tool call, whether allowed, held, blocked or refused, is recorded in the agent's activity log with the grant, client, arguments, decision, error code, response code and request id. The business's owners see it in the web app.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.