Use case

Number checks for OTP providers and SMS resellers

Check numbers before routing OTP and A2P traffic: live HLR status, current network after porting, line type and messaging-app registration, real time or bulk.

Last updated

A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.

OTP providers, verification platforms and SMS resellers check a number before routing it so they don't pay for codes and messages that can't arrive. One request returns live network status (HLR), the network a ported number is on now (MNP), the line type, and whether the number is registered on WhatsApp or Telegram for a channel fallback. Checks run in real time per message or in bulk per list, and unknown answers are free.

Why check numbers before you route traffic?

A provider in the middle of the message path pays for every attempt, whether or not the code is entered. Four things eat margin:

  • Routing by the original prefix. After a number is ported, its prefix still points at the old network. A message routed that way may take a more expensive path, be rejected or arrive late. The MNP lookup returns the current mcc_mnc, so you route by the network that serves the number today. See how to check if a number is ported.
  • Numbers that aren't live. Unassigned numbers can never enter a code, and a switched-off phone receives it after it has expired. The HLR lookup answers reachable, unreachable or invalid before you send.
  • Lines that can't receive a text. Fixed lines, some VoIP ranges and premium-rate numbers. The carrier lookup returns line_type, so you can switch to a voice code or refuse the request.
  • Artificial traffic. In SMS pumping, attackers make sign-up forms send codes to number ranges they profit from. The traffic lands on the provider first. Our SMS pumping by country guide covers where it concentrates.

A check costs a fraction of an international SMS to many destinations. Our OTP cost by country post compares channel costs per destination, so you can see where a check pays for itself.

How does the workflow look?

Real time, per request (OTP and transactional traffic):

  1. Normalize the number to E.164. Use default_country for numbers your clients send in national format.
  2. Call POST /v1/lookup with checks: ["carrier"] for every first code to a new number, and add hlr for destinations where a failed code is costly. Use a short wait, such as 5 seconds.
  3. Route by the HLR mcc_mnc when ported is true. If you need routing only, ask for mnp instead of hlr.
  4. If the line is fixed, send a voice code. If the phone is unreachable and the number has a WhatsApp or Telegram account, offer app delivery where your client's end user agreed to it.
  5. On pending or unknown, route as you normally would. Never hold a code because a check timed out.

Bulk, per list (routing tables and campaign traffic):

  1. Run the free POST /v1/jobs/estimate on the list. It counts valid, invalid, duplicate, cached and unsupported rows and returns the maximum cost.
  2. Create the job with POST /v1/jobs and pass the estimate as max_cost. Group lists by country where you can: per-country minimums apply (see below).
  3. Wait with GET /v1/jobs/{id}?wait=30 or a job.completed webhook, then download CSV or NDJSON. Each line keeps its row_no, so you join results back to your own file by row number.
  4. Refresh porting data on a schedule. Cached answers inside the freshness window are free.

Rate limits on your own sending endpoints still apply. Checks add a signal; they don't replace per-number and per-country limits.

Which checks answer which question?

CheckWhat it answersWhen to run itLink
carrierMobile, fixed line, VoIP, premium rate or toll free?First code to a new numberCarrier lookup
mnpWas the number ported? Which network serves it now?Least-cost routing, routing-table refreshMNP lookup
hlrIs it assigned and reachable now? Plus porting and current networkCostly destinations, old lists, retriesHLR lookup
whatsapp, telegramIs the number registered on the app?Channel fallback for consented app deliveryWhatsApp, Telegram
viber, rcs (bulk only)Viber account, or an RCS-capable number?Planning channels for opted-in listsViber, RCS

To translate an mcc_mnc code into a country and network name, use the free MCC/MNC lookup. The routing with MNP and HLR use case goes deeper on route selection.

What should you do with each result?

ResultSuggested action
line_type: mobile, HLR reachableSend the SMS on the normal route
HLR ported: true or MNP porting: portedRoute by the returned mcc_mnc, not the prefix
HLR status: unreachableOffer a voice code or a consented app channel now; retry SMS later
HLR status: invalidRefuse; the number isn't assigned
line_type: fixed_lineSend a voice code instead of SMS
line_type: voipPass to your client's risk rules; many genuine users have VoIP numbers
line_type: premium_rate or shared_costRefuse; a consumer is very unlikely to receive a code there
WhatsApp or Telegram registered: trueEligible for app delivery if the end user agreed to it
Any check unknown or pendingRoute as normal; unknown is free

These are starting points. Tune them per destination with your own delivery receipts and conversion data.

What does a request look like?

This request uses the public sandbox key from the test values page, so you can run it as is. +447700900006 is a ported test number, and +447700900002 is a phone that is switched off.

