KulPayAPI Live

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

NameInTypeDescription
id requiredpathstring

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

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
funds_flow requiredstring
One of: , collecting, direct
reason requiredstring

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

NameInTypeDescription
id requiredpathstring

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

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
beneficiary_namestring The bank account holder; required with method `bank`.
cycle_days requiredinteger
destinationstring
methodstring
reason requiredstring

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

NameInTypeDescription
scopequerystring
One of: psp, merchant
subject_idquerystring

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

FieldTypeDescription
reason requiredstring
rules requiredobject[]
rules[].currency requiredstring
rules[].fixedstring
rules[].max_amountstring Exclusive.
rules[].max_feestring
rules[].methodstring Empty or * for any.
rules[].min_amountstring Inclusive.
rules[].min_feestring
rules[].percentstring
rules[].tiersobject[]
rules[].tiers[].fixedstring
rules[].tiers[].monthly_volume_from requiredstring
rules[].tiers[].percentstring
rules[].typestring Transaction type (SALE, ...); empty or * for any.
scope requiredstring
One of: psp, merchant
subject_id requiredstring

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

FieldTypeDescription
account_id requiredstring
amount requiredstring
currency requiredstring
methodstring
modestring
psp_idstring
typestring

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

NameInTypeDescription
id requiredpathstring

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

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
reason requiredstring

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

NameInTypeDescription
template_idquerystring

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

NameInTypeDescription
id requiredpathstring

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

NameInTypeDescription
id requiredpathstring

Request body multipart/form-data required

FieldTypeDescription
file requiredstring (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

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
channel requiredstring
One of: portal, xml_upload, email, sftp, hand_delivery
reason requiredstring
referencestring

Responses

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

FieldTypeDescription
activeboolean
code requiredstring
fields requiredstring[] Grouped dimensions and measures (payments: count, amount, fees; ledger: count, debit, credit).
format requiredstring
One of: csv, xlsx, xml
group_bystring[] payments: date, psp_id, method, currency; ledger: date, tenant_id, currency, account_code, kind.
name requiredstring
period requiredstring
One of: daily, monthly, on_demand
source requiredstring
One of: payments, ledger

Responses

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

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
activeboolean
code requiredstring
fields requiredstring[] Grouped dimensions and measures (payments: count, amount, fees; ledger: count, debit, credit).
format requiredstring
One of: csv, xlsx, xml
group_bystring[] payments: date, psp_id, method, currency; ledger: date, tenant_id, currency, account_code, kind.
name requiredstring
period requiredstring
One of: daily, monthly, on_demand
source requiredstring
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

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
from requiredstring (date)
to requiredstring (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

FieldTypeDescription
account requiredstring
bankstring
currency requiredstring

Responses

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

NameInTypeDescription
id requiredpathstring
formatquerystring
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

NameInTypeDescription
modequerystring
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

NameInTypeDescription
modequerystring
One of: live, test
transaction_idquerystring
limitqueryinteger

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

NameInTypeDescription
id requiredpathstring

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

NameInTypeDescription
modequerystring
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

NameInTypeDescription
id requiredpathstring

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

NameInTypeDescription
id requiredpathstring
formatquerystring
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"
}