Limits
Use this page to check, in one place, every numeric limit: amounts, lengths, times, counts, page sizes and rate
limits. Every number is the value in the module's code; what you get when you exceed one is in
Error codes.
Amounts and text
| What |
Limit |
When exceeded |
An intent's amount |
whole ₮, 100 – 50 000 000 |
400 validation_error (amount) |
The app's max_amount (set by the operator; the ceiling for one payment) |
100 – 50 000 000, or none |
400 validation_error (amount) |
currency |
MNT only |
400 validation_error |
description |
1 – 80 characters (shown to the person) |
400 validation_error |
reference |
≤ 64 characters, no line breaks or tabs |
400 validation_error |
metadata |
≤ 20 keys; key 1 – 40, value ≤ 500 characters |
400 validation_error |
min_age |
13 – 21, not below the app's |
400 validation_error |
return_url |
https, host in the app's return_hosts, ≤ 2048 |
400 validation_error |
A refund's amount |
≥ 1, refunds in total not above what was paid; omitted = everything left |
400 validation_error |
A refund's reason |
≤ 255 characters |
400 validation_error |
| Withdrawing from a wallet to a bank |
1 000 – 50 000 000 ₮ (Withdrawals to a bank) |
400 invalid_amount |
Idempotency-Key |
1 – 128 printable ASCII characters, no spaces (1 – 100 for the console dashboard's actions, 8 – 128 for wallet withdrawals) |
400 idempotency_key_required |
Times
| What |
Value |
expires_in (time to pay) |
60 – 1 800 seconds, default 600 |
capture_within (manual hold) |
60 – 604 800 seconds (7 days), default 7 days; only with capture_method: "manual" |
| The hold's safety margin (on the ledger's side) |
automatic: expires_at + 15 minutes; manual: capture_before + half the window (1 – 15 minutes) — the platform releases at capture_before itself; this is only a backstop |
| An unconfirmed claim |
lapses after 90 seconds; the next person may take it |
| Unconfirmed claims one person may hold at once |
≤ 3 |
| Refund window |
90 days after the payment |
| Launch code |
60 seconds, single use |
| Access token |
30 minutes |
| Refresh chain (grant) |
8 hours from the launch |
| Payout link request |
expired when unanswered for 7 days |
An API key's expires_at |
optional; when given, 1 hour to 3 years ahead |
| Overlap when rolling a key |
0 – 604 800 seconds (7 days), default 24 hours |
Statement and statistics range (from/to) |
Ulaanbaatar dates, the last 30 days by default, ≤ 366 days |
Launches kept (analytics' launches, unique_persons) |
400 days; earlier days count 0 (consent and payment counts are unaffected) |
Webhooks
| What |
Value |
| Time to answer |
a 2xx within 10 seconds (connect 5 seconds) |
| Retry schedule |
1m, 5m, 30m, 2h, 6h, 12h, 24h — 8 attempts in all (~45 hours from first to last), then failed |
Window for the signature's t |
±300 seconds |
| Overlap when rotating the secret |
the previous secret keeps signing for 24 hours |
Events kept (the console's delivery list, GET /events) |
30 days after they were made (not while a delivery is still pending) |
| Webhook endpoints |
≤ 5 per app and environment (endpoint №1 included); description ≤ 200; event_types ≤ 20 (empty = all) |
| An endpoint turned off by itself |
every delivery failing for 24 hours — the admins are warned; for 5 days — auto_disabled |
webhook.test (console and POST /webhook-endpoints/{id}/test) |
20/minute |
More: Delivery and retries.
Registry and counts
| What |
Limit |
| API keys |
≤ 20 active per app and environment; name ≤ 60; IP allowlist ≤ 20 CIDRs; revocation reason ≤ 200 |
| Test users |
≤ 50 per app |
| Terminal batch |
1 – 500 rows per request (JSON or CSV, body ≤ 1 MiB) |
Sticker PDF (print-sheet) |
≤ 500 terminals per sheet; without terminal_ids every active one (choose when there are more than 500); layouts a4-24 · label-50 · label-40 · tent-a6; QR ≥ 25 mm |
Statement (/statements) |
≤ 100 000 rows (422 statement_too_large) |
return_hosts |
≤ 5, exact hosts (shop.mn) — no scheme, port, path or wildcard |
A terminal's label · location |
1 – 60 · ≤ 120 characters |
A terminal's reference |
^[A-Za-z0-9_.:-]{1,64}$, unique within the app |
| A terminal's open intents |
one unclaimed per terminal per environment (409 terminal_busy) |
A sub-merchant's code |
^[A-Za-z0-9_.:-]{1,40}$ |
A sub-merchant's display_name · legal_name · reg_no |
1 – 80 · ≤ 160 · ≤ 20 characters |
| Admins asked to approve a payout link |
up to the organisation's first 100 admins are notified |
| Pending wallet withdrawals |
≤ 3 per owner |
| List |
limit |
Default |
Shape |
GET /payment-intents |
1 – 100 |
20 |
{data, has_more}, after = the last id of the previous page |
GET /settlements |
1 – 100 |
20 |
{items, next_cursor, has_more} |
GET /settlements/{id} lines |
— |
1 000 |
truncated: true when there are more |
GET /events |
1 – 100 |
50 |
{data, next_cursor}, oldest first |
GET /terminals, GET /sub-merchants, GET /webhook-endpoints |
— |
all |
{data}, unpaginated |
GET /sub-merchants/{sid}/payout-links |
— |
the last 50 |
{data} |
Console: payments, refunds (/dev/payments, /dev/refunds) |
1 – 100 |
25 |
{data, next_cursor} |
| Console: API keys |
1 – 100 |
50 |
{data, next_cursor} |
| Console: webhook deliveries |
1 – 100 |
50 |
{items, next_cursor} |
| Console: test intents |
— |
20 |
{items, next_cursor} |
More: API conventions.
Request sizes
| Request |
Largest body |
POST /payment-intents, POST /oauth/token |
16 KiB |
| Refunds, terminals, sub-merchants, payout links, a person's confirmation |
4 KiB |
| A terminal batch, a sticker-PDF request |
1 MiB |
| The console's API keys |
8 KiB |
| Editing an app in the console |
32 KiB |
Beyond it: 400 invalid_request.
Rate limits
The platform's rate limiter counts each limit per caller (per minute; burst is the extra allowed in a short
spike). Exceeding it answers 429 with {"error": "…"} (no code, http_429 in the SDKs) — back off and retry
with the same Idempotency-Key.
| Route |
Per minute |
Burst |
POST /oauth/token |
120 |
30 |
GET /me |
600 |
120 |
/payment-intents…, /settlements…, /terminals…, /sub-merchants…, GET /events… |
600 |
120 |
POST /terminals/print-sheet |
30 |
10 |
POST /sub-merchants/{sid}/payout-link (each sends somebody a notification) |
30 |
10 |
/webhook-endpoints… |
120 |
30 |
POST /webhook-endpoints/{id}/test |
20 |
5 |
GET /statements |
60 |
10 |
| Console: creating, rolling an API key |
30 |
10 |
| Console: dashboard capture · cancel · unclaim / refunds, statements |
60 / 30 |
20 / 10 |
Public GET /pay/{token}, GET /pay/t/{code} |
120 |
30 |
Person: POST /payments/claim, …/confirm, opening an app |
30 |
10 |
| Person: a wallet-to-bank withdrawal request |
6 |
3 |
Coming soon
Built in 1.13.0: expires_in up to 604 800 seconds (7 days, an invoice) for qr and redirect intents — app and
terminal keep 1 800; a key's IP allowlist (≤ 20).