API 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"
}

POST /v1/admin/accounts/{id}/managed-settlements

Build a managed settlement batch now

Builds the merchant's managed pass-through batch for today under its newest managed arrangement, whatever the schedule (the hourly job builds it on scheduled days): provisional payables past their delay become available; the batch takes every available payable with immutable source lines (gross, platform fee, provider cost, reserve and each adjustment), reserves due for release and, under offset_future_settlements, open receivables (never below zero). The batch stops at ready: the gateway does not execute it and POST /v1/payouts is unchanged; executing settlement through the arrangement's destination is the next step, pending the owner's decision. One batch per merchant, mode and currency a day. Requires admin:write.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
currency requiredstring
modestring
One of: live, test

Responses

  • 201 The ready batch (GET /v1/settlements/{id} lists its lines).
  • 400 The request body or query failed validation. The response body lists each field-level issue. invalid_request
  • 409 nothing_to_settle: no available payable or released reserve, or the net is below the arrangement's minimum.

Request

curl -X POST "$KULPAY_URL/v1/admin/accounts/{id}/managed-settlements" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "currency": "MZN",
  "mode": "live"
}'
201 Response application/json
{
  "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "adjustments": "0",
  "arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
  "arrangement_version": 2,
  "created_at": "2026-10-12T09:00:00Z",
  "currency": "MZN",
  "cycle_date": "2026-10-12",
  "cycle_days": 1,
  "fees": "-9.87",
  "funds_flow": "managed",
  "gross": "123.45",
  "id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
  "line_count": 4,
  "mode": "live",
  "net": "107.41",
  "period_end": "2026-10-12T09:00:00Z",
  "refunds": "0",
  "reserves": "-6.17",
  "status": "ready",
  "updated_at": "2026-10-12T09:00:00Z"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "error": {
    "code": "invalid_request",
    "errors": [
      {
        "field": "money.amount",
        "message": "must be a positive decimal with at most 2 places"
      }
    ],
    "message": "The request body failed validation."
  },
  "errors": [
    {
      "field": "money.amount",
      "message": "must be a positive decimal with at most 2 places"
    }
  ],
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
409 Error · nothing_to_settle application/problem+json
{
  "code": "nothing_to_settle",
  "detail": "No available payable or released reserve reaches the arrangement's minimum.",
  "error": {
    "code": "nothing_to_settle",
    "message": "No available payable or released reserve reaches the arrangement's minimum."
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

GET /v1/admin/accounts/{id}/payables

A merchant's payables

As GET /v1/payables, for any merchant. Requires reconciliation:read.

Parameters

NameInTypeDescription
id requiredpathstring
modequerystring live or test; the key's mode when omitted.
One of: live, test
currencyquerystring ISO 4217 code.
statequerystring A payable state.
One of: provisional, available, reserved_for_settlement, settled, disputed, reversed
batch_idquerystring A settlement batch.

Responses

  • 200 {data, total, summary}: the payables, oldest first, and per state the count and net.

Request

curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/payables" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "adjustments_minor": 0,
      "arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
      "arrangement_version": 2,
      "available_at": "2026-10-12T08:15:00Z",
      "batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
      "created_at": "2026-10-11T08:15:00Z",
      "currency": "MZN",
      "fee_refunded_minor": 0,
      "gross_minor": 12345,
      "id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
      "mode": "live",
      "net_minor": 10741,
      "payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
      "platform_fee_minor": 864,
      "provider_cost_minor": 123,
      "refunded_minor": 0,
      "reserve_minor": 617,
      "reserve_release_at": "2026-10-18T08:15:00Z",
      "state": "reserved_for_settlement",
      "updated_at": "2026-10-12T09:00:00Z"
    }
  ],
  "summary": [
    {
      "count": 0,
      "net_minor": 0,
      "state": "provisional"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "available"
    },
    {
      "count": 1,
      "net_minor": 10741,
      "state": "reserved_for_settlement"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "settled"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "disputed"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "reversed"
    }
  ],
  "total": 1
}

GET /v1/admin/accounts/{id}/receivables

What a merchant owes back

As GET /v1/receivables, for any merchant. Requires reconciliation:read.

