API Live

API reference

Payouts

Send funds to a recipient.

POST /payouts

Create a payout

Sandbox only: an in-memory payout settled from fixtures; it calls no provider and moves no money. With a database it answers 404 not_found; send payouts with POST /v1/payouts. In the sandbox the gateway returns the payout and posts a webhook when it settles.

Parameters

NameInTypeDescription
Idempotency-Keyheaderstring (uuid) Optional client-supplied UUID. The gateway caches the response for this key for the process lifetime; replays return the cached response.

Request body application/json required

FieldTypeDescription
callback_urlstring (uri)
descriptionstring
destination requiredobject Polymorphic method picker. Set `type` to one of the listed values and populate the matching nested object.
destination.bank_transferobject
destination.bank_transfer.account_numberstring
destination.bank_transfer.beneficiary_namestring
destination.bank_transfer.ibanstring
destination.bank_transfer.referencestring
destination.bank_transfer.swift_bicstring
destination.cardobject
destination.card.saved_payment_idstring Reference to a previously stored card.
destination.card.three_d_secure_urlstring (uri)
destination.card.tokenstring The acquirer's card token or payment method ID (e.g. `pm_…` from Stripe's hosted fields). Mutually exclusive with `saved_payment_id`. KulPay never accepts a card number: one sent here is refused with 400 `gateway.raw_card_data_not_accepted` (PSP-12.2). In test mode the documented test card numbers are accepted and become their test tokens (`tok_test_<brand>_<bin6>_<last4>_<hash>`); a test token cannot pay a live payment.
destination.customobject
destination.custom.detailsobject
destination.custom.extension_typestring
destination.manualobject
destination.manual.external_reference requiredstring
destination.manual.notesstring
destination.manual.recorded_by requiredstring Operator that recorded the payment.
destination.mobile_walletobject
destination.mobile_wallet.network requiredstring
destination.mobile_wallet.phone_number requiredstring E.164 number.
destination.mockobject Force a specific outcome regardless of fixture matches.
destination.mock.delaystring Go duration before resolving.
destination.mock.failure_codestring
destination.mock.outcome requiredstring
One of: succeed, pending, fail
destination.referenceobject
destination.reference.entitystring Pin a specific entity from the catalog.
destination.reference.expires_atstring (date-time)
destination.reference.expires_instring Go duration ("24h").
destination.reference.networkstring
destination.reference.referencestring
destination.type requiredstring
One of: mobile_wallet, card, reference, bank_transfer, manual, mock, custom
metadataobject
money requiredobject
money.amount requiredstring Decimal string in major units (e.g. "19.99", not 1999). Up to 4 decimal places.
money.currency requiredstring ISO-4217 alpha-3 currency code.
payout_id requiredstring
recipientobject Counterparty for a payout.
recipient.namestring
recipient.organization_idstring
recipient.tax_idstring
recipient.user_idstring

Responses

  • 200 Payout accepted.
  • 400 The request body or query failed validation. The response body lists each field-level issue. invalid_request
  • 404 The named resource was not found. not_found

Request

curl -X POST "$KULPAY_URL/payouts" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "callback_url": "https://merchant.example.co.mz/kulpay/return",
  "description": "Pagamento de energia, outubro",
  "destination": {
    "mobile_wallet": {
      "network": "mpesa",
      "phone_number": "+258840000001"
    },
    "type": "mobile_wallet"
  },
  "metadata": {
    "key": "1250.00"
  },
  "money": {
    "amount": "100.00",
    "currency": "MZN"
  },
  "payout_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "recipient": {
    "name": "Electricidade de Moçambique",
    "organization_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
    "tax_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
    "user_id": "3KUXQ43bztiuf9tCMNgbcrYvne3"
  }
}'
200 Response application/json
{
  "failure_code": "EDM",
  "failure_message": "Pagamento de energia, outubro",
  "money": {
    "amount": "100.00",
    "currency": "MZN"
  },
  "payout_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "processed_at": "2026-10-10T09:30:00Z",
  "provider_payout_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "status": "pending"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "error": {
    "code": "invalid_request",
    "errors": [
      {
        "field": "money.amount",
        "message": "must be a positive decimal with at most 2 places"
      }
    ],
    "message": "The request body failed validation."
  },
  "errors": [
    {
      "field": "money.amount",
      "message": "must be a positive decimal with at most 2 places"
    }
  ],
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