curl https://api.mobilevalidate.com/v1/lookup \
  -H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
  -H "Content-Type: application/json" \
  -d '{"numbers":["+447700900006","+447700900002"],"checks":["hlr","carrier","whatsapp"],"wait":10}'

Runs as pasted with the public sandbox key, which answers the test values only. In the SDKs, omit the key to use MOBILEVALIDATE_API_KEY.

Response (excerpt, test mode: the checks of both items in results):

JSON
[
  {
    "number.hlr": {
      "service": "number.hlr", "status": "completed", "registered": true,
      "attributes": {"status": "reachable", "ported": true, "roaming": false, "network": "Test Mobile Two", "mcc_mnc": "23410", "country": "GB"},
      "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-11-08T02:49:40.617Z",
      "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
    },
    "network.carrier": {
      "service": "network.carrier", "status": "completed", "registered": true,
      "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"},
      "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-11-08T02:49:40.617Z",
      "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
    },
    "whatsapp.registered": {
      "service": "whatsapp.registered", "status": "completed", "registered": true, "attributes": null,
      "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-11-08T02:49:40.617Z",
      "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
    }
  },
  {
    "number.hlr": {
      "service": "number.hlr", "status": "completed", "registered": true,
      "attributes": {"status": "unreachable", "ported": false, "roaming": false, "network": "Test Mobile", "mcc_mnc": "23415", "country": "GB"},
      "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-11-08T02:49:40.617Z",
      "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
    },
    "network.carrier": {
      "service": "network.carrier", "status": "unknown", "registered": null, "attributes": null,
      "confidence": null, "confidence_score": null, "checked_at": null,
      "cached": false, "age_seconds": null, "billed": false, "reason": "NO_DATA", "poll_after_ms": null
    },
    "whatsapp.registered": {
      "service": "whatsapp.registered", "status": "completed", "registered": false, "attributes": null,
      "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-11-08T02:49:40.617Z",
      "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
    }
  }
]

The first number was ported to the network 23410, so route the code there, not by its original prefix. The second is assigned but switched off, has no WhatsApp account and no carrier data: offer a voice code or hold the SMS for a retry. The unknown carrier answer is free.

For a list, the free estimate shows what a routing refresh would bill. Five rows with two duplicates (the same number once in national format, with default_country: "GB"):

curl https://api.mobilevalidate.com/v1/jobs/estimate \
  -H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
  -H "Content-Type: application/json" \
  -d '{"numbers":["+447700900001","+447700900002","+447700900006","+447700900006","07700 900001"],"checks":["hlr","mnp"],"default_country":"GB"}'

Runs as pasted with the public sandbox key, which answers the test values only. In the SDKs, omit the key to use MOBILEVALIDATE_API_KEY.

JSON
{"object": "estimate", "total": 5, "valid": 3, "invalid": 0, "duplicate": 2, "cached": 0, "unsupported": 0,
 "suppressed": 0, "checks": ["number.hlr", "number.mnp"], "checks_total": 10, "billable_max": 0,
 "max_cost": {"amount": "0", "currency": "USD"},
 "breakdown": [
   {"check": "number.hlr", "price_mode": "batch", "reason": null, "checks": 3,
    "unit_price": {"amount": "0.005", "currency": "USD"}, "max_cost": {"amount": "0", "currency": "USD"}},
   {"check": "number.mnp", "price_mode": "batch", "reason": null, "checks": 3,
    "unit_price": {"amount": "0.002", "currency": "USD"}, "max_cost": {"amount": "0", "currency": "USD"}}],
 "unsupported_checks": []}

Sandbox keys are never billed, so max_cost is 0 here. Only the three unique numbers would be checked. In a real job you would pick hlr or mnp, not both: the HLR answer already carries porting.

How much does it cost?

As of 8 November 2026, the live catalog lists these prices per conclusive answer:

CheckReal timeBulk
Carrier lookup (carrier)$0.003$0.0015
MNP lookup (mnp)$0.002$0.002
HLR lookup (hlr)$0.006$0.005
WhatsApp (whatsapp)$0.004$0.00025
Telegram (telegram)$0.001$0.0003
Viber, RCSbulk only$0.00015, $0.0005

Current prices are always on the pricing page. Unknown, unsupported-country, timeout, invalid-format and duplicate results are free, and repeat checks inside the freshness window come from your account's cache for free. max_cost caps any single request or job.

Bulk jobs have two per-country rules. WhatsApp checks get the bulk price for groups of at least 132 numbers from one country; smaller groups are priced at the real-time price. Bulk-only checks, such as RCS, are billed at least the price of 500 checks for each country that returns an answer. Group your lists by destination to pay the bulk price per number. The estimate lists both cases as small_batch and country_minimum lines, as described in bulk jobs.

