Objects¶
Use this page to look up every field, type and meaning of the objects that API answers and webhooks'
data.object carry. It is a reference — keep it open beside your code.
The payment intent has its own page: Payment intent. The rest are here.
Common rules (API conventions):
- Money is whole ₮ (
integer); the currency is alwaysMNT. - Times are RFC 3339 in UTC (
2026-10-22T09:00:00Z). - A field marked «nullable» is always present and may be
null; every other field is nevernull. - Minor versions may add fields — ignore the ones you do not know.
Refund¶
The answer of POST /payment-intents/{id}/refunds, and data.object of refund.succeeded / refund.failed.
Explained in Refunds.
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string | re_ + 24 base62 |
|
object |
string | "refund" |
|
intent_id |
string | the refunded intent (pi_…) |
|
amount |
integer | the amount refunded, ₮ | |
status |
string | pending · succeeded · failed |
|
reason |
string | the reason you gave (≤ 255), or "". Never shown in the person's notification |
|
failure_code |
string | yes | when failed: insufficient_funds · settlement_shortfall · refund_rejected (Error codes) |
livemode |
boolean | whether it is live | |
created_at |
string (time) |
{
"id": "re_2kLm9Pq4Rs6Tu8Vw0Xy1Za3B",
"object": "refund",
"intent_id": "pi_4fQ2mX8kLp0aZr7TbN3cVw9E",
"amount": 10000,
"status": "succeeded",
"reason": "supplier_failed",
"failure_code": null,
"livemode": true,
"created_at": "2026-10-22T09:00:00Z"
}
Settlement¶
GET /settlements (items[]), GET /settlements/{id}, and data.object of settlement.paid. Live only.
Explained in Settlements.
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string | st_ + 24 base62 |
|
object |
string | "settlement" |
|
status |
string | pending · paid · failed |
|
cutoff |
string (time) | the payments captured before this instant are in it | |
gross |
integer | the payments' total | |
refunds |
integer | the refunds deducted | |
reserve_refunds · holdback · released |
integer | always 0 (this platform keeps no reserve or holdback; OMAS compatibility fields) |
|
fee |
integer | the fee this settlement actually took; negative when it returned more than it took | |
net |
integer | gross − refunds − fee |
|
recovered |
integer | earlier refunds that exceeded a settlement, deducted now (clearing_carried_in − clearing_carried_out; negative = carried on to the next one) |
|
paid |
integer | what went to the payout account (may be 0) |
|
intent_count · refund_count |
integer | the number of payments and refunds in it | |
destination_masked |
string | yes | the payout account, last 4 digits (******0042) |
holdback_release_at |
string (time) | yes | always null |
livemode |
boolean | true |
|
created_at |
string (time) | ||
paid_at |
string (time) | yes | when it became paid |
fee_assessed |
integer | the fee assessed on the payments | |
fee_returned |
integer | fee given back with refunds (when the rule has refund_fee) |
|
fee_carried_in · fee_carried_out |
integer | fee brought in from the previous / carried to the next settlement | |
clearing_carried_in · clearing_carried_out |
integer | refund excess brought in from the previous / carried to the next settlement | |
failure_code |
string | yes | when failed (Error codes) |
sub_merchant |
string (UUID) | yes | the sub-merchant's id for a sub-merchant's settlement; null for the app's own |
Identities: net = gross − refunds − fee, paid = net − holdback + released − reserve_refunds − recovered.
{
"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
}
GET /settlements/{id} adds to those fields intents[] (id, amount, fee, reference (nullable),
confirmed_at (nullable)), refund_items[] (id, intent_id, amount, fee_returned, source) and truncated
— with more than 1000 lines, truncated: true and only the first 1000 come.
{
"id": "st_7QwErTy2UiOp4AsDf6GhJk8L",
"object": "settlement",
"status": "paid",
"…": "…",
"intents": [
{ "id": "pi_4fQ2mX8kLp0aZr7TbN3cVw9E", "amount": 65000, "fee": 1300, "reference": "order_8812", "confirmed_at": "2026-10-21T03:12:00Z" }
],
"refund_items": [
{ "id": "re_2kLm9Pq4Rs6Tu8Vw0Xy1Za3B", "intent_id": "pi_4fQ2mX8kLp0aZr7TbN3cVw9E", "amount": 5000, "fee_returned": 0, "source": "clearing" }
],
"truncated": false
}
Terminal¶
The answers of /terminals… (a list is {"data": [Terminal]}). Explained in Pay with Gerege App.
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string (UUID) | an intent's terminal_id |
|
object |
string | "terminal" |
|
code |
string | 10 characters the server assigns (23456789abcdefghjkmnpqrstuvwxyz); never changes |
|
url |
string | what goes on the sticker's QR and the NFC tag: https://geregeapp.mn/pay/t/<code> — carries no amount |
|
label |
string | 1–60, shown on the payment sheet before Face ID | |
location |
string | ≤ 120, or "" |
|
reference |
string | yes | your number (^[A-Za-z0-9_.:-]{1,64}$), unique within the app |
sub_merchant |
string (UUID) | yes | the terminal's sub-merchant |
status |
string | active · disabled |
|
created_at · updated_at |
string (time) |
{
"id": "4b8e6c1a-2d3f-4a5b-9c7d-0e1f2a3b4c5d",
"object": "terminal",
"code": "7k2m9x4q3p",
"url": "https://geregeapp.mn/pay/t/7k2m9x4q3p",
"label": "Машин №7",
"location": "СБД, 1-р хороо",
"reference": "vm-7",
"sub_merchant": "9a7c5e3b-1d2f-4e6a-8b0c-7d9e1f3a5b7c",
"status": "active",
"created_at": "2026-10-22T08:00:00Z",
"updated_at": "2026-10-22T08:00:00Z"
}
SubMerchant¶
The answers of /sub-merchants… (a list is {"data": [SubMerchant]}).
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string (UUID) | an intent's sub_merchant_id |
|
object |
string | "sub_merchant" |
|
code |
string | its id on your platform (^[A-Za-z0-9_.:-]{1,40}$); never changes |
|
display_name |
string | 1–80, shown to the person paying | |
legal_name |
string | ≤ 160, or "" |
|
reg_no |
string | ≤ 20, or "" |
|
status |
string | active · disabled |
|
payouts_configured |
boolean | whether its holder approved a payout account — when false, a live payment is payouts_not_configured |
|
payout_account |
string | yes | the approved account, last 4 digits (******3456) |
payout_linked_at |
string (time) | yes | when it was approved |
created_at · updated_at |
string (time) |
{
"id": "9a7c5e3b-1d2f-4e6a-8b0c-7d9e1f3a5b7c",
"object": "sub_merchant",
"code": "owner-17",
"display_name": "Батаа ХХК",
"legal_name": "Батаа ХХК",
"reg_no": "6543210",
"status": "active",
"payouts_configured": true,
"payout_account": "******3456",
"payout_linked_at": "2026-10-22T08:30:00Z",
"created_at": "2026-10-22T08:00:00Z",
"updated_at": "2026-10-22T08:30:00Z"
}
PayoutLink¶
The answer of POST /sub-merchants/{sid}/payout-link, and GET …/payout-links ({"data": [PayoutLink]}, newest
first, ≤ 50). Explained in Payout accounts.
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string (UUID) | ||
object |
string | "payout_link" |
|
sub_merchant |
string (UUID) | ||
status |
string | pending · approved · declined · canceled (replaced by a newer request) · superseded (replaced by a later approved link) · expired (unanswered for 7 days) |
|
account_masked |
string | the account asked for, last 4 digits | |
requested_at |
string (time) | ||
expires_at |
string (time) | requested_at + 7 days |
|
decided_at |
string (time) | yes | when it was approved / declined |
terms |
object | {"mn", "en"} — what the holder agrees to by approving (below) |
terms (both languages, always the same): «Энэ данс руу орсон төлбөрүүдийн буцаалтыг (төлбөрөөс хойш 90 хоногийн
дотор) энэ данснаас хасна.» / «Refunds of payments made into this account (within 90 days of each payment) are taken
back from this account.»
{
"id": "5d1f0a2b-3c4d-4e5f-8a6b-7c8d9e0f1a2b",
"object": "payout_link",
"sub_merchant": "9a7c5e3b-1d2f-4e6a-8b0c-7d9e1f3a5b7c",
"status": "pending",
"account_masked": "****************2345",
"requested_at": "2026-10-22T08:10:00Z",
"expires_at": "2026-10-29T08:10:00Z",
"decided_at": null,
"terms": {
"mn": "Энэ данс руу орсон төлбөрүүдийн буцаалтыг (төлбөрөөс хойш 90 хоногийн дотор) энэ данснаас хасна.",
"en": "Refunds of payments made into this account (within 90 days of each payment) are taken back from this account."
}
}
Event¶
The body of every webhook, and the answer of GET /events · GET /events/{id} — the very same bytes (the stored,
signed body). The type of data.object follows type — Event reference,
Events API.
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string | evt_ + 24 base62 — dedupe on it (the same as the Gerege-Event-Id header) |
|
type |
string | payment_intent.succeeded etc. |
|
created_at |
string (time) | when the event was made, to the second | |
livemode |
boolean | ||
app |
string | the app's slug | |
data.object |
object | a PaymentIntent, Refund, Settlement, AppStatus, Consent or WebhookTest |
{
"id": "evt_Vec0000000000000000000003",
"type": "refund.succeeded",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "esim",
"data": { "object": { "id": "re_2kLm9Pq4Rs6Tu8Vw0Xy1Za3B", "object": "refund", "…": "…" } }
}
AppStatus¶
data.object of app.status_changed: the app's status or review_status changed.
| Field | Type | nullable | Meaning |
|---|---|---|---|
object |
string | "app" |
|
slug |
string | ||
status · previous_status |
string | draft · active · maintenance · disabled |
|
review_status · previous_review_status |
string | draft · in_review · approved · rejected · suspended |
|
review_note |
string | yes | the operator's note on a rejection or suspension |
{
"object": "app",
"slug": "esim",
"status": "active",
"review_status": "suspended",
"previous_status": "active",
"previous_review_status": "approved",
"review_note": "Хэрэглэгчийн гомдол шалгагдаж байна"
}
Consent¶
data.object of consent.revoked: the person disconnected the app; every token for them is already revoked.
| Field | Type | nullable | Meaning |
|---|---|---|---|
object |
string | "consent" |
|
user_ref |
string | that person's pairwise name (sub / ref) |
|
revoked_at |
string (time) |
{ "object": "consent", "revoked_at": "2026-10-22T09:00:00Z", "user_ref": "u_78a08c3afb344c1b4b765aead45e1693" }
WebhookTest¶
data.object of webhook.test (the console's test send; livemode is always false).
| Field | Type | nullable | Meaning |
|---|---|---|---|
object |
string | "webhook_test" |
|
message |
string | "Gerege App webhook test" |
Public /pay views¶
GET /api/v1/miniapp/pay/{token} and GET /api/v1/miniapp/pay/t/{code} — no authentication; the /pay/… page on a
computer reads them. No personal data: no description, reference, metadata, nor who claimed it.
Field (payment) |
Type | nullable | Meaning |
|---|---|---|---|
object |
string | "payment" |
|
merchant |
object | {name, icon_url} — the app's approved name and icon |
|
terminal |
object | yes | {code, label, location} |
amount · currency |
integer · string | ||
status |
string | the intent's status | |
claimed |
boolean | whether somebody claimed it («confirm on your phone») | |
expires_at |
string (time) | ||
livemode |
boolean |
{
"object": "payment",
"merchant": { "name": "Vending.mn", "icon_url": "https://vending.mn/icon-512.png" },
"terminal": { "code": "7k2m9x4q3p", "label": "Машин №7", "location": "СБД, 1-р хороо" },
"amount": 3500,
"currency": "MNT",
"status": "requires_confirmation",
"claimed": false,
"expires_at": "2026-10-22T09:05:00Z",
"livemode": true
}
/pay/t/{code} is {"object": "terminal", "merchant", "terminal", "pending"} — pending is the terminal's open,
unclaimed live intent (the payment object above) or null. An unknown or disabled terminal, or a suspended
merchant → 404.
{
"object": "terminal",
"merchant": { "name": "Vending.mn", "icon_url": "https://vending.mn/icon-512.png" },
"terminal": { "code": "7k2m9x4q3p", "label": "Машин №7", "location": "СБД, 1-р хороо" },
"pending": null
}
Token and /me¶
POST /oauth/token → {token_type: "Bearer", access_token, expires_in, refresh_token, scope, user: {ref, sub,
verified, locale}}; GET /me → {ref, sub, verified, locale, scope, environment} plus, when granted, email
(profile.email) and phone (profile.phone, E.164). ref and sub are the same pairwise value. More:
Mini app launch & tokens.
{
"token_type": "Bearer",
"access_token": "gma_at_…",
"expires_in": 1800,
"refresh_token": "gma_rt_…",
"scope": "profile.basic payments",
"user": { "ref": "u_9f2c4e7a1b3d5f60718293a4b5c6d7e8", "sub": "u_9f2c4e7a1b3d5f60718293a4b5c6d7e8", "verified": true, "locale": "mn" }
}
WebhookEndpoint¶
The answer of /webhook-endpoints… (a live key, webhooks:write) and of the console's
/dev/apps/{id}/webhook-endpoints…. A list is {"data": [WebhookEndpoint], "next_cursor": null}; creating (201)
and rotating the secret (200) answer {"endpoint": WebhookEndpoint, "webhook_secret": "whsec_…", "notice": "…"} —
the secret is in that answer only. More: Webhooks.
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string | we_ + 24 base62 |
|
object |
string | "webhook_endpoint" |
|
environment · livemode |
string · boolean | test / live — an endpoint receives its own environment's events only |
|
url |
string | https, a public address | |
description |
string | ≤ 200, or "" |
|
event_types |
string[] | the filter, ≤ 20; [] = every event (webhook.test is not filterable — a test always arrives) |
|
status |
string | active · disabled (you turned it off) · auto_disabled (every delivery failed for 5 days) |
|
disabled_reason |
string | yes | why, when auto_disabled |
legacy |
boolean | true = endpoint №1 — moved from the app's old single webhook URL (the console's POST /dev/apps/{id}/webhook edits it) |
|
previous_secret_until |
string (time) | yes | after a rotation, until when the previous secret still co-signs (24 hours) |
failing_since |
string (time) | yes | when every delivery started failing; a successful one clears it |
last_success_at |
string (time) | yes | the last successful delivery |
deliveries_24h |
object | (lists only) | {delivered, failing} — deliveries of the last 24 hours |
created_at · updated_at |
string (time) |
{
"id": "we_8kQ2mX4pL7zR1tV9bN3cW5yA",
"object": "webhook_endpoint",
"environment": "live",
"livemode": true,
"url": "https://shop.mn/hooks/gerege",
"description": "Order server",
"event_types": ["payment_intent.succeeded", "refund.succeeded", "refund.failed"],
"status": "active",
"disabled_reason": null,
"legacy": false,
"previous_secret_until": null,
"failing_since": null,
"last_success_at": "2026-10-27T03:12:09Z",
"created_at": "2026-10-20T08:00:00Z",
"updated_at": "2026-10-20T08:00:00Z"
}
TerminalBatch¶
The answer (200) of POST /terminals/batch — each row's outcome, in the order sent. When any row is wrong (400
validation_error, terminals[i].…) the whole batch is refused and this object does not come.
| Field | Type | nullable | Meaning |
|---|---|---|---|
object |
string | "terminal_batch" |
|
data[].index |
integer | the row's position (from 0) | |
data[].status |
string | created · existing (that reference is registered — retrying is safe) · failed · skipped (not attempted — send the batch again) |
|
data[].terminal |
Terminal | yes | for created / existing |
data[].error |
object | (failed only) |
{code, error} — reference_taken, invalid_sub_merchant, … |
created · existing · failed · skipped |
integer | counts |
{
"object": "terminal_batch",
"data": [
{ "index": 0, "status": "created", "terminal": { "id": "4b8e6c1a-2d3f-4a5b-9c7d-0e1f2a3b4c5d", "object": "terminal", "code": "7k2m9x4q3p", "…": "…" } },
{ "index": 1, "status": "failed", "terminal": null, "error": { "code": "invalid_sub_merchant", "error": "…" } }
],
"created": 1, "existing": 0, "failed": 1, "skipped": 0
}
Statement¶
GET /statements?from=&to=&sub_merchant_id=&format=json|csv (settlements:read) — the app's money movements, one row
each, by Ulaanbaatar day. With format=csv the same rows as CSV (UTF-8 BOM, ,, header: date, type,
payment_intent, refund, settlement, sub_merchant, reference, gross, fee, net, balance_after, currency). More:
Statements.
| Field | Type | nullable | Meaning |
|---|---|---|---|
object |
string | "statement" |
|
app_id |
string (UUID) | ||
sub_merchant |
string (UUID) | yes | that stream when sub_merchant_id was given ("" = the app's own — null) |
environment |
string | the key's environment | |
from · to |
string | YYYY-MM-DD (both included), timezone: Asia/Ulaanbaatar |
|
currency |
string | MNT |
|
streams[] |
object | each stream (the app's own, each sub-merchant's): {sub_merchant, opening_balance, closing_balance} |
|
totals |
object | {captured, refunded, fees, settlement_paid, net} |
|
data[].date · data[].at |
string | the Ulaanbaatar date · the UTC instant | |
data[].type |
string | captured · refunded · fee · settlement_paid · fee_carried · clearing_carried |
|
data[].payment_intent · refund · settlement · sub_merchant · reference |
string | yes | the row's objects |
data[].gross · fee · net |
integer | the row's amounts (₮, signed) | |
data[].balance_after |
integer | the stream's clearing balance after the row | |
data[].currency |
string | MNT |
A statement is ≤ 100 000 rows (422 statement_too_large — shorten the range). In a stream without settlements (most
apps today, and test mode) every capture went straight to the payout account, so each captured row is followed by a
settlement_paid row of the same amount and the balance stays 0.
Key¶
The console's «Keys» (/dev/apps/{id}/keys…) — not a server API: only the console creates and shows keys.
Creating and rolling also answer client_secret (once), notice, and on a roll previous_key. Rules:
API conventions.
| Field | Type | nullable | Meaning |
|---|---|---|---|
id |
string (UUID) | the console's id | |
object |
string | "api_key" |
|
name · environment |
string | ≤ 60 · test / live |
|
client_id |
string | gk_live_… / gk_test_… (mapp_… for the old pair) |
|
secret_hint |
string | yes | the secret's last 4 characters |
scopes |
string[] | payments:read, … |
|
allowed_ips |
string[] | CIDRs; [] = anywhere |
|
legacy |
boolean | the «default key» (the old mapp_… pair) |
|
status |
string | active · expired · revoked |
|
created_by |
object | yes | {user_id, name} |
created_at · expires_at · last_used_at · revoked_at |
string (time) | all but created_at |
last_used_at is updated at most once a minute |
last_used_ip · revoke_reason · rolled_from · rolled_to |
string | yes |