Skip to content
SkanPay Docs

Errors

Every error has the same shape, a stable code to branch on, and a request id to quote.

400 Bad Request
{
  "error": {
    "type": "invalid_request_error",
    "code": "currency_country_mismatch",
    "message": "UG settles in UGX, but this charge is in KES.",
    "param": "currency",
    "doc_url": "https://docs.skanpay.website/errors/currency_country_mismatch",
    "request_id": "req_01J9Z3K8Q4W6XK2M7N5P0R3T8V"
  }
}
FieldMeaning
typeThe broad category, listed below.
codeA stable identifier. Branch on this. Codes are part of the API contract and do not change.
messageFor developers. It may be reworded at any time, so never parse it or show it to customers as-is.
paramPresent when one field is to blame, e.g. msisdn or amount.
doc_urlThe page on this site for the code.
request_idAlso in the X-Request-Id header. Quote it to support.

Error types

typeHTTPMeaning
invalid_request_error400, 404, 413, 415Something about the request is wrong. Fix it before retrying.
authentication_error401The API key is missing or not valid.
permission_error403The key is valid but not allowed to do this.
idempotency_error409A conflict over an Idempotency-Key.
rate_limit_error429Too many requests.
provider_error502The mobile money network could not be reached for this request.
api_error500, 503A problem on SkanPay's side. Safe to retry with the same Idempotency-Key.

Handling errors

const res = await fetch(url, options);
const body = await res.json();

if (!res.ok) {
  const { code, message, param, request_id } = body.error;
  switch (code) {
    case "network_required":
      return askCustomerForNetwork();          // then retry with network: "MTN" | "AIRTEL"
    case "invalid_msisdn":
      return showError("Please check the phone number.");
    case "amount_below_minimum":
    case "amount_exceeds_transaction_limit":
      return showError("This amount can't be paid by mobile money.");
    case "idempotency_key_in_progress":
    case "rate_limit_exceeded":
    case "internal_error":
    case "database_unavailable":
    case "no_route_available":
      return retryLaterWithSameKey();
    default:
      console.error("SkanPay error", code, message, param, request_id);
      return showError("Payment could not be started. Please try again.");
  }
}

Retry rules of thumb:

  • 4xx: do not retry the same request; it will fail the same way. Change it first. The exceptions are 409 idempotency_key_in_progress and 429.
  • 5xx and network timeouts: retry with the same Idempotency-Key and body, with backoff. You can never double-charge that way.

All error codes

HTTPcodeSummaryRetry?
400invalid_parameterA field is missing, malformed, or not allowed on this endpoint.after a change
400empty_bodyThis endpoint needs a JSON body and none was sent.after a change
400invalid_requestThe request could not be read.after a change
400idempotency_key_requiredThis endpoint requires an `Idempotency-Key` header.after a change
400invalid_msisdnThe phone number could not be understood.after a change
400unsupported_countryThe phone number belongs to a country SkanPay does not serve.no
400network_requiredWe cannot tell which network the number belongs to.after a change
400network_not_supported_in_countryThat network does not operate in that country.after a change
400currency_country_mismatchThe currency is not the wallet's country currency.after a change
400country_not_enabledThat country is not currently enabled.later
400amount_below_minimumThe amount is below the country's minimum.after a change
400amount_exceeds_transaction_limitThe amount is above your single-transaction limit.after a change
400daily_limit_exceededThis would take you over your rolling 24-hour limit.later
400monthly_limit_exceededThis would take you over your rolling 30-day limit.later
400insufficient_balanceYour available balance does not cover the amount plus the fee.after a change
400destination_not_activeThat payout wallet cannot receive money yet.later
401missing_api_keyNo API key was sent.after a change
401invalid_api_keyThe API key is malformed, unknown, or revoked.after a change
403publishable_key_not_allowedA publishable (`pk_`) key was used where a secret key is required.after a change
403live_mode_not_enabledYour account has not been approved for live payments yet.later
403merchant_suspendedThis account cannot transact.no
403origin_not_allowedThe key is restricted to another domain.no
403payouts_not_enabled_livePayouts are not available yet.later
403withdrawals_not_enabled_liveWithdrawals are not available yet.later
404charge_not_foundNo charge with that id on your account.no
404payout_not_foundNo payout with that id on your account.no
404deposit_not_foundNo deposit with that id on your account.no
404withdrawal_not_foundNo withdrawal with that id on your account.no
404transaction_not_foundNo transaction with that id on your account.no
404destination_not_foundNo payout wallet with that id on your account.after a change
404unknown_endpointThere is no endpoint at that method and path.after a change
409idempotency_key_reuseThis Idempotency-Key was already used with a different body.after a change
409idempotency_key_in_progressThe first request with this key is still being processed.yes
413payload_too_largeThe request body is too large.after a change
415unsupported_media_typeThe endpoint does not accept that Content-Type.after a change
429rate_limit_exceededToo many requests.later
500internal_errorSomething went wrong on SkanPay's side.yes
502no_route_availableNo mobile money rail can carry this payment right now.later
503database_unavailableSkanPay is briefly unable to reach its database.yes