GET /payouts/{provider_payout_id}

Read a payout snapshot

Sandbox only: reads a payout created by POST /payouts. With a database it answers 404 not_found; read payouts with GET /v1/payouts/{id}.

Parameters

NameInTypeDescription
provider_payout_id requiredpathstring

Responses

  • 200 Snapshot.
  • 404 The named resource was not found. not_found

Request

curl -X GET "$KULPAY_URL/payouts/{provider_payout_id}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "destination": {
    "mobile_wallet": {
      "network": "mpesa",
      "phone_number": "+258840000001"
    },
    "type": "mobile_wallet"
  },
  "failure_code": "EDM",
  "failure_message": "Pagamento de energia, outubro",
  "last_updated_at": "2026-10-10T09:30:00Z",
  "money": {
    "amount": "100.00",
    "currency": "MZN"
  },
  "payout_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "provider_payout_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "status": "pending"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

GET /v1/balance

Available balance

What payouts can draw on: settled charges less fees, refunds and payouts, in one currency. Requires payouts:read.

Parameters

NameInTypeDescription
currencyquerystring

Responses

  • 200 {currency, mode, available}.

Request

curl -X GET "$KULPAY_URL/v1/balance" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "available": "1020",
  "currency": "MZN",
  "mode": "test"
}

POST /v1/name-lookups

Look up a beneficiary name

Asks who holds a wallet (MSISDN) or account before you pay it (PSP-6.9): M-Pesa customer name query, eMola beneficiary name. With method: "cash_deposit" and a reference, a partner bank or agent asks whom a cash-deposit reference belongs to before taking the cash (PSP-7.7): the KulPay CMS answers through the platform's kulpay_cms connector, which serves every partner (no binding of its own is needed; the partner's capability matrix needs a NAME_LOOKUP row for method cash_deposit), and the response adds the deposit constraints and an expires_at (at most 15 minutes) until which the lookup_id can initiate a deposit (POST /v1/cash-deposits/intents). Routed like any other transaction type (NAME_LOOKUP). The name is returned to you and cached for at most 10 minutes (cached: true on a repeat), except a cash_deposit reference: it is always answered live and gets a new lookup_id, because whether a reference can take money changes (a request closes, a limit is reached); KulPay never stores it, and its event (name_lookup.completed) keeps only a masked form. Limited to 60 lookups per minute per key (429 with Retry-After). Requires payouts:write or transactions:write. The launch capability matrix (plan phase 10) holds the route first: one it does not enable is 422 route_not_enabled, and anything off_net 422 movement_scope_disabled, before any limit is reserved or provider called.

