> ## 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.

# Handle payments that need approval

> What pending_approval means when you create a PanoFlow payment through the API, how to track an approval, and why approving happens in the web app.

PanoFlow applies a two-person rule: a payment above the business's approval threshold, or another sensitive change, waits for a second person before it happens. When your integration creates such a payment, it is created in `pending_approval` and someone in the business approves or rejects it in the PanoFlow web app.

## What your integration sees

When a transfer needs approval, `POST /v1/transfers` succeeds with `201` and returns the transfer with `status: pending_approval` and `status_reason: awaiting_approval`. It is not an error: you don't get `approval_threshold_exceeded` from this endpoint.

While it waits, the amount and fee stay reserved in the source account. Then:

| Decision | Transfer becomes |
| - | - |
| Approved | `processing`, then `completed` or `failed` like any other payout |
| Rejected | `rejected` (`status_reason: rejected_by_approver`) |
| Canceled, or the approval expired | `canceled` (`status_reason: canceled_by_user` or `approval_expired`) |

PanoFlow sends `payment.created` when the pending transfer is created, so you can follow it with [webhooks](https://docs.panoflow.io/webhooks.md) or by polling `GET /v1/transfers/{transfer_id}`.

To find a business's threshold, read `GET /v1/limits` (scope `transfers:read`).

## The approval object

Each pending decision is an `approval` (`apr_...`):

```json theme={null}
{
  "id": "apr_...",
  "object": "approval",
  "kind": "transfer",
  "subject_id": "trf_...",
  "status": "pending",
  "requested_by": { ... },
  "decided_by": null,
  "reason": "...",
  "expires_at": "..."
}
```

| Field | Meaning |
| - | - |
| `kind` | What it is for: `transfer`, `batch`, `pay_run`, `agent_payment`, `member_role_change` or `limit_change` |
| `subject_id` | The id of the thing being approved, such as the transfer |
| `status` | `pending`, `approved`, `rejected`, `expired` or `canceled` |
| `expires_at` | When an undecided approval expires |

Read approvals with `GET /v1/approvals` and `GET /v1/approvals/{approval_id}` (scope `approvals:read`). The list defaults to `status=pending` and accepts a comma-separated `kind` filter.

## Approving happens in the web app

API keys and OAuth tokens can't approve or reject. Deciding an approval needs a signed-in person in the PanoFlow web app who confirms with two-factor authentication, and the person who requested a payment can never approve it themselves.

If you no longer want a payment that is waiting, cancel it before it is processed with `POST /v1/transfers/{transfer_id}/cancel`, or `POST /v1/batches/{batch_id}/cancel` for a batch.

## Agent payments

A payment an AI agent makes that hits one of its ask-first rules, such as an amount above its ask-first amount, is held the same way with an `agent_payment` approval, and PanoFlow sends `agent.payment.held`. If nobody decides within 30 minutes, it is canceled with `status_reason: approval_expired`. See [Agents and MCP](https://docs.panoflow.io/agents-and-mcp.md).


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