There are two error shapes, not one
This is the single thing most integrations get wrong on day one.422 is a decline: your request was fine, but the payment cannot proceed for a specific
business reason. It returns application/json with a machine-readable code.
Everything else — 400, 401, 403, 404, 409, 500 — returns
text/plain; charset=utf-8: one line of English with a trailing newline, no JSON, no code.
One exception: the reverse route’s policy 403s (REFUND_WINDOW_EXPIRED,
REFUND_FOREIGN_TERMINAL) use the same JSON envelope as a 422, because they carry an
instruction your cashier needs — see Refunds. The generic 403 (forbidden) stays
plain text.
Decline codes
All ten arrive as422. Branch on code; never on the English error text, which is written for
your logs and can be reworded.
The customer’s money
The employer’s spend rules
Employers decide when and how their benefit may be spent. These are not errors — they are the policy working.Days and times are evaluated in Asia/Ulaanbaatar, and the daily limit resets at Mongolian
midnight — not UTC midnight and not your server’s local midnight. A charge at 00:30 in
Ulaanbaatar is on the new day’s allowance.
The card or wallet
The scanned code
Both are recoverable at the till. Offer a Rescan action rather than failing the sale.
Plain-text errors
400 — your request is wrong
These are integration bugs. Retrying will not change the answer.
401 — credentials dead
Always the bodyunauthorized, whatever the cause. Stop the sale and re-pair the terminal. See
Authentication.
403 — not yours, or not this terminal’s to undo
Plain-textforbidden: on a reversal, either the transaction belongs to another merchant or it
is not a charge. The two are deliberately indistinguishable.
JSON with a code: the reversal was refused by the POS refund policy, and the cashier should be
sent to the merchant dashboard instead. Show the instruction, not “forbidden”.
404 — not found
wallet not found on a charge means the code verified but the card behind it no longer exists.
transaction not found on a reversal means the id is unknown — check you saved the id from the
charge response and not something else.
409 — already done
This is the one to handle carefully.429 and 5xx — outcome unknown
{"message":"Too Many Requests"} and internal error respectively. Neither tells you whether the
charge committed. Retry with the same Idempotency-Key, or reconcile against
GET /v1/transactions. See Environments and limits.
A decision table for your POS
Do not match on message text
Theerror string and the plain-text bodies are for humans reading logs. They may be reworded at
any time. The contract you can rely on is the HTTP status and, on a 422, the code.
If you need Mongolian text for a cashier, map the code to your own strings — the codes are
stable and the English is not.