Bills at the counter (BIL-5.11). The same call looks up a bill. A valid ISO 11649 RF reference (an invoice's payment_reference, typed with spaces or not) is that invoice. With biller (and optionally product) the cashier chose from GET /v1/billers, KulPay looks among that biller's invoices (by number as written, without separators or case, or by KulPay ID) and asks its products' suppliers (a meter, an account). The answer has a destination (kind kulpay_invoice or kulpay_bill, the biller, product, invoice number, the holder's name masked, the amount due and the payer's fee) and constraints that include the fee: what the payer must bring. Several matches answer ambiguous with options: the payer chooses (POST /v1/name-lookups/{id}/select); KulPay never guesses. None answers not_found with the reason (for example "the invoice is paid"). Then initiate and confirm the deposit as for any reference: your confirmation is trusted, and the bill is paid at once with you as its channel.

References of billers' own (BIL-5.14). Many billers keep their own references (a meter, a contract, a student number) and answer for them at their own lookup. A reference without biller that is no RF reference is sent to the billers whose routable products claim its shape (their reference_pattern), at most 5, each asked in parallel for at most 3 s: one that knows it is found; several are ambiguous, each option naming its biller (biller, biller_name, logo_url) for the payer to choose; a biller that fails or is late is left out; when none answered with an option and some failed or were late, the answer is 503 biller_unavailable with Retry-After: try again (the reference may be theirs, so it is not taken as a wallet deposit). When more than 5 billers claim the shape, the answer is biller_required with billers (code, name, logo): ask the payer which, and look up again with biller. When no biller knows the reference, the lookup goes on to the deposit resolver (a wallet or account), as before.

Request body application/json required

FieldTypeDescription
accountstring
billerstring A bill (BIL-5.11): the biller the cashier chose; KulPay looks the reference up among its invoices and products. Not needed for an RF reference.
method requiredstring
msisdnstring
productstring With biller: only this product.
referencestring A cash-deposit reference, with `method: "cash_deposit"` (PSP-7.7).

Responses

  • 200 The answer.
  • 400 No method, or no MSISDN, account or reference. gateway.invalid_request
  • 422 not_supported: the method cannot look names up. Or the route is not in the launch matrix (route_not_enabled, movement_scope_disabled).
  • 429 Too many lookups. gateway.rate_limited
  • 502 The provider did not answer. gateway.provider_error
  • 503 biller_unavailable (BIL-5.14): a reference without biller that billers' routable products claim, and none of them answered with an option while some failed or were late. The reference may be theirs, so it is not taken as a wallet deposit: try again after Retry-After.

Request

curl -X POST "$KULPAY_URL/v1/name-lookups" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "account": "2003014567",
  "method": "mpesa",
  "msisdn": "258841234567",
  "reference": "INV-1001"
}'
200 Response application/json
{
  "cached": true,
  "constraints": {
    "currency": "MZN",
    "exact_amount": "1250.00",
    "expires_at": "2026-10-10T09:30:00Z",
    "max_amount": "1250.00",
    "min_amount": "1250.00"
  },
  "expires_at": "2026-10-10T09:30:00Z",
  "lookup_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "method": "string",
  "name": "Electricidade de Moçambique",
  "psp_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "reference": "2003014567",
  "status": "found"
}
400 Error · gateway.invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "a method and an MSISDN (or account) are required",
  "error": {
    "code": "gateway.invalid_request",
    "message": "a method and an MSISDN (or account) are required"
  },
  "legacy_code": "gateway.invalid_request",
  "status": 400,
  "title": "Invalid request",
  "type": "about:blank"
}
422 Error · not_supported application/problem+json
{
  "code": "not_supported",
  "detail": "name lookup is not supported for method card",
  "error": {
    "code": "not_supported",
    "message": "name lookup is not supported for method card",
    "rule": "method"
  },
  "rule": "method",
  "status": 422,
  "title": "Not supported",
  "type": "about:blank"
}
429 Error · gateway.rate_limited application/problem+json
{
  "code": "rate_limited",
  "detail": "Name lookups are limited to 60 per minute per key.",
  "error": {
    "code": "gateway.rate_limited",
    "message": "Name lookups are limited to 60 per minute per key."
  },
  "legacy_code": "gateway.rate_limited",
  "status": 429,
  "title": "Too many lookups",
  "type": "about:blank"
}
502 Error · gateway.provider_error application/problem+json
{
  "code": "upstream_error",
  "detail": "The provider could not answer the lookup. Try again later.",
  "error": {
    "code": "gateway.provider_error",
    "message": "The provider could not answer the lookup. Try again later."
  },
  "legacy_code": "gateway.provider_error",
  "status": 502,
  "title": "Provider error",
  "type": "about:blank"
}
503 Error · biller_unavailable application/problem+json
{
  "code": "biller_unavailable",
  "detail": "The billers this reference may belong to did not answer (EDM, LUZ). Try again in a moment.",
  "status": 503,
  "title": "Biller unavailable",
  "type": "about:blank"
}

