<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: A A</title>
    <description>The latest articles on DEV Community by A A (@aadeveloper).</description>
    <link>https://dev.to/aadeveloper</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4160174%2Ff5944533-26f2-4008-b804-a2ac51118f34.png</url>
      <title>DEV Community: A A</title>
      <link>https://dev.to/aadeveloper</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aadeveloper"/>
    <language>en</language>
    <item>
      <title>A valid phone number is not necessarily a usable phone number</title>
      <dc:creator>A A</dc:creator>
      <pubDate>Wed, 07 Oct 2026 14:18:24 +0000</pubDate>
      <link>https://dev.to/aadeveloper/a-valid-phone-number-is-not-necessarily-a-usable-phone-number-5f01</link>
      <guid>https://dev.to/aadeveloper/a-valid-phone-number-is-not-necessarily-a-usable-phone-number-5f01</guid>
      <description>&lt;h1&gt;
  
  
  A valid phone number is not necessarily a usable phone number
&lt;/h1&gt;

&lt;p&gt;Most applications treat phone number validation as a boolean:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;js&lt;br&gt;
if (isValidPhoneNumber(number)) {&lt;br&gt;
  // good to go&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is useful, but it answers a much narrower question than it looks like it does.&lt;/p&gt;

&lt;p&gt;A number can be perfectly valid according to a country's numbering plan and still be the wrong kind of number for what your application is about to do.&lt;/p&gt;

&lt;p&gt;If you are building OTPs, SMS notifications, account recovery, fraud checks or outbound calling, it helps to think of phone validation as several separate layers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 1: can the number be parsed?
&lt;/h2&gt;

&lt;p&gt;The first problem is simply turning whatever the user typed into a consistent format.&lt;/p&gt;

&lt;p&gt;These could all represent the same UK number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;07400 123456
07400123456
+44 7400 123456
0044 7400 123456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, you generally want to reduce that to E.164:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+447400123456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Libraries such as &lt;code&gt;libphonenumber-js&lt;/code&gt; handle most of this for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;parsePhoneNumber&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;libphonenumber-js/max&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizePhone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultCountry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parsePhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultCountry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;e164&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets you a normalized number and checks it against numbering-plan metadata.&lt;/p&gt;

&lt;p&gt;But it still does not tell you very much about the actual line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: is it valid according to the numbering plan?
&lt;/h2&gt;

&lt;p&gt;This is the distinction that often gets lost.&lt;/p&gt;

&lt;p&gt;A phone-number library can usually tell you things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+447400123456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Does the length make sense?&lt;/li&gt;
&lt;li&gt;Does the prefix fit the country's numbering plan?&lt;/li&gt;
&lt;li&gt;Can it be normalized?&lt;/li&gt;
&lt;li&gt;Which country does it appear to belong to?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is static validation.&lt;/p&gt;

&lt;p&gt;It does not mean somebody currently owns the number.&lt;/p&gt;

&lt;p&gt;It also does not automatically mean the number can receive an SMS.&lt;/p&gt;

&lt;p&gt;And it does not necessarily tell you which network serves it today.&lt;/p&gt;

&lt;p&gt;For plenty of applications, static validation is enough. A contact form probably does not need much more.&lt;/p&gt;

&lt;p&gt;An authentication system probably does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: what type of line is it?
&lt;/h2&gt;

&lt;p&gt;Suppose two numbers both pass your validation library.&lt;/p&gt;

&lt;p&gt;One is a mobile number.&lt;/p&gt;

&lt;p&gt;The other is a fixed line.&lt;/p&gt;

&lt;p&gt;From the point of view of &lt;code&gt;isValid()&lt;/code&gt;, both may be completely legitimate phone numbers.&lt;/p&gt;

&lt;p&gt;From the point of view of an SMS workflow, they are very different.&lt;/p&gt;

&lt;p&gt;This is where line-type information becomes useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+447400123456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"line_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mobile"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical classifications include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mobile
fixed_line
voip
toll_free
premium_rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not the label itself. It is what your application does with it.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sendSms&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fixed_line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;askForAnotherNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;applyYourOwnRules&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a much better model than treating every syntactically valid number identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: which network is it actually on?
&lt;/h2&gt;

&lt;p&gt;There is another complication: number portability.&lt;/p&gt;

&lt;p&gt;Someone can start with one mobile operator and later move their number to another.&lt;/p&gt;

&lt;p&gt;The phone number does not change.&lt;/p&gt;

&lt;p&gt;That means the prefix can tell you something about how a number was originally allocated, but it should not automatically be treated as the current carrier.&lt;/p&gt;

&lt;p&gt;So there are really two different pieces of information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;numbering-plan information
current network information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They come from different places.&lt;/p&gt;

&lt;p&gt;A static library is extremely useful for the first.&lt;/p&gt;

&lt;p&gt;Current carrier information normally requires a lookup against telecom data.&lt;/p&gt;

&lt;p&gt;This distinction matters whenever your application makes decisions based on the serving network rather than merely formatting the number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation is still not reachability
&lt;/h2&gt;

&lt;p&gt;There is one final distinction worth keeping in mind.&lt;/p&gt;

&lt;p&gt;Even after you know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valid: true
line_type: mobile
carrier: Example Mobile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you still have not mathematically proven that your next SMS will be delivered.&lt;/p&gt;

&lt;p&gt;Phones can be switched off. Accounts can be suspended. Networks can reject traffic. Numbers can change state.&lt;/p&gt;

&lt;p&gt;So I find it useful to keep these concepts separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;syntax
    ↓
numbering-plan validity
    ↓
line type
    ↓
current carrier
    ↓
reachability / delivery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer answers a different question.&lt;/p&gt;

&lt;p&gt;Trying to compress all of them into a single field called &lt;code&gt;valid&lt;/code&gt; usually creates bad assumptions elsewhere in the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the API around the decision
&lt;/h2&gt;

&lt;p&gt;Instead of returning this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer responses that expose why the application should make a decision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"normalized"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+447400123456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"line_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mobile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"carrier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Mobile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then your business logic stays explicit.&lt;/p&gt;

&lt;p&gt;Your signup service might care about &lt;code&gt;line_type&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your routing service might care about &lt;code&gt;carrier&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your fraud system might care about portability or other risk signals.&lt;/p&gt;

&lt;p&gt;Your address book might only care that the number was normalized successfully.&lt;/p&gt;

&lt;p&gt;There is no universal definition of a "good phone number". It depends on what you are about to do with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I ran into this
&lt;/h2&gt;

&lt;p&gt;Disclosure: I work on &lt;a href="https://www.carrierreveal.com/" rel="noopener noreferrer"&gt;Carrier Reveal&lt;/a&gt;, which provides carrier, line-type and portability information for phone numbers.&lt;/p&gt;

&lt;p&gt;Building around this problem made me realize that the mistake is usually not bad phone-number validation. It is asking one validation step to answer questions it was never designed to answer.&lt;/p&gt;

&lt;p&gt;A library such as libphonenumber is very good at understanding telephone numbering plans.&lt;/p&gt;

&lt;p&gt;Use it for that.&lt;/p&gt;

&lt;p&gt;Then add live data only when your application actually needs to know something about the number as it exists today.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What a phone number tells you before you send an SMS</title>
      <dc:creator>A A</dc:creator>
      <pubDate>Sat, 03 Oct 2026 16:56:03 +0000</pubDate>
      <link>https://dev.to/aadeveloper/what-a-phone-number-tells-you-before-you-send-an-sms-cji</link>
      <guid>https://dev.to/aadeveloper/what-a-phone-number-tells-you-before-you-send-an-sms-cji</guid>
      <description>&lt;p&gt;If you send an SMS to a landline it disappears. No bounce, no error you can act on, and you are still billed for it. The same goes for a disconnected number and for plenty of VoIP numbers. The fix is to find out what a number actually is before you send to it.&lt;/p&gt;

&lt;p&gt;Here is what a phone number can tell you, and what it cannot.&lt;/p&gt;

&lt;p&gt;Start by normalizing to E.164&lt;br&gt;
Before anything else, get the number into E.164: a plus sign, the country code, then the national number, with no spaces, dashes or brackets.&lt;/p&gt;

&lt;p&gt;07400 123456      -&amp;gt;  +447400123456&lt;br&gt;
(202) 555-0143    -&amp;gt;  +12025550143&lt;br&gt;
Two things go wrong at this step.&lt;/p&gt;

&lt;p&gt;First, a national number with no country code is ambiguous. 07400123456 is a UK mobile, but a bare 7400123456 could belong to several countries. You need a default country to parse against, usually the country of the account that submitted the number.&lt;/p&gt;

&lt;p&gt;Second, people paste extensions, letters and stray leading zeros. Anything that fails to parse should stop here rather than being sent anywhere downstream.&lt;/p&gt;

&lt;p&gt;Line type is the decision you actually care about&lt;br&gt;
Once you have a valid E.164 number, the useful question is what kind of line it is.&lt;/p&gt;

&lt;p&gt;mobile: can receive SMS&lt;/p&gt;

&lt;p&gt;fixed_line: cannot receive SMS in most countries&lt;/p&gt;

&lt;p&gt;voip: sometimes can, often cannot, and a meaningful fraud signal at signup&lt;/p&gt;

&lt;p&gt;toll_free, premium_rate, pager: do not send&lt;/p&gt;

&lt;p&gt;A validation library on its own will not tell you this reliably. Number ranges are allocated by type, so a library that knows only the allocation table tells you what the range was issued as, not what the number is today.&lt;/p&gt;

&lt;p&gt;Portability is why the allocated range is not enough&lt;br&gt;
Most countries let a subscriber keep their number when they switch network. That is mobile number portability, and it means the carrier that was allocated a range is often no longer the carrier serving a given number in it.&lt;/p&gt;

&lt;p&gt;If you route SMS by allocated range in a country with portability, you pay the wrong carrier's rate and your delivery rate quietly drops. Getting the current network needs a live lookup, not a static table.&lt;/p&gt;

&lt;p&gt;What a lookup gives you back&lt;br&gt;
A useful response is more than valid: true. A real one looks like this:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "input_phone": "+447400123456",&lt;br&gt;
  "normalized_e164": "+447400123456",&lt;br&gt;
  "valid": true,&lt;br&gt;
  "country": "GB",&lt;br&gt;
  "country_name": "United Kingdom",&lt;br&gt;
  "region": "Europe",&lt;br&gt;
  "line_type": "mobile",&lt;br&gt;
  "carrier": "EE",&lt;br&gt;
  "sms_ready": true,&lt;br&gt;
  "call_ready": true,&lt;br&gt;
  "reason": "valid_mobile",&lt;br&gt;
  "cached": false,&lt;br&gt;
  "checked_at": "2026-06-30T10:04:11.204Z",&lt;br&gt;
  "credits_debited": 1&lt;br&gt;
}&lt;br&gt;
The fields worth building logic on:&lt;/p&gt;

