Error Codes Reference
Complete reference for HTTP status codes, error response formats, module-specific error codes, and troubleshooting guidance.
HTTP status codes
Salami uses standard HTTP status codes. The table below lists every code you may encounter.
| Code |
Name |
When it occurs |
200 |
OK |
Request succeeded. Response body contains the result. |
201 |
Created |
Resource created successfully (e.g. new token, new item registration). |
400 |
Bad Request |
Malformed request syntax, missing required headers, or unparseable JSON body. |
401 |
Unauthorized |
Missing Authorization header, invalid token, expired token, or revoked token. |
403 |
Forbidden |
Token is valid but lacks the required scope, or the authenticated user does not have permission. |
404 |
Not Found |
The requested resource (payment app, transaction, SMS app, item, etc.) does not exist or does not belong to the authenticated user. |
422 |
Unprocessable Entity |
Validation failed. The request body was well-formed JSON but contained invalid data. See the errors object for field-level details. |
429 |
Too Many Requests |
Rate limit exceeded (200 requests per minute per token). Back off and retry. |
500 |
Internal Server Error |
Unexpected server error. Contact support with the request_id if available. |
502 |
Bad Gateway |
Upstream provider (KRA, M-Pesa, Airtel, SMS gateway) returned an error. The error field contains details from the provider. |
503 |
Service Unavailable |
Salami or a downstream provider is temporarily unavailable. Retry with exponential backoff. |
Standard error response format
Basic error
{
"success": false,
"error": "Human-readable error message"
}
Validation error (422)
{
"success": false,
"error": "Validation failed",
"errors": {
"KRAPIN": ["The KRAPIN field is required."],
"amount": ["The amount must be greater than 0."]
}
}
The errors object is keyed by field name. Each value is an array of validation messages for that field.
Validation error with missing fields (KRA module)
{
"success": false,
"error": "Missing required fields for PIN check",
"missing_fields": ["KRAPIN"]
}
Provider error (502)
{
"success": false,
"error": "KRA returned an error: Invalid PIN format",
"response_code": "E003"
}
The response_code field is only present when Salami can extract a structured error code from the upstream provider.
Payments error codes
General payment errors
| Error |
HTTP Code |
Cause |
Resolution |
| Payment app not found |
404 |
The {payment_app} ID in the URL does not exist or does not belong to the authenticated user |
Verify the app ID in your dashboard under Payments > Payment Apps |
| Payment app inactive |
403 |
The app exists but is disabled |
Enable the app in the dashboard |
| Transaction not found |
404 |
The {transactionId} or {transaction} does not exist for this app |
Check the transaction ID; it may belong to a different app |
| Validation failed |
422 |
Required fields missing or invalid (phone, amount, reference, etc.) |
Fix the fields listed in the errors object |
| Provider error |
500 |
The payment provider returned an unexpected error |
Check the error message, retry, or contact support |
M-Pesa result codes
These codes come directly from Safaricom and appear in callback payloads and status-check responses.
| Result Code |
Description |
User action |
0 |
Success |
Transaction completed. No action needed. |
1 |
Insufficient balance |
Customer does not have enough funds. Inform them to top up. |
1019 |
Transaction limit exceeded |
Customer's per-transaction or daily limit reached. Reduce the amount or try later. |
1032 |
Request cancelled by user |
Customer dismissed the STK Push prompt. Retry or use a different payment method. |
1037 |
Timeout waiting for user input |
Customer did not respond to the STK Push within ~30 seconds. Retry the request. |
2001 |
Invalid MSISDN |
The phone number is not a valid Safaricom number. Verify the format (254...). |
2006 |
Invalid initiator credentials |
Consumer Key, Consumer Secret, or Passkey is wrong. Update credentials in the dashboard. |
404012 |
Invalid shortcode or account reference |
The shortcode or account number is not registered. Verify in the dashboard. |
500.001.1001 |
Duplicate request |
A transaction with the same parameters was already submitted. Use a unique reference. |
400.008.02 |
Invalid receiver |
The B2C recipient phone number is invalid or not registered. |
Airtel Money error codes
| Error |
Description |
Resolution |
| Invalid PIN |
Customer entered incorrect Airtel Money PIN |
Customer should retry with correct PIN |
| Insufficient balance |
Customer or merchant balance too low |
Top up before retrying |
| KYC verification required |
Customer has not completed KYC |
Customer must complete Airtel Money KYC verification |
| Limit exceeded |
Daily or per-transaction limit reached |
Reduce amount or wait for the next day |
MTN Mobile Money error codes
| Error |
Description |
Resolution |
| Payer not found |
The phone number is not registered for MTN MoMo |
Verify the phone number |
| Insufficient balance |
Payer does not have enough funds |
Customer must top up |
| Duplicate request |
A transaction with the same external reference exists |
Use a unique reference for each request |
Equity Bank error codes
| Code |
Description |
Resolution |
EB001 |
Invalid account number |
Verify the bank account number |
EB002 |
Insufficient funds |
Top up the merchant account |
EB003 |
Account suspended |
Contact Equity Bank support |
EB004 |
Daily transaction limit exceeded |
Increase limits with the bank or wait for the next day |
SMS error codes
Send errors
| Error |
HTTP Code |
Cause |
Resolution |
| SMS app not found |
404 |
Invalid {sms_app} ID |
Check the app ID in your dashboard |
| SMS app inactive |
403 |
App is disabled |
Re-enable the app |
| Invalid phone number |
422 |
Phone number is not in valid international format |
Use E.164 format: +254712345678 |
| Empty message |
422 |
The message field is empty or missing |
Provide a non-empty message string |
| Message too long |
422 |
Message exceeds 1600 characters |
Split into multiple messages |
| Invalid recipients |
422 |
All phone numbers in the request are invalid |
Fix the recipient list |
| Insufficient credits |
402 |
SMS account balance too low |
Top up SMS credits in the dashboard |
| Invalid sender ID |
422 |
Sender ID is not approved by the carrier |
Use a registered sender ID or the default phone number |
| Blocked number |
422 |
Recipient has opted out or is on a blocklist |
Remove this number from your list |
| Provider error |
500/502 |
The SMS provider returned an error |
Check the error message and retry |
SMS delivery status codes
These statuses appear in outbox queries and delivery report callbacks.
| Status |
Description |
Is final? |
queued |
Message accepted and queued for sending |
No |
sent |
Message dispatched to the carrier network |
No |
delivered |
Carrier confirmed delivery to the handset |
Yes |
failed |
Sending failed (network error, invalid number, provider rejection) |
Yes |
rejected |
Carrier rejected the message (spam filter, invalid sender, etc.) |
Yes |
expired |
Carrier could not deliver within the validity period (phone off, out of range) |
Yes |
KRA Portal error codes
KRA ResponseCode values
When Salami proxies a request to the KRA developer.go.ke APIs, KRA returns a ResponseCode in its response. Salami maps failures to HTTP 502 and includes the code in response_code.
| ResponseCode |
Meaning |
Resolution |
00 |
Success |
Request processed successfully |
01 |
Invalid credentials |
Client ID or Client Secret is wrong. Update the KRA app credentials. |
02 |
Token expired |
KRA OAuth token expired mid-request. This is auto-retried by Salami; if persistent, re-test the app. |
03 |
Unauthorized scope |
Your KRA app does not have access to this API on the developer portal |
E001 |
Invalid request payload |
Required fields are missing or malformed |
E002 |
Taxpayer not found |
The KRA PIN does not exist in the KRA database |
E003 |
Invalid PIN format |
PIN does not match the expected pattern |
E004 |
Service temporarily unavailable |
KRA backend is down or overloaded |
E005 |
Rate limit exceeded |
Too many requests to the KRA API |
E010 |
Certificate not found |
TCC, import certificate, or excise licence number does not exist |
E011 |
Declaration not found |
The customs declaration number does not exist |
KRA validation errors
These are returned as HTTP 422 before the request reaches KRA.
| Field |
Rule |
Example error |
KRAPIN |
Required, exactly 11 characters |
The KRAPIN must be 11 characters. |
TaxpayerID |
Required string |
The TaxpayerID field is required. |
TaxpayerType |
Must be KE or FR |
The selected TaxpayerType is invalid. |
invoiceNumber |
Required string |
The invoiceNumber field is required. |
invoiceDate |
Required, format YYYY-MM-DD |
The invoiceDate does not match the format Y-m-d. |
tccNumber |
Required string |
The tccNumber field is required. |
DeclarationNo |
Required string |
The DeclarationNo field is required. |
ObligationCode |
Must be 1 through 8 |
The selected ObligationCode is invalid. |
Month |
Exactly 2 characters (01-12) |
The Month must be 2 characters. |
Year |
Exactly 4 characters |
The Year must be 4 characters. |
eTIMS OSCU error codes
eTIMS resultCd values
KRA's eTIMS OSCU system returns a resultCd in every response. 000 means success; anything else is an error.
| resultCd |
Meaning |
Resolution |
000 |
Success |
Request processed and accepted by KRA |
001 |
Invalid request |
Request structure is malformed. Check required fields. |
002 |
Authentication failed |
TIN, BHFID, or device serial is wrong. Re-initialize the device. |
003 |
Device not initialized |
The OSCU device has not been registered with KRA |
004 |
Device not authorized |
The device is registered but not authorized for this operation |
005 |
Duplicate invoice |
An invoice with this number has already been submitted |
006 |
Item not found |
The item code referenced in the sale does not exist in KRA's registry |
007 |
Invalid tax calculation |
Tax amounts do not match the expected values for the given tax type and rate |
008 |
Invalid date format |
Date field not in the expected format |
009 |
Invalid customer TIN |
Customer TIN is not registered with KRA |
010 |
Stock insufficient |
Attempted to sell more than available stock |
011 |
Invalid receipt type |
Receipt type code is not valid |
012 |
Server error |
KRA eTIMS backend encountered an internal error |
013 |
Service maintenance |
eTIMS is undergoing scheduled maintenance |
eTIMS validation errors (Salami-side)
These are returned as HTTP 422 before the request is forwarded to KRA.
| Field |
Rule |
Example error |
tin |
Required string |
The tin field is required. |
bhf_id |
Required string |
The bhf_id field is required. |
dvc_srl_no |
Required string |
The dvc_srl_no field is required. |
date |
Required, valid date |
The date field is required. |
trader_invoice_number |
Required string |
The trader_invoice_number field is required. |
receipt_type_code |
Must be S, P, or T |
The selected receipt_type_code is invalid. |
sales_tax_summary |
Required array |
The sales_tax_summary field is required. |
sales_tax_summary.taxable_amount_a |
Required numeric |
The sales_tax_summary.taxable_amount_a field is required. |
sales_tax_summary.tax_rate_b |
Required numeric |
The sales_tax_summary.tax_rate_b field is required. |
sales_tax_summary.tax_amount_b |
Required numeric |
The sales_tax_summary.tax_amount_b field is required. |
customer_tin |
Optional string (validated if present) |
The customer_tin must be a string. |
callback_url |
Optional, must be valid URL |
The callback_url must be a valid URL. |
eTIMS tax type codes
| Code |
Name |
Rate |
A |
Excise Duty |
0% (exempt) |
B |
VAT (standard) |
16% |
C |
VAT (zero-rated) |
0% |
D |
Non-VAT (exempt) |
0% |
E |
VAT (tourism levy) |
2% |
eTIMS payment type codes
| Code |
Description |
01 |
Cash |
02 |
Credit |
03 |
Cheque |
04 |
Bank transfer |
05 |
Mobile money |
06 |
Other |
General API errors
These apply across all modules.
| Error |
HTTP Code |
Cause |
Resolution |
Unauthenticated. |
401 |
No Bearer token or token is invalid/expired/revoked |
Include a valid Authorization: Bearer <token> header |
| Token expired |
401 |
The token's expires_at date has passed |
Create a new token |
| Insufficient permissions |
403 |
Token does not have the required scope for this endpoint |
Create a token with the appropriate scope (see Authentication) |
kra_app_id is required |
422 |
KRA endpoint called without specifying an app |
Add X-KRA-App-Id header or kra_app_id parameter |
| Resource not found |
404 |
The ID in the URL does not match any record |
Verify the resource exists and belongs to your account |
| Method not allowed |
405 |
Wrong HTTP method (e.g. GET on a POST-only endpoint) |
Check the API reference for the correct method |
| Too many requests |
429 |
Exceeded 200 requests/minute for this token |
Implement exponential backoff and request caching |
| Internal server error |
500 |
Unhandled exception on the server |
Contact support@dgl.co.ke with the request details |
| Bad gateway |
502 |
Upstream provider (KRA, M-Pesa, etc.) returned an error |
Check the error and response_code fields. Retry if transient. |
| Service unavailable |
503 |
Salami or a provider is temporarily down |
Retry with backoff. Check https://status.salami.dgl.co.ke |
Rate limiting
| Limit |
Value |
| Requests per minute per token |
200 |
| HTTP code when exceeded |
429 Too Many Requests |
| Retry header |
Retry-After (seconds until the window resets) |
When you receive a 429, wait for the number of seconds indicated in the Retry-After header before retrying.
Troubleshooting flowchart
Request failed
|
+-- HTTP 401?
| Check Authorization header. Is the token correct, not expired, not revoked?
|
+-- HTTP 403?
| Check token scopes. Does the token have the scope required by this endpoint?
|
+-- HTTP 404?
| Does the resource (app ID, transaction ID, item code) exist and belong to you?
|
+-- HTTP 422?
| Read the `errors` object. Fix the validation issues and retry.
|
+-- HTTP 429?
| You are sending too many requests. Wait, then retry with backoff.
|
+-- HTTP 500?
| Server bug. Log the full response and contact support@dgl.co.ke.
|
+-- HTTP 502?
| Upstream provider error. Check `error` and `response_code`.
| Is the provider down? Retry after a delay.
|
+-- HTTP 503?
Service unavailable. Check status.salami.dgl.co.ke. Retry later.
Error handling best practices
1. Always check the success field
$response = json_decode($httpResponse->getBody(), true);
if ($response['success'] === true) {
$data = $response['data'];
// Process successful response
} else {
$error = $response['error'] ?? $response['message'] ?? 'Unknown error';
Log::error('Salami API error', [
'error' => $error,
'response_code' => $response['response_code'] ?? null,
'errors' => $response['errors'] ?? null,
]);
}
2. Implement exponential backoff for transient errors
async function callWithRetry(fn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const status = error.response?.status;
const isRetryable = [429, 500, 502, 503].includes(status);
if (!isRetryable || attempt === maxRetries - 1) {
throw error;
}
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
3. Map error codes to user-friendly messages
USER_MESSAGES = {
401: "Your session has expired. Please log in again.",
403: "You do not have permission to perform this action.",
422: "Please check your input and try again.",
429: "Too many requests. Please wait a moment.",
500: "Something went wrong. Please try again later.",
502: "The payment provider is temporarily unavailable. Please retry.",
503: "Service is under maintenance. Please try again shortly.",
}
def get_user_message(status_code, error_detail=None):
return USER_MESSAGES.get(status_code, "An unexpected error occurred.")
4. Log everything
For every failed API call, log:
- The HTTP method and URL
- The request body (redact sensitive fields like PINs and tokens)
- The HTTP status code
- The full response body
- A timestamp
This makes debugging with Salami support significantly faster.
Getting help
- Check this reference -- most errors are documented above.
- Review the endpoint documentation -- ensure your request matches the expected format.
- Test with curl -- isolate the issue from your application code.
- Check the status page -- https://status.salami.dgl.co.ke for outage information.
- Contact support -- email support@dgl.co.ke with the error response, request details, and timestamps.
Back to documentation home