Parameters

NameInTypeDescription
id requiredpathstring
modequerystring live or test; the key's mode when omitted.
One of: live, test
currencyquerystring ISO 4217 code.
statusquerystring open or offset.
One of: open, offset

Responses

  • 200 {data, total, outstanding_minor}.

Request

curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/receivables" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "amount_minor": 37200,
      "created_at": "2026-10-13T14:02:11Z",
      "currency": "MZN",
      "id": "3LBvE2pK9wYq1nTb6mRcFs3xZd7",
      "kind": "refund_after_settlement",
      "mode": "live",
      "outstanding_minor": 37200,
      "payable_id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
      "payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
      "policy": "offset_future_settlements",
      "source_id": "3LBvF8nQ4rTz2kWm7pXcHd5sYe1",
      "status": "open",
      "updated_at": "2026-10-13T14:02:11Z"
    }
  ],
  "outstanding_minor": 37200,
  "total": 1
}

GET /v1/admin/accounts/{id}/settlement

A merchant's settlement cycle and destination

The cycle (T+n days), the method and the destination (masked), and a pending change. Requires accounts:read.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 200 {account_id, cycle_days, method, destination, pending}.
  • 404 No such account. not_found

Request

curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/settlement" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "cycle_days": 1,
  "destination": "********4567",
  "method": "mpesa",
  "pending": {
    "created_at": "2026-10-10T15:05:44Z",
    "id": "3KMuWRw3JaLEf2SolKp9TuBxIGy",
    "kind": "settlement.destination",
    "prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
    "reason": "O comerciante pediu liquidação a cada 2 dias",
    "status": "pending",
    "subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
    "tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
  }
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "no such account",
  "error": {
    "code": "not_found",
    "message": "no such account"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

PUT /v1/admin/accounts/{id}/settlement

Request a settlement change

Where a merchant's money is paid is a fraud target, so a change of the cycle or destination waits for a second person (POST /v1/admin/approvals/{id}/decide). method is a wallet (mpesa, emola: destination is the MSISDN) or bank (the account number or IBAN). Requires accounts:write.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
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 '{
  "cycle_days": 0,
  "destination": "string",
  "method": "string",
  "reason": "Pagamento de energia, outubro"
}'
202 Response application/json
{
  "approval": {
    "created_at": "2026-10-10T15:05:44Z",
    "id": "3KMuWRw3JaLEf2SolKp9TuBxIGy",
    "kind": "settlement.destination",
    "payload": "{\"cycle_days\":2,\"method\":\"mpesa\",\"destination\":\"+258841234567\"}",
    "prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
    "reason": "O comerciante pediu liquidação a cada 2 dias",
    "status": "pending",
    "subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
    "tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
  }
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "cycle_days must be 0 to 30",
  "error": {
    "code": "invalid_request",
    "message": "cycle_days must be 0 to 30"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "no such account",
  "error": {
    "code": "not_found",
    "message": "no such account"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

GET /v1/admin/accounts/{id}/settlement-arrangements

A merchant's settlement arrangement versions

Every version, newest first; currency narrows to one. Requires accounts:read.

Parameters

NameInTypeDescription
id requiredpathstring
currencyquerystring ISO 4217 code.

Responses

  • 200 {data, total}.

Request

curl -X GET "$KULPAY_URL/v1/admin/accounts/{id}/settlement-arrangements" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "activated_at": "2026-10-11T10:40:17Z",
      "approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
      "approved_by": "operator:rui",
      "available_after_days": 1,
      "created_at": "2026-10-11T09:12:03Z",
      "created_by": "operator:ana",
      "currency": "MZN",
      "custody": "managed_pass_through",
      "destination_ref": "*******************0001",
      "id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
      "minimum_settlement_minor": 100000,
      "platform_fee_bps": 0,
      "platform_fee_fixed_minor": 0,
      "provider_cost_bps": 100,
      "provider_cost_fixed_minor": 0,
      "reason": "Adenda de cobrança gerida assinada",
      "refund_fee_policy": "retain",
      "refund_offset_policy": "offset_future_settlements",
      "reserve_bps": 500,
      "reserve_hold_days": 7,
      "schedule": "daily",
      "status": "active",
      "version": 2
    }
  ],
  "total": 1
}

