Settlements¶
When, by which route and with what fee the money people pay reaches your account, and how to read and reconcile a
settlement. About 10 minutes to read. You need a live API key with the settlements:read scope (a test key sees no settlements).
How the money moves¶
There are two routes; which one applies depends on whether the operator has switched settlements on for your app.
| Settlements off (every app today) | Settlements on | |
|---|---|---|
| Capture | the money goes straight to the app's payout account (for a sub-merchant's intent, to its approved payout account) | the money goes to the app's clearing account (the app's own stream and each sub-merchant's are separate) |
| Fee | none | fixed per payment at capture, moved to the platform by the settlement |
| Refunds | out of the payout account | out of the clearing account |
| To the payout account | on every capture | by a settlement on the schedule: the net amount (payments − refunds − fees) |
GET /settlements |
empty | the settlements |
Today no app in production has settlements on — every capture goes straight to the payout account, with no fee. Before switching settlements on for an app the operator sets up a payment rule (Fees) and a payout account (Payout accounts). A payment captured before the switch never enters a settlement (it keeps no fee, and its refunds come out of the payout account). If the app's settlements are switched off again, new captures go straight to the payout account again and whatever is left in clearing keeps being settled.
Schedule and cutoff¶
The schedule is part of the operator's payment rule (per app):
schedule |
When |
|---|---|
daily |
every day at settle_time |
weekdays |
Monday – Friday at settle_time (no public-holiday calendar yet) |
weekly:mon … weekly:sun |
once a week, on that day |
manual |
never by itself — the operator runs it by hand |
settle_timeis Ulaanbaatar time (HH:MM);delay_days(T+N) is 0–14.- Cutoff — how far a run reaches: with T+N, the payments captured up to the end of the day N days before the run's (Ulaanbaatar) day (for example T+1 at 10:00 → everything up to yesterday 24:00). T+0 covers everything up to the moment of the run.
- With no rule, the default: no fee, weekdays at 10:00, T+1.
- The settlement job runs every 15 minutes — a settlement may come up to 15 minutes after its scheduled time. A run that was missed is not lost: the next run's cutoff covers it.
- A suspended app is not settled on schedule (only by the operator, by hand).
The Settlement object¶
The body of GET /settlements/{id} and GET /settlements, and data.object of the settlement.paid webhook:
{
"id": "st_7QwErTy2UiOp4AsDf6GhJk8L",
"object": "settlement",
"status": "paid",
"cutoff": "2026-10-21T16:00:00Z",
"gross": 100000,
"refunds": 5000,
"reserve_refunds": 0,
"fee": 2000,
"net": 93000,
"holdback": 0,
"released": 0,
"recovered": 0,
"paid": 93000,
"intent_count": 3,
"destination_masked": "******0042",
"holdback_release_at": null,
"livemode": true,
"created_at": "2026-10-22T08:58:00Z",
"paid_at": "2026-10-22T08:59:00Z",
"refund_count": 1,
"fee_assessed": 2000,
"fee_returned": 0,
"fee_carried_in": 0,
"fee_carried_out": 0,
"clearing_carried_in": 0,
"clearing_carried_out": 0,
"failure_code": null,
"sub_merchant": null
}
| Field | Meaning |
|---|---|
cutoff |
the payments captured before this instant are in it |
gross · intent_count |
the sum and number of the payments in it |
refunds · refund_count |
the refunds deducted (of payments in this or an earlier settlement) |
fee |
the fee this settlement took (fee_assessed − fee_returned + fee_carried_in − fee_carried_out); negative when it gave back more than it took |
fee_assessed · fee_returned |
the payments' fees · the fees given back with refunds |
fee_carried_in · fee_carried_out |
fee carried in from the previous settlement · carried on to the next (when there was nothing to take it from) |
clearing_carried_in · clearing_carried_out |
refunds that left clearing after earlier settlements beyond what that settlement held — deducted here · carried on to the next |
recovered |
clearing_carried_in − clearing_carried_out |
net |
gross − refunds − fee |
paid |
what was paid to the payout account, net − holdback + released − reserve_refunds − recovered (≥ 0; may be 0) |
holdback · released · reserve_refunds · holdback_release_at |
always 0 / null — there is no holdback |
destination_masked |
the payout account, last digits only |
sub_merchant |
the sub-merchant's id for its stream, null for the app's own |
failure_code |
when failed: insufficient_funds, destination_unavailable, limit_exceeded, idempotency_conflict, rejected |
livemode |
always true |
Fee shares and carries are whole ₮ and every identity is checked on the server — you need not recompute paid, but
you may use these identities in your reconciliation.
Statuses¶
status |
Meaning |
|---|---|
pending |
computed and frozen, being posted to the ledger (retried with the same key when the answer is unknown) |
paid |
paid was moved to the payout account (paid_at); settlement.paid is sent |
failed |
the ledger refused (failure_code) — the operator retries it with the same lines or voids it; a voided settlement's payments go into a later one |
One stream of an app has at most one open (pending / failed) settlement — the next one waits for it. A voided
(void) settlement is not shown to the app's backend; the console shows it. A settlement that moves nothing (all of
it fee, or carried) is paid at once and sends settlement.paid with paid: 0.
Example¶
Rule: 2 % fee (fee_bps: 200), refund_fee: false, daily 10:00, T+1.
| Date (UB) | What happened | In clearing | To the payout account |
|---|---|---|---|
| 10-21 | 3 payments: 10 000, 25 000, 65 000 (fees 200 + 500 + 1 300) | 100 000 | 0 |
| 10-21 | 5 000 refunded from the 3rd payment | 95 000 | 0 |
| 10-22 00:00 | a 50 000 payment (fee 1 000) — exactly at the cutoff, so it goes into the next settlement | 145 000 | 0 |
| 10-22 10:00 | Settlement (cutoff 10-22 00:00): gross 100 000, refunds 5 000, fee 2 000 → paid 93 000 |
50 000 | 93 000 |
| 10-22 | 10 000 refunded from the 1st payment (already settled; clearing holds 50 000) | 40 000 | 93 000 |
| 10-22 | a 45 000 refund of the 3rd payment requested → clearing holds 40 000 < 45 000 → refund.failed (settlement_shortfall) |
40 000 | 93 000 |
| 10-23 10:00 | Settlement: gross 50 000, refunds 10 000, fee 1 000 → paid 39 000 |
0 | 132 000 |
Check: people paid 150 000 and got 15 000 back; the platform's fee is 3 000; you received 132 000 = 150 000 − 15 000 − 3 000.
Shortfall. A refund of a payment that was already settled to your account comes out of what clearing holds now
(later payments). If clearing does not hold enough, the refund is refused with settlement_shortfall — receivables
(advancing the refund and deducting it from later settlements) are not built yet; the operator sees a list of such
refunds.
Carry. When refunds that left clearing after a settlement exceed the gross of the next one, the excess is
deducted from the one after (clearing_carried_*); a fee with no money to take it from (a full refund with
refund_fee: false, for example) moves to the next settlement (fee_carried_*).
Reading — the API¶
The app's backend (Basic with an API key holding settlements:read, or a person's token with the payments consent), live only — a test credential always
gets an empty list.
curl -sS "https://geregeapp.mn/api/v1/miniapp/settlements?status=paid&limit=20" \
-u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET"
# → {"items": [Settlement, …], "next_cursor": "st_…" | null, "has_more": true | false}
| Parameter | Meaning |
|---|---|
status |
pending · paid · failed; anything else → 400 invalid_status |
cursor |
next_cursor of the previous page; invalid → 400 invalid_cursor |
limit |
1 – 100, default 20; anything else → 400 invalid_limit |
Newest first. GET /settlements/{id} is the Settlement + intents[] (id, amount, fee, reference,
confirmed_at) + refund_items[] (id, intent_id, amount, fee_returned, source) + truncated — with more
than 1000 lines, the first 1000 and truncated: true. Tell sub-merchants' settlements apart by sub_merchant.
An installation without a settlement store answers 503 settlements_unavailable.
The developer console shows the same settlements per app (voided ones included). Every money row (JSON or CSV) is in
Statements (also settlements:read).
Webhook¶
settlement.paid — when a settlement becomes paid, always livemode: true, data.object = Settlement. paid may
be 0. Event reference.