Skip to main content
Pay a supplier from a funded customer account using the same objects as any other payout — customers, counterparties, transfers, and webhooks — with an agent (agt_) bound to that customer, source account, spend limits, and a hard destination allowlist enforced on the agent key. This is the accounts payable path for an agent key. There is no separate “pay invoice” API. Compose the steps below.
Agents (customer pin, source-account pin, destination allowlist, spend limits) are early access — ask your account team to enable them (403 agents_not_enabled until then). Instruction-audit export is beta.

Flow

Prerequisites

  • Verified business customer in ACTIVE
  • Dedicated funded account for the agent (balance account, treasury wallet, or other payable source your corridor supports) — see Balance accounts
  • An agent in the default PAYMENT role (not PAYMENT_OPERATIONS, which is read-only and cannot create or submit transfers) created for that customer + source account with a non-null destination allowlist ([] until suppliers are approved), and an agent API key (sk_*_agent_*) issued from the console Agents page or POST /v1/agents/{id}/keys (see Credentials and scopes) — do not reuse broad org keys for payouts
  • Webhook endpoint subscribed to payee, agent, and transfer events (Webhooks)
Until agents are enabled for your org, you cannot use this playbook’s network perimeter — keep automation behind your own controls.
allowedDestinationAccountIds: null on the agent (the default when unset) still allows every valid destination owned by the pinned customer. Before issuing the agent key, set a non-null allowlist on the agent — start with [] (deny all) and add approved eba_… / ewa_… ids, or withhold the key secret until the allowlist is saved. Empty [] denies all destinations; only listed ids are payable.

Credentials and scopes

transfers:write alone is not enough for this playbook. Split operator / JWT work (open the perimeter, register payees, attach documents) from the restricted agent key (submit allowlisted payouts).
Agent pins and allowlists only gate transfer operations (create / confirm / submit / related money-movement). They do not filter reads on organization API keys.Agent keys use fixed scopes by MCP role on the agent:Read scoping on agent keys: account list/get and customer get are limited to the agent’s principal customer. Transfer list/get is limited to that agent’s transfers (PAYMENT) or the pinned customer’s transfers (PAYMENT_OPERATIONS). GET /v1/counterparties without customerId is organization-wide even on agent keys — for payee reuse in this playbook, always list with customerId= (operator or agent). Agent keys cannot call PATCH /v1/agents/{id} or /v1/internal/api-keys/.../policy.Organization API keys and JWT sessions use normal scope grants. accounts:read and transfers:read on those credentials list organization-wide data — every customer in the org, not only the pinned customer. In multi-customer orgs, keep clientReferenceId recovery and cross-customer reconciliation on an operator credential with intentional scope, not on assumptions that agent pins hide other customers’ rows.
Least privilege
  • Agent key (PAYMENT role): inherits customer + source pins and allowlist from the agent; scopes are fixed (above). Do not hand out the key secret while the agent’s allowedDestinationAccountIds is still null.
  • Operator / organization key or JWT: human or trusted automation that registers suppliers, uploads invoice documents, updates the agent allowlist via GET / PATCH /v1/agents/{id} (apiKeys:read / apiKeys:write), and polls / recovers transfers with org-wide reads when needed. Payee reuse needs accounts:read and accounts:write.
  • Webhooks do not consume API scopes; they require a verified endpoint secret.

1. Invoice / vendor intake

Two valid starts: Known vendor — you already have legal name, bank (or wallet) details, and amount. You may skip document upload and go to Register the payee only when your payout corridor does not require supporting documents. Third-party SWIFT still needs at least one documentIds entry on transfer create (see step 5) — upload/confirm the invoice first even for a known vendor. Invoice evidence — upload supporting documents via the public Documents API, then attach them on the transfer with documentIds. Create the upload request with all of these fields (there is no category field):
  1. POST /v1/documents — body as above; see Create document upload request
  2. Upload bytes to the returned URL
  3. POST /v1/documents/{id}/confirm — Confirm document upload
  4. Keep the doc_ id for the transfer
Structured invoice parse (vendor / amount / bank prefill) is available in the console transfer-create flow and as an internal assist API. It does not create counterparties or move money. Agents and integrators should treat parse output as suggestions and still register payees through the counterparty + external-account path below.
Console invoice.* webhooks are accounts-receivable (you are collecting). They are not this supplier-payout playbook. Use transfer.* and external_account.* for accounts payable.