GET /v1/payouts

List payouts

Parameters

NameInTypeDescription
statusquerystring

Responses

  • 200 {data, total}, newest first.

Request

curl -X GET "$KULPAY_URL/v1/payouts" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "account_id": "3KWPoS1bR2IZiR2ksqOoozIDdc2",
      "amount": "600",
      "approval_id": "3KWPoiCe7qzdyB7tV5IdxdJ3W8B",
      "approved_by": "key_approver",
      "completed_at": "2026-10-10T22:15:27.115651007Z",
      "connector_name": "mock_mpesa",
      "connector_ref": "po_3KWPog5AqNigZC2DABGSG1qEabV",
      "created_at": "2026-10-10T22:15:27.016275765Z",
      "created_by": "key_payouts",
      "currency": "MZN",
      "deleted_at": null,
      "destination": "258841234567",
      "destination_type": "msisdn",
      "id": "3KWPog5AqNigZC2DABGSG1qEabV",
      "method": "mpesa",
      "mode": "test",
      "status": "succeeded",
      "submitted_at": "2026-10-10T22:15:27.093792703Z",
      "updated_at": "2026-10-10T22:15:27.115651007Z"
    },
    {
      "account_id": "3KWPoS1bR2IZiR2ksqOoozIDdc2",
      "amount": "300",
      "beneficiary_name": "Joao",
      "completed_at": "2026-10-10T22:15:26.873505789Z",
      "connector_name": "mock_mpesa",
      "connector_ref": "po_3KWPobOie4IrltWBWASMFIESXUk",
      "created_at": "2026-10-10T22:15:26.792088415Z",
      "created_by": "key_payouts",
      "currency": "MZN",
      "deleted_at": null,
      "description": "invoice 9",
      "destination": "258841234567",
      "destination_type": "msisdn",
      "id": "3KWPobOie4IrltWBWASMFIESXUk",
      "method": "mpesa",
      "mode": "test",
      "status": "succeeded",
      "submitted_at": "2026-10-10T22:15:26.839332751Z",
      "updated_at": "2026-10-10T22:15:26.873505789Z"
    }
  ],
  "total": 2
}

POST /v1/payouts

Send a payout

