Phone number format
The rule
Section titled “The rule”Use international format: country code, then the subscriber number, digits only.
2348020000000│ └────────── subscriber number└──────────── country code (234 = Nigeria)A leading + or 00 is accepted and stripped. These are all the same number:
2348020000000 ✅ canonical+2348020000000 ✅ accepted002348020000000 ✅ acceptedThe API always returns numbers without a +, so 2348020000000 is what
you’ll get back and what you should store.
Normalising
Section titled “Normalising”Nigerian mobile numbers are 0 + 10 digits locally (08020000000), which maps
to 234 + those same 10 digits.
function toMSISDN(input) { const digits = String(input).replace(/\D/g, ''); // strip +, spaces, dashes
if (digits.startsWith('234')) return digits; // 2348020000000 if (digits.startsWith('0')) return '234' + digits.slice(1); // 08020000000 if (digits.length === 10) return '234' + digits; // 8020000000 return digits;}
toMSISDN('0802 000 0000'); // → '2348020000000'toMSISDN('+234 802 000 0000') // → '2348020000000'toMSISDN('8020000000'); // → '2348020000000'import re
def to_msisdn(raw: str) -> str: digits = re.sub(r"\D", "", str(raw)) if digits.startswith("234"): return digits if digits.startswith("0"): return "234" + digits[1:] if len(digits) == 10: return "234" + digits return digitsFor anything beyond Nigeria, use a real library — libphonenumber — rather than growing your own regex.
Strings or integers
Section titled “Strings or integers”to accepts both, so this works:
{ "to": ["2348020000000", 2348030000000] }Numeric entries are parsed digit-exactly, so a 13-digit MSISDN won’t lose precision to floating point.
That said, send strings. An MSISDN is an identifier, not a quantity — you never do arithmetic on it, and a leading zero in some other country’s format would be silently destroyed by a JSON number. Store them as strings too.
Operator routing
Section titled “Operator routing”We resolve the network from the number’s prefix, which is how cost_estimate
can price a batch before it sends:
"per_operator": { "mtn": { "recipients": 2, "cost": 6.4 }, "airtel": { "recipients": 1, "cost": 3.2 }}A number whose prefix matches no known Nigerian network lands in
unknown_operator_count. Those are rejected per-message and not charged —
but they also never arrive, so check that field:
if (batch.cost_estimate.unknown_operator_count > 0) { logger.warn({ unroutable: batch.cost_estimate.unknown_operator_count, reference_id: batch.reference_id, }, 'some recipients could not be routed');}If a whole batch is unroutable you get no_known_operators and nothing is sent.
De-duplication
Section titled “De-duplication”Recipients are de-duplicated after normalisation. If you send:
{ "to": ["2348020000000", "08020000000", "+2348020000000"] }all three normalise to the same MSISDN, so recipients_accepted comes back as
1 and you’re charged once.
This is why recipients_accepted can be lower than the length of the array you
sent — it’s not an error, and it’s worth logging when the two disagree.