Two terminal windows showing OTP API error resolution - red error codes transforming to green success responses

OTP API Errors: 10 Common Issues and How to Fix Them (2026)

You’ve wired up the Arkesel OTP API endpoint. You fire off your first request. Instead of a delivered OTP, you get this:

{
  "code": "1004",
  "message": "SMS gateway not active or credential not found"
}

Now what?

This OTP API integration troubleshooting guide maps every Arkesel OTP API error code to its root cause, shows the exact API response you’re seeing, and gives you working code to fix it. Whether you’re debugging a failed OTP generation for the first time or tracking down a verification failure in production, find your error code and resolve it in minutes.

Quick Setup Reference: A Known-Good Baseline

Before troubleshooting, make sure your baseline implementation is correct. Compare your code against these working examples.

Generate OTP

The Generate OTP endpoint requires three parameters: phone_number, sender_id, and a message containing the %otp_code% placeholder. The API replaces %otp_code% with the actual code before delivery.

cURL:

curl -X POST https://sms.arkesel.com/api/otp/generate \
  -H "api-key: $ARKESEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "233241234567",
    "sender_id": "YourApp",
    "message": "Your verification code is %otp_code%. It expires in 5 minutes."
  }'

Python:

import os
import requests

api_key = os.environ["ARKESEL_API_KEY"]

response = requests.post(
    "https://sms.arkesel.com/api/otp/generate",
    headers={
        "api-key": api_key,
        "Content-Type": "application/json"
    },
    json={
        "phone_number": "233241234567",
        "sender_id": "YourApp",
        "message": "Your verification code is %otp_code%. It expires in 5 minutes."
    }
)

data = response.json()
if data.get("code") == "1000":
    print("OTP sent successfully")
else:
    print(f"Error {data['code']}: {data['message']}")

Node.js:

const response = await fetch("https://sms.arkesel.com/api/otp/generate", {
  method: "POST",
  headers: {
    "api-key": process.env.ARKESEL_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    phone_number: "233241234567",
    sender_id: "YourApp",
    message: "Your verification code is %otp_code%. It expires in 5 minutes."
  })
});

const data = await response.json();
if (data.code === "1000") {
  console.log("OTP sent successfully");
} else {
  console.error(`Error ${data.code}: ${data.message}`);
}

Success response:

{
  "code": "1000",
  "message": "Successful, Message delivered"
}

Verify OTP

The Verify endpoint takes the phone_number (must match the generation request) and the code the user entered.

cURL:

curl -X POST https://sms.arkesel.com/api/otp/verify \
  -H "api-key: $ARKESEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "233241234567",
    "code": "482910"
  }'

Python:

response = requests.post(
    "https://sms.arkesel.com/api/otp/verify",
    headers={
        "api-key": os.environ["ARKESEL_API_KEY"],
        "Content-Type": "application/json"
    },
    json={
        "phone_number": "233241234567",
        "code": "482910"
    }
)

data = response.json()
if data.get("code") == "1100":
    print("OTP verified successfully")
else:
    print(f"Verification failed — {data['code']}: {data['message']}")

Success response:

{
  "code": "1100",
  "message": "Successful"
}

If your baseline code matches these examples and still fails, your error code will tell you exactly what went wrong. Find it below.

Need the complete API reference? Visit the Arkesel API documentation.

Authentication Errors: 401 and 422

Authentication errors fire before the API even processes your OTP request. These are the first thing to rule out.

HTTP 401 — Missing or Invalid API Key

Error response:

{
  "code": "401",
  "message": "Authentication failed (Missing or Invalid API key)"
}

Common causes:

  • API key not included in the request headers
  • Using an unsupported header name (e.g., Authorization: Bearer)
  • API key copied with leading or trailing whitespace
  • Key revoked or regenerated in your Arkesel dashboard

The fix:

import os

api_key = os.environ["ARKESEL_API_KEY"]

headers = {
    "api-key": api_key,
    "Content-Type": "application/json"
}

The Arkesel OTP API accepts two authentication header names: api-key and X-API-Token. Both are valid — use whichever your codebase standardizes on. The header name is case-insensitive for HTTP, but use the lowercase hyphenated form (api-key) or the exact casing (X-API-Token) to be safe. Double-check your Arkesel dashboard for the current active key.

