Payouts

A payout moves money from your Plugipay available balance to your bank account. The Python SDK exposes a full payout lifecycle (create → mark_in_transit → mark_paid or mark_failed, with cancel as the escape hatch) plus balance / bank-account management — all behind plugipay.payouts. For wire format, settlement timing, and provider-specific quirks, see API → Payouts.

Namespace

plug.payouts        # _Payouts

Methods

create

plug.payouts.create(
    *,
    amount: int,
    currency: str,
    bank_code: str | None = None,
    bank_name: str | None = None,
    bank_account_number: str | None = None,
    bank_account_holder: str | None = None,
    note: str | None = None,
) -> Payout

Schedules a payout. amount is integer minor units. If bank_* are omitted, Plugipay uses the workspace's configured payout bank account (see get_bank_account / update_bank_account). Passing them inline routes this single payout elsewhere — useful for refunding to a specific buyer or routing to a different settlement account for tax reasons. SDK auto-generates an idempotency key.

# Send to the workspace's default bank
payout = plug.payouts.create(
    amount=10_000_000_00,    # IDR 10,000,000.00
    currency="IDR",
    note="Weekly settlement, week 19",
)
print(payout["id"])         # "pyt_01HX..."
print(payout["status"])     # "requested"
# Override the destination
plug.payouts.create(
    amount=5_000_000_00,
    currency="IDR",
    bank_code="014",
    bank_name="BCA",
    bank_account_number="1234567890",
    bank_account_holder="PT Acme Indonesia",
    note="Special settlement to subsidiary",
)

get

plug.payouts.get(payout_id: str) -> Payout

Retrieves the current state. Status moves requested → in_transit → paid (or failed, or cancelled if cancelled before transit).

list

plug.payouts.list(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    status: str | None = None,
) -> PageResult[Payout]

Standard cursor-paginated list. Filter by status for dashboards.

cursor = None
while True:
    page = plug.payouts.list(limit=100, status="in_transit", cursor=cursor)
    for p in page.data:
        print(p["id"], p["amount"], p["currency"])
    if not page.has_more:
        break
    cursor = page.cursor

cancel

plug.payouts.cancel(payout_id: str) -> Payout

Cancels a payout still in requested. Returns to available balance. Auto idempotency key. Cancelling an in_transit or paid payout returns 409.

mark_in_transit

plug.payouts.mark_in_transit(payout_id: str, *, reference: str | None = None) -> Payout

Manually moves a payout to in_transit. Plugipay's managed payouts call this automatically; you'd only call it directly for manual payouts (e.g. your finance team wired the money themselves). reference is the bank transaction reference. Auto idempotency key.

mark_paid

plug.payouts.mark_paid(payout_id: str, *, reference: str | None = None) -> Payout

Marks an in_transit payout as settled. Again, automatic for managed payouts; manual only otherwise. Auto idempotency key.

mark_failed

plug.payouts.mark_failed(payout_id: str, *, failure_reason: str) -> Payout

Marks the payout as failed and returns funds to available balance. failure_reason is required. Auto idempotency key.

plug.payouts.mark_failed(
    "pyt_01HX...",
    failure_reason="invalid_account_number",
)

balance

plug.payouts.balance() -> AvailableBalance

Returns the workspace's payout balance: how much is available to pay out, how much is pending settlement from recent payments, broken down by currency.

balance = plug.payouts.balance()
for entry in balance.get("available", []):
    print(entry["currency"], entry["amount"])

get_bank_account

plug.payouts.get_bank_account() -> BankAccount

Returns the workspace's configured destination bank account.

update_bank_account

plug.payouts.update_bank_account(
    *,
    bank_name: str,
    bank_account_number: str,
    bank_account_holder: str,
    bank_code: str | None = None,
) -> BankAccount

Replaces the configured account. No auto idempotency key — wrap your own if needed. Plugipay may run a small verification deposit before the next payout settles to the new account.

plug.payouts.update_bank_account(
    bank_code="014",
    bank_name="BCA",
    bank_account_number="9876543210",
    bank_account_holder="PT Acme Indonesia",
)

mark_* methods are for manual-payout adapters only. If your workspace uses the managed Xendit adapter, Plugipay calls these for you on provider callbacks — calling them yourself will conflict with the state machine and return 409. Use them only in adapters where you handle settlement out-of-band.

Types

from plugipay import Payout, AvailableBalance, BankAccount, PageResult

Payout likely-read fields:

  • payout["id"]pyt_ + ULID.
  • payout["status"]"requested" | "in_transit" | "paid" | "failed" | "cancelled".
  • payout["amount"], payout["currency"].
  • payout.get("reference"), payout.get("failureReason").
  • payout["bankName"], payout["bankAccountNumber"] — masked on read.

Full reference at API → Payouts.

Common patterns

Drain available balance. Periodic sweep that pays out everything settled:

balance = plug.payouts.balance()
for entry in balance.get("available", []):
    if entry["amount"] > 0:
        plug.payouts.create(amount=entry["amount"], currency=entry["currency"])

Manual payout reconciliation. For workspaces on the manual adapter, mirror your bank's transfer state:

def reconcile_bank_transfer(plug, payout_id, *, bank_ref, succeeded):
    if succeeded:
        plug.payouts.mark_in_transit(payout_id, reference=bank_ref)
        plug.payouts.mark_paid(payout_id, reference=bank_ref)
    else:
        plug.payouts.mark_failed(payout_id, failure_reason="bank_rejected")

Bank-account rotation with grace. Don't update mid-payout — wait for in_transit ones to clear:

page = plug.payouts.list(limit=100, status="in_transit")
if page.data:
    raise RuntimeError("In-flight payouts; rotate later")
plug.payouts.update_bank_account(
    bank_code="009",
    bank_name="BNI",
    bank_account_number="...",
    bank_account_holder="...",
)

Errors

err.status err.code Cause
400 validation_error Bad currency, negative amount, missing required bank fields when overriding.
404 not_found Payout id missing or in another workspace.
409 invalid_state_transition cancel on a non-requested payout, mark_* out of order.
409 idempotency_key_reused Same Idempotency-Key against a different body.
422 insufficient_balance amount exceeds available balance in currency.

All raise PlugipayError. See Errors.

Next

  • API → Payouts — full state machine and provider quirks.
  • Ledger — every payout produces ledger entries.
  • Adapters — managed vs manual payout adapters.
Plugipay — Payments that don't tax your success