Coming soon. This check is not available yet. The page describes what it will return; prices will be published at launch.
Coming soon. The HLR lookup will send a live query to a mobile number's home network and tell you whether the number is reachable right now, whether it has been ported and whether the subscriber is roaming, plus the network currently serving it. The service is not switched on yet. This page describes what it will return so you can plan for it.
What will the HLR lookup tell you?
It will tell you whether a mobile number is live on its network at the moment of the query. It is based on the network's own subscriber records, not on a static table.
The home network keeps a register of its subscribers, known as the Home Location Register (HLR) or, in newer networks, its successors. The HLR lookup glossary entry explains how it works. A query to that register can show whether the number is assigned and whether the subscriber can currently be reached. That goes further than a carrier lookup, which only describes the number from reference data.
The main answer will be status:
reachable: the network knows the subscriber and considers them reachable.unreachable: the number exists, but the subscriber can't be reached right now (for example, the phone is switched off or out of coverage).invalid: the network reports the number as not assigned.unknown: no conclusive answer, for example after a network error or timeout.
Who will use it, and why?
- SMS senders will use it to drop numbers that are no longer assigned before a send, and to hold back messages to unreachable numbers.
- OTP flows can use
unreachableto offer another verification channel straight away instead of waiting for an SMS that won't arrive. - Fraud teams can compare
portedand the current network with what a customer told them. - Routing will use
mcc_mncandnetwork, which show the network actually serving a ported number.
See number reachability for the difference between a valid number and a reachable one.
What will you get back?
| Field | Type | Meaning |
|---|---|---|
attributes.status | enum | reachable, unreachable, invalid or unknown (primary answer) |
attributes.ported | boolean | The number has been ported to another network |
attributes.roaming | boolean | The subscriber is roaming. True or false only, never a location |
attributes.network | string | Name of the current network |
attributes.mcc_mnc | string | Current network code (MCC + MNC) |
attributes.country | string | ISO country of the current network |
Never returned: IMSI or any other SIM identifier, the serving switch, cell or location area, or any other detail that could locate a person. We don't store these values either.
How will it be billed?
reachable, unreachable and invalid are conclusive answers and will be billed. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Two cases look alike but are billed differently. Input the API can't parse as a phone number (number_status: invalid_number) is never checked and never charged. A number the network reports as unassigned (status: invalid) is a conclusive answer and is billed. Prices will appear on the pricing page when the service launches.
What are the limits today?
The service is switched off. GET /v1/services doesn't list it, and every request is refused with 403 service_disabled, whether you use a live or a test key. When it launches, it will follow the same rules as other checks: up to 100 numbers per real-time lookup, 50,000 per job and 20 checks per request. Requests that look like sequential number ranges or generated e-mail lists are rejected.
Test-mode answers are already defined, so your integration tests will work on day one. The test numbers are listed on the test mode page.
How will you use it responsibly?
An HLR answer is information about a person's phone, not just a number. Check only numbers you have a lawful reason to process, such as your own customers and people who gave you their number. Don't use it to track people or to find out whether someone is travelling. The API returns roaming as true or false only for that reason. The acceptable use policy applies, and people can object through the opt-out form.
What does a request return today?
A request made now with a test key gets the real current answer:
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"checks":["hlr"]}'// npm install mobilevalidate · Node.js 20+ · save as check.mjs and run: node check.mjs
import { MobileValidate } from "mobilevalidate";
// Omit apiKey to read MOBILEVALIDATE_API_KEY from the environment.
const mv = new MobileValidate({ apiKey: "mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" });
const { data, error } = await mv.lookup({
numbers: ["+447700900001"],
checks: ["hlr"],
});
if (error) console.error(error.code, error.message);
else console.dir(data.results, { depth: null });// Node.js 18+, Deno, Bun or the browser console (ES module: save as .mjs or use "type": "module").
const res = await fetch("https://api.mobilevalidate.com/v1/lookup", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({ numbers: ["+447700900001"], checks: ["hlr"] }),
});
console.log(res.status, res.headers.get("x-request-id"));
console.dir(await res.json(), { depth: null });# pip install mobilevalidate-sdk
from mobilevalidate import MobileValidate
# Omit api_key to read MOBILEVALIDATE_API_KEY from the environment.
mv = MobileValidate(api_key="mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
result = mv.lookup(numbers=["+447700900001"], checks=["hlr"])
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/lookup');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'numbers' => ['+447700900001'],
'checks' => ['hlr'],
]),
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), PHP_EOL, $response, PHP_EOL;package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"numbers":["+447700900001"],"checks":["hlr"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/lookup", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}require "net/http"
require "json"
uri = URI("https://api.mobilevalidate.com/v1/lookup")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"numbers" => ["+447700900001"],
"checks" => ["hlr"]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code, res.bodyRuns as pasted with the public sandbox key, which answers the test values only. In the SDKs, omit the key to use MOBILEVALIDATE_API_KEY.
{
"error": {
"code": "service_disabled",
"message": "The check 'number.hlr' is currently unavailable.",
"status": 403,
"retryable": false,
"param": "checks[0]",
"doc_url": "https://mobilevalidate.com/docs/errors#service_disabled",
"request_id": "req_0VWF4BNLKS5WtS8z2IF3"
}
}Try it now
Realtime lookup via POST /v1/lookup. The example uses a test value; this check needs an account with access to it.
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"checks":["number.hlr"]}'// npm install mobilevalidate · Node.js 20+ · save as check.mjs and run: node check.mjs
import { MobileValidate } from "mobilevalidate";
// Omit apiKey to read MOBILEVALIDATE_API_KEY from the environment.
const mv = new MobileValidate({ apiKey: "mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" });
const { data, error } = await mv.lookup({
numbers: ["+447700900001"],
checks: ["number.hlr"],
});
if (error) console.error(error.code, error.message);
else console.dir(data.results, { depth: null });// Node.js 18+, Deno, Bun or the browser console (ES module: save as .mjs or use "type": "module").
const res = await fetch("https://api.mobilevalidate.com/v1/lookup", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({
numbers: ["+447700900001"],
checks: ["number.hlr"],
}),
});
console.log(res.status, res.headers.get("x-request-id"));
console.dir(await res.json(), { depth: null });# pip install mobilevalidate-sdk
from mobilevalidate import MobileValidate
# Omit api_key to read MOBILEVALIDATE_API_KEY from the environment.
mv = MobileValidate(api_key="mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
result = mv.lookup(numbers=["+447700900001"], checks=["number.hlr"])
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/lookup');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'numbers' => ['+447700900001'],
'checks' => ['number.hlr'],
]),
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), PHP_EOL, $response, PHP_EOL;package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"numbers":["+447700900001"],"checks":["number.hlr"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/lookup", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}require "net/http"
require "json"
uri = URI("https://api.mobilevalidate.com/v1/lookup")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"numbers" => ["+447700900001"],
"checks" => ["number.hlr"]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code, res.body// .mcp.json (Claude Code), ~/.cursor/mcp.json (Cursor) or .vscode/mcp.json ("servers" instead of "mcpServers")
{
"mcpServers": {
"mobilevalidate": {
"type": "http",
"url": "https://mcp.mobilevalidate.com/mcp",
"headers": { "Authorization": "Bearer ${MOBILEVALIDATE_API_KEY}" }
}
}
}
// Then ask your agent, e.g.:
// "Check +447700900001 with number.hlr" → tool: lookup_numbersThe hosted MCP server is live for customers with an agent key (mv_agent_…) or a test key. Or run it locally with npx -y @mobilevalidate/mcp.
What you get
Fields of checks["number.hlr"], generated from the public service catalog.
| Field | Type | Meaning |
|---|---|---|
| registered | boolean | null | true when data was found; null when unknown (not charged). |
| status | enum | completed, pending, unknown, unsupported_country or failed. |
| checked_at | timestamp | When the answer was obtained. |
| attributes.status | enum | Reachability from the home network.reachable · unreachable · invalid · unknown |
| attributes.ported | boolean | Number has been ported to another network. |
| attributes.roaming | boolean | Subscriber is roaming (no location detail). |
| attributes.network | string | Current network name. |
| attributes.mcc_mnc | string | Current network code (MCC+MNC). |
| attributes.country | string | ISO country of the current network. |
Frequently asked questions
Can I use the HLR lookup today?
Not yet. The service is switched off, so requests are refused with 403 service_disabled, including requests made with test keys. This page describes what it will return.
How is an HLR lookup different from a carrier lookup?
A carrier lookup describes the number from reference data: line type and carrier. An HLR lookup asks the number's home network directly, so it can also tell you whether the subscriber is currently reachable.
Will it show where a subscriber is?
No. Roaming is reported as true or false only. Location, cell, serving switch and SIM identifiers such as the IMSI are never returned.
Will unreachable answers be charged?
Yes. reachable, unreachable and invalid are conclusive answers and will be billed. unknown answers, for example after a network error or timeout, will be free.