HTTP 422 — Validation Error

Error response:

{
  "code": "422",
  "message": "Validation error (Missing required field)"
}

This means your request body is missing a required field. Check that you’re sending phone_number, sender_id, and message for generation — or phone_number and code for verification.

OTP Generation Errors: Codes 1001–1009 and 1011

These OTP API error codes return with HTTP 200 but contain an error code in the response body. Your code must check the response code field, not just the HTTP status.

1001 — Validation Error

{
  "code": "1001",
  "message": "Validation error (Missing required field)"
}

A required parameter is missing from your request body. The Generate OTP endpoint requires phone_number, sender_id, and message. Check for typos in your JSON keys.

// Wrong — missing sender_id
const body = {
  phone_number: "233241234567",
  message: "Your code is %otp_code%"
};

// Correct — all three required fields
const body = {
  phone_number: "233241234567",
  sender_id: "YourApp",
  message: "Your code is %otp_code%"
};

1002 — Missing %otp_code% Placeholder

{
  "code": "1002",
  "message": "Message must contain a slot for otp code, like %otp_code%"
}

This is the most common first-integration mistake. Your message field must contain the literal string %otp_code%. The API replaces it with the generated code before sending.

# Wrong — no placeholder
message = "Your verification code is 1234"

# Wrong — wrong placeholder format
message = "Your code is {otp_code}"
message = "Your code is {{OTP_CODE}}"

# Correct — exact placeholder
message = "Your verification code is %otp_code%. It expires in 5 minutes."

The placeholder is case-sensitive and must include the percent signs: %otp_code%.

1003 — Blocked Sender ID

{
  "code": "1003",
  "message": "This Sender ID have Blocked By Administrator"
}

Your sender ID has been blocked at the platform level. This typically happens when a sender ID violates carrier naming guidelines or gets flagged for misuse.

Resolution: Contact Arkesel support to verify your sender ID status and request reactivation or register a new one.

1004 — Gateway Not Active (“Failed to Generate OTP”)

{
  "code": "1004",
  "message": "SMS gateway not active or credential not found"
}

This is the error behind the search query “failed to generate OTP.” It means one of two things:

  1. Your SMS gateway is not activated. Log into your Arkesel dashboard and confirm the OTP/SMS gateway is enabled for your account.
  2. Your credentials don’t match an active account. Regenerate your API key from the dashboard and update your environment variable.
# Verify your API key is set and not empty
echo $ARKESEL_API_KEY

# Test with a fresh key from your dashboard
export ARKESEL_API_KEY="your-new-key-from-dashboard"

1005 — Invalid Phone Number

{
  "code": "1005",
  "message": "Invalid phone number"
}

The phone number format is wrong. Use international format with the country code, no leading plus sign, no spaces, no dashes.

# Wrong formats
phone = "0241234567"       # Local format, missing country code
phone = "+233241234567"    # Leading plus sign
phone = "233 24 123 4567"  # Spaces
phone = "233-24-123-4567"  # Dashes

# Correct format
phone = "233241234567"     # Country code + number, digits only

1006 — Country Not Supported

{
  "code": "1006",
  "message": "OTP is not allowed in your Country"
}

OTP delivery is not available for the destination country. Verify that the country code in your phone number is correct. If you’re targeting a supported region and still see this error, reach out to Arkesel support to confirm coverage for your destination.

1007 / 1008 — Insufficient Balance

{
  "code": "1007",
  "message": "Insufficient balance"
}

Your Arkesel account balance is too low to send the OTP. Top up your account through the Arkesel dashboard or set up balance alerts to catch this before it hits production.

For production systems, build a balance check into your monitoring:

def send_otp(phone_number, sender_id, message):
    response = requests.post(
        "https://sms.arkesel.com/api/otp/generate",
        headers={
            "api-key": os.environ["ARKESEL_API_KEY"],
            "Content-Type": "application/json"
        },
        json={
            "phone_number": phone_number,
            "sender_id": sender_id,
            "message": message
        }
    )
    data = response.json()

    if data.get("code") in ("1007", "1008"):
        notify_ops_team("Arkesel balance depleted — OTP delivery blocked")
        raise Exception("OTP service unavailable: insufficient balance")

    return data

1009 — Voice Message Too Long

