What is phone line type? Mobile, landline, and VoIP
Line type sorts mobile from landline from VoIP — but it comes from the numbering plan, not the live network, and sometimes the plan honestly cannot say.
Contents
You searched for phone number line type because a phone field on its own won't tell you whether you can text a number, call it, or trust it. Line type is the answer to that question — is this a mobile, a landline, or a VoIP number? — and it is both more useful and more slippery than it first looks. This post covers where line type comes from, what the labels mean, the one common case where the honest answer is "the data can't tell," and the gap between what a number was assigned as and which network actually serves it today.
Where line type comes from
Line type is metadata baked into a country's national numbering plan. Every telecom regulator publishes ranges of numbers and what each range is designated for — this block is mobile, that block is geographic landline, this one is toll-free. Google's libphonenumber ships those published ranges as data. When you ask the library for a number's type, it matches the number against those ranges. It is a table lookup, not a phone call to the network.
That is the whole trick, and it is worth saying out loud: line type from metadata describes how a number was designated by the plan, not who is holding the handset right now. Feed the library a clean E.164 number and it will tell you which published range that number falls in. Nothing more.
The line types you'll see
libphonenumber classifies numbers with a PhoneNumberType enum. The members you will actually encounter:
MOBILE— a range designated for mobile handsets.FIXED_LINE— a geographic landline range.FIXED_LINE_OR_MOBILE— the plan does not separate the two (more on this below).VOIP— a range designated for internet-telephony.TOLL_FREE— 1-800 and equivalents.PREMIUM_RATE,SHARED_COST,PERSONAL_NUMBER,PAGER,UAN,VOICEMAIL.UNKNOWN— the number parsed but matched no typed range.
In Python's phonenumbers port, you read it directly:
import phonenumbers
from phonenumbers import PhoneNumberType
number = phonenumbers.parse("+16504472983")
kind = phonenumbers.number_type(number)
print(kind == PhoneNumberType.FIXED_LINE_OR_MOBILE) # True for most US numbers
For most projects, this stdlib-grade call is genuinely all you need, and you should reach for it first. If you are working in JavaScript, the validate-phone-in-JavaScript walkthrough shows the same idea with libphonenumber-js.
FIXED_LINE_OR_MOBILE: when the plan can't tell
Here is the case that trips people up. Run a normal US number through libphonenumber and you do not get MOBILE. You get FIXED_LINE_OR_MOBILE:
import { parsePhoneNumber } from 'libphonenumber-js/max'
const phone = parsePhoneNumber('+16504472983')
console.log(phone.getType()) // 'FIXED_LINE_OR_MOBILE'
(Note the /max import — getType() needs the full metadata bundle, not the slim default.)
This is not the library being lazy. The North American Numbering Plan simply does not encode mobile-versus-landline in the number itself. A +1 prefix cannot tell you which is which, so libphonenumber reports exactly that: it could be either, and the plan won't say. The same happens in other countries whose ranges overlap.
The correct response to FIXED_LINE_OR_MOBILE is to treat it as no information about line type, not as a soft "probably mobile." Any tool that hands you a confident "Mobile" for a bare US number is either guessing or has done a separate live lookup — and if it doesn't tell you which, you can't tell either.
Boundstone returns the ambiguity as-is. Ask it about that number and line_type comes back "fixed_line_or_mobile". We don't upgrade the guess to make the field look more decisive than the data is.
Metadata is not the live network
Even a clean MOBILE classification tells you what the number's range was designated for — not what is happening on the network today. Numbers get ported between carriers. A "mobile" number can sit behind a VoIP gateway. A landline range can front a virtual system. Number portability means the assigned type and the current serving carrier drift apart over time.
To answer "which carrier serves this number right now" or "is this handset reachable at this moment," you need a live query against the network — a carrier lookup or an HLR (Home Location Register) dip — not a metadata table. Those are real, useful, and they cost money per query because they touch live infrastructure.
Boundstone does not perform them today, and the response says so plainly. Every phone verification carries the honesty contract:
{
"valid": true,
"e164": "+16504472983",
"country": "US",
"line_type": "fixed_line_or_mobile",
"national_format": "(650) 447-2983",
"checks": {
"performed": ["format", "region", "line_type_metadata"],
"not_performed": ["carrier_lookup", "ported_status", "hlr_liveness"]
}
}
checks.performed is ["format", "region", "line_type_metadata"]. checks.not_performed is ["carrier_lookup", "ported_status", "hlr_liveness"]. That last array is the point: it tells you precisely which questions this answer did not settle, so a valid: true means what it says and nothing more. Carrier lookup and HLR liveness are a paid opt-in — hlr:true, 5 credits, priced against what a live dip actually costs and refunded when the network cannot answer. Left off, we will not pretend a metadata result is a network result.
Why line type matters anyway
Even as metadata, line type earns its keep:
- SMS versus voice. You can't reliably text a
FIXED_LINE, and you shouldn't waste a voice-only campaign on a range that's SMS-first. Line type routes the message. - Fraud triage. VoIP and toll-free numbers turn up often in disposable, throwaway signups, because they're cheap and easy to spin up. Line type is a signal in that triage — not a verdict — and pairing it with the honest
not_performedlist keeps you from over-trusting it. - Data hygiene. Storing the designated type alongside the E.164 number makes downstream routing and dedupe decisions cheaper.
The short version
- Line type = numbering-plan metadata. It describes how a number's range was designated, from published data — not a live network query.
FIXED_LINE_OR_MOBILEis a real answer, not a failure. For US numbers especially, the plan can't split mobile from landline. Treat it as "unknown line type," never as "probably mobile."- Assigned type ≠ current carrier. Ported and VoIP-fronted numbers make metadata drift from reality. Carrier and reachability need a live lookup.
- Start with the stdlib.
phonenumbersorlibphonenumber-js/maxgives you the type for free, and often that's the whole job. - Reach for the API when you want one consistent contract across languages — plus the carrier/HLR layer on request, with
hlr:true. Boundstone returnsline_typefromline_type_metadataand markscarrier_lookupandhlr_livenessasnot_performed, so you always know what a result did and didn't check.
Want to see it on your own numbers? The keyless phone validator runs the same metadata classification with no signup and no card.