Guide

Money-out

Withdrawals and payroll disbursement move real money, so they sit behind more checks than any other service. This page covers each check and what happens after a request is accepted.

The routes

RouteScopeWhat it does
GET /v1/payout-accountsmoneyOut:readLists the payout accounts the school has registered, so you can pick a destination id.
POST /v1/withdrawalsmoneyOut:writeRequests a withdrawal from the school wallet to one registered payout account.
GET /v1/withdrawals/{reference}moneyOut:readReads a withdrawal and its status.
POST /v1/payroll/runs/{id}/paymoneyOut:writeDisburses an approved payroll run.

Every check, before anything runs

Every money-out call, reads included, passes these checks before its handler runs. A refusal moves nothing and is written to the school audit log.

  • A live secret key. Test keys are refused with money_out_live_key_required, and there is no test response: money-out has no test mode.
  • An IP allowlist on the app, with the caller's address on it. An empty allowlist refuses every call with money_out_ip_not_allowlisted.
  • The moneyOut scope granted by a school administrator, who confirms the grant with a step-up check. Without it the answer is money_out_grant_required.
  • An Idempotency-Key on every write. Without one the answer is money_out_idempotency_key_required.
  • The app itself, never a person. An end-user token is refused with money_out_end_user_token_not_allowed, and a publishable key never reaches these routes.
  • An app registered by the school. Organization apps can read across their branches but cannot move money: money_out_org_app_not_allowed.

Payout accounts are registered in the dashboard

The API cannot add or change a payout account. A school administrator registers each destination account in the dashboard, where it is verified. A withdrawal names one of those accounts by id and can go nowhere else.

Requesting a withdrawal

bash
curl -X POST https://api.klasstack.com/v1/withdrawals \
  -H "Authorization: Bearer ks_live_sk_your_key_here" \
  -H "Idempotency-Key: 6d0c7f0e-2a41-4b8e-9f3a-1c2d3e4f5a6b" \
  -H "Content-Type: application/json" \
  -d '{"payoutAccountId": 12, "amount": 50000, "narration": "September supplier run"}'

amount is in naira, not kobo. The response carries a reference and the status PENDING_APPROVAL.

The 24-hour approval queue

  • An accepted withdrawal does not pay out at once. It enters the same 24-hour approval queue as a withdrawal made in the dashboard.
  • Statuses run PENDING_APPROVAL, then APPROVED, then COMPLETED. A withdrawal can instead end REJECTED, CANCELLED, FAILED or REVERSED.
  • A withdrawal nobody approves within 24 hours is declined.
  • Follow progress with GET /v1/withdrawals/{reference}, or subscribe to the withdrawal webhook events rather than polling.