{
  "code": "1009",
  "message": "You can not send more than 500 characters using voice medium"
}

When using "medium": "voice" for voice OTP delivery, your message must stay under 500 characters. Keep voice messages concise.

# Wrong — message too long for voice delivery
response = requests.post(
    "https://sms.arkesel.com/api/otp/generate",
    headers={"api-key": os.environ["ARKESEL_API_KEY"], "Content-Type": "application/json"},
    json={
        "phone_number": "233241234567",
        "sender_id": "YourApp",
        "message": "A very long message..." * 50,  # Exceeds 500 chars
        "medium": "voice"
    }
)

# Correct — short, clear voice message
response = requests.post(
    "https://sms.arkesel.com/api/otp/generate",
    headers={"api-key": os.environ["ARKESEL_API_KEY"], "Content-Type": "application/json"},
    json={
        "phone_number": "233241234567",
        "sender_id": "YourApp",
        "message": "Your verification code is %otp_code%.",
        "medium": "voice"
    }
)

1011 — Internal Error

{
  "code": "1011",
  "message": "Internal error"
}

A server-side issue on Arkesel’s end. Retry with exponential backoff:

import time

def send_otp_with_retry(phone_number, sender_id, message, max_retries=3):
    for attempt in range(max_retries):
        response = requests.post(
            "https://sms.arkesel.com/api/otp/generate",
            headers={
                "api-key": os.environ["ARKESEL_API_KEY"],
                "Content-Type": "application/json"
            },
            json={
                "phone_number": phone_number,
                "sender_id": sender_id,
                "message": message
            }
        )
        data = response.json()

        if data.get("code") != "1011":
            return data

        wait = 2 ** attempt
        print(f"Internal error, retrying in {wait}s...")
        time.sleep(wait)

    raise Exception("OTP generation failed after retries — contact Arkesel support")

If the error persists after retries, contact Arkesel support.

OTP Verification Errors: Codes 1101–1106

These errors occur when verifying the OTP code a user entered. The most common issue: the phone number in your verify request doesn’t match the one used during generation.

1101 — Missing Required Field

{
  "code": "1101",
  "message": "Validation error (Missing required field)"
}

The Verify endpoint requires both phone_number and code. Make sure neither is null or empty.

// Wrong — missing code field
const body = { phone_number: "233241234567" };

// Correct
const body = {
  phone_number: "233241234567",
  code: "482910"
};

1102 / 1103 — Invalid Phone Number

{
  "code": "1102",
  "message": "Invalid phone number"
}

Same format rules as generation: international format, digits only, no plus sign. Critically, the number must exactly match the one used in the Generate request.

# Generation used this number:
generate_phone = "233241234567"

# Verification must use the exact same number:
verify_phone = "233241234567"  # Correct
verify_phone = "0241234567"    # Wrong — local format won't match

Store the phone number from the generation step and reuse it exactly for verification.

1104 — Invalid Code (OTP Verification Failed)

{
  "code": "1104",
  "message": "Invalid code"
}

The OTP code doesn’t match. Possible causes:

  • User mistyped the code
  • Your app is sending a different code than what the user entered
  • The code was already used (OTPs are single-use)

Build in user-friendly retry logic:

def verify_otp(phone_number, user_input, max_attempts=3):
    for attempt in range(max_attempts):
        response = requests.post(
            "https://sms.arkesel.com/api/otp/verify",
            headers={
                "api-key": os.environ["ARKESEL_API_KEY"],
                "Content-Type": "application/json"
            },
            json={
                "phone_number": phone_number,
                "code": user_input
            }
        )
        data = response.json()

        if data.get("code") == "1100":
            return {"verified": True}

        if data.get("code") == "1104":
            remaining = max_attempts - attempt - 1
            if remaining > 0:
                user_input = prompt_user(f"Invalid code. {remaining} attempts remaining.")
            else:
                return {"verified": False, "reason": "max attempts exceeded"}

    return {"verified": False}

1105 — Code Expired

{
  "code": "1105",
  "message": "Code has expired"
}

The OTP code has passed its expiry window. Generate a new one. In your UI, show a clear “Resend OTP” button and communicate the time limit to users.

const data = await verifyOtp(phoneNumber, userCode);

