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"
}
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, beneficiary_name, 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: destination is the account's NIB (21 digits, check digits verified; spaces are ignored) and beneficiary_name its holder, both required. A bank destination is paid by the bank rail: the payout goes in the next batch to the sponsor bank, the settlement batch stays paying until the bank debits it, then paid, with bank_reference (the EndToEndId on the bank's statement). The per-beneficiary daily limit of the rail does not apply to settlements. Requires accounts:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
beneficiary_name | string | The bank account holder; required with method `bank`. |
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 '{
"beneficiary_name": "Loja Ana, Lda",
"cycle_days": 1,
"destination": "000300010000001234585",
"method": "bank",
"reason": "the merchant'\''s bank letter of 2026-10-10"
}'
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/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:checker@example.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:checker@example.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:checker@example.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/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}/receipt
Keep the regulator's receipt
The regulator's receipt of a submitted report, as the multipart field file (10 MB at most): kept write-once beside the signed file, once (regulatory_report.receipt_attached). Requires reconciliation:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body multipart/form-data required
| Field | Type | Description |
|---|---|---|
file required | string (binary) |
Responses
- 200 The run, with its receipt.
- 409 Not submitted yet, or the receipt is already kept (
not_submittable).
Request
curl -X POST "$KULPAY_URL/v1/admin/regulatory/runs/{id}/receipt" \
-H "Authorization: Bearer $KULPAY_KEY" \
-F "file=@./document.pdf"
200 Response application/json
{
"approved_by": "key_ops_rui",
"channel": "portal",
"code": "BM-PAY-DAILY",
"id": "3KLkM29zNWk8XOFM5SgHNsugPV9",
"receipt_file_id": "3KZk1Pq3Rs5Tu7Vw9Xy1Za3Bc5D",
"retain_until": "2036-10-12T08:05:00Z",
"status": "submitted",
"submission_reference": "BDM-2026-10-0042"
}
409 Error · not_submittable application/problem+json
{
"code": "not_submittable",
"detail": "the receipt is already kept",
"error": {
"code": "not_submittable",
"message": "the receipt is already kept"
},
"status": 409,
"title": "Conflict",
"type": "about:blank"
}
POST /v1/admin/regulatory/runs/{id}/submit
Submit a report (maker)
Submitting a reconciled report to Banco de Moçambique takes two people (F3-7): this prepares a regulatory.submit approval with the channel (portal, xml_upload, email, sftp, hand_delivery) and the reference the submission got; a different person approves it (POST /v1/admin/approvals/{id}/decide) and the run becomes submitted (regulatory_report.submitted), kept until retain_until. A blocked run, one already submitted or one waiting is 409 not_submittable. Requires reconciliation:write.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string |
Request body application/json required
| Field | Type | Description |
|---|---|---|
channel required | string | One of: portal, xml_upload, email, sftp, hand_delivery |
reason required | string | |
reference | string |
Responses
- 202 Prepared; a second person must approve it.
- 404 No such run.
not_found - 409
not_submittable.
Request
curl -X POST "$KULPAY_URL/v1/admin/regulatory/runs/{id}/submit" \
-H "Authorization: Bearer $KULPAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "portal",
"reason": "monthly payments report",
"reference": "BDM-2026-10-0042"
}'
202 Response application/json
{
"approval": {
"created_at": "2026-10-12T08:00:00Z",
"id": "3KZk0Ab2Cd4Ef6Gh8Ij0Kl2Mn4O",
"kind": "regulatory.submit",
"prepared_by": "key_ops_ana",
"reason": "submit BM-PAY-DAILY for 2026-10-10 to 2026-10-11 by portal: monthly payments report",
"status": "pending",
"subject_id": "3KLkM29zNWk8XOFM5SgHNsugPV9",
"tenant_id": "platform"
},
"message": "submission prepared; a second person must approve it (POST /v1/admin/approvals/{id}/decide)"
}
404 Error · not_found application/problem+json
{
"code": "not_found",
"detail": "not found",
"error": {
"code": "not_found",
"message": "not found"
},
"status": 404,
"title": "Not Found",
"type": "about:blank"
}
409 Error · not_submittable application/problem+json
{
"code": "not_submittable",
"detail": "blocked: MZN: report 17500, ledger 15000",
"error": {
"code": "not_submittable",
"message": "blocked: MZN: report 17500, ledger 15000"
},
"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"
}
POST /v1/admin/settlements/run
Build the settlement batches due now
Runs the hourly settlement job at once. 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
}
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/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: the net is paid to the settlement destination (paid; paying while a bank transfer waits for the bank), or carried into the next batch when it cannot be (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. Cash deposits credited on their confirmation (the PSP's credit_on confirmation) are settled only once the statement shows them (IO-6.1): one waiting for its statement line, or whose line differs or never came while its exception is open, is held back and goes in the batch after it is reconciled or resolved. The report lists each deposit by its deposit ID (transaction_id).
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), the transaction and the amount. A batch never changes once built. Requires settlements:read.
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"
}
KulPay