API reference
Ledger
The double-entry ledger: a chart of accounts per tenant, mode and currency, and append-only balanced journals (PSP-10.1).
GET /v1/admin/accounts/{id}/funds-flow
A merchant's funds-flow model
PSP-10.2. override is the merchant's own model (collecting or direct); empty follows each PSP's approved commercial terms, and default (collecting) applies where neither sets one. pending is a requested change awaiting its checker. Requires accounts:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200
{account_id, override, default, pending}. - 404 No such account.
not_found
Request
curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/funds-flow" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"default": "collecting",
"pending": {
"created_at": "2026-10-09T11:20:31Z",
"id": "3KJobAemOZPNz4LOibuGrDbqqB5",
"kind": "funds_flow.change",
"payload": "{\"account_id\":\"3KY8VJtMMG2V1cNjNhTgPt1mSFI\",\"funds_flow\":\"direct\",\"previous\":\"\"}",
"prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"reason": "O PSP passa a liquidar diretamente ao comerciante (adenda contratual de outubro)",
"status": "pending",
"subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
}
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such account",
"error": {
"code": "not_found",
"message": "no such account"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
PUT /v1/admin/accounts/{id}/funds-flow
Request a funds-flow change
collecting: PSP to KulPay's safeguarding account to the merchant, with the client-funds liability and payouts in the ledger. direct: PSP to the merchant; KulPay keeps a shadow ledger of what the merchant should receive and the fees it is owed, outside the available balance. The change waits for a second person (POST /v1/admin/approvals/{id}/decide) and then applies to new payments only; each payment keeps the model in force when its PSP was chosen. Records config.funds_flow.changed. Requires accounts:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
funds_flow required | string | One of: , collecting, direct |
reason required | string |
Responses
- 202 Waiting for approval:
{approval}. - 400 Unknown model, no change, or a change already pending.
invalid_request - 404 No such account.
not_found
Request
curl -X PUT "$KULPAY_URL/v1/admin/accounts/{id}/funds-flow" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"funds_flow": "",
"reason": "Pagamento de energia, outubro"
}'
202 Response application/json
{
"approval": {
"created_at": "2026-10-09T11:20:31Z",
"id": "3KJobAemOZPNz4LOibuGrDbqqB5",
"kind": "funds_flow.change",
"payload": "{\"account_id\":\"3KY8VJtMMG2V1cNjNhTgPt1mSFI\",\"funds_flow\":\"direct\",\"previous\":\"\"}",
"prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"reason": "O PSP passa a liquidar diretamente ao comerciante (adenda contratual de outubro)",
"status": "pending",
"subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
}
}
400 Error · invalid_request application/problem+json
{
"code": "invalid_request",
"detail": "a funds-flow change is already waiting for approval (3KJobAemOZPNz4LOibuGrDbqqB5)",
"error": {
"code": "invalid_request",
"message": "a funds-flow change is already waiting for approval (3KJobAemOZPNz4LOibuGrDbqqB5)"
},
"status": 400,
"title": "Bad Request",
"type": "about:blank"
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such account",
"error": {
"code": "not_found",
"message": "no such account"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
POST /v1/admin/accounts/{id}/managed-settlements
Build a managed settlement batch now
Builds the merchant's managed pass-through batch for today under its newest managed arrangement, whatever the schedule (the hourly job builds it on scheduled days): provisional payables past their delay become available; the batch takes every available payable with immutable source lines (gross, platform fee, provider cost, reserve and each adjustment), reserves due for release and, under offset_future_settlements, open receivables (never below zero). The batch stops at ready: the gateway does not execute it and POST /v1/payouts is unchanged; executing settlement through the arrangement's destination is the next step, pending the owner's decision. One batch per merchant, mode and currency a day. Requires admin:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
currency required | string | |
mode | string | One of: live, test |
Responses
- 201 The ready batch (
GET /v1/settlements/{id}lists its lines). - 400 The request body or query failed validation. The response body lists each field-level issue.
invalid_request - 409
nothing_to_settle: no available payable or released reserve, or the net is below the arrangement's minimum.
Request
curl -X POST "$KULPAY_URL/v1/admin/accounts/{id}/managed-settlements" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"currency": "MZN",
"mode": "live"
}'
201 Response application/json
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"adjustments": "0",
"arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"arrangement_version": 2,
"created_at": "2026-10-12T09:00:00Z",
"currency": "MZN",
"cycle_date": "2026-10-12",
"cycle_days": 1,
"fees": "-9.87",
"funds_flow": "managed",
"gross": "123.45",
"id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
"line_count": 4,
"mode": "live",
"net": "107.41",
"period_end": "2026-10-12T09:00:00Z",
"refunds": "0",
"reserves": "-6.17",
"status": "ready",
"updated_at": "2026-10-12T09:00:00Z"
}
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"
}
409 Error · nothing_to_settle application/problem+json
{
"code": "nothing_to_settle",
"detail": "No available payable or released reserve reaches the arrangement's minimum.",
"error": {
"code": "nothing_to_settle",
"message": "No available payable or released reserve reaches the arrangement's minimum."
},
"status": 409,
"title": "Conflict",
"type": "about:blank"
}
GET /v1/admin/accounts/{id}/payables
A merchant's payables
As GET /v1/payables, for any merchant. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | |
mode | query | string | live or test; the key's mode when omitted. One of: live, test |
currency | query | string | ISO 4217 code. |
state | query | string | A payable state. One of: provisional, available, reserved_for_settlement, settled, disputed, reversed |
batch_id | query | string | A settlement batch. |
Responses
- 200
{data, total, summary}: the payables, oldest first, and per state the count and net.
Request
curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/payables" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"adjustments_minor": 0,
"arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"arrangement_version": 2,
"available_at": "2026-10-12T08:15:00Z",
"batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
"created_at": "2026-10-11T08:15:00Z",
"currency": "MZN",
"fee_refunded_minor": 0,
"gross_minor": 12345,
"id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
"mode": "live",
"net_minor": 10741,
"payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
"platform_fee_minor": 864,
"provider_cost_minor": 123,
"refunded_minor": 0,
"reserve_minor": 617,
"reserve_release_at": "2026-10-18T08:15:00Z",
"state": "reserved_for_settlement",
"updated_at": "2026-10-12T09:00:00Z"
}
],
"summary": [
{
"count": 0,
"net_minor": 0,
"state": "provisional"
},
{
"count": 0,
"net_minor": 0,
"state": "available"
},
{
"count": 1,
"net_minor": 10741,
"state": "reserved_for_settlement"
},
{
"count": 0,
"net_minor": 0,
"state": "settled"
},
{
"count": 0,
"net_minor": 0,
"state": "disputed"
},
{
"count": 0,
"net_minor": 0,
"state": "reversed"
}
],
"total": 1
}
GET /v1/admin/accounts/{id}/receivables
What a merchant owes back
As GET /v1/receivables, for any merchant. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | |
mode | query | string | live or test; the key's mode when omitted. One of: live, test |
currency | query | string | ISO 4217 code. |
status | query | string | open or offset. One of: open, offset |
Responses
- 200
{data, total, outstanding_minor}.
Request
curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/receivables" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"amount_minor": 37200,
"created_at": "2026-10-13T14:02:11Z",
"currency": "MZN",
"id": "3LBvE2pK9wYq1nTb6mRcFs3xZd7",
"kind": "refund_after_settlement",
"mode": "live",
"outstanding_minor": 37200,
"payable_id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
"payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
"policy": "offset_future_settlements",
"source_id": "3LBvF8nQ4rTz2kWm7pXcHd5sYe1",
"status": "open",
"updated_at": "2026-10-13T14:02:11Z"
}
],
"outstanding_minor": 37200,
"total": 1
}
GET /v1/admin/accounts/{id}/settlement
A merchant's settlement cycle and destination
The cycle (T+n days), the method and the destination (masked), and a pending change. Requires accounts:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200
{account_id, cycle_days, method, destination, pending}. - 404 No such account.
not_found
Request
curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/settlement" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"cycle_days": 1,
"destination": "********4567",
"method": "mpesa",
"pending": {
"created_at": "2026-10-10T15:05:44Z",
"id": "3KMuWRw3JaLEf2SolKp9TuBxIGy",
"kind": "settlement.destination",
"prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"reason": "O comerciante pediu liquidação a cada 2 dias",
"status": "pending",
"subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
}
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such account",
"error": {
"code": "not_found",
"message": "no such account"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
PUT /v1/admin/accounts/{id}/settlement
Request a settlement change
Where a merchant's money is paid is a fraud target, so a change of the cycle or destination waits for a second person (POST /v1/admin/approvals/{id}/decide). method is a wallet (mpesa, emola: destination is the MSISDN) or bank (the account number or IBAN). Requires accounts:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
cycle_days required | integer | |
destination | string | |
method | string | |
reason required | string |
Responses
- 202 Waiting for approval:
{approval}. - 400 Invalid settings, or a change already pending.
invalid_request - 404 No such account.
not_found
Request
curl -X PUT "$KULPAY_URL/v1/admin/accounts/{id}/settlement" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"cycle_days": 0,
"destination": "string",
"method": "string",
"reason": "Pagamento de energia, outubro"
}'
202 Response application/json
{
"approval": {
"created_at": "2026-10-10T15:05:44Z",
"id": "3KMuWRw3JaLEf2SolKp9TuBxIGy",
"kind": "settlement.destination",
"payload": "{\"cycle_days\":2,\"method\":\"mpesa\",\"destination\":\"+258841234567\"}",
"prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"reason": "O comerciante pediu liquidação a cada 2 dias",
"status": "pending",
"subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
}
}
400 Error · invalid_request application/problem+json
{
"code": "invalid_request",
"detail": "cycle_days must be 0 to 30",
"error": {
"code": "invalid_request",
"message": "cycle_days must be 0 to 30"
},
"status": 400,
"title": "Bad Request",
"type": "about:blank"
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such account",
"error": {
"code": "not_found",
"message": "no such account"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
GET /v1/admin/accounts/{id}/settlement-arrangements
A merchant's settlement arrangement versions
Every version, newest first; currency narrows to one. Requires accounts:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | |
currency | query | string | ISO 4217 code. |
Responses
- 200
{data, total}.
Request
curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/settlement-arrangements" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"activated_at": "2026-10-11T10:40:17Z",
"approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
"approved_by": "operator:rui",
"available_after_days": 1,
"created_at": "2026-10-11T09:12:03Z",
"created_by": "operator:ana",
"currency": "MZN",
"custody": "managed_pass_through",
"destination_ref": "*******************0001",
"id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"minimum_settlement_minor": 100000,
"platform_fee_bps": 0,
"platform_fee_fixed_minor": 0,
"provider_cost_bps": 100,
"provider_cost_fixed_minor": 0,
"reason": "Adenda de cobrança gerida assinada",
"refund_fee_policy": "retain",
"refund_offset_policy": "offset_future_settlements",
"reserve_bps": 500,
"reserve_hold_days": 7,
"schedule": "daily",
"status": "active",
"version": 2
}
],
"total": 1
}
POST /v1/admin/accounts/{id}/settlement-arrangements
Propose a settlement arrangement version
A new version of the merchant's arrangement in a currency: custody, the approved destination's identifier, schedule, availability delay, minimum, reserve, merchant-borne platform fee and provider cost (basis points of the gross plus a fixed amount, each rounded half up), the refund fee policy and the refund-offset policy. It waits for a second person (POST /v1/admin/approvals/{id}/decide, kind settlement_arrangement.activate); once active it applies to new payments only, each payment recording the version it was routed under. managed_pass_through makes a collecting payment managed (a merchant payable); merchant_direct makes it direct. KulPay-balance payments stay internal. Requires accounts:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
available_after_days | integer | |
currency required | string | |
custody required | string | One of: managed_pass_through, merchant_direct |
destination_ref | string | The identifier of a destination approved in KulOffice's register (4 to 64 letters, digits, `_:.-`), never an account number or MSISDN. Required for managed_pass_through; not allowed for merchant_direct. |
minimum_settlement_minor | integer | |
platform_fee_bps | integer | No longer used (IG-29, owner decision 2026-10-12): the platform fee is the one KulOffice captured, sent as each payment's fee instruction. A new version must leave it 0. |
platform_fee_fixed_minor | integer | No longer used (IG-29, owner decision 2026-10-12): the platform fee is the one KulOffice captured, sent as each payment's fee instruction. A new version must leave it 0. |
provider_cost_bps | integer | |
provider_cost_fixed_minor | integer | |
reason required | string | |
refund_fee_policy | string | One of: retain, proportional |
refund_offset_policy | string | One of: offset_future_settlements, invoice |
reserve_bps | integer | |
reserve_hold_days | integer | |
schedule | string | One of: daily, weekly, manual |
schedule_weekday | integer |
Responses
- 202 Waiting for approval:
{arrangement, approval}. - 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/v1/admin/accounts/{id}/settlement-arrangements" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"available_after_days": 1,
"currency": "MZN",
"custody": "managed_pass_through",
"destination_ref": "dst_mpesa_merchant_0001",
"minimum_settlement_minor": 100000,
"provider_cost_bps": 100,
"reason": "Adenda de cobrança gerida assinada",
"refund_fee_policy": "retain",
"refund_offset_policy": "offset_future_settlements",
"reserve_bps": 500,
"reserve_hold_days": 7,
"schedule": "daily"
}'
202 Response application/json
{
"approval": {
"created_at": "2026-10-11T09:12:03Z",
"id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
"kind": "settlement_arrangement.activate",
"payload": "{\"arrangement_id\":\"3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz\",\"currency\":\"MZN\",\"custody\":\"managed_pass_through\",\"version\":2}",
"prepared_by": "operator:ana",
"reason": "Adenda de cobrança gerida assinada",
"status": "pending",
"subject_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
},
"arrangement": {
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
"available_after_days": 1,
"created_at": "2026-10-11T09:12:03Z",
"created_by": "operator:ana",
"currency": "MZN",
"custody": "managed_pass_through",
"destination_ref": "*******************0001",
"id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"minimum_settlement_minor": 100000,
"platform_fee_bps": 0,
"platform_fee_fixed_minor": 0,
"provider_cost_bps": 100,
"provider_cost_fixed_minor": 0,
"reason": "Adenda de cobrança gerida assinada",
"refund_fee_policy": "retain",
"refund_offset_policy": "offset_future_settlements",
"reserve_bps": 500,
"reserve_hold_days": 7,
"schedule": "daily",
"status": "pending",
"version": 2
}
}
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 /v1/admin/fee-schedules
Fee schedule versions
Versions of PSP and merchant fee schedules, newest first (PSP-10.5). Requires accounts:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
scope | query | string | One of: psp, merchant |
subject_id | query | string |
Responses
- 200
{data: [schedule], total}.
Request
curl -X GET "$KULPAY_URL/v1/admin/fee-schedules" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"activated_at": "2026-02-03T16:22:09Z",
"approval_id": "3KJslQCJSqDHZTJwZ7NMOHDLvqV",
"approved_by": "operator:checkerexample.co.mz",
"created_at": "2026-02-03T10:00:00Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"document": "{\"rules\":[{\"type\":\"SALE\",\"method\":\"mpesa\",\"currency\":\"MZN\",\"fixed\":\"5\",\"percent\":\"1.5\",\"min_fee\":\"10\",\"max_fee\":\"500\"}]}",
"id": "3KVJAYtz9ILvHWq4UNOkbO8klRW",
"reason": "Tarifa negociada para 2026",
"scope": "merchant",
"status": "active",
"subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"version": 2
},
{
"activated_at": "2026-01-12T09:10:41Z",
"approval_id": "3Kh8Qx4H3yHpjhVVX645vFcRXuU",
"approved_by": "operator:checkerexample.co.mz",
"created_at": "2026-01-12T08:30:00Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"document": "{\"rules\":[{\"currency\":\"MZN\",\"percent\":\"2\"}]}",
"id": "3KTWhTkMF6ror2SF88hNezhwVi8",
"reason": "Tabela padrão do PSP",
"scope": "psp",
"status": "active",
"subject_id": "3KtGTPT0AQaKTIWS8Xv6tRyJwCs",
"tenant_id": "3K75aZx53yJUW0o5JRyLpwMufMq",
"version": 1
}
],
"total": 2
}
POST /v1/admin/fee-schedules
Propose a fee schedule version
An ordered list of rules for a PSP or a merchant (PSP-10.5); the first rule matching the transaction type, method, currency and amount band prices it: fixed, percent or both, bounded by min_fee and max_fee, with tiers by the merchant's monthly volume replacing the fixed and percentage parts. Fees are worked out at posting time in decimal arithmetic, rounded with the currency's rule; the merchant's schedule applies first, then its PSP's, then the platform default. The version waits for a second person (POST /v1/admin/approvals/{id}/decide); events config.fee_schedule.version_created and config.fee_schedule.activated. Requires accounts:write.
Request body application/json required
| Field | Type | Description |
|---|---|---|
reason required | string | |
rules required | object[] | |
rules[].currency required | string | |
rules[].fixed | string | |
rules[].max_amount | string | Exclusive. |
rules[].max_fee | string | |
rules[].method | string | Empty or * for any. |
rules[].min_amount | string | Inclusive. |
rules[].min_fee | string | |
rules[].percent | string | |
rules[].tiers | object[] | |
rules[].tiers[].fixed | string | |
rules[].tiers[].monthly_volume_from required | string | |
rules[].tiers[].percent | string | |
rules[].type | string | Transaction type (SALE, ...); empty or * for any. |
scope required | string | One of: psp, merchant |
subject_id required | string |
Responses
- 202
{schedule, approval}. - 400 A rule is invalid, or a version is already pending; the problems are named.
invalid_request - 404 No such PSP or merchant.
not_found
Request
curl -X POST "$KULPAY_URL/v1/admin/fee-schedules" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Pagamento de energia, outubro",
"rules": [
{
"currency": "MZN",
"fixed": "string",
"max_amount": "1250.00",
"max_fee": "1250.00",
"method": "string",
"min_amount": "1250.00",
"min_fee": "1250.00",
"percent": "string",
"tiers": [
{
"fixed": "string",
"monthly_volume_from": "string",
"percent": "string"
}
],
"type": "standard"
}
],
"scope": "psp",
"subject_id": "3KUXQ43bztiuf9tCMNgbcrYvne3"
}'
202 Response application/json
{
"approval": {
"created_at": "2026-10-11T08:12:55Z",
"id": "3KMuWRw3JaLEf2SolKp9TuBxIGy",
"kind": "fee_schedule.activate",
"payload": "{\"schedule_id\":\"3KJslQCJSqDHZTJwZ7NMOHDLvqV\",\"version\":3}",
"prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"reason": "Revisão de tarifa após aumento de volume",
"status": "pending",
"subject_id": "3KJslQCJSqDHZTJwZ7NMOHDLvqV",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
},
"schedule": {
"approval_id": "3KMuWRw3JaLEf2SolKp9TuBxIGy",
"created_at": "2026-10-11T08:12:55Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"document": "{\"rules\":[{\"type\":\"SALE\",\"method\":\"mpesa\",\"currency\":\"MZN\",\"fixed\":\"5\",\"percent\":\"1.25\",\"min_fee\":\"10\",\"max_fee\":\"450\"}]}",
"id": "3KJslQCJSqDHZTJwZ7NMOHDLvqV",
"reason": "Revisão de tarifa após aumento de volume",
"scope": "merchant",
"status": "pending",
"subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"version": 3
}
}
400 Error · invalid_request application/problem+json
{
"code": "invalid_request",
"detail": "rules[0].percent must be at most 100; rules[1].max_fee must be at least min_fee",
"error": {
"code": "invalid_request",
"message": "rules[0].percent must be at most 100; rules[1].max_fee must be at least min_fee"
},
"status": 400,
"title": "Bad Request",
"type": "about:blank"
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such PSP or merchant",
"error": {
"code": "not_found",
"message": "no such PSP or merchant"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
POST /v1/admin/fee-schedules/quote
Price a transaction
What a transaction would pay with the schedules in force, with the breakdown each payment records (fee_breakdown: scope, schedule version, rule, tier, parts, minimum or maximum applied). Requires accounts:read.
Request body application/json required
| Field | Type | Description |
|---|---|---|
account_id required | string | |
amount required | string | |
currency required | string | |
method | string | |
mode | string | |
psp_id | string | |
type | string |
Responses
- 200
{fee, currency, breakdown}. - 400 Missing or invalid input.
invalid_request
Request
curl -X POST "$KULPAY_URL/v1/admin/fee-schedules/quote" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
"amount": "1250.00",
"currency": "MZN",
"method": "string",
"mode": "test",
"psp_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
"type": "standard"
}'
200 Response application/json
{
"breakdown": {
"fee": "42.5",
"fixed": "5",
"percent": "1.5",
"percent_part": "37.5",
"rule": 0,
"schedule_id": "3KVJAYtz9ILvHWq4UNOkbO8klRW",
"scope": "merchant",
"version": 2
},
"currency": "MZN",
"fee": "42.5"
}
400 Error · invalid_request application/problem+json
{
"code": "invalid_request",
"detail": "account_id, a positive amount and a 3-letter currency are required",
"error": {
"code": "invalid_request",
"message": "account_id, a positive amount and a 3-letter currency are required"
},
"status": 400,
"title": "Bad Request",
"type": "about:blank"
}
GET /v1/admin/fee-schedules/{id}
A fee schedule version
Requires accounts:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200 The version.
- 404 No such schedule.
not_found
Request
curl -X GET "$KULPAY_URL/v1/admin/fee-schedules/{id}" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"activated_at": "2026-02-03T16:22:09Z",
"approval_id": "3KJslQCJSqDHZTJwZ7NMOHDLvqV",
"approved_by": "operator:checkerexample.co.mz",
"created_at": "2026-02-03T10:00:00Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"document": "{\"rules\":[{\"type\":\"SALE\",\"method\":\"mpesa\",\"currency\":\"MZN\",\"fixed\":\"5\",\"percent\":\"1.5\",\"min_fee\":\"10\",\"max_fee\":\"500\"}]}",
"id": "3KVJAYtz9ILvHWq4UNOkbO8klRW",
"reason": "Tarifa negociada para 2026",
"scope": "merchant",
"status": "active",
"subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"version": 2
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such fee schedule",
"error": {
"code": "not_found",
"message": "no such fee schedule"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
POST /v1/admin/ledger/journals/{id}/reverse
Correct a journal by reversing it
Posts the mirror image of a journal, referencing it (reverses_id), with the reason as its memo; journals are never edited or deleted. A journal is reversed at most once and a reversal is not itself reversed. The available balance moves with it. Records ledger.journal.posted. Requires admin:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
reason required | string |
Responses
- 201 The reversing journal.
- 404 No such journal.
not_found - 409 Already reversed, or itself a reversal (
not_reversible).
Request
curl -X POST "$KULPAY_URL/v1/admin/ledger/journals/{id}/reverse" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Pagamento de energia, outubro"
}'
201 Response application/json
{
"currency": "MZN",
"entry_id": "3K3YD5LnG4uFl4lRYgi3k6rEyrm",
"event_id": "3KtGTPT0AQaKTIWS8Xv6tRyJwCs",
"id": "3K7ZnIEvw02DpWLConn6DR7kTKw",
"kind": "journal_reversal",
"line_count": 2,
"memo": "Taxa lançada em duplicado na cobrança de 9 de outubro",
"mode": "live",
"posted_at": "2026-10-11T10:20:05Z",
"reverses_id": "3K1zixgG7mZ8P6eT8Gua1XCk5Se",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"total_minor": 4250,
"transaction_id": "3KKflkCic0KwrGzM5hzGTfngNiH"
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such journal",
"error": {
"code": "not_found",
"message": "no such journal"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
409 Error · not_reversible application/problem+json
{
"code": "not_reversible",
"detail": "journal 3K1zixgG7mZ8P6eT8Gua1XCk5Se is already reversed",
"error": {
"code": "not_reversible",
"message": "journal 3K1zixgG7mZ8P6eT8Gua1XCk5Se is already reversed"
},
"status": 409,
"title": "Conflict",
"type": "about:blank"
}
GET /v1/admin/payables/{id}
A merchant payable
Any merchant's payable with its adjustments and receivables. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200 The payable with
adjustments(refunds, fee refunds, reversals before it was batched) andreceivables(what the merchant owes back for it). - 404 The named resource was not found.
not_found
Request
curl -X GET "$KULPAY_URL/v1/admin/payables/{id}" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"adjustments": [],
"adjustments_minor": 0,
"arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"arrangement_version": 2,
"available_at": "2026-10-12T08:15:00Z",
"batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
"created_at": "2026-10-11T08:15:00Z",
"currency": "MZN",
"fee_refunded_minor": 0,
"gross_minor": 12345,
"id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
"mode": "live",
"net_minor": 10741,
"payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
"platform_fee_minor": 864,
"provider_cost_minor": 123,
"receivables": [],
"refunded_minor": 0,
"reserve_minor": 617,
"reserve_release_at": "2026-10-18T08:15:00Z",
"state": "reserved_for_settlement",
"updated_at": "2026-10-12T09:00:00Z"
}
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/admin/regulatory/runs
Generated regulatory reports
Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
template_id | query | string |
Responses
- 200
{data, total}.
Request
curl -X GET "$KULPAY_URL/v1/admin/regulatory/runs" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"code": "BM-PAY-DAILY",
"created_at": "2026-10-11T01:00:03Z",
"file_id": "3KFmKhlYELQZGQLw3WZICSjn9u0",
"format": "csv",
"id": "3KLkM29zNWk8XOFM5SgHNsugPV9",
"ledger_totals": "{\"MZN\":125000000}",
"period_end": "2026-10-11T00:00:00Z",
"period_start": "2026-10-10T00:00:00Z",
"reconciled": true,
"rows": 6,
"sha256": "15e20481e573d63f895dc0d253eb93cf04e61bc4a07b151c3d57e7564f5b7c5c",
"signature": "MEUCIQDx3v…",
"signing_key_id": "reg-signing-2026",
"status": "submitted",
"submitted_at": "2026-10-11T09:02:47Z",
"submitted_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"template_id": "3KIaajCSOfi46dTWod2FEl5eSMW",
"template_version": 2,
"totals": "{\"MZN\":125000000}",
"trigger": "scheduled"
},
{
"code": "BM-PAY-DAILY",
"created_at": "2026-10-10T01:00:02Z",
"detail": "totals do not reconcile to the ledger: MZN: report 98750000, ledger 98625000",
"file_id": "3KRlP4WSt8uUE2E2OYdHrWSK4QF",
"format": "csv",
"id": "3KhLW2YPbDu1SMgSpNcE9FdzNh0",
"ledger_totals": "{\"MZN\":98625000}",
"period_end": "2026-10-10T00:00:00Z",
"period_start": "2026-10-09T00:00:00Z",
"reconciled": false,
"rows": 5,
"sha256": "8b1a9953c4611296a827abf8c47804d7e6c49c6b2f1c0e5a3d4f7b9e2a6c1d30",
"signature": "MEQCIF9a2k…",
"signing_key_id": "reg-signing-2026",
"status": "blocked",
"template_id": "3KIaajCSOfi46dTWod2FEl5eSMW",
"template_version": 2,
"totals": "{\"MZN\":98750000}",
"trigger": "scheduled"
}
],
"total": 2
}
GET /v1/admin/regulatory/runs/{id}/file
A report's signed file
With X-Signature and X-Signing-Key-ID. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200 The file.
Request
curl -X GET "$KULPAY_URL/v1/admin/regulatory/runs/{id}/file" \
-H "Authorization: Bearer $KULPAY_KEY"
POST /v1/admin/regulatory/runs/{id}/submit
Mark a report submitted
Only a reconciled report (regulatory_report.submitted). Requires reconciliation:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200 The run.
- 409 Blocked or already submitted.
not_submittable
Request
curl -X POST "$KULPAY_URL/v1/admin/regulatory/runs/{id}/submit" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"code": "BM-PAY-DAILY",
"created_at": "2026-10-11T01:00:03Z",
"file_id": "3KFmKhlYELQZGQLw3WZICSjn9u0",
"format": "csv",
"id": "3KLkM29zNWk8XOFM5SgHNsugPV9",
"ledger_totals": "{\"MZN\":125000000}",
"period_end": "2026-10-11T00:00:00Z",
"period_start": "2026-10-10T00:00:00Z",
"reconciled": true,
"rows": 6,
"sha256": "15e20481e573d63f895dc0d253eb93cf04e61bc4a07b151c3d57e7564f5b7c5c",
"signature": "MEUCIQDx3v…",
"signing_key_id": "reg-signing-2026",
"status": "submitted",
"submitted_at": "2026-10-11T09:02:47Z",
"submitted_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"template_id": "3KIaajCSOfi46dTWod2FEl5eSMW",
"template_version": 2,
"totals": "{\"MZN\":125000000}",
"trigger": "scheduled"
}
409 Error · not_submittable application/problem+json
{
"code": "not_submittable",
"detail": "blocked: totals do not reconcile to the ledger: MZN: report 98750000, ledger 98625000",
"error": {
"code": "not_submittable",
"message": "blocked: totals do not reconcile to the ledger: MZN: report 98750000, ledger 98625000"
},
"status": 409,
"title": "Conflict",
"type": "about:blank"
}
GET /v1/admin/regulatory/templates
Regulatory report templates
Requires reconciliation:read.
Responses
- 200
{data, total}.
Request
curl -X GET "$KULPAY_URL/v1/admin/regulatory/templates" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"active": true,
"code": "BM-PAY-DAILY",
"created_at": "2026-03-02T09:15:00Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"fields": "[\"date\",\"psp_id\",\"method\",\"count\",\"amount\",\"fees\"]",
"format": "csv",
"group_by": "[\"date\",\"psp_id\",\"method\"]",
"id": "3KIaajCSOfi46dTWod2FEl5eSMW",
"name": "Pagamentos diários por PSP e método",
"period": "daily",
"source": "payments",
"updated_at": "2026-06-18T14:40:12Z",
"version": 2
},
{
"active": true,
"code": "BM-LEDGER-MONTHLY",
"created_at": "2026-04-01T08:00:00Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"fields": "[\"account_code\",\"currency\",\"count\",\"debit\",\"credit\"]",
"format": "xlsx",
"group_by": "[\"account_code\",\"currency\"]",
"id": "3KpIXlMpv6U6uHUiJY7dFShREqZ",
"name": "Movimentos do razão por conta",
"period": "monthly",
"source": "ledger",
"updated_at": "2026-04-01T08:00:00Z",
"version": 1
}
],
"total": 2
}
POST /v1/admin/regulatory/templates
Create a regulatory report template
A configurable report (PSP-10.6): source, grouping, fields, period and format. The exact list Banco de Moçambique requires is to be confirmed with compliance; reports are configured, not coded. Records config.regulatory_template.saved. Requires accounts:write.
Request body application/json required
| Field | Type | Description |
|---|---|---|
active | boolean | |
code required | string | |
fields required | string[] | Grouped dimensions and measures (payments: count, amount, fees; ledger: count, debit, credit). |
format required | string | One of: csv, xlsx, xml |
group_by | string[] | payments: date, psp_id, method, currency; ledger: date, tenant_id, currency, account_code, kind. |
name required | string | |
period required | string | One of: daily, monthly, on_demand |
source required | string | One of: payments, ledger |
Responses
- 201 The template.
- 400 Invalid; the problems are named.
invalid_request
Request
curl -X POST "$KULPAY_URL/v1/admin/regulatory/templates" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"active": true,
"code": "EDM",
"fields": [
"money.amount"
],
"format": "csv",
"group_by": [
"string"
],
"name": "Electricidade de Moçambique",
"period": "daily",
"source": "payments"
}'
201 Response application/json
{
"active": true,
"code": "BM-PAY-DAILY",
"created_at": "2026-03-02T09:15:00Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"fields": "[\"date\",\"psp_id\",\"method\",\"count\",\"amount\",\"fees\"]",
"format": "csv",
"group_by": "[\"date\",\"psp_id\",\"method\"]",
"id": "3KIaajCSOfi46dTWod2FEl5eSMW",
"name": "Pagamentos diários por PSP e método",
"period": "daily",
"source": "payments",
"updated_at": "2026-03-02T09:15:00Z",
"version": 1
}
400 Error · invalid_request application/problem+json
{
"code": "invalid_request",
"detail": "period must be daily, monthly or on_demand; field \"tenant_id\" is neither a grouped dimension nor a payments measure (count, amount, fees)",
"error": {
"code": "invalid_request",
"message": "period must be daily, monthly or on_demand; field \"tenant_id\" is neither a grouped dimension nor a payments measure (count, amount, fees)"
},
"status": 400,
"title": "Bad Request",
"type": "about:blank"
}
PUT /v1/admin/regulatory/templates/{id}
New version of a template
Requires accounts:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
active | boolean | |
code required | string | |
fields required | string[] | Grouped dimensions and measures (payments: count, amount, fees; ledger: count, debit, credit). |
format required | string | One of: csv, xlsx, xml |
group_by | string[] | payments: date, psp_id, method, currency; ledger: date, tenant_id, currency, account_code, kind. |
name required | string | |
period required | string | One of: daily, monthly, on_demand |
source required | string | One of: payments, ledger |
Responses
- 201 The template, version incremented.
Request
curl -X PUT "$KULPAY_URL/v1/admin/regulatory/templates/{id}" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"active": true,
"code": "EDM",
"fields": [
"money.amount"
],
"format": "csv",
"group_by": [
"string"
],
"name": "Electricidade de Moçambique",
"period": "daily",
"source": "payments"
}'
201 Response application/json
{
"active": true,
"code": "BM-PAY-DAILY",
"created_at": "2026-03-02T09:15:00Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"fields": "[\"date\",\"psp_id\",\"method\",\"count\",\"amount\",\"fees\"]",
"format": "csv",
"group_by": "[\"date\",\"psp_id\",\"method\"]",
"id": "3KIaajCSOfi46dTWod2FEl5eSMW",
"name": "Pagamentos diários por PSP e método",
"period": "daily",
"source": "payments",
"updated_at": "2026-06-18T14:40:12Z",
"version": 2
}
POST /v1/admin/regulatory/templates/{id}/run
Generate a report now
For the period [from, to): the report is signed, stored write-once, and its totals checked against the ledger for the same period (payments: amount against the charge journals; ledger: debits against credits). A mismatch blocks submission (status blocked). Daily and monthly templates also run on their own for yesterday and last month (regulatory_report.generated). Requires reconciliation:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
from required | string (date) | |
to required | string (date) |
Responses
- 201 The run.
Request
curl -X POST "$KULPAY_URL/v1/admin/regulatory/templates/{id}/run" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-10-31",
"to": "2026-10-31"
}'
201 Response application/json
{
"code": "BM-PAY-DAILY",
"created_at": "2026-10-11T07:30:04Z",
"created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
"file_id": "3KFmKhlYELQZGQLw3WZICSjn9u0",
"format": "csv",
"id": "3KLkM29zNWk8XOFM5SgHNsugPV9",
"ledger_totals": "{\"MZN\":125000000}",
"period_end": "2026-10-11T00:00:00Z",
"period_start": "2026-10-10T00:00:00Z",
"reconciled": true,
"rows": 6,
"sha256": "15e20481e573d63f895dc0d253eb93cf04e61bc4a07b151c3d57e7564f5b7c5c",
"signature": "MEUCIQDx3v…",
"signing_key_id": "reg-signing-2026",
"status": "generated",
"template_id": "3KIaajCSOfi46dTWod2FEl5eSMW",
"template_version": 2,
"totals": "{\"MZN\":125000000}",
"trigger": "on_demand"
}
GET /v1/admin/safeguarding/accounts
Safeguarding accounts
Bank accounts holding client funds (PSP-10.4). Requires reconciliation:read.
Responses
- 200
{data, total}.
Request
curl -X GET "$KULPAY_URL/v1/admin/safeguarding/accounts" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"account": "000800001234567890157",
"bank": "BCI",
"created_at": "2026-02-10T09:00:00Z",
"created_by": "operator:7f3c9a2e-5b1d-4e8a-9c6f-2d4b8e1a0c37",
"currency": "MZN",
"id": "3KoNx2PvLSxvXFC8nNUvulGJMKI"
},
{
"account": "0001234567001",
"bank": "Millennium bim",
"created_at": "2026-02-10T09:05:00Z",
"created_by": "operator:7f3c9a2e-5b1d-4e8a-9c6f-2d4b8e1a0c37",
"currency": "MZN",
"id": "3K5ADXkHFgUdUiBg7e5rmAarOGp"
}
],
"total": 2
}
POST /v1/admin/safeguarding/accounts
Register a safeguarding account
Its statements (received under a PSP statement profile) feed the daily check. Records config.safeguarding_account.added. Requires accounts:write.
Request body application/json required
| Field | Type | Description |
|---|---|---|
account required | string | |
bank | string | |
currency required | string |
Responses
- 201 Registered.
- 400 Invalid or already registered.
invalid_request
Request
curl -X POST "$KULPAY_URL/v1/admin/safeguarding/accounts" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"account": "2003014567",
"bank": "string",
"currency": "MZN"
}'
201 Response application/json
{
"account": "0001234567001",
"bank": "Millennium bim",
"created_at": "2026-10-11T10:40:00Z",
"created_by": "3KIhzpLdNupTuLgu8bwZOQspar2",
"currency": "MZN",
"id": "3K5ADXkHFgUdUiBg7e5rmAarOGp"
}
400 Error · invalid_request application/problem+json
{
"code": "invalid_request",
"detail": "the account is already registered",
"error": {
"code": "invalid_request",
"message": "the account is already registered"
},
"status": 400,
"title": "Bad Request",
"type": "about:blank"
}
GET /v1/admin/safeguarding/checks
Daily safeguarding checks
Each business day, once the safeguarding accounts' statements are in (or by 18:00 UTC), their closing balances are compared per currency with the ledger's client-funds liability (merchant available, pending and payable balances, reserves and suspense; live, collecting model). compliant is false on a shortfall of any amount or a missing statement (safeguarding.check.shortfall, critical, compliance emailed the same day); else safeguarding.check.passed. Requires reconciliation:read.
Responses
- 200
{data: [check], total}.
Request
curl -X GET "$KULPAY_URL/v1/admin/safeguarding/checks" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"compliant": true,
"created_at": "2026-10-10T07:00:03Z",
"csv_file_id": "3KJ5EjFon9jvDDSDRLTImADqQFz",
"csv_signature": "Mf3x9…",
"currency": "MZN",
"date": "2026-10-10",
"difference_minor": 5833175,
"held_minor": 1875043250,
"id": "3KHvA6Ef1pziaGSF0wXf4SileRg",
"owed_minor": 1869210075,
"pdf_file_id": "3KEKunzWl0OtBzYAL3qgpjqv49A",
"pdf_signature": "q0Lk2…",
"signing_key_id": "e11a3b4af7f9e28bb44e6f53a8bd4952",
"statements": "[{\"account\":\"000800001234567890157\",\"statement_file_id\":\"3KdhOrsZV8VzDwRsx2nNjQAi6kX\",\"closing_minor\":1250031250,\"received_at\":\"2026-10-10T06:02:11Z\"},{\"account\":\"0001234567001\",\"statement_file_id\":\"3KDLNgOU39cCXqtNJmVrtCTVqzG\",\"closing_minor\":625012000,\"received_at\":\"2026-10-10T06:15:40Z\"}]"
}
],
"total": 1
}
POST /v1/admin/safeguarding/checks/run
Run today's check now
For every currency not yet checked today, with the statements at hand. Requires admin:write.
Responses
- 200 The checks made.
Request
curl -X POST "$KULPAY_URL/v1/admin/safeguarding/checks/run" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"compliant": false,
"created_at": "2026-10-11T10:45:12Z",
"csv_file_id": "3KJ5EjFon9jvDDSDRLTImADqQFz",
"csv_signature": "Mf3x9…",
"currency": "MZN",
"date": "2026-10-11",
"difference_minor": -621410830,
"held_minor": 1250031250,
"id": "3KO5Pm9L3HOitFWVTYnNF2gUdVD",
"owed_minor": 1871442080,
"pdf_file_id": "3KEKunzWl0OtBzYAL3qgpjqv49A",
"pdf_signature": "q0Lk2…",
"signing_key_id": "e11a3b4af7f9e28bb44e6f53a8bd4952",
"statements": "[{\"account\":\"000800001234567890157\",\"statement_file_id\":\"3KdhOrsZV8VzDwRsx2nNjQAi6kX\",\"closing_minor\":1250031250,\"received_at\":\"2026-10-11T06:02:11Z\"},{\"account\":\"0001234567001\",\"closing_minor\":0,\"missing\":true}]"
}
],
"total": 1
}
GET /v1/admin/safeguarding/checks/{id}/report
Signed safeguarding report
The CSV (default) or PDF report, write-once in the region's object storage, with X-Signature (base64 Ed25519 over the body) and X-Signing-Key-ID. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | |
format | query | string | One of: csv, pdf |
Responses
- 200 The report.
- 404 No such report.
not_found
Request
curl -X GET "$KULPAY_URL/v1/admin/safeguarding/checks/{id}/report" \
-H "Authorization: Bearer $KULPAY_KEY"
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such report",
"error": {
"code": "not_found",
"message": "no such report"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
GET /v1/admin/settlement-arrangements/{id}
A settlement arrangement version
One version, the destination masked. Requires accounts:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200 The version.
- 404 The named resource was not found.
not_found
Request
curl -X GET "$KULPAY_URL/v1/admin/settlement-arrangements/{id}" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"activated_at": "2026-10-11T10:40:17Z",
"approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
"approved_by": "operator:rui",
"available_after_days": 1,
"created_at": "2026-10-11T09:12:03Z",
"created_by": "operator:ana",
"currency": "MZN",
"custody": "managed_pass_through",
"destination_ref": "*******************0001",
"id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"minimum_settlement_minor": 100000,
"platform_fee_bps": 0,
"platform_fee_fixed_minor": 0,
"provider_cost_bps": 100,
"provider_cost_fixed_minor": 0,
"reason": "Adenda de cobrança gerida assinada",
"refund_fee_policy": "retain",
"refund_offset_policy": "offset_future_settlements",
"reserve_bps": 500,
"reserve_hold_days": 7,
"schedule": "daily",
"status": "active",
"version": 2
}
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"
}
POST /v1/admin/settlements/run
Build the settlement batches due now
Runs the hourly settlement job at once. Collecting batches are paid out only when settlement.collecting_auto_payout is on (then ready ones left from while it was off are paid too); off (the default), they stop at ready. Requires admin:write.
Responses
- 200
{built, paid}.
Request
curl -X POST "$KULPAY_URL/v1/admin/settlements/run" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"built": 3,
"paid": 2
}
POST /v1/admin/settlements/{id}/confirmations
Record a ready batch settled outside the gateway
For a ready managed pass-through batch, or a ready collecting batch (collecting auto-payout off, settlement.collecting_auto_payout; G14): asks a second person (POST /v1/admin/approvals/{id}/decide, kind settlement.batch.confirm) to record that it was settled outside the gateway, with the external reference as evidence. One request per batch waits at a time. On approval the batch is settled. Managed: its payables are settled and the ledger moves the net from the payable out of provider custody. Collecting: a settlement_external ledger entry takes the net out of the merchant's available balance against client funds, as the batch's payout would have. The gateway moves no money. Requires reconciliation:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
external_reference required | string | |
reason required | string |
Responses
- 202 Waiting for approval: the approval request.
- 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 - 409 The resource is not in a state that allows this operation (e.g. capturing an already-voided charge).
invalid_state
Request
curl -X POST "$KULPAY_URL/v1/admin/settlements/{id}/confirmations" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_reference": "MPESA-B2C-7781",
"reason": "Extracto bancário mostra a transferência"
}'
202 Response application/json
{
"created_at": "2026-10-11T09:12:03Z",
"id": "3LBvH5tW8yRc3mQn1kZpLd6sXf2",
"kind": "settlement.batch.confirm",
"payload": "{\"batch_id\":\"3LBvC7rM2tXn5qWe8kJdLs4vHg0\",\"external_reference\":\"MPESA-B2C-7781\"}",
"prepared_by": "operator:ana",
"reason": "Extracto bancário mostra a transferência",
"status": "pending",
"subject_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
"tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
}
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"
}
409 Error · invalid_state application/problem+json
{
"code": "invalid_state",
"detail": "The payment is already captured.",
"error": {
"code": "invalid_state",
"message": "The payment is already captured."
},
"status": 409,
"title": "Conflict",
"type": "about:blank"
}
GET /v1/ledger/accounts
Chart of accounts and balances
The account's double-entry ledger (PSP-10.1): one chart per mode and currency (client_funds, merchant_payable, merchant_available, merchant_pending, fee_revenue, psp_receivable, psp_fees_payable, reserves, suspense, fx_adjustment, rounding_adjustment), each with running debits_minor, credits_minor and balance_minor on its normal side. Running totals are verified nightly against the journal lines. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
mode | query | string | One of: live, test |
Responses
- 200
{data: [ledger account], total}.
Request
curl -X GET "$KULPAY_URL/v1/ledger/accounts" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"balance_minor": 3331524,
"code": "merchant_available",
"created_at": "2026-02-10T09:00:00Z",
"credits_minor": 4581524,
"currency": "MZN",
"debits_minor": 1250000,
"id": "3K1QnKiD3oyu03HYXKLfzzdD7ta",
"mode": "live",
"name": "Merchant available balance",
"normal": "credit",
"tenant_id": "3Kg6UFQm6mLoKocUxwifotF73Ob",
"updated_at": "2026-10-11T10:12:44Z"
},
{
"balance_minor": 4331524,
"code": "client_funds",
"created_at": "2026-02-10T09:00:00Z",
"credits_minor": 5543501,
"currency": "MZN",
"debits_minor": 9875025,
"id": "3K2OzaB1FMyyVeCj2X0ub9kvk9Q",
"mode": "live",
"name": "Client funds (safeguarding)",
"normal": "debit",
"tenant_id": "3Kg6UFQm6mLoKocUxwifotF73Ob",
"updated_at": "2026-10-11T10:12:44Z"
}
],
"total": 2
}
GET /v1/ledger/journals
Journals
Balanced journals, newest first. Each links to its transaction (transaction_id) and to the ledger.journal.posted event recorded with it (event_id); a reversing journal names the one it corrects (reverses_id). Journals are append-only. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
mode | query | string | One of: live, test |
transaction_id | query | string | |
limit | query | integer |
Responses
- 200
{data: [journal], total}.
Request
curl -X GET "$KULPAY_URL/v1/ledger/journals" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"currency": "MZN",
"entry_id": "3KUYR4NCw1DPG7QSHJjWA2YuLE0",
"event_id": "3KUYQ1MBv7COF4PRGIiVz8XtKD9",
"id": "3KUYP7LAu4BNE1OQFHhUy5WsJC8",
"kind": "fee",
"line_count": 2,
"mode": "live",
"posted_at": "2026-10-10T14:32:09Z",
"tenant_id": "3KUYB1xNg7nZq4ACrTtGk8IeVo4",
"total_minor": 3127,
"transaction_id": "3KUYH1DSm7tFw4GIxZzMq8OkBu0"
},
{
"currency": "MZN",
"entry_id": "3KUYU4QFz1GSJ7TVKMmZD2BxOH3",
"event_id": "3KUYT1PEy7FRI4SUJLlYC8AwNG2",
"id": "3KUYS7ODx4EQH1RTIKkXB5ZvMF1",
"kind": "charge",
"line_count": 2,
"mode": "live",
"posted_at": "2026-10-10T14:32:09Z",
"tenant_id": "3KUYB1xNg7nZq4ACrTtGk8IeVo4",
"total_minor": 125075,
"transaction_id": "3KUYH1DSm7tFw4GIxZzMq8OkBu0"
}
],
"total": 2
}
GET /v1/ledger/journals/{id}
A journal with its lines
A journal and its lines: each line debits or credits one ledger account; debits equal credits. Requires reconciliation:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200 The journal with
lines. - 404 No such journal.
not_found
Request
curl -X GET "$KULPAY_URL/v1/ledger/journals/{id}" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"currency": "MZN",
"entry_id": "3KUYU4QFz1GSJ7TVKMmZD2BxOH3",
"event_id": "3KUYT1PEy7FRI4SUJLlYC8AwNG2",
"id": "3KUYS7ODx4EQH1RTIKkXB5ZvMF1",
"kind": "charge",
"line_count": 2,
"lines": [
{
"account_code": "psp_receivable",
"created_at": "2026-10-10T14:32:09Z",
"credit_minor": 0,
"currency": "MZN",
"debit_minor": 125075,
"id": "3KUYV7RGA4HTK1UWLNnAE5CyPI4",
"journal_id": "3KUYS7ODx4EQH1RTIKkXB5ZvMF1",
"ledger_account_id": "3KUYW1SHB7IUL4VXMOoBF8DzQJ5"
},
{
"account_code": "merchant_pending",
"created_at": "2026-10-10T14:32:09Z",
"credit_minor": 125075,
"currency": "MZN",
"debit_minor": 0,
"id": "3KUYX4TIC1JVM7WYNPpCG2EAaK6",
"journal_id": "3KUYS7ODx4EQH1RTIKkXB5ZvMF1",
"ledger_account_id": "3KUYY7UJD4KWN1XZOQqDH5FBbL7"
}
],
"mode": "live",
"posted_at": "2026-10-10T14:32:09Z",
"tenant_id": "3KUYB1xNg7nZq4ACrTtGk8IeVo4",
"total_minor": 125075,
"transaction_id": "3KUYH1DSm7tFw4GIxZzMq8OkBu0"
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such journal",
"error": {
"code": "not_found",
"message": "no such journal"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
GET /v1/payables
The caller's merchant payables
What the merchant is owed for each managed pass-through payment: gross less the merchant-borne platform fee (7% in the managed-collection example), the provider cost and the reserve, plus adjustments, in minor units. States: provisional, available, reserved_for_settlement (in a ready batch), settled, disputed, reversed. A payment can be succeeded while its payable is still pending settlement. Requires settlements:read. Plan phase 8: managed pass-through money is never in the available balance and nothing here executes a payout.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
mode | query | string | live or test; the key's mode when omitted. One of: live, test |
currency | query | string | ISO 4217 code. |
state | query | string | A payable state. One of: provisional, available, reserved_for_settlement, settled, disputed, reversed |
batch_id | query | string | A settlement batch. |
Responses
- 200
{data, total, summary}: the payables, oldest first, and per state the count and net. - 401 Missing or invalid bearer token.
unauthorized
Request
curl -X GET "$KULPAY_URL/v1/payables" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"adjustments_minor": 0,
"arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"arrangement_version": 2,
"available_at": "2026-10-12T08:15:00Z",
"batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
"created_at": "2026-10-11T08:15:00Z",
"currency": "MZN",
"fee_refunded_minor": 0,
"gross_minor": 12345,
"id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
"mode": "live",
"net_minor": 10741,
"payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
"platform_fee_minor": 864,
"provider_cost_minor": 123,
"refunded_minor": 0,
"reserve_minor": 617,
"reserve_release_at": "2026-10-18T08:15:00Z",
"state": "reserved_for_settlement",
"updated_at": "2026-10-12T09:00:00Z"
}
],
"summary": [
{
"count": 0,
"net_minor": 0,
"state": "provisional"
},
{
"count": 0,
"net_minor": 0,
"state": "available"
},
{
"count": 1,
"net_minor": 10741,
"state": "reserved_for_settlement"
},
{
"count": 0,
"net_minor": 0,
"state": "settled"
},
{
"count": 0,
"net_minor": 0,
"state": "disputed"
},
{
"count": 0,
"net_minor": 0,
"state": "reversed"
}
],
"total": 1
}
401 Error · unauthorized application/problem+json
{
"code": "unauthorized",
"detail": "Missing or invalid API key.",
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
},
"status": 401,
"title": "Unauthorized",
"type": "about:blank"
}
GET /v1/payables/{id}
A merchant payable
One of the caller's payables with its adjustments and receivables. Requires settlements:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200 The payable with
adjustments(refunds, fee refunds, reversals before it was batched) andreceivables(what the merchant owes back for it). - 404 The named resource was not found.
not_found
Request
curl -X GET "$KULPAY_URL/v1/payables/{id}" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"adjustments": [],
"adjustments_minor": 0,
"arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"arrangement_version": 2,
"available_at": "2026-10-12T08:15:00Z",
"batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
"created_at": "2026-10-11T08:15:00Z",
"currency": "MZN",
"fee_refunded_minor": 0,
"gross_minor": 12345,
"id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
"mode": "live",
"net_minor": 10741,
"payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
"platform_fee_minor": 864,
"provider_cost_minor": 123,
"receivables": [],
"refunded_minor": 0,
"reserve_minor": 617,
"reserve_release_at": "2026-10-18T08:15:00Z",
"state": "reserved_for_settlement",
"updated_at": "2026-10-12T09:00:00Z"
}
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/receivables
What the caller owes back
Managed pass-through money the merchant owes: a refund or reversal after its payable was batched or settled, or the retained fee and cost of a payment refunded beyond its payable. Never a negative balance: offset_future_settlements receivables are deducted from later batches, invoice ones wait for a KulOffice service invoice. Requires settlements:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
mode | query | string | live or test; the key's mode when omitted. One of: live, test |
currency | query | string | ISO 4217 code. |
status | query | string | open or offset. One of: open, offset |
Responses
- 200
{data, total, outstanding_minor}. - 401 Missing or invalid bearer token.
unauthorized
Request
curl -X GET "$KULPAY_URL/v1/receivables" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"amount_minor": 37200,
"created_at": "2026-10-13T14:02:11Z",
"currency": "MZN",
"id": "3LBvE2pK9wYq1nTb6mRcFs3xZd7",
"kind": "refund_after_settlement",
"mode": "live",
"outstanding_minor": 37200,
"payable_id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
"payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
"policy": "offset_future_settlements",
"source_id": "3LBvF8nQ4rTz2kWm7pXcHd5sYe1",
"status": "open",
"updated_at": "2026-10-13T14:02:11Z"
}
],
"outstanding_minor": 37200,
"total": 1
}
401 Error · unauthorized application/problem+json
{
"code": "unauthorized",
"detail": "Missing or invalid API key.",
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
},
"status": 401,
"title": "Unauthorized",
"type": "about:blank"
}
GET /v1/settlement-arrangements
The caller's settlement arrangements
The versions in force (one per currency) and those waiting for a checker. The destination is shown masked. Requires settlements:read. Plan phase 8: managed pass-through money is never in the available balance and nothing here executes a payout.
Responses
- 200
{account_id, active, pending}. - 401 Missing or invalid bearer token.
unauthorized
Request
curl -X GET "$KULPAY_URL/v1/settlement-arrangements" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"active": [
{
"account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
"activated_at": "2026-10-11T10:40:17Z",
"approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
"approved_by": "operator:rui",
"available_after_days": 1,
"created_at": "2026-10-11T09:12:03Z",
"created_by": "operator:ana",
"currency": "MZN",
"custody": "managed_pass_through",
"destination_ref": "*******************0001",
"id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
"minimum_settlement_minor": 100000,
"platform_fee_bps": 0,
"platform_fee_fixed_minor": 0,
"provider_cost_bps": 100,
"provider_cost_fixed_minor": 0,
"reason": "Adenda de cobrança gerida assinada",
"refund_fee_policy": "retain",
"refund_offset_policy": "offset_future_settlements",
"reserve_bps": 500,
"reserve_hold_days": 7,
"schedule": "daily",
"status": "active",
"version": 2
}
],
"pending": []
}
401 Error · unauthorized application/problem+json
{
"code": "unauthorized",
"detail": "Missing or invalid API key.",
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
},
"status": 401,
"title": "Unauthorized",
"type": "about:blank"
}
GET /v1/settlements
Settlement batches
The account's settlement batches, newest first (PSP-10.3). At each cycle (T+n, settlement_cycle_days), the ledger movements up to the cut-off that no batch holds form a batch per mode, currency and funds-flow model: gross (charges), fees, refunds and adjustments (reversals, chargebacks, manual payouts, carried amounts), and net. Collecting model: with the deployment's collecting auto-payout off (the default, settlement.collecting_auto_payout; G14) a batch with a net to pay stops at ready and becomes settled once an operator's record of its external settlement is approved; with it on, the net is paid to the settlement destination (paid). Either way it is carried into the next batch when it cannot be paid (failed, carried_forward). Direct model: recorded, what the PSP should pay and the fees owed. Webhooks: settlement.batch.created, settlement.batch.paid, settlement.batch.failed. Requires settlements:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
mode | query | string | One of: live, test |
Responses
- 200
{data: [batch], total}.
Request
curl -X GET "$KULPAY_URL/v1/settlements" \ -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"data": [
{
"account_id": "3Ky2WEM0zMfI2pD5i2iBhSEQcB2",
"adjustments": "0",
"created_at": "2026-10-10T02:00:05Z",
"currency": "MZN",
"cycle_date": "2026-10-10",
"cycle_days": 1,
"fees": "2811.76",
"funds_flow": "collecting",
"gross": "187450.75",
"id": "3Kvzd6L2Pcs92PdmK3dW77IpqDS",
"line_count": 143,
"mode": "live",
"net": "183388.24",
"paid_at": "2026-10-10T10:02:44Z",
"payout_id": "3KZt4Xw9QmRb2NcV7pLs5YhEdJf",
"period_end": "2026-10-09T00:00:00Z",
"refunds": "1250.75",
"status": "paid",
"updated_at": "2026-10-10T10:02:44Z"
},
{
"account_id": "3Ky2WEM0zMfI2pD5i2iBhSEQcB2",
"adjustments": "0",
"created_at": "2026-10-10T02:00:05Z",
"currency": "MZN",
"cycle_date": "2026-10-10",
"cycle_days": 1,
"fees": "1443.01",
"funds_flow": "direct",
"gross": "96200.5",
"id": "3KugoejTSN0O7wRyLqNPHNR10Qx",
"line_count": 61,
"mode": "live",
"net": "94757.49",
"period_end": "2026-10-09T00:00:00Z",
"refunds": "0",
"status": "recorded",
"updated_at": "2026-10-10T06:05:12Z",
"verification": "verified"
}
],
"total": 2
}
GET /v1/settlements/{id}
A settlement batch with its lines
Every line: the ledger entry (or carried batch) it settles, its kind and bucket (gross, fees, refunds, adjustments, reserves), the transaction and the amount. A batch never changes once built. Requires settlements:read. A managed pass-through batch (funds_flow managed, plan phase 8) is built from the merchant's settlement arrangement: its lines are each payable's gross, platform fee, provider cost and reserve, its adjustments, reserves released and receivables offset; it stops at ready (no payout) and becomes settled when an external settlement is recorded.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Responses
- 200
{batch, lines}. - 404 No such settlement.
not_found
Request
curl -X GET "$KULPAY_URL/v1/settlements/{id}" \
-H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
"batch": {
"account_id": "3Kg6UFQm6mLoKocUxwifotF73Ob",
"adjustments": "0",
"created_at": "2026-10-10T06:00:02Z",
"currency": "MZN",
"cycle_date": "2026-10-10",
"cycle_days": 1,
"fees": "-85.01",
"funds_flow": "collecting",
"gross": "3400.25",
"id": "3KBsl1R1SZjAG7lN66md59RgpOh",
"line_count": 2,
"mode": "live",
"net": "3315.24",
"paid_at": "2026-10-10T08:30:15Z",
"payout_id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
"period_end": "2026-10-09T00:00:00Z",
"refunds": "0",
"status": "paid",
"updated_at": "2026-10-10T08:30:15Z"
},
"lines": [
{
"amount": "3400.25",
"batch_id": "3KBsl1R1SZjAG7lN66md59RgpOh",
"bucket": "gross",
"entry_id": "3KiPDnFE3Q9HIZp1ALEj9Q5ELK4",
"id": "3K3eILCnMrCFupEJAC2iMgj0ZSg",
"kind": "charge",
"occurred_at": "2026-10-08T14:21:09Z",
"ref": "entry:3KiPDnFE3Q9HIZp1ALEj9Q5ELK4",
"transaction_id": "3KjLY7BV6lTzyJvGXr3mZ6e2cY8"
},
{
"amount": "-85.01",
"batch_id": "3KBsl1R1SZjAG7lN66md59RgpOh",
"bucket": "fees",
"entry_id": "3KWAICWHTUyv0N9xGLHm286dPvJ",
"id": "3Kwx2xzxmJwbFWTphgpnTTgwcS8",
"kind": "fee",
"occurred_at": "2026-10-08T14:21:09Z",
"ref": "entry:3KWAICWHTUyv0N9xGLHm286dPvJ",
"transaction_id": "3KjLY7BV6lTzyJvGXr3mZ6e2cY8"
}
]
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such settlement",
"error": {
"code": "not_found",
"message": "no such settlement"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
GET /v1/settlements/{id}/report
Settlement report (CSV or PDF)
The merchant's settlement report: the totals and every line. Requires settlements:read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | |
format | query | string | One of: csv, pdf |
Responses
- 200 text/csv or application/pdf.
- 404 No such settlement.
not_found
Request
curl -X GET "$KULPAY_URL/v1/settlements/{id}/report" \
-H "Authorization: Bearer $KULPAY_KEY"
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "no such settlement",
"error": {
"code": "not_found",
"message": "no such settlement"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}