Seev PlusDocs
Docs
Payouts API

Payouts API

Withdraw funds from your Seev balance to a mobile money recipient from your own server, and track each payout to completion.

⬡ API product

Requires an API secret key with the payments:write scope

The Payouts API withdraws money from your organization's Seev balance to a mobile money recipient, initiated by your own server rather than from the Dashboard. It is the programmatic counterpart to the Dashboard Send Money flow. A payout debits your balance the moment you create it, so the request is authorized with a payments:write key and every create must carry an Idempotency-Key.

This page assumes you already hold a key. If you do not, create one first in API Keys, and read Idempotency before you send money.

A payout moves real money to a recipient. Amounts are expressed in the currency's smallest unit, so 10000 means GHS 100.00. Send the wrong figure and the funds leave your balance. Confirm the amount, the channel, and the recipient before you call this endpoint.

Scope and limits at launch

PropertyValueDescription
Source balanceMain balancePayouts debit your organization's main balance only. Subaccount balances cash out through the settlement flow, not this API.
CurrencyGHSThe payout currency is GHS. A payout currency must match the balance currency.
Channelsmobile_moneyPayouts are sent to a mobile money wallet.
Environmentsandbox, productionDetermined by the key you authenticate with. Sandbox payouts move no real money and never touch production balances.

Check your balance before a payout

Read your balance first so a payout is not rejected for insufficient funds. The balance endpoint is authorized with a payments:read key.

curl "https://api.seevplus.com/api/v1/merchants/balance" \
  -H "Authorization: Bearer $SEEV_API_KEY"
{
  "total_deposits": 5000.0,
  "total_withdrawals": 1200.0,
  "total_fees": 38.0,
  "total_merchant_fees": 12.0,
  "available_balance": 3750.0,
  "pending_balance": 100.0,
  "paid_settlements": 0.0,
  "reserved_settlements": 0.0,
  "currency": "GHS",
  "transaction_count": 42,
  "balance_source": "wallet"
}
FieldTypeRequiredDescription
available_balancenumberYesSpendable balance in major units (GHS, not pesewas). This is the figure a payout draws against, and it already excludes funds held by in-flight payouts and settlement reservations.
pending_balancenumberYesFunds currently held for in-flight payouts and reservations, in major units. Present as 0 while the balance is read from the legacy derived source.
currencystringYesThe balance currency, GHS.
total_depositsnumberYesLifetime deposits credited to the balance, in major units.
total_withdrawalsnumberYesLifetime withdrawals from the balance, in major units.
total_feesnumberYesTotal fees charged, in major units.
total_merchant_feesnumberYesThe merchant-borne portion of fees, in major units.
paid_settlementsnumberYesSettlements already paid out, in major units.
reserved_settlementsnumberYesSettlements reserved and not yet paid, in major units.
transaction_countnumberYesNumber of transactions contributing to the balance.
balance_sourcestringNowallet when the authoritative wallet balance is reported. Absent when the balance is derived from the legacy source.

The balance endpoint returns amounts in major units (3750.0 means GHS 3,750.00), while the Payouts API takes and returns amounts in the smallest unit (10000 means GHS 100.00). Convert before you compare a balance figure against a payout amount: multiply the balance by 100, or divide the payout amount by 100.

Check that available_balance covers your payout amount plus the withdrawal fee before you create the payout. Because available_balance already excludes funds held by earlier in-flight payouts, reading it immediately before each payout is the reliable way to avoid a 422 INSUFFICIENT_BALANCE.

Create a payout

Send the request from your server with an Idempotency-Key. The amount is in the currency's smallest unit, and the merchant is charged the withdrawal fee on top of the amount, so your available balance must cover amount + merchant fee.

curl -X POST "https://api.seevplus.com/api/v1/merchants/payouts" \
  -H "Authorization: Bearer $SEEV_API_KEY" \
  -H "Idempotency-Key: payout_order_123_attempt_1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "channel": "mobile_money",
    "recipient": {
      "name": "Kwame Asante",
      "phone": "0551234567",
      "network": "MTN"
    }
  }'

A successful create returns 202 Accepted. The payout is now processing; the funds are already reserved against your balance, and the transfer resolves asynchronously (see Follow a payout to completion).

Request headers

HeaderTypeRequiredDescription
AuthorizationstringConditionalBearer <secret key>. Required unless X-API-Key is sent.
X-API-KeystringConditionalThe secret key with no scheme prefix. Takes precedence over Authorization.
Idempotency-KeystringYesRetrying with the same key returns the original payout instead of sending a second one. Persisted per organization, so it is safe across restarts. Send a fresh key only for a genuinely new payout.
Content-TypestringYesapplication/json.

Always send an Idempotency-Key on a payout create. Without a stable key, a network retry or a double-click can send the money twice, and a mobile money transfer is not something you can quietly undo.

Request body

FieldTypeRequiredDescription
amountnumberYesThe amount to pay the recipient, in the currency's smallest unit and greater than zero. 10000 means GHS 100.00.
channelstringYesmobile_money. Any other value is rejected.
recipientobjectYesThe destination.
recipient.namestringYesThe recipient's name.
recipient.phonestringYesMobile money wallet number.
recipient.networkstringYesMobile money network, such as MTN, VODAFONE, TELECEL, or AIRTELTIGO.

A mobile_money payout requires the recipient's phone and network. Omitting either returns 400 VALIDATION_ERROR.

Read a payout response

Create, get, and list all return the same payout object. It carries the status, the amount, and the fee breakdown you are charged, and it deliberately omits the upstream provider name, provider reference, and raw provider payloads.

