MoniWave

Errors

Envelope

Every error response uses one shape:

{
  "error": {
    "code": "invalid_amount",
    "message": "Amount must be a positive integer in minor units.",
    "details": [
      { "code": "invalid_amount", "message": "Amount must be a positive integer in minor units." },
      { "code": "invalid_msisdn", "message": "MSISDN must be 8-15 digits in international format without '+'." }
    ]
  }
}
  • code — stable, machine-readable. Branch on this.
  • message — English, for developers and logs. Not for end users, and subject to rewording.
  • details — every error found, when a request has more than one problem. code/message at the top level repeat the first entry, so simple clients can ignore details entirely.

Validation reports all problems at once rather than one per round trip.

Localisation: branch on code and render your own message. message is English-only and may be reworded without notice; code is contractual and will not change without a /v2.

HTTP status codes

StatusWhen
200Successful read.
201Resource created (collection, payout, refund).
400Validation failure or malformed request.
401Authentication problem.
403Authenticated but not permitted.
404No such resource for this merchant — see the note below.
409Idempotency conflict or concurrent modification.
5xxOur fault. Safe to retry with the same idempotency key.

On 404: another merchant's transaction returns 404, identical to a genuinely nonexistent id. This is deliberate — a 403 would confirm the id exists, letting an attacker enumerate other merchants' transactions. The same reasoning applies to checkout tokens, where unknown and malformed values answer identically.

Stable error codes

The complete catalogue. Codes not listed here are not part of the contract.

Authentication

CodeHTTPMeaning
missing_api_key401No Authorization header.
invalid_api_key401Unknown, malformed, or revoked key.
api_key_expired401The key was ours and has passed its expiry. Issue a replacement; keys live at most 180 days. Distinct from invalid_api_key because there is something the holder can do about it.
environment_mismatch401Live key against sandbox URL, or vice versa.
key_limit_reached400A third active key was requested for one environment; at most two are allowed. Revoke one, or let it expire.

Idempotency

CodeHTTPMeaning
idempotency_key_required400Header missing on a mutating request.
idempotency_key_invalid400Header is not a UUID.
idempotency_key_reuse409Key already used with a different body.
idempotency_in_progress409Original request with this key still running.

Request validation

CodeHTTPMeaning
invalid_amount400Amount is not a positive integer in minor units.
amount_exceeds_maximum400Amount is above the per-transaction ceiling (1014 minor units). A safety bound, not a commercial limit — nothing legitimate approaches it.
invalid_currency400Unsupported currency code.
invalid_msisdn400Not 8–15 digits in international format (no +).
description_required400Description missing or blank.
invalid_request400Malformed request with no more specific code.

Payouts

CodeHTTPMeaning
payout_exceeds_balance400The payout plus its fee exceeds your spendable balance in that currency. Message states the required and spendable amounts.

Spendable is your posted balance minus payouts and refunds you have already requested that have not settled yet — the ledger only posts on completion, so a second payout cannot spend money the first one has already committed. Distinct from insufficient_float, which is our problem, not yours.

Refunds

CodeHTTPMeaning
invalid_collection_id400collectionId is not a valid id.
not_a_collection400The referenced id is not a collection (col_…).
not_refundable400The collection is not completed.
refund_exceeds_refundable400Amount exceeds the remaining refundable balance. Message states the remainder.

Hosted checkout

See checkout.md.

CodeHTTPMeaning
invalid_return_url400returnUrl is not an absolute http(s) URL.
checkout_already_paid400The session has been paid. One session yields at most one successful payment.
checkout_expired400The payment link is past its 24-hour deadline.

Transaction limits

Every merchant has a per-transaction ceiling and a daily total for each of collections, payouts and refunds. The figures are visible in the panel under Settings, and are set by MoniWave rather than by the merchant.

Only the per-transaction ceilings are enforced today. The daily totals are recorded and monitored but do not currently refuse a request.

CodeHTTPMeaning
amount_exceeds_limit400Above this merchant's per-transaction ceiling for that operation. The message names the limit that was hit.
amount_below_minimum400Below the minimum for a single transaction.
currency_not_permitted400No limits are configured for that currency, so it cannot be transacted in. XAF is the launch currency.
kind_not_permitted400No limits are configured for that operation.

amount_exceeds_maximum is a different thing and stays where it is under Common: it is an absolute arithmetic ceiling that protects the ledger, and it applies regardless of what any merchant's limits say.

Webhooks

See webhooks.md.

CodeHTTPMeaning
invalid_webhook_url400Not an absolute http(s) URL.
webhook_url_not_public400The URL points at a private, loopback, link-local or internally-resolvable address. Webhook endpoints must be publicly reachable.
webhook_secret_required400A signing secret is required.

Transaction state

CodeHTTPMeaning
invalid_transaction_id400Not a valid transaction id.
already_terminal400Transaction already completed/failed/expired; terminal states never change.
invalid_transition400The requested change is not legal from the current status.

Concurrency

CodeHTTPMeaning
EntityAlreadyExists409Resource already exists.
ConcurrentError409Another writer modified the transaction concurrently. Re-read and retry.

Provider failure reasons

Distinct from API errors: these describe why a transaction failed and appear in the failureReasonCode field of transaction responses and webhook payloads, not in an error envelope. A failed transaction is a successful API call.

Reason codeMeaning
payer_declinedThe customer declined the prompt.
insufficient_fundsThe customer's wallet balance was too low.
invalid_subscriberNot a registered mobile-money subscriber.
insufficient_floatOur float at the provider was too low to fund a payout or refund. Not the customer's fault; contact support.

Retrying safely

  • 4xx other than 409 idempotency_in_progress — do not retry unchanged; fix the request.
  • 409 idempotency_in_progress — retry after a short delay with the same key.
  • 409 ConcurrentError — re-read the transaction and retry.
  • 5xx, timeouts, connection failures — retry with the same idempotency key. That is what makes retries safe rather than double-charging (idempotency.md).