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