POST /v1/admin/accounts/{id}/settlement-arrangements

Propose a settlement arrangement version

A new version of the merchant's arrangement in a currency: custody, the approved destination's identifier, schedule, availability delay, minimum, reserve, merchant-borne platform fee and provider cost (basis points of the gross plus a fixed amount, each rounded half up), the refund fee policy and the refund-offset policy. It waits for a second person (POST /v1/admin/approvals/{id}/decide, kind settlement_arrangement.activate); once active it applies to new payments only, each payment recording the version it was routed under. managed_pass_through makes a collecting payment managed (a merchant payable); merchant_direct makes it direct. KulPay-balance payments stay internal. Requires accounts:write.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
available_after_daysinteger
currency requiredstring
custody requiredstring
One of: managed_pass_through, merchant_direct
destination_refstring The identifier of a destination approved in KulOffice's register (4 to 64 letters, digits, `_:.-`), never an account number or MSISDN. Required for managed_pass_through; not allowed for merchant_direct.
minimum_settlement_minorinteger
platform_fee_bpsinteger No longer used (IG-29, owner decision 2026-10-12): the platform fee is the one KulOffice captured, sent as each payment's fee instruction. A new version must leave it 0.
platform_fee_fixed_minorinteger No longer used (IG-29, owner decision 2026-10-12): the platform fee is the one KulOffice captured, sent as each payment's fee instruction. A new version must leave it 0.
provider_cost_bpsinteger
provider_cost_fixed_minorinteger
reason requiredstring
refund_fee_policystring
One of: retain, proportional
refund_offset_policystring
One of: offset_future_settlements, invoice
reserve_bpsinteger
reserve_hold_daysinteger
schedulestring
One of: daily, weekly, manual
schedule_weekdayinteger

Responses

  • 202 Waiting for approval: {arrangement, approval}.
  • 400 The request body or query failed validation. The response body lists each field-level issue. invalid_request
  • 404 The named resource was not found. not_found

Request

curl -X POST "$KULPAY_URL/v1/admin/accounts/{id}/settlement-arrangements" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "available_after_days": 1,
  "currency": "MZN",
  "custody": "managed_pass_through",
  "destination_ref": "dst_mpesa_merchant_0001",
  "minimum_settlement_minor": 100000,
  "provider_cost_bps": 100,
  "reason": "Adenda de cobrança gerida assinada",
  "refund_fee_policy": "retain",
  "refund_offset_policy": "offset_future_settlements",
  "reserve_bps": 500,
  "reserve_hold_days": 7,
  "schedule": "daily"
}'
202 Response application/json
{
  "approval": {
    "created_at": "2026-10-11T09:12:03Z",
    "id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
    "kind": "settlement_arrangement.activate",
    "payload": "{\"arrangement_id\":\"3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz\",\"currency\":\"MZN\",\"custody\":\"managed_pass_through\",\"version\":2}",
    "prepared_by": "operator:ana",
    "reason": "Adenda de cobrança gerida assinada",
    "status": "pending",
    "subject_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
    "tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
  },
  "arrangement": {
    "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
    "approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
    "available_after_days": 1,
    "created_at": "2026-10-11T09:12:03Z",
    "created_by": "operator:ana",
    "currency": "MZN",
    "custody": "managed_pass_through",
    "destination_ref": "*******************0001",
    "id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
    "minimum_settlement_minor": 100000,
    "platform_fee_bps": 0,
    "platform_fee_fixed_minor": 0,
    "provider_cost_bps": 100,
    "provider_cost_fixed_minor": 0,
    "reason": "Adenda de cobrança gerida assinada",
    "refund_fee_policy": "retain",
    "refund_offset_policy": "offset_future_settlements",
    "reserve_bps": 500,
    "reserve_hold_days": 7,
    "schedule": "daily",
    "status": "pending",
    "version": 2
  }
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "error": {
    "code": "invalid_request",
    "errors": [
      {
        "field": "money.amount",
        "message": "must be a positive decimal with at most 2 places"
      }
    ],
    "message": "The request body failed validation."
  },
  "errors": [
    {
      "field": "money.amount",
      "message": "must be a positive decimal with at most 2 places"
    }
  ],
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

GET /v1/admin/fee-schedules

Fee schedule versions

Versions of PSP and merchant fee schedules, newest first (PSP-10.5). Requires accounts:read.

Parameters

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:checkerexample.co.mz",
      "created_at": "2026-02-03T10:00:00Z",
      "created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
      "document": "{\"rules\":[{\"type\":\"SALE\",\"method\":\"mpesa\",\"currency\":\"MZN\",\"fixed\":\"5\",\"percent\":\"1.5\",\"min_fee\":\"10\",\"max_fee\":\"500\"}]}",
      "id": "3KVJAYtz9ILvHWq4UNOkbO8klRW",
      "reason": "Tarifa negociada para 2026",
      "scope": "merchant",
      "status": "active",
      "subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "version": 2
    },
    {
      "activated_at": "2026-01-12T09:10:41Z",
      "approval_id": "3Kh8Qx4H3yHpjhVVX645vFcRXuU",
      "approved_by": "operator:checkerexample.co.mz",
      "created_at": "2026-01-12T08:30:00Z",
      "created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
      "document": "{\"rules\":[{\"currency\":\"MZN\",\"percent\":\"2\"}]}",
      "id": "3KTWhTkMF6ror2SF88hNezhwVi8",
      "reason": "Tabela padrão do PSP",
      "scope": "psp",
      "status": "active",
      "subject_id": "3KtGTPT0AQaKTIWS8Xv6tRyJwCs",
      "tenant_id": "3K75aZx53yJUW0o5JRyLpwMufMq",
      "version": 1
    }
  ],
  "total": 2
}