To try it on real numbers, every account can run 20 phone numbers and 10 e-mail addresses through the real-time checks for free on the dashboard's Try it page. A free test key answers the documented test values for integration work.

Can you resell checks or pass results to your clients?

Running checks to route, price and protect the traffic you carry is internal business use. Making results or the service available to third parties is different. Section 18.2 of the Terms says you may not resell, license or publish results, or make the service available to others, without our written agreement. That covers, for example, returning line type or HLR status to your clients through your own API, or adding number checks to a verification product you sell.

There is no self-serve reseller program. If you want to offer checks to your own customers, e-mail sales@mobilevalidate.com with your use case, the checks you need and your expected monthly volume. Committed-volume pricing is available on request.

What limits and rules apply?

  • A real-time lookup takes up to 100 numbers and 20 checks, and numbers × checks can't exceed 2,000.
  • A bulk job takes up to 50,000 numbers and e-mails, and rows × checks can't exceed 100,000.
  • Requests that look like sequential number ranges are rejected. Twenty or more consecutive numbers in one request return suspected_enumeration. Check the numbers your clients' users entered, not generated ranges.
  • Each account has a daily cap on numbers, which GET /v1/limits reports. Ask sales if your traffic needs a higher cap.
  • Numbers from sanctioned countries are never checked and answer unsupported_country, free.

You check numbers on behalf of your clients, so agree the purpose with them in your contract and data processing terms. The numbers should be ones end users gave to receive a code or a message they expect. Checks are signals about a number, not identity proof. The HLR lookup returns roaming as true or false only and never a location, SIM identifier or network switch address. Messaging-app registration is not consent: deliver codes on an app only where the end user chose that channel. Our acceptable use policy forbids unsolicited bulk messaging and enumeration. People can object through the opt-out form. For code wording, the free OTP message template generator and SMS character counter help keep codes to one segment.

What are the common mistakes?

  • Paying for HLR and MNP on the same number. The HLR answer includes porting. Use MNP alone when routing is all you need.
  • Routing ported numbers by prefix after the check. Use the returned mcc_mnc; that is the point of the check.
  • Treating unreachable as invalid. Unreachable numbers exist; the phone is off or out of coverage. Retry or switch channel, don't delete.
  • Sending small country groups in WhatsApp bulk jobs. Below 132 numbers per country, the real-time price applies. Batch per destination.
  • Running HLR on fixed lines. HLR queries exist only for mobile numbers. Filter by line type first on mixed lists.
  • Exposing results to clients without an agreement. Talk to sales first; section 18.2 of the Terms applies.

Frequently asked questions

Which check should an OTP provider run before every code?

Usually the carrier lookup for line type, because it is cheap and catches fixed lines, VoIP and premium-rate numbers. Add the HLR lookup where a code that never arrives is expensive, and a WhatsApp or Telegram check where you offer app delivery as a fallback.

HLR or MNP for least-cost routing?

For routing alone, the MNP lookup is enough and costs less: it returns porting and the current mcc_mnc. The HLR lookup returns the same network fields plus live status, so use it when you also want to stop sending to unassigned or unreachable numbers. Don't pay for both on one number.

Can I pass the check results on to my own clients?

Using checks to route and protect traffic you carry is internal use. Showing or selling the results to your clients, or building them into a verification product you offer, needs a written agreement with us under section 18.2 of the Terms. E-mail sales@mobilevalidate.com to set one up.

Do you have a reseller program?

There is no self-serve reseller or affiliate program. Providers who want to offer checks to their own customers sign a written agreement with us. Contact sales with your use case and expected volume.

What are the per-country minimums in bulk jobs?

WhatsApp checks get the bulk price for groups of at least 132 numbers from one country; smaller groups are priced at the real-time price. Bulk-only checks such as RCS or iMessage are billed at least the price of 500 checks per country that returns an answer. The free estimate shows both before a job starts.

Is an unknown or timed-out check charged?

No. Unknown, unsupported-country, timeout, invalid-format and duplicate results are never charged. Route the number as you normally would.

Does the HLR lookup return the subscriber's location or SIM identifiers?

No. It returns status, ported, roaming as true or false, network, mcc_mnc and country. SIM identifiers, switch addresses and cell data are never returned.

Know before you send.

Create a free account: approved automatically, usually within 10–15 minutes.

Free to start: check 20 phone numbers and 10 e-mail addresses for real on your account, plus an unlimited test mode. You pay only for conclusive answers, from $20.