Balance and settlements

Every paid payment increases your pending balance. On a recurring schedule, Hexolus sweeps pending funds into your available balance and (when above the disbursement floor) creates a settlement record that an operator pays out to your registered bank account.

This page covers:

All endpoints require bearer authentication.


GET /v1/balance

Returns the current ledger balance for the authenticated client.

Request

GET /v1/balance
Authorization: Bearer hxk_*
Query param Default Notes
currency IDR Reserved for future multi-currency support. IDR is the only value.

Response — 200 OK

{
  "client_id": "client_01HZX...",
  "currency": "IDR",
  "available_minor": 1245700,
  "pending_minor": 73300,
  "updated_at": "2026-05-30T14:32:45Z"
}
Field Meaning
pending_minor Funds from succeeded payments that have not yet rolled into a settlement window. Per-payment value = client_net_minor.
available_minor Funds rolled into the settlement window and awaiting (or completed) disbursement to your bank account.
updated_at Last ledger mutation timestamp. null for a client that has never received a payment.

A brand-new client (no payments yet) gets zeros back rather than a 404 — the ledger row is materialised lazily on the first successful payment, so its absence is the normal initial state.

Common errors

HTTP code When
401 auth Missing / invalid bearer.
500 internal_error Database read failed.

List settlements

A settlement is one batched payout. It aggregates many succeeded payments into a single bank transfer.

Request

GET /v1/settlements?page=1&per_page=25
Authorization: Bearer hxk_*
Query param Default Notes
page 1 1-indexed.
per_page 25 Clamped to [1, 100].

Settlements are returned newest-first (by period_end DESC).

Response — 200 OK

{
  "data": [
    {
      "id": "stl_01HZX9K3M7N2BZQ7A8RVT5P3X4",
      "client_id": "client_01HZX...",
      "period_start": "2026-05-28T00:00:00Z",
      "period_end": "2026-05-29T00:00:00Z",
      "gross_minor": 1500000,
      "xendit_fees_minor": 9450,
      "hexolus_markup_minor": 1500,
      "net_minor": 1489050,
      "currency": "IDR",
      "payment_count": 12,
      "status": "manual_paid",
      "triggered_by": "auto",
      "bank_name": "BCA",
      "bank_account_no": "1234567890",
      "bank_account_name": "PT Reseller Anda",
      "notes": "Settlement for 2026-05-28",
      "settled_at": "2026-05-29T03:14:22Z",
      "created_at": "2026-05-29T02:00:08Z"
    }
  ],
  "pagination": { "page": 1, "per_page": 25 }
}

Field reference

Field Type Notes
id string Settlement UUID.
period_start / _end timestamp The window over which succeeded payments were swept. Half-open: includes start, excludes end.
gross_minor int64 Sum of notional_minor for in-window payments.
xendit_fees_minor int64 Sum of actual Xendit fees deducted on those payments.
hexolus_markup_minor int64 Sum of Hexolus 0.1% markup on those payments.
net_minor int64 The actual disbursed amount: gross − xendit_fees − hexolus_markup.
payment_count int How many payments rolled into this settlement.
status string enum recorded, manual_paid, or failed. See lifecycle.
triggered_by string enum manual (operator clicked "Settle Now") or auto (cron sweep).
bank_* string | null Snapshot of your registered bank details at settlement time. null until populated.
notes string | null Operator-set note. Visible on payment confirmations.
settled_at timestamp | null When the operator marked the disbursement as manual_paid. null while recorded.
created_at timestamp When the settlement record was first created.

Common errors

HTTP code When
401 auth Missing / invalid bearer.
500 internal_error Database read failed.

Fetch a settlement

Request

GET /v1/settlements/{id}
Authorization: Bearer hxk_*

Response — 200 OK

Returns the same DTO shape as one element of the data array in List settlements.

Common errors

HTTP code When
401 auth Missing / invalid bearer.
404 not_found {id} doesn't exist OR belongs to a different client. Indistinguishable by design.

Settlement lifecycle

                  ┌─────────────────────────────┐
                  │                             │
        ┌────►  recorded  ──────────────► manual_paid    (operator confirms bank transfer)
auto/   │         │
manual  │         │
trigger │         └─────────────────────────► failed     (bank transfer rejected; operator records reason)
status Meaning
recorded The settlement row exists and available_minor was decremented, but the bank transfer is pending operator action.
manual_paid An operator has confirmed the bank transfer succeeded. settled_at is set.
failed The bank transfer was rejected (wrong account, etc). The operator notes the cause. Funds remain on the row but are not re-credited automatically — contact support.

Only recorded → manual_paid and recorded → failed are valid transitions. Once a settlement reaches a terminal status, it cannot be reopened.


Schedule and floor

Schedule

Hexolus runs the automated settlement cron daily at 02:00 UTC (09:00 WIB) by default. The job sweeps every client's pending_minor for payments that meet the T+1 age rule: a payment must have been in succeeded state for at least 24 hours before it is eligible for the next settlement window. This buffer lets us absorb late-arriving Xendit fee adjustments without re-issuing settlement records.

A typical timeline for a payment received on Monday morning:

Mon 10:00 WIB   customer pays                       (status: succeeded; pending_minor += net)
Tue 02:00 UTC   payment has aged > 24h              (skipped; would only be ~16h old)
Wed 02:00 UTC   payment included in settlement      (recorded; available_minor += net)
Wed daytime     operator initiates bank transfer    (manual_paid; settled_at set)

Minimum amount floor

Settlements are only created when the aggregated net_minor for a client exceeds the per-tenant floor (default Rp 10.000). This prevents sub-rounding noise and respects bank-side minimum disbursement amounts. Funds below the floor remain in pending_minor and are re-evaluated on the next cron tick. Operators may also manually trigger a settlement above this floor from the admin UI; the same Rp 10.000 floor applies and is validated server-side.


See also