POST /v1/admin/fee-schedules

Propose a fee schedule version

An ordered list of rules for a PSP or a merchant (PSP-10.5); the first rule matching the transaction type, method, currency and amount band prices it: fixed, percent or both, bounded by min_fee and max_fee, with tiers by the merchant's monthly volume replacing the fixed and percentage parts. Fees are worked out at posting time in decimal arithmetic, rounded with the currency's rule; the merchant's schedule applies first, then its PSP's, then the platform default. The version waits for a second person (POST /v1/admin/approvals/{id}/decide); events config.fee_schedule.version_created and config.fee_schedule.activated. Requires accounts:write.

Request body application/json required

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:checkerexample.co.mz",
  "created_at": "2026-02-03T10:00:00Z",
  "created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
  "document": "{\"rules\":[{\"type\":\"SALE\",\"method\":\"mpesa\",\"currency\":\"MZN\",\"fixed\":\"5\",\"percent\":\"1.5\",\"min_fee\":\"10\",\"max_fee\":\"500\"}]}",
  "id": "3KVJAYtz9ILvHWq4UNOkbO8klRW",
  "reason": "Tarifa negociada para 2026",
  "scope": "merchant",
  "status": "active",
  "subject_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "version": 2
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "no such fee schedule",
  "error": {
    "code": "not_found",
    "message": "no such fee schedule"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

POST /v1/admin/ledger/journals/{id}/reverse

Correct a journal by reversing it

Posts the mirror image of a journal, referencing it (reverses_id), with the reason as its memo; journals are never edited or deleted. A journal is reversed at most once and a reversal is not itself reversed. The available balance moves with it. Records ledger.journal.posted. Requires admin:write.

Parameters

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/payables/{id}

A merchant payable

Any merchant's payable with its adjustments and receivables. Requires reconciliation:read.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 200 The payable with adjustments (refunds, fee refunds, reversals before it was batched) and receivables (what the merchant owes back for it).
  • 404 The named resource was not found. not_found

Request

curl -X GET "$KULPAY_URL/v1/admin/payables/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "adjustments": [],
  "adjustments_minor": 0,
  "arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
  "arrangement_version": 2,
  "available_at": "2026-10-12T08:15:00Z",
  "batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
  "created_at": "2026-10-11T08:15:00Z",
  "currency": "MZN",
  "fee_refunded_minor": 0,
  "gross_minor": 12345,
  "id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
  "mode": "live",
  "net_minor": 10741,
  "payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
  "platform_fee_minor": 864,
  "provider_cost_minor": 123,
  "receivables": [],
  "refunded_minor": 0,
  "reserve_minor": 617,
  "reserve_release_at": "2026-10-18T08:15:00Z",
  "state": "reserved_for_settlement",
  "updated_at": "2026-10-12T09:00:00Z"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

GET /v1/admin/regulatory/runs

Generated regulatory reports

Requires reconciliation:read.

Parameters

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}/submit

