MagiaPay· Errors

Errors

Every failed response follows the same JSON envelope. The code field is stable and machine-readable; the message is human-friendly (don't match on it).

Error envelope
{
  "error": {
    "code": "invalid_request",
    "message": "amount must be a positive number",
    "issues": {
      "formErrors": [],
      "fieldErrors": { "amount": ["Required"] }
    }
  }
}

Common codes

HTTPCodeMeaning
400invalid_requestBody failed validation — see issues for field-level details.
400invalid_stateOperation not allowed in the resource's current state (e.g. refunding a non-succeeded payment).
400no_routeNo enabled provider supports this method/currency combination.
400insufficient_balancePayout amount exceeds available balance.
401unauthenticatedMissing or invalid API key, or key type mismatch (test key on live payment).
403insufficient_scopeAPI key scopes don't include read or write as needed.
404not_foundResource doesn't exist or belongs to another merchant / environment.
409email_takenSignup collision — account already exists.
409already_usedEmail verification / activation token already consumed.
410invalid_tokenToken expired or invalid (password reset, email verify).
429rate_limitedPer-key rate limit exceeded. Check Retry-After header for wait time.
500internal_errorUnexpected server error. Safe to retry with backoff.
502/504provider_errorUpstream provider (DirectPay, PayGram, etc.) failed. Safe to retry.

Retrying

Safe to retry: 429, 500, 502, 504, and network errors (timeouts, ECONNRESET). Always include an Idempotency-Key header so duplicates don't create extra resources.

Do not retry on 400, 401, 403, 404, 409 — fix the request and call once.

Handling pattern

Node.js error handling
async function createPayment(body) {
  const res = await fetch('https://magiapay.innoserver.cloud/v1/payments', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MAGIAPAY_SECRET}`,
      'Idempotency-Key': crypto.randomUUID(),
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const { error } = await res.json();
    switch (error.code) {
      case 'invalid_request':  throw new UserInputError(error.message, error.issues);
      case 'unauthenticated':  await rotateCreds(); throw new RetryableError();
      case 'rate_limited':     await sleep(Number(res.headers.get('Retry-After') || '1') * 1000);
                               throw new RetryableError();
      case 'provider_error':   throw new RetryableError();
      default:                 throw new Error(`${error.code}: ${error.message}`);
    }
  }
  return res.json();
}