&lt;p&gt;valid together with reason, so you know why a number failed and not just that it did&lt;/p&gt;

&lt;p&gt;line_type, the routing decision&lt;/p&gt;

&lt;p&gt;carrier, the current network after portability&lt;/p&gt;

&lt;p&gt;sms_ready and call_ready, if you would rather not write that logic yourself&lt;/p&gt;

&lt;p&gt;cached, so you can tell a fresh lookup from a cached one&lt;/p&gt;

&lt;p&gt;Three places the check pays for itself&lt;br&gt;
At signup. Reject a number that cannot receive your OTP before the user presses send, instead of after they wait for a code that was never going to arrive.&lt;/p&gt;

&lt;p&gt;Before a campaign. Split the list on sms_ready and suppress the rest. You pay per message either way.&lt;/p&gt;

&lt;p&gt;Before a dialler run. call_ready and line_type keep agents off numbers that will not connect.&lt;/p&gt;

&lt;p&gt;Cache, and deduplicate first&lt;br&gt;
Lookups cost money per number, so cache them. The network a number sits on does not change often, and a 24 hour cache absorbs most repeat traffic in practice.&lt;/p&gt;

&lt;p&gt;Deduplicate inside a batch before you send it, too. The same number turns up several times in one CSV more often than you would expect, and each copy would otherwise be a separate paid lookup.&lt;/p&gt;

&lt;p&gt;Trying it&lt;br&gt;
Disclosure: this is our own product. We built Carrier Reveal around exactly this, so treat the example below as one implementation rather than a recommendation. The API is one POST per number, or up to 100 in a batch:&lt;/p&gt;

&lt;p&gt;curl -X POST &lt;a href="https://api.phonelistcleaner.com/v1/lookup" rel="noopener noreferrer"&gt;https://api.phonelistcleaner.com/v1/lookup&lt;/a&gt; \&lt;br&gt;
  -H "Authorization: Bearer plc_" \&lt;br&gt;
  -H "Content-Type: application/json" \&lt;br&gt;
  -d '{"phone":"+447400123456","defaultCountry":"GB"}'&lt;br&gt;
The docs are at Carrier Lookup API | Carrier Reveal. Twilio Lookup and Numverify solve the same problem if you would rather use something you already have.&lt;/p&gt;

&lt;p&gt;The general point holds whatever you use: normalize first, check the line type, and never trust the allocated range in a country with number portability.&lt;/p&gt;

</description>
      <category>api</category>
      <category>sms</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