{
  "id": "3f1c9a2e-...",
  "reference": "PAY-20260915-3f1c9a2e-...",
  "type": "payout",
  "env": "production",
  "status": "processing",
  "currency": "GHS",
  "amount": 10000,
  "final_amount": 10000,
  "channels": ["mobile_money"],
  "phone": "0551234567",
  "network": "MTN",
  "recipient": { "name": "Kwame Asante", "phone": "0551234567" },
  "transactions": [
    {
      "id": "b8e0...",
      "channel": "mobile_money",
      "status": "processing",
      "amount": 10000,
      "currency": "GHS",
      "gross_amount": 10000,
      "fee_amount": 100,
      "net_amount": 9900,
      "fee_bearer": "merchant",
      "customer_fee": 0,
      "merchant_fee": 100,
      "failure_reason": "",
      "created_at": "2026-09-15T10:12:00Z",
      "updated_at": "2026-09-15T10:12:00Z"
    }
  ],
  "expires_at": null,
  "completed_at": null,
  "created_at": "2026-09-15T10:12:00Z",
  "updated_at": "2026-09-15T10:12:00Z"
}
FieldTypeRequiredDescription
idstringYesThe payout's unique identifier.
referencestringYesThe payout reference. Use this to fetch or reconcile the payout, and store it against your own record.
typestringYesAlways payout.
envstringYessandbox or production, matching the key that created it.
statusstringYesprocessing, completed, or failed. See the status table below.
currencystringYesThe payout currency, GHS.
amountnumberYesThe amount sent to the recipient, in the smallest unit.
final_amountnumberYesThe amount the payout settled for, in the smallest unit.
channelsarrayYesThe channel used, ["mobile_money"].
phonestringNoThe mobile money number, when the channel is mobile money.
networkstringNoThe mobile money network, when the channel is mobile money.
recipientobjectYesThe recipient's name and, for mobile money, phone.
transactionsarrayYesThe disbursement attempts, each with its own status and fee breakdown.
expires_atstringNoWhen set, when the payout stops being actionable.
completed_atstringNoWhen the payout reached a final status.
created_atstringYesWhen the payout was created.
updated_atstringYesWhen the status last changed.

The transactions[] entries expose the fee math you are charged (gross_amount, fee_amount, net_amount, merchant_fee, customer_fee, fee_bearer) and a failure_reason when a transfer fails. In the withdraw direction the recipient absorbs the customer-borne portion of the fee, and only the merchant-borne fee is added to what your balance must cover.

Follow a payout to completion

A payout create returns 202 and a processing status. The transfer itself completes asynchronously, and Seev resolves the payout when the provider confirms the result. Poll the payout by reference, or wait for the corresponding webhook, rather than treating the 202 as proof the money arrived.

curl "https://api.seevplus.com/api/v1/merchants/payouts/PAY-20260915-3f1c9a2e-..." \
  -H "Authorization: Bearer $SEEV_API_KEY"
StatusCauseWhat to do
processingThe payout was created and the reserved funds are held while the transfer runs. No final result has arrived.Keep the payout open and check again. A payout stuck in processing for an unusually long time is being re-checked by Seev; do not create a replacement.
completedThe provider confirmed the transfer and the held funds have left your balance.The recipient has been paid. Retain the reference for your records.
failedThe transfer did not go through. The held funds are returned to your available balance.Read the transaction failure_reason, correct the recipient details if needed, and create a new payout with a new Idempotency-Key.

Do not create a second payout while the first is still processing. The original funds are already reserved, and a replacement can send the money twice. Wait for completed or failed, or fetch the payout by reference to confirm its current status, before you retry.

List payouts

Fetch a paginated history for the authenticated organization, newest first.

curl "https://api.seevplus.com/api/v1/merchants/payouts?limit=20&offset=0" \
  -H "Authorization: Bearer $SEEV_API_KEY"
QueryTypeRequiredDescription
limitnumberNoPage size. Defaults to 20; values outside 1–100 fall back to 20.
offsetnumberNoNumber of payouts to skip. Defaults to 0.

The response wraps the payout objects with a total count:

{
  "payouts": [ { "reference": "PAY-...", "status": "completed" } ],
  "total": 42
}

Fix a rejected payout

Errors return a code and a message. Branch on the HTTP status; the message is a human-readable detail for your logs, not a stable contract.

StatusCodeCauseWhat to do
400VALIDATION_ERRORThe body is malformed, amount is not greater than zero, channel is not mobile_money, or the recipient is missing phone or network.Fix the request and resend. Do not retry it unchanged.
401UNAUTHORIZEDThe request reached the endpoint without a valid organization context, or the key is not accepted.Check the key, its product, and its environment against API Keys.
422INSUFFICIENT_BALANCEYour available balance does not cover amount + merchant fee.Fund the balance or lower the amount, then retry. Reserved funds from in-flight payouts reduce what is available.
404NOT_FOUNDThe payout reference does not belong to this organization and environment.Confirm the reference and that you are using the same environment that created it.
500PAYOUT_FAILEDThe disbursement provider rejected the request when the payout was created. The reserved funds are released back to your balance.Retry with a new Idempotency-Key once the underlying issue is resolved.

An INSUFFICIENT_BALANCE check counts money already reserved by in-flight payouts as unavailable. Two rapid payouts for the same funds serialize, and the second sees the reduced balance and is rejected. This is what stops a balance from being spent twice.

Check before you send money in production

  • Send a stable Idempotency-Key on every create, derived from your own order or payout intent plus an attempt counter.
  • Read GET /api/v1/merchants/balance and confirm available_balance covers amount + merchant fee, converting for the major-unit difference.
  • Send the recipient fields mobile money requires: phone and network.
  • Treat the 202 as "accepted", not "paid". Confirm completed by polling the reference or handling the webhook before you tell anyone the recipient was paid.
  • Never create a replacement while a payout is still processing.
  • Store the payout reference against your own record for reconciliation.

On this page