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:
GET /v1/balanceGET /v1/settlementsGET /v1/settlements/{id}- Settlement lifecycle
- Schedule and floor
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.