Mark a report submitted

Only a reconciled report (regulatory_report.submitted). Requires reconciliation:write.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

Request

curl -X POST "$KULPAY_URL/v1/admin/regulatory/runs/{id}/submit" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "code": "BM-PAY-DAILY",
  "created_at": "2026-10-11T01:00:03Z",
  "file_id": "3KFmKhlYELQZGQLw3WZICSjn9u0",
  "format": "csv",
  "id": "3KLkM29zNWk8XOFM5SgHNsugPV9",
  "ledger_totals": "{\"MZN\":125000000}",
  "period_end": "2026-10-11T00:00:00Z",
  "period_start": "2026-10-10T00:00:00Z",
  "reconciled": true,
  "rows": 6,
  "sha256": "15e20481e573d63f895dc0d253eb93cf04e61bc4a07b151c3d57e7564f5b7c5c",
  "signature": "MEUCIQDx3v…",
  "signing_key_id": "reg-signing-2026",
  "status": "submitted",
  "submitted_at": "2026-10-11T09:02:47Z",
  "submitted_by": "3KWTqydMTGykEIsWuGEP683gqgc",
  "template_id": "3KIaajCSOfi46dTWod2FEl5eSMW",
  "template_version": 2,
  "totals": "{\"MZN\":125000000}",
  "trigger": "scheduled"
}
409 Error · not_submittable application/problem+json
{
  "code": "not_submittable",
  "detail": "blocked: totals do not reconcile to the ledger: MZN: report 98750000, ledger 98625000",
  "error": {
    "code": "not_submittable",
    "message": "blocked: totals do not reconcile to the ledger: MZN: report 98750000, ledger 98625000"
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

GET /v1/admin/regulatory/templates

Regulatory report templates

Requires reconciliation:read.

Responses

  • 200 {data, total}.

Request

curl -X GET "$KULPAY_URL/v1/admin/regulatory/templates" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "active": true,
      "code": "BM-PAY-DAILY",
      "created_at": "2026-03-02T09:15:00Z",
      "created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
      "fields": "[\"date\",\"psp_id\",\"method\",\"count\",\"amount\",\"fees\"]",
      "format": "csv",
      "group_by": "[\"date\",\"psp_id\",\"method\"]",
      "id": "3KIaajCSOfi46dTWod2FEl5eSMW",
      "name": "Pagamentos diários por PSP e método",
      "period": "daily",
      "source": "payments",
      "updated_at": "2026-06-18T14:40:12Z",
      "version": 2
    },
    {
      "active": true,
      "code": "BM-LEDGER-MONTHLY",
      "created_at": "2026-04-01T08:00:00Z",
      "created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
      "fields": "[\"account_code\",\"currency\",\"count\",\"debit\",\"credit\"]",
      "format": "xlsx",
      "group_by": "[\"account_code\",\"currency\"]",
      "id": "3KpIXlMpv6U6uHUiJY7dFShREqZ",
      "name": "Movimentos do razão por conta",
      "period": "monthly",
      "source": "ledger",
      "updated_at": "2026-04-01T08:00:00Z",
      "version": 1
    }
  ],
  "total": 2
}

POST /v1/admin/regulatory/templates

Create a regulatory report template

A configurable report (PSP-10.6): source, grouping, fields, period and format. The exact list Banco de Moçambique requires is to be confirmed with compliance; reports are configured, not coded. Records config.regulatory_template.saved. Requires accounts:write.

Request body application/json required

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"
}

GET /v1/admin/settlement-arrangements/{id}

A settlement arrangement version

One version, the destination masked. Requires accounts:read.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

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

Request

