Error Codes
Understand and handle errors returned by the Venshack API.
Error Response Format
All API errors return a consistent JSON format:
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Wallet balance is insufficient for this transaction",
"details": {
"required": 5000,
"available": 2500
}
},
"requestId": "req_xyz789"
}
Error Object Fields
| Field | Type | Description |
|---|---|---|
code |
string | Machine-readable error code |
message |
string | Human-readable error description |
details |
object | Additional context (optional) |
requestId |
string | Unique ID for support inquiries |
HTTP Status Codes
The API uses standard HTTP status codes to indicate success or failure:
| Code | Meaning | Description |
|---|---|---|
200 |
OK | Request succeeded |
201 |
Created | Resource created successfully |
400 |
Bad Request | Invalid request parameters |
401 |
Unauthorized | Missing or invalid API key |
403 |
Forbidden | API key lacks required permissions |
404 |
Not Found | Resource doesn't exist |
409 |
Conflict | Resource already exists or state conflict |
422 |
Unprocessable Entity | Valid format but logical error |
429 |
Too Many Requests | Rate limit exceeded |
500 |
Internal Server Error | Server error (retry with backoff) |
503 |
Service Unavailable | Service temporarily down |
Authentication Errors
| Code | HTTP | Description | Resolution |
|---|---|---|---|
MISSING_API_KEY |
401 | No API key provided | Include API key in Authorization header |
INVALID_API_KEY |
401 | API key is malformed or doesn't exist | Check your API key in the dashboard |
EXPIRED_API_KEY |
401 | API key has been revoked or expired | Generate a new API key |
INSUFFICIENT_PERMISSIONS |
403 | API key lacks required scope | Use a key with appropriate permissions |
Validation Errors
| Code | HTTP | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR |
400 | Request body validation failed | Check the details field for specific issues |
INVALID_METER_NUMBER |
400 | Meter number format is invalid | Verify meter number (usually 11-13 digits) |
INVALID_AMOUNT |
400 | Amount is invalid or out of range | Check minimum (20000 kobo = ₦200) and maximum limits |
INVALID_DISCO |
400 | Unknown or missing distribution company code | Use a canonical DISCO code (IKEJA, EKO, ABUJA, KANO, PH, JOS, KADUNA, ENUGU, IBADAN, BENIN, ABA, YOLA) |
Vending Errors
| Code | HTTP | Description | Resolution |
|---|---|---|---|
INSUFFICIENT_BALANCE |
422 | Wallet balance is too low | Fund wallet before attempting vend |
METER_NOT_FOUND |
404 | Meter not registered in system | Register the meter first via /meters endpoint |
METER_VALIDATION_FAILED |
422 | Meter couldn't be validated with DISCO | Verify meter number and DISCO are correct |
VEND_IN_PROGRESS |
409 | A vend is already processing for this meter | Wait for current transaction to complete |
DISCO_UNAVAILABLE |
503 | DISCO service is temporarily unavailable | Retry after a few minutes |
VEND_FAILED |
500 | Vend failed at provider level | Funds auto-refund; check webhook for details |
MINIMUM_VEND_AMOUNT |
400 | Amount below minimum | Increase amount to at least ₦500 |
MAXIMUM_VEND_AMOUNT |
400 | Amount exceeds maximum | Reduce amount to ₦500,000 or less |
Disco Vending Errors
Disco vending (/v1/disco-vending/*) is a separate collection
from Venshack vending. Provider rejections are returned as HTTP 200 with
success: false and an errorCode. When a
transaction is still processing, requery it with the returned
reference — do not start a new purchase.
| Code | HTTP | Description | Resolution |
|---|---|---|---|
PENDING |
200 | DISCO transaction is still processing | Requery with the returned reference; do not start a new purchase |
INVALID_METER |
200 | Meter number not found or invalid | Confirm the meter number and meter category with the customer |
INVALID_DISCO |
400 | Unsupported or missing DISCO code | Use a supported DISCO code from the Disco Vending guide |
INVALID_AMOUNT |
200/400 | Amount is invalid or unsupported for this meter | Send 20000-50000000 kobo (₦200-₦500,000) |
INVALID_SERVICE |
200 | The DISCO service is currently unavailable | Try again later or use another DISCO |
PROVIDER_UNAVAILABLE |
200 | Electricity provider is temporarily unavailable | Retry after a few minutes |
DUPLICATE_REQUEST |
200 | A transaction with this reference already exists | Requery the existing reference instead of retrying |
TRANSACTION_NOT_FOUND |
200/404 | The reference could not be found | Check the reference returned by the original purchase |
TRANSACTION_FAILED |
200 | The provider rejected the transaction | Check the message, then retry if the customer confirms |
TIMEOUT |
200 | Provider did not respond in time | Requery the reference before retrying to avoid double vending |
NETWORK_ERROR |
200 | Could not reach the electricity provider | Requery the reference before retrying |
Wallet Errors
| Code | HTTP | Description | Resolution |
|---|---|---|---|
WALLET_NOT_FOUND |
404 | Wallet doesn't exist for this account | Contact support to initialize wallet |
WALLET_LOCKED |
423 | Wallet is locked due to suspicious activity | Contact support to unlock |
DUPLICATE_REFERENCE |
409 | Transaction reference already used | Generate a unique reference for each transaction |
Access Code Errors
| Code | HTTP | Description | Resolution |
|---|---|---|---|
CODE_ALREADY_USED |
409 | Access code has already been used | Generate a new code for the visitor |
CODE_EXPIRED |
410 | Access code has expired | Generate a new code with valid expiry |
CODE_NOT_FOUND |
404 | Access code doesn't exist | Verify the code or generate a new one |
MAX_CODES_REACHED |
429 | Maximum active codes limit reached | Wait for existing codes to expire or delete some |
Rate Limiting
When you exceed rate limits, you'll receive a 429 response:
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after 60 seconds.",
"details": {
"retryAfter": 60,
"limit": 100,
"window": "1m"
}
}
}
Check response headers for rate limit info:
X-RateLimit-Limit: Maximum requests per windowX-RateLimit-Remaining: Requests remainingX-RateLimit-Reset: Unix timestamp when limit resets
Error Handling Best Practices
1. Check Error Codes, Not Messages
Error messages may change over time. Always use the code field
for programmatic handling:
// ✅ Good
if (error.code === 'INSUFFICIENT_BALANCE') {
promptUserToFundWallet();
}
// ❌ Bad
if (error.message.includes('insufficient')) {
promptUserToFundWallet();
}
2. Implement Retry Logic
For transient errors (5xx, DISCO_UNAVAILABLE), implement exponential backoff:
async function vendWithRetry(params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await venshack.vend(params);
} catch (error) {
if (!isRetryable(error) || i === maxRetries - 1) {
throw error;
}
await sleep(Math.pow(2, i) * 1000); // 1s, 2s, 4s
}
}
}
function isRetryable(error) {
return error.status >= 500 ||
error.code === 'DISCO_UNAVAILABLE';
}
3. Log Request IDs
Always log the requestId from error responses. This helps
support debug issues quickly:
try {
await venshack.vend(params);
} catch (error) {
console.error('Vend failed', {
code: error.code,
message: error.message,
requestId: error.requestId // Important for support
});
}
4. Handle Validation Errors Gracefully
Validation errors include details about which fields failed:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"fields": [
{ "field": "meterNumber", "error": "Must be 11-13 digits" },
{ "field": "amount", "error": "Must be at least 500" }
]
}
}
}
If you encounter persistent errors, contact support with the
requestId from the error response. We can trace exactly what
happened.