Skip to main content

There are two error shapes, not one

This is the single thing most integrations get wrong on day one.
A 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.
Calling response.json() on a 409 will throw. Check the status first, and parse JSON only on a 422 — or, on the reverse route’s 403, after checking the Content-Type.

Decline codes

All ten arrive as 422. 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 body unauthorized, whatever the cause. Stop the sale and re-pair the terminal. See Authentication.

403 — not yours, or not this terminal’s to undo

Plain-text forbidden: 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.
A 409 almost always means the thing you were trying to do already happened. It is far more often a success you did not hear about than a failure.

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

The error 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.