curl -X GET "$KULPAY_URL/v1/admin/settlement-arrangements/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "activated_at": "2026-10-11T10:40:17Z",
  "approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
  "approved_by": "operator:rui",
  "available_after_days": 1,
  "created_at": "2026-10-11T09:12:03Z",
  "created_by": "operator:ana",
  "currency": "MZN",
  "custody": "managed_pass_through",
  "destination_ref": "*******************0001",
  "id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
  "minimum_settlement_minor": 100000,
  "platform_fee_bps": 0,
  "platform_fee_fixed_minor": 0,
  "provider_cost_bps": 100,
  "provider_cost_fixed_minor": 0,
  "reason": "Adenda de cobrança gerida assinada",
  "refund_fee_policy": "retain",
  "refund_offset_policy": "offset_future_settlements",
  "reserve_bps": 500,
  "reserve_hold_days": 7,
  "schedule": "daily",
  "status": "active",
  "version": 2
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

POST /v1/admin/settlements/run

Build the settlement batches due now

Runs the hourly settlement job at once. Collecting batches are paid out only when settlement.collecting_auto_payout is on (then ready ones left from while it was off are paid too); off (the default), they stop at ready. Requires admin:write.

Responses

  • 200 {built, paid}.

Request

curl -X POST "$KULPAY_URL/v1/admin/settlements/run" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "built": 3,
  "paid": 2
}

POST /v1/admin/settlements/{id}/confirmations

Record a ready batch settled outside the gateway

For a ready managed pass-through batch, or a ready collecting batch (collecting auto-payout off, settlement.collecting_auto_payout; G14): asks a second person (POST /v1/admin/approvals/{id}/decide, kind settlement.batch.confirm) to record that it was settled outside the gateway, with the external reference as evidence. One request per batch waits at a time. On approval the batch is settled. Managed: its payables are settled and the ledger moves the net from the payable out of provider custody. Collecting: a settlement_external ledger entry takes the net out of the merchant's available balance against client funds, as the batch's payout would have. The gateway moves no money. Requires reconciliation:write.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
external_reference requiredstring
reason requiredstring

Responses

  • 202 Waiting for approval: the approval request.
  • 400 The request body or query failed validation. The response body lists each field-level issue. invalid_request
  • 404 The named resource was not found. not_found
  • 409 The resource is not in a state that allows this operation (e.g. capturing an already-voided charge). invalid_state

Request

