Connect
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
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 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.
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. Forpay, 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 sendsagent.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_approvalwith anagent_paymentapproval, and PanoFlow sendsagent.payment.held. The money is reserved and counts toward the daily limit. A person approves or rejects it in the web app. Rejected payments becomerejected; payments nobody decides within 30 minutes becomecanceledwithstatus_reason: approval_expired. The agent pollsget_paymentto find out.
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_paymentand eachlist_transactionsrow) carryresponse_codeandresponse_message:09held or processing,01bank status not confirmed yet,00completed,21when a person rejected it or the approval expired, or the bank’s NIP code (or PanoFlow’s) when it failed. See Payment statuses. - A failed tool call returns
isError: truewithstructuredContentset to the PanoFlow error object (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 returnsrate_limited with details.retry_after in seconds.