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 is the only status that returns application/json and the only one carrying 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.
Calling response.json() on a 409 will throw. Check the status first, and only parse JSON on a 422.

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

forbidden. On a reversal this means either the transaction belongs to another merchant, or it is not a charge. The two are deliberately indistinguishable.

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.