curl -X POST "$KULPAY_URL/v1/admin/settlements/{id}/confirmations" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "external_reference": "MPESA-B2C-7781",
  "reason": "Extracto bancário mostra a transferência"
}'
202 Response application/json
{
  "created_at": "2026-10-11T09:12:03Z",
  "id": "3LBvH5tW8yRc3mQn1kZpLd6sXf2",
  "kind": "settlement.batch.confirm",
  "payload": "{\"batch_id\":\"3LBvC7rM2tXn5qWe8kJdLs4vHg0\",\"external_reference\":\"MPESA-B2C-7781\"}",
  "prepared_by": "operator:ana",
  "reason": "Extracto bancário mostra a transferência",
  "status": "pending",
  "subject_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
  "tenant_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "error": {
    "code": "invalid_request",
    "errors": [
      {
        "field": "money.amount",
        "message": "must be a positive decimal with at most 2 places"
      }
    ],
    "message": "The request body failed validation."
  },
  "errors": [
    {
      "field": "money.amount",
      "message": "must be a positive decimal with at most 2 places"
    }
  ],
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}
409 Error · invalid_state application/problem+json
{
  "code": "invalid_state",
  "detail": "The payment is already captured.",
  "error": {
    "code": "invalid_state",
    "message": "The payment is already captured."
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

GET /v1/ledger/accounts

Chart of accounts and balances

The account's double-entry ledger (PSP-10.1): one chart per mode and currency (client_funds, merchant_payable, merchant_available, merchant_pending, fee_revenue, psp_receivable, psp_fees_payable, reserves, suspense, fx_adjustment, rounding_adjustment), each with running debits_minor, credits_minor and balance_minor on its normal side. Running totals are verified nightly against the journal lines. Requires reconciliation:read.

Parameters

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/payables

The caller's merchant payables

What the merchant is owed for each managed pass-through payment: gross less the merchant-borne platform fee (7% in the managed-collection example), the provider cost and the reserve, plus adjustments, in minor units. States: provisional, available, reserved_for_settlement (in a ready batch), settled, disputed, reversed. A payment can be succeeded while its payable is still pending settlement. Requires settlements:read. Plan phase 8: managed pass-through money is never in the available balance and nothing here executes a payout.

Parameters

NameInTypeDescription
modequerystring live or test; the key's mode when omitted.
One of: live, test
currencyquerystring ISO 4217 code.
statequerystring A payable state.
One of: provisional, available, reserved_for_settlement, settled, disputed, reversed
batch_idquerystring A settlement batch.

Responses

  • 200 {data, total, summary}: the payables, oldest first, and per state the count and net.
  • 401 Missing or invalid bearer token. unauthorized

Request

curl -X GET "$KULPAY_URL/v1/payables" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "adjustments_minor": 0,
      "arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
      "arrangement_version": 2,
      "available_at": "2026-10-12T08:15:00Z",
      "batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
      "created_at": "2026-10-11T08:15:00Z",
      "currency": "MZN",
      "fee_refunded_minor": 0,
      "gross_minor": 12345,
      "id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
      "mode": "live",
      "net_minor": 10741,
      "payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
      "platform_fee_minor": 864,
      "provider_cost_minor": 123,
      "refunded_minor": 0,
      "reserve_minor": 617,
      "reserve_release_at": "2026-10-18T08:15:00Z",
      "state": "reserved_for_settlement",
      "updated_at": "2026-10-12T09:00:00Z"
    }
  ],
  "summary": [
    {
      "count": 0,
      "net_minor": 0,
      "state": "provisional"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "available"
    },
    {
      "count": 1,
      "net_minor": 10741,
      "state": "reserved_for_settlement"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "settled"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "disputed"
    },
    {
      "count": 0,
      "net_minor": 0,
      "state": "reversed"
    }
  ],
  "total": 1
}
401 Error · unauthorized application/problem+json
{
  "code": "unauthorized",
  "detail": "Missing or invalid API key.",
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key."
  },
  "status": 401,
  "title": "Unauthorized",
  "type": "about:blank"
}

GET /v1/payables/{id}

A merchant payable

One of the caller's payables with its adjustments and receivables. Requires settlements:read.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 200 The payable with adjustments (refunds, fee refunds, reversals before it was batched) and receivables (what the merchant owes back for it).
  • 404 The named resource was not found. not_found

Request

curl -X GET "$KULPAY_URL/v1/payables/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "adjustments": [],
  "adjustments_minor": 0,
  "arrangement_id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
  "arrangement_version": 2,
  "available_at": "2026-10-12T08:15:00Z",
  "batch_id": "3LBvC7rM2tXn5qWe8kJdLs4vHg0",
  "created_at": "2026-10-11T08:15:00Z",
  "currency": "MZN",
  "fee_refunded_minor": 0,
  "gross_minor": 12345,
  "id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
  "mode": "live",
  "net_minor": 10741,
  "payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
  "platform_fee_minor": 864,
  "provider_cost_minor": 123,
  "receivables": [],
  "refunded_minor": 0,
  "reserve_minor": 617,
  "reserve_release_at": "2026-10-18T08:15:00Z",
  "state": "reserved_for_settlement",
  "updated_at": "2026-10-12T09:00:00Z"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

GET /v1/receivables

What the caller owes back

Managed pass-through money the merchant owes: a refund or reversal after its payable was batched or settled, or the retained fee and cost of a payment refunded beyond its payable. Never a negative balance: offset_future_settlements receivables are deducted from later batches, invoice ones wait for a KulOffice service invoice. Requires settlements:read.

Parameters

NameInTypeDescription
modequerystring live or test; the key's mode when omitted.
One of: live, test
currencyquerystring ISO 4217 code.
statusquerystring open or offset.
One of: open, offset

Responses

  • 200 {data, total, outstanding_minor}.
  • 401 Missing or invalid bearer token. unauthorized

Request

