Resources
Error codes
Every V2 error code and V1 error response: meaning, what to do, whether to retry, suspended features and SDK error classes.
API V2#
A V2 error has the shape { "message", "code" } (plus errors for a validation). The message is in French and may change: base your logic on the code, and accept an unknown code.
{
"message": "Solde insuffisant : 1200.00 CDF disponibles, 5175.00 CDF demandés.",
"code": "INSUFFICIENT_BALANCE"
}| Code | HTTP | Meaning | What to do | Retry |
|---|---|---|---|---|
UNAUTHENTICATED | 401 | API token (or test key) missing or invalid.All operations | Send Authorization: Bearer <token>; check the key in the merchant area. | No |
TOKEN_BLOCKED | 403 | The token is blocked by the platform.POST /deposits, POST /payouts, POST /refunds | Contact support. | No |
MERCHANT_INACTIVE | 403 | The merchant account linked to the token is inactive.POST /deposits, POST /payouts, POST /refunds | Contact support. | No |
KYC_REQUIRED | 403 | Account KYC verification required to collect or send live (never with the test key).POST /deposits, POST /payouts | Complete the account verification in the merchant area. | No |
LIVE_TOKEN_REQUIRED | 403 | Live token only: the test key cannot rotate secrets.POST /account/webhook-secret/rotate, POST /account/test-key/rotate | Call again with the live token. | No |
PAYMENT_NOT_FOUND | 404 | No V2 payment of this token (and mode) has this identifier.GET /payments/{id}, POST /payments/{id}/sync, POST /payouts/{id}/cancel | Check the id (e.g. DEP-…) and the key used (test or live). | No |
DEPOSIT_NOT_FOUND | 404 | payment_id matches no V2 deposit of this token.POST /refunds | Pass the id of a deposit (DEP-…). | No |
NOTIFICATION_NOT_FOUND | 404 | Notification not found for this token.POST /notifications/{id}/resend | Take the id from GET /notifications. | No |
NOT_A_PAYOUT | 409 | Only a payout can be cancelled.POST /payouts/{id}/cancel | For a completed deposit, use a refund. | No |
NOT_CANCELLABLE | 409 | The payout is not (or no longer) ENQUEUED, or the cancellation was refused.POST /payouts/{id}/cancel | Read the payment again: it follows its normal course. | No |
DEPOSIT_NOT_COMPLETED | 409 | Only a completed deposit can be refunded.POST /refunds | Wait for the deposit's final status. | No |
REFUND_IN_PROGRESS | 409 | A refund of this deposit is already in progress (one at a time).POST /refunds | Wait for the current refund to finish. | Later |
DEPOSIT_ALREADY_REFUNDED | 409 | The deposit has already been fully refunded.POST /refunds | Nothing to do. | No |
VALIDATION_ERROR | 422 | Missing or invalid field; errors lists messages per field (in French).All operations | Fix the request. | No |
INVALID_PHONE_NUMBER | 422 | Phone number not valid for the country (local or international).POST /deposits, POST /payouts, POST /predict-provider | Fix the number, or pass country. | No |
UNSUPPORTED_COUNTRY | 422 | Country not open to payments (the message lists available countries). 503 when no country is open.POST /deposits, POST /payouts, POST /predict-provider | Use a country from Countries and providers. | No |
INVALID_CURRENCY | 422 | Currency not offered for this country or provider.POST /deposits, POST /payouts | Use a currency of the phone number's country (the message lists them; see Countries and providers). | No |
UNSUPPORTED_PROVIDER | 422 | Unknown provider for this country and operation (the message lists available providers).POST /deposits, POST /payouts | Omit provider (inferred from the number) or use a code from GET /providers. | No |
INVALID_AMOUNT | 422 | The provider does not accept decimals in this currency (e.g. Vodacom in CDF).POST /payouts, POST /refunds | Send a whole amount. | No |
AMOUNT_TOO_SMALL | 422 | Amount below the platform or country minimum.POST /deposits, POST /payouts, POST /refunds | Increase the amount. | No |
AMOUNT_TOO_LARGE | 422 | Amount above the cap (country or platform), or above what is left to refund on a deposit.POST /deposits, POST /payouts, POST /refunds | Lower the amount (the message gives the maximum). | No |
AMOUNT_OUT_OF_BOUNDS | 422 | Amount outside the provider's own limits.POST /deposits, POST /payouts, POST /refunds | Check GET /providers (min_amount, max_amount). | No |
INSUFFICIENT_BALANCE | 422 | The account balance (in that currency) does not cover the amount and its fees: nothing is sent.POST /payouts, POST /refunds | Collect first, or lower the amount (GET /balances). | No |
DAILY_LIMIT_REACHED | 429 | Daily transaction limit reached for a token not yet enabled for production (live_enabled: false).POST /deposits | Retry the next day or ask support for activation. | Later |
MAINTENANCE | 503 | Platform under maintenance: nothing is done.POST /deposits, POST /payouts, POST /refunds | Retry later, without a tight loop. | Later |
API_DISABLED | 503 | Payment API temporarily disabled by the platform.POST /deposits, POST /payouts, POST /refunds | Retry later. | Later |
DEPOSITS_DISABLED | 503 | Deposits temporarily disabled by the platform.POST /deposits | Retry later; the status is shown on the home page. | Later |
WITHDRAWALS_DISABLED | 503 | Payouts and refunds temporarily disabled by the platform.POST /payouts, POST /refunds | Retry later. | Later |
FEATURE_DISABLED | 503 | Operation switched off from the LabPay console: refunds (feature: "operations.refunds") or payout cancellation (feature: "operations.cancellations"). Nothing is done.POST /refunds, POST /payouts/{id}/cancel | Retry later, without a tight loop; message gives the reason, if any. | Later |
PROVIDER_TEMPORARILY_UNAVAILABLE | 503 | The provider is CLOSED for this operation.POST /deposits, POST /payouts, POST /refunds | Retry later or offer another provider (GET /availability). | Later |
GATEWAY_UNAVAILABLE | 503 | The LabPay payment service does not respond: provider list, availability or cancellation temporarily impossible.GET /providers, GET /availability, POST /payouts/{id}/cancel | Retry after a few seconds. | Later |
API_VERSION_DISABLED | 503 | The called API version is switched off from the LabPay console (feature: "api.v2", or "api.v1" for V1). Checked before authentication.All operations | Retry later; message gives the reason, if any. | Later |
V2_DISABLED | 503 | The V2 API is temporarily disabled (server configuration).All operations | Retry later. | Later |
TEST_MODE_DISABLED | 503 | Test mode is temporarily disabled: call made with a test_… key (feature: "operations.test_mode" on deposits, payouts, cancellations, refunds and sync). The live token is not affected.All operations | Retry later. | Later |
- Payment refused by the provider:
422response with the payment indata(status: "failed"); the reason is indata.failure.codeanddata.failure.message, passed on from LabPay. - Rate limit (
429):{ "message": "Too Many Attempts." }response withoutcode, with theRetry-Afterheader. - Invalid filters on
GET /V2/providers,GET /V2/availabilityandGET /V2/notifications:422{ "message", "errors" }, withoutcode.
A 202 response, a network error or a timeout on a payment request do not mean "failure": resend the same reference or read the payment (see Idempotency).
Suspended features (503)#
From its console, LabPay can suspend an API version or an operation (maintenance, provider incident…). The request is then refused before any processing: nothing is created or debited. The 503 response carries the code and, for console switches, the feature field (feature concerned); message gives the reason entered, if any.
{
"message": "Les remboursements sont momentanément désactivés.",
"code": "FEATURE_DISABLED",
"feature": "operations.refunds"
}Feature (feature) | Code | Refused requests |
|---|---|---|
api.v2 | API_VERSION_DISABLED | The whole V2 merchant API (before authentication). |
api.v1 | API_VERSION_DISABLED | /V1/payment/* and /V1/user/* (the legacy callback stays open). |
| Collections (platform setting) | DEPOSITS_DISABLED | POST /V2/deposits; V1: POST /V1/payment/mobile. |
| Withdrawals (platform setting) | WITHDRAWALS_DISABLED | POST /V2/payouts and POST /V2/refunds; V1: POST /V1/payment/withdrawal. |
operations.refunds | FEATURE_DISABLED | POST /V2/refunds |
operations.cancellations | FEATURE_DISABLED | POST /V2/payouts/{id}/cancel |
operations.transfers | FEATURE_DISABLED | POST /V1/payment/inter-transfer |
operations.test_mode | TEST_MODE_DISABLED | With a test_… key: POST /V2/deposits, /payouts, /payouts/{id}/cancel, /refunds, /payments/{id}/sync. |
- A switch applies within about thirty seconds. Notifications (webhooks) are never cut, and reads only are when the whole version is.
- Do not retry in a loop: wait, or offer another payment method. The SDKs only retry these refusals after a
Retry-Afterheader. - The live status (open versions and operations, reasons) is shown on the documentation home page.
With the SDKs#
The SDKs raise an error typed by code (then by HTTP status); a payment refused at initiation (422 with the payment) is returned with status: failed, not raised. Codes newer than the SDK (e.g. API_VERSION_DISABLED, FEATURE_DISABLED) arrive with the class of their status (503 → service unavailable), code intact. Full mapping on each SDK page.
Handle errors
<?php
use Labyrinthe\LabPay\Client;
use Labyrinthe\LabPay\Exception\ApiException;
use Labyrinthe\LabPay\Exception\ConnectionException;
use Labyrinthe\LabPay\Exception\ServiceUnavailableException;
use Labyrinthe\LabPay\Exception\ValidationException;
$labpay = new Client('test_xxxxxxxxxxxxxxxx'); // ou Client::fromEnv() (LABPAY_API_KEY)
try {
$payment = $labpay->deposits->create(amount: '1000', currency: 'CDF', phone: '0813456789', reference: 'ORDER-42');
if ($payment->isFailed()) {
// refusé dès l'initiation (422 avec le paiement) : renvoyé, pas levé
echo $payment->failure->code;
}
} catch (ValidationException $e) {
$e->getErrors(); // messages par champ
} catch (ServiceUnavailableException $e) {
$e->getErrorCode(); // MAINTENANCE, DEPOSITS_DISABLED… ; $e->getRetryAfter()
} catch (ConnectionException $e) {
// PAS un échec : renvoyer la même référence ou payments->findByReference('ORDER-42')
} catch (ApiException $e) {
$e->getErrorCode(); // toujours le code, jamais le message
}API V1 (legacy)#
V1 has no code field: an error has success: false, a readable message, results.status (status from the table below) and errors (list) for a validation. The table gives the response name and its HTTP status.
| Response | HTTP | Meaning | What to do | Retry |
|---|---|---|---|---|
failed | 400 | Request refused (currency, phone number, incomplete profile, insufficient balance, LabPay refusal…); message gives the reason.All operations | Read message and fix. | No |
not_allowed | 403 | Validation failed (unknown token, missing field…): errors lists the messages; results.status.name is failed.All operations | Fix the fields listed in errors. | No |
excessive_amount | 403 | Amount above 10,000 USD or 20,000,000 CDF; results.status.name is failed.POST /payment/mobile, POST /payment/withdrawal | Lower the amount. | No |
limit exceeded | 403 | Daily transaction limit of a token not yet enabled for production.POST /payment/mobile | Retry the next day or ask support for activation. | Later |
token blocked | 403 | Token blocked by the platform.All operations | Contact support. | No |
internal error | 500 | Internal error (provider not recognised by LabPay, unexpected response…).All operations | Retry later or contact support. | Later |
Suspended features (V1)#
A suspended version or operation also answers 503 in V1, in the usual envelope (success: false, message…) with code and feature added: API_VERSION_DISABLED (api.v1), DEPOSITS_DISABLED (payment/mobile), WITHDRAWALS_DISABLED (payment/withdrawal), FEATURE_DISABLED (payment/inter-transfer).