if (data.code === "1105") {
  const resend = await generateOtp(phoneNumber, senderId, message);
  if (resend.code === "1000") {
    showMessage("Code expired. A new code has been sent.");
  }
}

1106 — Internal Error

{
  "code": "1106",
  "message": "Internal error"
}

Server-side issue during verification. Apply the same retry logic as error 1011. If persistent, contact Arkesel support.

Delivery Issues That Aren’t API Errors

Sometimes the API returns code 1000 (success), but the user never receives the OTP. No error code to debug — just silence. Here’s what to check.

Phone number format. The OTP was sent, but to the wrong number. Double-check the country code. 0241234567 (local) vs. 233241234567 (international) will route to different destinations — or nowhere.

Carrier filtering. Some carriers filter messages from unregistered sender IDs. If your sender ID is new, verify it’s approved for the destination network. MTN, Vodafone, and AirtelTigo each have their own registration requirements.

Message content. Messages that look like spam — ALL CAPS, excessive special characters, or suspicious keywords — may get filtered by carrier-level spam detection.

Network delays. During peak traffic or network congestion, SMS delivery can take 30–90 seconds. Build a reasonable timeout into your UX before showing a “Resend” option.

Voice OTP as a fallback. If SMS delivery is unreliable for a particular number or region, use voice OTP delivery by setting "medium": "voice" in your generate request. Keep the message under 500 characters. Learn more about SMS vs voice for business communication channels.

Error Code Quick Reference

OTP Generation (POST /api/otp/generate)

CodeMessageCommon CauseFix
1000Successful, Message deliveredOTP sent. No action needed.
1001Validation errorMissing phone_number, sender_id, or messageInclude all three required fields
1002Missing %otp_code% placeholdermessage field lacks %otp_code%Add %otp_code% to your message string
1003Sender ID blockedSender ID flagged or deregisteredContact Arkesel support
1004Gateway not activeAccount gateway disabled or invalid credentialsActivate gateway in dashboard; verify API key
1005Invalid phone numberWrong format (local, spaces, dashes)Use international format, digits only
1006Country not allowedOTP not available for destination countryVerify country code; contact support
1007Insufficient balanceAccount balance depletedTop up via Arkesel dashboard
1008Insufficient balanceAccount balance depletedTop up via Arkesel dashboard
1009Voice message too longMessage exceeds 500 characters with medium: voiceShorten message to under 500 characters
1011Internal errorServer-side issueRetry with backoff; contact support if persistent

OTP Verification (POST /api/otp/verify)

CodeMessageCommon CauseFix
1100SuccessfulOTP verified. No action needed.
1101Validation errorMissing phone_number or codeInclude both required fields
1102Invalid phone numberWrong format or doesn’t match generationUse exact same number from generate request
1103Invalid phone numberWrong format or doesn’t match generationUse exact same number from generate request
1104Invalid codeMistyped, already used, or wrong codePrompt user to re-enter; resend OTP if needed
1105Code has expiredOTP exceeded its time-to-liveGenerate a new OTP and prompt user
1106Internal errorServer-side issueRetry with backoff; contact support if persistent

HTTP-Level Errors

CodeMessageFix
401Authentication failedVerify api-key or X-API-Token header with valid key
422Validation errorCheck request body for missing required fields
500Internal errorRetry; contact support if persistent

Start Building With Arkesel OTP

Every error code in the Arkesel OTP API maps to a specific, fixable issue. Bookmark this reference for your next debugging session.

For the complete endpoint reference, visit the Arkesel API documentation. To explore SMS, voice, and email verification options, see the Phone Number Verification product page. For broader implementation patterns, read our guide on implementing OTP APIs in modern applications.

Ready to integrate? Create your Arkesel account and get your API key in minutes. If you’re starting from scratch, follow the OTP API setup tutorial for a step-by-step walkthrough.

Popular Posts

Easter is one of the busiest seasons for businesses in Ghana. Retail stores, restaurants, hotels, and online businesses all compete for customer attention during this period. To stand out, many companies use SMS campaigns. They

Holiday SMS & USSD Campaign Guide: A 12-Month Playbook for African Businesses Every December, mobile money platforms across Africa strain under record transaction volumes as urban workers send remittances home for the festive season. In

10 USSD menu design best practices to boost completion rates. Covers session timeouts, error handling, security, accessibility, and menu personalization.
Scroll to Top