Skip to main content
Migo Docs

Error Handling

Every Migo API returns responses wrapped in a CustomResponse<T> envelope.

The envelope shape is:

interface CustomResponse<T> {
success: boolean;
message?: string;
data?: T;
errors?: { code: string; message: string }[];
}

Note that code is a string (e.g. "7100"), and errors is an array of { code, message } objects. There is no statusCode, error.name, or error.detail field on the wire payload β€” the name (e.g. USER_NOT_FOUND) is the internal MigoNameErrors key, not part of the response body.

Success​

{
"success": true,
"message": "ok",
"data": { "...": "..." }
}

Error​

{
"success": false,
"message": "Validation failed",
"errors": [
{
"code": "7100",
"message": "User not found in the system."
}
]
}

Status code mapping​

HTTP statusWhen it's returned
200 OKSuccess (including async jobs that return a batch/job id in the body)
201 CreatedResource created (e.g. statement creation accepted)
204 No ContentSuccess, no body
400 Bad RequestDTO validation failed
401 UnauthorizedMissing / expired / invalid JWT
403 ForbiddenAuthenticated but permission denied
404 Not FoundResource does not exist
409 ConflictResource state conflict
422 Unprocessable EntitySemantic validation failed (e.g. card expired)
500 Internal Server ErrorMigo bug β€” please report with the request timestamp and details
502 Bad GatewayUpstream processor unreachable
503 Service UnavailableScheduled maintenance or partial outage
504 Gateway TimeoutUpstream processor timeout

Business error code ranges​

Migo uses two distinct business error code spaces depending on the API you are calling. The Wallet Gateway and CMS Backoffice surface a string code inside the errors[] array; the Middleware uses a separate envelope with an ownCode. The numeric ranges do not overlap and the field names differ.

Wallet Gateway, CMS Backoffice β€” 7000–8099​

These codes appear as a string in each entry of the errors array of the standard CustomResponse envelope shown above (e.g. "code": "7100").

RangeMeaning
7000–7099General / validation / terminal-processor
7100–7199Users (incl. OTP, FCM, password, auth-user)
7200–7299Cards (incl. third-party cards, view tokens)
7300–7399Transfers & terminal payments
7400–7499Terminals, businesses & branches
7500–7599Settlements & tax regime
7600–7699Clients, auth, roles, permissions, files & socials
7700–7799Project config & app integrity
8000–8099App / mobile (process, balance, permissions)

Ranges 7800–7899 and 7900–7999 are currently unused.

Full list in the Error Catalog.

The Middleware uses a separate envelope (ErrorResponseDto) and carries the business error in a nested error object, where ownCode is a small numeric value (e.g. "2002", "2007", "5000", "5004"). The shape is:

interface ErrorResponseDto {
message?: string;
error?: {
code?: number; // HTTP status associated with the error
ownCode?: string; // Migo business error code
message?: string; // human-readable description
};
}

These do not clash with the 7000–8099 space because they live in a different envelope and field path (error.ownCode, not errors[].code).

RangeMeaning
2000–2099Transaction creation / business validation (excluded user, amount out of range, etc.)
5000–5099DTO / configuration validation (missing fields, unknown client slug)

Full list in the Error Catalog β†’ Payment Link / Middleware ownCodes.

When you wrap calls to both surfaces in the same client, branch on the field path:

const code = response?.errors?.[0]?.code ?? response?.error?.ownCode;

Retry semantics​

Error classRetry?Strategy
Network timeoutYesExponential backoff
500 / 502 / 504YesExponential backoff, cap at 3 retries
503YesExponential backoff with long ceiling (up to 5 min)
400 / 422NoFix the request
401No (refresh instead)Refresh token, then retry
403 / 404 / 409NoDo not retry

Client pattern​

async function call<T>(fn: () => Promise<T>, attempt = 0): Promise<T> {
try {
return await fn();
} catch (err) {
const status = err.response?.status;
if (status === 401) {
await refreshToken();
return call(fn, attempt + 1);
}
if ([500, 502, 503, 504].includes(status) && attempt < 3) {
const wait = 2 ** attempt * 500;
await new Promise(r => setTimeout(r, wait));
return call(fn, attempt + 1);
}
throw err;
}
}

Reporting issues​

When opening a support ticket include:

  • The request timestamp in UTC (Migo correlates it to the internal trace)
  • Full request URL, method, and headers (redact the Bearer token)
  • Request body (redact PANs and CVVs)
  • Full response body