Guide
Errors
One error envelope, stable codes to branch on, and what to do about each class of error.
Errors share one JSON shape:
{
"error": {
"code": "validation_error",
"message": "Human-readable summary",
"request_id": "req_74447c85dfc22dec",
"details": [
{ "field": "payout.beneficiary.phone", "message": "must be E.164" }
]
}
}- Branch on
error.code. It is stable.messageis for humans and can change. request_id(also theX-Request-Idheader) identifies the request. Quote it to support.details[]names each offending field on validation errors and on refusals about a field.payment_idis present when the error concerns one payment.- Every operation in the API reference lists the responses it can return.
Two responses that are not errors
A compliance refusal is a 201. Screening runs after a payment is persisted.
A block returns 201 with status: FAILED and failure.code: compliance_blocked. Never show failure.message or failure.stage to an end
user; they are for your systems.
A 500 on POST /payments can carry the Payment. If processing failed after
the payment was persisted, the body is the payment (it has id and status),
not an envelope (which has error). Retry with the same Idempotency-Key to
read it back; you will never get a second payment.
What to do, by status
| HTTP | Meaning | What to do |
|---|---|---|
400 |
Malformed request: bad JSON, an unknown field, a missing or non-UUID Idempotency-Key |
Fix the request. Do not retry it unchanged. |
401 |
Missing, unknown or revoked API key | Check the key and that it matches the environment. |
403 |
Not allowed: scope, entitlement, account or product state | Do not retry. Fix the key's scopes, or ask Moja Switch to enable you. |
404 |
Not found, or not yours. The two are deliberately indistinguishable. | Check the id. |
409 |
Conflict: idempotency key reuse, duplicate reference, or a state that forbids the action |
Read the resource, then decide. |
422 |
A business rule refused the request. Nothing was created. | Fix the named fields or the cause. |
429 |
Rate limited | Wait Retry-After seconds, then retry. |
500, 502 |
Unexpected or upstream failure | Retry money calls only with the same Idempotency-Key. |
503 |
A dependency the request needs is unavailable; the request failed closed | Retry later. For quotes, show "no price available". |
Codes you will meet first
| Code | HTTP | Meaning |
|---|---|---|
invalid_request |
400 | Body unreadable, unknown field, or Idempotency-Key missing or not a UUID |
unauthorized |
401 | Missing or invalid API key |
forbidden |
403 | Partner account disabled |
insufficient_scope |
403 | The key lacks the route's scope |
corridor_not_entitled |
403 | You are not entitled to that corridor |
tenant_not_activated |
403 | Moja Switch has not enabled your tenant for this flow in this environment |
product_not_available |
403 | The product (for example collections) is not offered in this environment |
validation_error |
422 | A field failed validation; see details[] |
fx_required, fx_inconsistent, fx_not_accepted |
422 | FX instruction problems; see FX |
quote_required, quote_invalid, quote_expired |
422 | Quote problems on a Moja Switch FX payment |
idempotency_key_conflict |
409 | Key reused with a different body |
duplicate_reference |
409 | reference already used |
cancel_not_allowed |
409 | The payment is past its cancellable state |
payment_not_found |
404 | Unknown payment, or another partner's |
rate_limit_exceeded |
429 | Over the rate limit |
entitlement_unavailable |
503 | Entitlement could not be checked; nothing was forwarded |
fx_unavailable |
503 | No valid, fresh reference rate |
upstream_unavailable |
502 | A Moja Switch service was briefly unreachable; retry with the same Idempotency-Key |
internal_error |
500 | Unexpected failure before anything was persisted |
Treat an unknown code by its HTTP status, using the table above.
Payment failures
A payment that fails after it was created is not an HTTP error: it is a payment
with status: FAILED and a failure object. Branch on failure.code. Use
funds_taken (on Interac-funded transfers) to tell a customer whether their
money left their account, and a generic "did not complete" message otherwise.
