Errors
Every error has the same shape, a stable code to branch on, and a request id to quote.
{
"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"
}
}| Field | Meaning |
|---|---|
type | The broad category, listed below. |
code | A stable identifier. Branch on this. Codes are part of the API contract and do not change. |
message | For developers. It may be reworded at any time, so never parse it or show it to customers as-is. |
param | Present when one field is to blame, e.g. msisdn or amount. |
doc_url | The page on this site for the code. |
request_id | Also in the X-Request-Id header. Quote it to support. |
Error types
| type | HTTP | Meaning |
|---|---|---|
invalid_request_error | 400, 404, 413, 415 | Something about the request is wrong. Fix it before retrying. |
authentication_error | 401 | The API key is missing or not valid. |
permission_error | 403 | The key is valid but not allowed to do this. |
idempotency_error | 409 | A conflict over an Idempotency-Key. |
rate_limit_error | 429 | Too many requests. |
provider_error | 502 | The mobile money network could not be reached for this request. |
api_error | 500, 503 | A 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_progressand 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
| HTTP | code | Summary | Retry? |
|---|---|---|---|
| 400 | invalid_parameter | A field is missing, malformed, or not allowed on this endpoint. | after a change |
| 400 | empty_body | This endpoint needs a JSON body and none was sent. | after a change |
| 400 | invalid_request | The request could not be read. | after a change |
| 400 | idempotency_key_required | This endpoint requires an `Idempotency-Key` header. | after a change |
| 400 | invalid_msisdn | The phone number could not be understood. | after a change |
| 400 | unsupported_country | The phone number belongs to a country SkanPay does not serve. | no |
| 400 | network_required | We cannot tell which network the number belongs to. | after a change |
| 400 | network_not_supported_in_country | That network does not operate in that country. | after a change |
| 400 | currency_country_mismatch | The currency is not the wallet's country currency. | after a change |
| 400 | country_not_enabled | That country is not currently enabled. | later |
| 400 | amount_below_minimum | The amount is below the country's minimum. | after a change |
| 400 | amount_exceeds_transaction_limit | The amount is above your single-transaction limit. | after a change |
| 400 | daily_limit_exceeded | This would take you over your rolling 24-hour limit. | later |
| 400 | monthly_limit_exceeded | This would take you over your rolling 30-day limit. | later |
| 400 | insufficient_balance | Your available balance does not cover the amount plus the fee. | after a change |
| 400 | destination_not_active | That payout wallet cannot receive money yet. | later |
| 401 | missing_api_key | No API key was sent. | after a change |
| 401 | invalid_api_key | The API key is malformed, unknown, or revoked. | after a change |
| 403 | publishable_key_not_allowed | A publishable (`pk_`) key was used where a secret key is required. | after a change |
| 403 | live_mode_not_enabled | Your account has not been approved for live payments yet. | later |
| 403 | merchant_suspended | This account cannot transact. | no |
| 403 | origin_not_allowed | The key is restricted to another domain. | no |
| 403 | payouts_not_enabled_live | Payouts are not available yet. | later |
| 403 | withdrawals_not_enabled_live | Withdrawals are not available yet. | later |
| 404 | charge_not_found | No charge with that id on your account. | no |
| 404 | payout_not_found | No payout with that id on your account. | no |
| 404 | deposit_not_found | No deposit with that id on your account. | no |
| 404 | withdrawal_not_found | No withdrawal with that id on your account. | no |
| 404 | transaction_not_found | No transaction with that id on your account. | no |
| 404 | destination_not_found | No payout wallet with that id on your account. | after a change |
| 404 | unknown_endpoint | There is no endpoint at that method and path. | after a change |
| 409 | idempotency_key_reuse | This Idempotency-Key was already used with a different body. | after a change |
| 409 | idempotency_key_in_progress | The first request with this key is still being processed. | yes |
| 413 | payload_too_large | The request body is too large. | after a change |
| 415 | unsupported_media_type | The endpoint does not accept that Content-Type. | after a change |
| 429 | rate_limit_exceeded | Too many requests. | later |
| 500 | internal_error | Something went wrong on SkanPay's side. | yes |
| 502 | no_route_available | No mobile money rail can carry this payment right now. | later |
| 503 | database_unavailable | SkanPay is briefly unable to reach its database. | yes |