2. Register the payee

Do not always create a new counterparty. For repeat invoices to the same supplier, find and reuse the existing counterparty and external bank account or wallet first — list counterparties with customerId= (paying customer) and list accounts for that customer; match on bank or wallet identity. Do not rely on an unfiltered counterparty list on an agent key — it is organization-wide. This playbook covers bank accounts (eba_…) and external wallets (ewa_…) only. Do not use proxy destinations (epa_… / alias rails such as PIX, UPI, PromptPay) here — proxy create does not preserve the supplier counterpartyId binding this flow requires, and proxy rail reuse rules differ. Prefer a bank or wallet payee for AP payouts. External-bank / external-wallet create deduplicates by bank or wallet identity for that customer — not by requested rail, chain, or currency. If the same identifiers already exist:
  • The API returns the existing account with created: false (no new screening cycle)
  • Create does not merge capability fields from your request onto the reused row
  • For bank accounts and wallets, passing a different counterpartyId than the one already linked is rejected
  • Already-ACTIVE / approved reused accounts emit no new external_account.approved — do not wait forever for a webhook that will not fire; branch on the create response
On every create/reuse response, check created and status, then the fields that matter for this payout: Do not rely on a second create to widen rails, chains, or currencies. When you do create, supplier destinations are third-party bank accounts or wallets. Create defaults isThirdParty to false and rejects counterpartyId unless isThirdParty is true — omitting these fields misclassifies the supplier destination as the customer’s own account. Set all of: New rows (created: true) start IN_REVIEW and are not payable until screening finishes. On reuse (created: false), inspect status and the capability fields above (paymentRails, or chains / currencies) before waiting on webhooks or submitting a transfer. For crypto → fiat SWIFT supplier payouts, see SWIFT payouts for bank-account field shape.

3. Wait for destination approval

Subscribe to: Poll GET /v1/accounts/{id} (or list accounts for the customer) if you need authoritative status. On account-create reuse (created: false), use the returned status — do not block on a new approval webhook for an already-approved destination. Do not race the agent key against an unapproved destination: even if you omit allowlist updates, other validation may still reject the transfer.

4. Add the destination to the allowlist

Only after external_account.approved, add that account id (eba_… / ewa_…) to the agent (agt_…). Use an organization API key or JWT org session — not the agent key (sk_*_agent_* cannot update agents or legacy key policy). Do not allowlist proxy (epa_…) destinations for this playbook.
  • Console (read-only today): Agents → select the agent → Payees shows the current allowlist but has no editor — use the API below until console editing ships.
  • API: GET /v1/agents/{id} with the agent key (read own allowlist) or operator credential (apiKeys:read), then PATCH /v1/agents/{id} with an unconstrained org key or JWT (apiKeys:write) and the full allowedDestinationAccountIds array
allowedDestinationAccountIds on PATCH /v1/agents/{id} replaces the entire allowlist — it does not append. Read the agent first, then send the complete desired array including the new id. Sending only the new id drops every previously allowlisted destination.
Allowlist read-modify-PATCH has no version / CAS. Concurrent updates can stale-write and resurrect a supplier another operator just removed. Treat allowlist edits as a security perimeter: use a single serialized writer (one ops queue, lock, or equivalent concurrency control) per agent. Do not fan out parallel GET→PATCH jobs against the same agent.
Keep the allowlist small. Agents accept at most 50 destination IDs — larger supplier sets need pruning, multiple segmented agents/keys, or another perimeter design. An empty allowlist denies all destinations. PAUSED blocks new agent submit paths — POST /v1/transfers with confirm: true and POST /v1/transfers/{id}/submit (including submitting an existing DRAFT) — but does not stop in-flight processing of transfers already submitted, and does not block other transfer writes such as PATCH /v1/transfers/{id}/fund-from-balance on an existing pending crypto transfer (agent policy pins still apply). After credential compromise, revoke the agent key or agent — pausing alone leaves the key authenticated with scoped reads.
Changing a supplier’s bank details means a new external account (new id), a new approval, and an allowlist update. Treat bank swaps as a controlled perimeter change, not an in-place edit the agent can invent.

