Running payouts

Top up your balance and pay partners via Stripe Connect from the dashboard or on a schedule.

Running payouts moves money from your pre-funded payout balance to each partner's Stripe Connect account. As the workspace owner, you fund the balance once via Stripe Checkout, then either trigger a run manually or let the monthly cron do it. Partli never fronts money — every transfer draws from the balance you've already topped up.

In the dashboard

  1. Top up your payout balance

    Go to /account/billing and top up via Stripe Checkout. The minimum is $1 and the cap is $1,000,000 per top-up. Once the payment succeeds, the amount lands in your account-level balance (users.payout_balance_cents) and every transfer draws from it.

  2. Open the Payouts page

    Visit /<workspace>/payouts. The stat cards show Ready to pay, Eligible partners, Total paid out, and your Payout balance. If a partner has earnings but hasn't connected Stripe, or your balance is short, a warning banner appears here before you run anything.

  3. Click Run payouts now

    The Run payouts now button opens a confirmation dialog showing eligible partner count, the total partners will receive (100% of approved commissions — no fee), and your current balance. The button is disabled when there are no eligible partners or nothing is ready to pay.

  4. Confirm the transfer

    Click Send $X to N partners to fire the run. Each eligible partner receives a Stripe Connect transfer, and a toast reports how many payouts were sent. The Recent payouts table then lists each payout with its status and Stripe transfer ID.

Who counts as eligible

A partner is paid on a run only if all three hold: their Stripe Connect status is active, their approved-commission total is at or above the program's minPayoutCents (default $10), and your balance can cover it. Otherwise they're skipped and their commissions stay approved to retry on the next run.

Via the API / for agents

Two endpoints back this flow: one to top up the balance and one to trigger a run.

Top up the payout balance

POST /api/account/billing/topup

Authenticated as the workspace owner. Returns a Stripe Checkout URL to complete payment.

{
  "amountCents": 50000   // required, $1 min (100), $1M cap (100000000)
}
{
  "url": "https://checkout.stripe.com/c/pay/...",  // redirect the owner here
  "topupId": "uuid-of-topup"
}

The balance is credited only after Stripe confirms the payment (the Checkout session carries metadata.kind = "payout_topup"). On success Stripe redirects to /account/billing?topup=success.

Trigger a payout run

POST /api/workspaces/<workspace>/payouts/run

This is what the Run payouts now button calls. It runs runPayoutsForWorkspace and returns a per-partner result array — each entry has either a payoutId (paid) or a skipped reason.

{
  "results": [
    { "partnerId": "…", "payoutId": "…" },              // paid
    { "partnerId": "…", "skipped": "connect_not_active" },
    { "partnerId": "…", "skipped": "below_minimum" },
    { "partnerId": "…", "skipped": "insufficient_balance" },
    { "partnerId": "…", "skipped": "stripe_error" }
  ]
}

Automatic monthly run

A cron endpoint runs payouts for every workspace on the 1st of each month. It's protected by verifyCron and is not a public endpoint.

POST /api/cron/run-payouts   // cron-authenticated only

Webhook events

Each run emits webhooks you can subscribe to for an external agent to react:

payout.sent        // { payoutId, partnerId, amountCents, currency, stripeTransferId }
payout.failed      // { payoutId, partnerId, amountCents, currency, reason }
commission.paid    // { commissionId, partnerId, payoutId }

Gotchas

Balance is debited atomically, and refunded on failure

For each partner, Partli debits the owner's balance in a single conditional update that only succeeds when the balance is high enough — so concurrent runs can't overdraw. If the subsequent stripe.transfers.create throws, the debit is refunded, the payout is marked failed with a failureReason, and a payout.failed webhook fires. Those commissions stay approved and retry on the next run.

Partners are processed one at a time in balance order of discovery, so if your balance is partially short, some partners pay and later ones are skipped with insufficient_balance — top up and re-run to clear them. The action can't be undone, but nothing over-transfers.