Create a transfer
Pays a bank account (type: payout) or moves money between two of the business’s own accounts in the same
currency (type: own_account).
Payout (Pay → Review → Confirm it’s you → Sending → Sent):
- Pay a saved recipient with
recipient_id, or a new account withbank_account(account number + bank code). A new account is name-enquired (or the 24-hour cache is used). Ifbank_account.account_nameis sent and differs from the bank’s answer, the request fails with422 recipient_name_mismatchunlessallow_name_mismatch: true.save_recipient: truesaves it as a recipient (recipient_nicknameoptional) and the response carries the newrecipient_id. - Step-up: a customer session must send
X-Step-Up-Token;403 step_up_requiredotherwise. API keys and agent tokens don’t step up (agents are held by guardrails and ask-first approvals instead). - Balance (amount + fee), per-payment and daily limits and risk rules are checked, then a hold is placed
on the amount. Only then does the transfer exist.
422 insufficient_balance,limit_exceeded,daily_limit_exceeded,amount_too_small,amount_too_large,currency_not_enabled. - If the caller’s payments over the approval threshold need a second person, the transfer is created as
pending_approvalwith anapproval(kindtransfer, oragent_paymentfor agents) and the response is201. The hold stays in place until it is approved (→processing), rejected (→rejected), canceled or expires (→canceled).approval_threshold_exceededis not returned as an error here. - Otherwise the transfer is
processingand the response is202: pollGET /transfers/{id}or wait forpayment.completed/payment.failed. It is sent over NIP; if the bank doesn’t answer in time we confirm the outcome with the bank, never by sending again.
Own account (“Transfer now”): no fee, no step-up, no approval, settles straight away and
returns 201 with status: completed. destination_account_id must be another account of this business in
the same currency (422 currency_mismatch).
Agents (OAuth tokens from the MCP server): type must be payout, source_account_id must be the agent’s
own account and recipient_id is required (no raw bank accounts). Guardrails return 422 recipient_not_allowed,
limit_exceeded or daily_limit_exceeded (concurrent payments can never take an agent over its daily
limit), 403 agent_paused; a
payment over the agent’s ask-first amount, or to a new recipient, becomes pending_approval with an
agent_payment approval and fires agent.payment.held. The transfer carries agent_id.
Fires payment.created for every transfer created, including pending_approval ones.
Error codes: invalid_api_key, parameter_invalid, parameter_missing, resource_not_found, scope_missing, step_up_required, csrf_token_invalid, idempotency_key_reused, agent_paused, recipient_name_mismatch, insufficient_balance, limit_exceeded, daily_limit_exceeded, amount_too_small, amount_too_large, currency_not_enabled, currency_mismatch, recipient_not_allowed, partner_timeout, bank_unavailable, business_frozen, business_not_approved, rate_limited.
Authorizations
Server API key, scoped. pk_test_ keys hit the sandbox tenant.
Headers
Required on money-moving POSTs. Same key + same body replays the first response; same key + different body returns 409 idempotency_key_reused. Kept 7 days.
1 - 255Step-up proof (2FA within the last 5 minutes, from POST /v1/auth/step-up). Required when the caller is a
customer session and the request would send money to someone else (payouts, batches, scheduled payments);
missing or stale returns 403 step_up_required. Not used for own_account transfers, API keys or agent tokens.
Same header as the shared StepUpToken parameter, which is unconditionally required.
Optional client correlation id, up to 128 characters. It is not echoed: the response's X-Request-Id is always
our own req_… id. We log your value with the request as client_request_id, so support can find a request by
either id. A longer value is refused with 400 parameter_invalid.
128Body
Body of POST /transfers and POST /transfers/quote. A payout takes exactly one of recipient_id or
bank_account; an own_account transfer takes destination_account_id. Breaking either rule returns
400 parameter_missing (or parameter_unknown for a field that doesn't apply) with param set.
Customers create payout or own_account; only PanoFlow creates reversal.
payout, own_account acc_… to pay from. For agents, the agent's own account.
What the recipient receives. The fee is charged on top. Currency must match the source account.
rcp_…, a saved recipient (payout). Required for agents.
A new account to pay (payout). Name-enquired before the transfer is created.
Pay even though bank_account.account_name differs from the bank's name.
acc_…, another account of this business in the same currency (own_account).
Shows on the recipient's bank statement.
60Why this payment is being made. Shown to the approver if it needs approval; agents should always send it.
500With bank_account, also save it as a recipient ("Save as a recipient for next time").
Nickname for the recipient saved with save_recipient.
60doc_… files already uploaded (Documents API) to keep with the payment, e.g. the invoice.
5Up to 20 string keys (≤ 40 chars) with string values (≤ 500 chars). Returned on the resource and its events.
Response
Created and not processing: an own_account transfer that has completed, or a payout waiting for approval
(pending_approval, with approval).
Money leaving (or, for a reversal, coming back to) an account. Lifecycle is MovementStatus.
A payout always has one source account and, once sent, one NIP session id.
Carries the ISO 8583 outcome (ResponseOutcome, CONVENTIONS §7a). Batch rows and scheduled-payment runs are
transfers, so they carry it too:
Two-character ISO 8583 response code, using the NIBSS NIP meanings (CONVENTIONS §7a). 00 is success.
Carried on every error (mapped from error.code, see errors.yaml x-codes) and on every money-movement
object (the bank's final code, or our own while it is in flight). Clients must tolerate codes added later.
00, 01, 03, 05, 06, 07, 08, 09, 12, 13, 14, 15, 16, 17, 21, 25, 26, 30, 34, 35, 51, 57, 58, 61, 63, 65, 68, 91, 92, 94, 96, 97 ^[0-9A-Z]{2}$Standard meaning of the code (the x-codes text), not customer copy. Customer-safe wording is in status_reason / error.message.
"Approved or completed successfully"
trf_…
Always transfer.
"transfer"payout: to a bank account outside PanoFlow, over NIP.own_account: between two of this business's accounts in the same currency.reversal: created by PanoFlow to reverse a completed transfer; read-only for customers and linked byreversal_of.
payout, own_account, reversal Lifecycle shared by every money-moving resource (CONVENTIONS §8). UI labels: completed → "Settled", pending_approval → "Pending approval".
draft, pending_approval, scheduled, processing, completed, failed, rejected, canceled, reversed What the recipient receives.
Fee charged on top (zero for own_account). Refunded with the amount if the transfer fails.
Amount + fee taken from the source account.
Who did something. Shown in activity, approvals and the audit log.
RFC 3339, UTC.
"2026-10-27T08:41:00Z"
RFC 3339, UTC.
"2026-10-27T08:41:00Z"
Sub-state of status (clients must tolerate new values):
awaiting_approval, awaiting_run_date, queued, sent_to_bank, awaiting_bank_confirmation, settled, rejected_by_receiving_bank, name_enquiry_failed, insufficient_balance, limit_exceeded, could_not_process, rejected_by_approver, canceled_by_user, approval_expired, schedule_skipped, schedule_canceled, batch_canceled, reversed Account the money left. Omitted on a reversal (the other side is not shown).
For own_account and reversal, this business's account that received the money.
rcp_… when paid to (or saved as) a recipient.
Who was paid, as the UI shows it ("Kano Freight Services", "Naira · Payroll").
For payouts, the account paid, with the name-enquiry account_name.
Shows on the recipient's bank statement.
Why it was made (shown to approvers; sent by agents).
NIP session id, present once the payout has been sent. Quote it to the receiving bank.
^\d{30}$txn_…, the line in the Transactions view and on the receipt.
Present when status is failed.
The approval, when this transfer needed one (not on batch rows; see the batch).
bat_… when this is a row of a batch.
The row's line number in the batch file.
x >= 1sch_… when this is a run of a scheduled payment.
Run date, for a scheduled transfer.
"2026-11-07"
agt_… when an agent created it.
On a reversal, the trf_… it reverses.
On a reversed transfer, the trf_… of the reversal.
doc_… files kept with the payment (invoice, receipt).
Expected arrival while processing.
"2026-10-27T08:41:00Z"
Tracking steps, oldest first. Returned on GET /transfers/{id}; omitted in lists.
RFC 3339, UTC.
"2026-10-27T08:41:00Z"
Up to 20 string keys (≤ 40 chars) with string values (≤ 500 chars). Returned on the resource and its events.