curl -X GET "$KULPAY_URL/v1/receivables" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "amount_minor": 37200,
      "created_at": "2026-10-13T14:02:11Z",
      "currency": "MZN",
      "id": "3LBvE2pK9wYq1nTb6mRcFs3xZd7",
      "kind": "refund_after_settlement",
      "mode": "live",
      "outstanding_minor": 37200,
      "payable_id": "3LBvA1cU7mUe4pTnqR5dKx2wYb9",
      "payment_id": "3LBv9qH2sRk8wZcP0uYfTa6eNm1",
      "policy": "offset_future_settlements",
      "source_id": "3LBvF8nQ4rTz2kWm7pXcHd5sYe1",
      "status": "open",
      "updated_at": "2026-10-13T14:02:11Z"
    }
  ],
  "outstanding_minor": 37200,
  "total": 1
}
401 Error · unauthorized application/problem+json
{
  "code": "unauthorized",
  "detail": "Missing or invalid API key.",
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key."
  },
  "status": 401,
  "title": "Unauthorized",
  "type": "about:blank"
}

GET /v1/settlement-arrangements

The caller's settlement arrangements

The versions in force (one per currency) and those waiting for a checker. The destination is shown masked. Requires settlements:read. Plan phase 8: managed pass-through money is never in the available balance and nothing here executes a payout.

Responses

  • 200 {account_id, active, pending}.
  • 401 Missing or invalid bearer token. unauthorized

Request

curl -X GET "$KULPAY_URL/v1/settlement-arrangements" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
  "active": [
    {
      "account_id": "3KY8VJtMMG2V1cNjNhTgPt1mSFI",
      "activated_at": "2026-10-11T10:40:17Z",
      "approval_id": "3LBv0ZkS1yQd9mVhfN2pWc8rXo4",
      "approved_by": "operator:rui",
      "available_after_days": 1,
      "created_at": "2026-10-11T09:12:03Z",
      "created_by": "operator:ana",
      "currency": "MZN",
      "custody": "managed_pass_through",
      "destination_ref": "*******************0001",
      "id": "3LBv0Wf4mWq6bLr3Qx1cSdAq7Tz",
      "minimum_settlement_minor": 100000,
      "platform_fee_bps": 0,
      "platform_fee_fixed_minor": 0,
      "provider_cost_bps": 100,
      "provider_cost_fixed_minor": 0,
      "reason": "Adenda de cobrança gerida assinada",
      "refund_fee_policy": "retain",
      "refund_offset_policy": "offset_future_settlements",
      "reserve_bps": 500,
      "reserve_hold_days": 7,
      "schedule": "daily",
      "status": "active",
      "version": 2
    }
  ],
  "pending": []
}
401 Error · unauthorized application/problem+json
{
  "code": "unauthorized",
  "detail": "Missing or invalid API key.",
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key."
  },
  "status": 401,
  "title": "Unauthorized",
  "type": "about:blank"
}

GET /v1/settlements

Settlement batches

The account's settlement batches, newest first (PSP-10.3). At each cycle (T+n, settlement_cycle_days), the ledger movements up to the cut-off that no batch holds form a batch per mode, currency and funds-flow model: gross (charges), fees, refunds and adjustments (reversals, chargebacks, manual payouts, carried amounts), and net. Collecting model: with the deployment's collecting auto-payout off (the default, settlement.collecting_auto_payout; G14) a batch with a net to pay stops at ready and becomes settled once an operator's record of its external settlement is approved; with it on, the net is paid to the settlement destination (paid). Either way it is carried into the next batch when it cannot be paid (failed, carried_forward). Direct model: recorded, what the PSP should pay and the fees owed. Webhooks: settlement.batch.created, settlement.batch.paid, settlement.batch.failed. Requires settlements:read.

Parameters

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, reserves), the transaction and the amount. A batch never changes once built. Requires settlements:read. A managed pass-through batch (funds_flow managed, plan phase 8) is built from the merchant's settlement arrangement: its lines are each payable's gross, platform fee, provider cost and reserve, its adjustments, reserves released and receivables offset; it stops at ready (no payout) and becomes settled when an external settlement is recorded.

Parameters

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"
}