5. Create the transfer under the pinned key

Call POST /v1/transfers with the agent API key (not an organization key). Required discipline:
For this playbook’s supplier SWIFT path, upload and confirm the invoice document first, then pass its doc_… in documentIds. See SWIFT payouts for rail-specific destination fields.
Example (USD balance → ACH supplier):
rawValue is minor units (cents for USD). For quotes, FX, and multi-rail patterns see Quotes and transfers. For crypto deposit → SWIFT fiat, see SWIFT payouts.

Policy deny paths (expected)

When the agent has pins and allowlist set, the network fail-closes: Denied creates do not produce a transfer id and are not in instruction-audit export. Capture your request id and error body for ops review.
POST /v1/transfers idempotency caches failed responses for about 24 hours — not only 4xx. Client errors (allowlist/pin denials), 5xx, and other recorded failures are returned again on the same Idempotency-Key. Reusing the key after a definitive cached failure only returns that cached error; it does not re-run create. After that window, the same key no longer short-circuits create.Always send an organization-wide unique clientReferenceId on the original create — namespace so two agents (or two customers) cannot collide (e.g. ap:{agentId}:{customerId}:{voucher}). Public transfer list supports exact clientReferenceId filter across the whole organization; optional customerId narrows the list, but you must still verify the returned transfer’s amount / destination / sourceCustomerId match the payout you intended (sourceCustomerId is the paying customer on list/get responses — the create body field is onBehalfOf). The API does not filter by amount and does not enforce uniqueness. Do not decide “no transfer exists” from customer/amount/time heuristics; that can miss a committed payout and a new idempotency key will duplicate the supplier payment.Serialize recovery per logical payout. An empty clientReferenceId list is not a distributed lock. Two operators recovering the same voucher concurrently can both see empty results and POST with different Idempotency-Key values — idempotency only dedupes identical keys, and clientReferenceId is not unique — so you get a double pay. Coordinate recovery (single writer, lease/lock, or queue) so only one writer runs list → decide → create for that payout at a time.On any ambiguous or delayed outcome (timeout, unknown whether create finished, or any non-immediate retry): list by exact clientReferenceId first before reusing or replacing the Idempotency-Key. A transfer can already exist even when replaying the key does not return a cached success. Prefer reusing the original key after an empty list while still within the ~24h failure-cache window; mint a new key only after another empty list when you must. Past that window, POST only after an empty list.
Agent keys also cannot open other blast-radius surfaces (for example global payout batches, agent management, or fixed-route create). Use direct POST /v1/transfers for this playbook.

6. Reconcile and audit

When a transfer is in IN_REVIEW, poll until a terminal status on GET /v1/transfers/{id}: COMPLETED, REVERSED, FAILED, or CANCELLED. Some monitoring alerts do not hold funds — do not treat every compliance signal as a hard stop without checking transfer status. After first completion, keep re-checking status on your reconciliation schedule so a later silent REVERSED is not missed.

Operator checklist

  1. Fund only the pinned source account
  2. Human-vet suppliers; keep allowlist ≤ the set finance approved (max 50 destination IDs per key)
  3. Agent: customer pin + source pin + non-null destination allowlist ([] until approved) — never allowlist-null on a multi-customer org
  4. Prove deny paths before production volume (unknown dest, empty allowlist, wrong source, wrong customer, revoked key)
  5. Export instruction-audit for sample completed payouts; schedule periodic GET /v1/transfers/{id} through the accounting reconciliation window (silent REVERSED has no webhook)
  6. Revoke compromised agent keys (DELETE /v1/agents/{id}/keys/{keyId}) or the agent — do not rely on PAUSED alone after compromise (paused keys remain authenticated). Use PAUSED when you need to block new confirm: true creates and submit on drafts while existing submitted transfers keep processing; it is not a substitute for revocation after compromise

Out of scope for this playbook

Next steps

Agents

Map wallet, pay, identity, and observability to live APIs

Webhooks

Signed events for payees and transfers

Quotes and transfers

Amounts, quotes, and execution patterns

SWIFT payouts

Crypto to fiat supplier wires

Idempotency

Safe retries for agent writers

Error handling

Read API errors without guessing