Pays a mobile wallet (M-Pesa B2C, eMola) or account (PSP-6.7). The amount leaves your available balance (GET /v1/balance) before anything is sent, under a lock so two payouts cannot spend the same money, and comes back if the payout fails, is rejected or is returned. Above the deployment's approval threshold (payouts.approval_threshold) the payout waits in pending_approval for a second person with payouts:approve; a risk review also holds it there. The risk stage (PSP-6.3) applies: a blocked beneficiary is refused (422 risk_declined). An answer lost after sending leaves it processing until a status query settles it; KulPay never guesses. States: created, pending_approval, processing, succeeded, failed, returned. Webhooks: payout.succeeded, payout.failed, payout.returned. Requires payouts:write and an Idempotency-Key header. The launch capability matrix (plan phase 10) holds the route first: one it does not enable is 422 route_not_enabled, and anything off_net 422 movement_scope_disabled, before any limit is reserved or provider called. Payouts go only to the account's approved settlement destination (the settlement method and wallet or account, set by a settlement change a second person approved; IG-26a, owner's decision of 2026-10-12): the method, the beneficiary's type (an account for bank, an MSISDN for a wallet) and its number, normalized, must match. Anything else, or an account without an approved destination, is 422 payout_destination_not_approved, before any limit is reserved, any balance debited or any journal entry written. The rule applies at acceptance: a replay of an accepted payout returns it, and one already accepted (e.g. waiting for approval) is sent.

Parameters

NameInTypeDescription
Idempotency-Key requiredheaderstring

Request body application/json required

FieldTypeDescription
amount requiredstring
beneficiary requiredobject
beneficiary.accountstring
beneficiary.msisdnstring
beneficiary.namestring The name you expect (see POST /v1/name-lookups).
currency requiredstring
descriptionstring
method requiredstring

Responses

  • 201 The payout (a retry with the same key returns the same payout).
  • 400 Invalid amount, no beneficiary, or no Idempotency-Key. idempotency_key_required
  • 422 insufficient_balance, not_supported (the method cannot pay out) or risk_declined. Or the route is not in the launch matrix (route_not_enabled, movement_scope_disabled). Or the destination is not the account's approved settlement destination (payout_destination_not_approved).

Request

curl -X POST "$KULPAY_URL/v1/payouts" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "300",
  "beneficiary": {
    "msisdn": "258841234567",
    "name": "Joao"
  },
  "currency": "MZN",
  "description": "invoice 9",
  "method": "mpesa"
}'
201 Response application/json
{
  "account_id": "3KWPoS1bR2IZiR2ksqOoozIDdc2",
  "amount": "300",
  "beneficiary_name": "Joao",
  "completed_at": "2026-10-10T22:15:26.873505789Z",
  "connector_name": "mock_mpesa",
  "connector_ref": "po_3KWPobOie4IrltWBWASMFIESXUk",
  "created_at": "2026-10-10T22:15:26.792088415Z",
  "created_by": "key_payouts",
  "currency": "MZN",
  "deleted_at": null,
  "description": "invoice 9",
  "destination": "258841234567",
  "destination_type": "msisdn",
  "id": "3KWPobOie4IrltWBWASMFIESXUk",
  "method": "mpesa",
  "mode": "test",
  "status": "succeeded",
  "submitted_at": "2026-10-10T22:15:26.839332751Z",
  "updated_at": "2026-10-10T22:15:26.873505789Z"
}
400 Error · idempotency_key_required application/problem+json
{
  "code": "idempotency_key_required",
  "detail": "This request moves money: send an Idempotency-Key header so a retry can never run it twice.",
  "error": {
    "code": "idempotency_key_required",
    "message": "This request moves money: send an Idempotency-Key header so a retry can never run it twice."
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
422 Error · insufficient_balance application/problem+json
{
  "code": "insufficient_balance",
  "detail": "The available balance does not cover this payout.",
  "error": {
    "code": "insufficient_balance",
    "message": "The available balance does not cover this payout."
  },
  "status": 422,
  "title": "Insufficient balance",
  "type": "about:blank"
}

GET /v1/payouts/{id}

Get a payout

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 200 The payout.
  • 404 No such payout for this account. not_found

Request

curl -X GET "$KULPAY_URL/v1/payouts/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "account_id": "3KWPoS1bR2IZiR2ksqOoozIDdc2",
  "amount": "300",
  "beneficiary_name": "Joao",
  "completed_at": "2026-10-10T22:15:26.873505789Z",
  "connector_name": "mock_mpesa",
  "connector_ref": "po_3KWPobOie4IrltWBWASMFIESXUk",
  "created_at": "2026-10-10T22:15:26.792088415Z",
  "created_by": "key_payouts",
  "currency": "MZN",
  "deleted_at": null,
  "description": "invoice 9",
  "destination": "258841234567",
  "destination_type": "msisdn",
  "id": "3KWPobOie4IrltWBWASMFIESXUk",
  "method": "mpesa",
  "mode": "test",
  "status": "succeeded",
  "submitted_at": "2026-10-10T22:15:26.839332751Z",
  "updated_at": "2026-10-10T22:15:26.873505789Z"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "payout not found",
  "error": {
    "code": "not_found",
    "message": "payout not found"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

POST /v1/payouts/{id}/approve

Approve a payout

The second person on a payout waiting for approval; it is submitted at once. The creator cannot approve their own. Requires payouts:approve.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
reason requiredstring

Responses

  • 200 The payout after submission.
  • 400 The body is not JSON. invalid_request
  • 403 same_approver: whoever created the payout cannot approve it (four eyes).
  • 409 invalid_state: the payout is not waiting for approval (already decided, or never needed it).

Request

curl -X POST "$KULPAY_URL/v1/payouts/{id}/approve" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "checked"
}'
200 Response application/json
{
  "account_id": "3KWPoS1bR2IZiR2ksqOoozIDdc2",
  "amount": "600",
  "approval_id": "3KWPoiCe7qzdyB7tV5IdxdJ3W8B",
  "approved_by": "key_approver",
  "completed_at": "2026-10-10T22:15:27.115651007Z",
  "connector_name": "mock_mpesa",
  "connector_ref": "po_3KWPog5AqNigZC2DABGSG1qEabV",
  "created_at": "2026-10-10T22:15:27.016275765Z",
  "created_by": "key_payouts",
  "currency": "MZN",
  "deleted_at": null,
  "destination": "258841234567",
  "destination_type": "msisdn",
  "id": "3KWPog5AqNigZC2DABGSG1qEabV",
  "method": "mpesa",
  "mode": "test",
  "status": "succeeded",
  "submitted_at": "2026-10-10T22:15:27.093792703Z",
  "updated_at": "2026-10-10T22:15:27.115651007Z"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "body must be JSON: unexpected EOF",
  "error": {
    "code": "invalid_request",
    "message": "body must be JSON: unexpected EOF"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
403 Error · same_approver application/problem+json
{
  "code": "same_approver",
  "detail": "the person who created the payout cannot approve it",
  "error": {
    "code": "same_approver",
    "message": "the person who created the payout cannot approve it"
  },
  "status": 403,
  "title": "Forbidden",
  "type": "about:blank"
}
409 Error · invalid_state application/problem+json
{
  "code": "invalid_state",
  "detail": "the payout is not waiting for approval",
  "error": {
    "code": "invalid_state",
    "message": "the payout is not waiting for approval"
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

POST /v1/payouts/{id}/reject

Reject a payout

Fails a payout waiting for approval; the amount comes back to the available balance. Requires payouts:approve.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
reason requiredstring

Responses

  • 200 The failed payout.
  • 400 The body is not JSON. invalid_request
  • 409 invalid_state: the payout is not waiting for approval (already decided, or never needed it).

Request

curl -X POST "$KULPAY_URL/v1/payouts/{id}/reject" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Pagamento de energia, outubro"
}'
200 Response application/json
{
  "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "amount": "15000",
  "approval_id": "3KJobAemOZPNz4LOibuGrDbqqB5",
  "beneficiary_name": "Ana Macuácua",
  "completed_at": "2026-10-11T10:04:18Z",
  "created_at": "2026-10-11T09:47:02Z",
  "created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
  "currency": "MZN",
  "deleted_at": null,
  "description": "Pagamento a fornecedor",
  "destination": "258841234567",
  "destination_type": "msisdn",
  "failure_code": "rejected",
  "failure_msg": "Rejected by an approver.",
  "id": "3KmID0XKAlrxxCdU4SeehnmmDs8",
  "method": "mpesa",
  "mode": "live",
  "status": "failed",
  "updated_at": "2026-10-11T10:04:18Z"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "body must be JSON: unexpected EOF",
  "error": {
    "code": "invalid_request",
    "message": "body must be JSON: unexpected EOF"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
409 Error · invalid_state application/problem+json
{
  "code": "invalid_state",
  "detail": "the payout is not waiting for approval",
  "error": {
    "code": "invalid_state",
    "message": "the payout is not waiting for approval"
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}