The US and Canada carrier lookup returns the current carrier and line type of a North American number. It follows numbers that have been ported to another network. It covers US and Canadian numbers only and runs in bulk jobs. Use it to clean a contact list before an SMS or calling campaign that people have agreed to receive.
What does the US/CA carrier lookup tell you?
It tells you which carrier serves a US or Canadian number today and what kind of line it is: mobile, landline, VoIP, toll-free and so on.
In North America the number itself says little about the carrier. The US introduced wireless local number portability on 24 November 2003 (FCC), and Canada followed for wireless numbers on 14 March 2007 (CRTC). Since then, subscribers can keep their number when they change carriers, and they can move a landline number to a mobile or VoIP service. A lookup table built on area code and exchange (NPA-NXX) therefore shows where a number was first assigned, not where it lives now. This service answers with the carrier serving the number today.
Unlike the general carrier lookup, it doesn't return original_carrier or country. The number is always in the US or Canada, and the current carrier is what matters there.
Who uses it, and why?
It is built for teams that message or call North American numbers at volume.
- A2P SMS senders. US carriers apply their own rules and fees to business messaging. Knowing the current carrier and whether a number is a landline or VoIP line before a send avoids failed messages and lets you estimate costs per carrier.
- Call centers. Landline, mobile and VoIP numbers are handled differently under US calling rules, and dialing strategy often depends on line type. See call-center screening.
- Lead verification. A lead form in the US that returns a
voiportoll_freeline in the "mobile phone" field is worth checking again before you spend sales time on it. - List hygiene. Run a quarterly job over the whole CRM to find numbers that moved from mobile to VoIP or landline.
Combine it with spam reputation, which covers the US and Canada too, to see reported nuisance numbers in the same job.
What do you get back?
| Field | Type | Meaning |
|---|---|---|
attributes.line_type | enum | mobile, fixed_line, fixed_line_or_mobile, voip, toll_free, premium_rate, shared_cost, personal, pager, uan, voicemail or unknown |
attributes.carrier | string | Current carrier name (up to 80 characters) |
registered | boolean or null | true when data was found; null when not conclusive |
status / reason | enum / string | completed, unknown, unsupported_country (non-US/CA numbers) |
checked_at, cached, billed | — | When the answer was obtained, whether it came from cache, whether it was charged |
In bulk downloads (CSV or NDJSON), the attributes appear as the columns network.carrier_us.line_type and network.carrier_us.carrier, next to your original rows in their original order.
How is it billed?
You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). A number from outside the US and Canada, including other +1 countries, is unsupported_country and free. The bulk estimate (POST /v1/jobs/estimate) is free and shows the maximum cost before you commit.
The service has a single bulk price per number checked. See pricing. Repeat checks of the same number inside the freshness window can come from your account's cache, and cache hits are free. Pass max_cost when you create the job to set a hard ceiling on the spend.
What are the limits?
- Bulk only.
POST /v1/lookuprefuses the check with403 service_disabled. UsePOST /v1/jobs(up to 50,000 numbers and e-mails per job, 100,000 numbers × checks). - US and Canada only. Everything else is
unsupported_country, which is free. The +1 prefix is shared with other countries in the North American Numbering Plan, so +1 alone doesn't guarantee coverage. - Requests that look like sequential number ranges or generated e-mail lists are rejected (20 or more consecutive numbers →
suspected_enumeration). - A daily numbers cap applies per account (
GET /v1/limits). - It reports the carrier and line type, not whether a number was reassigned to a new subscriber and not whether the handset is reachable. It doesn't replace the FCC Reassigned Numbers Database, and it doesn't create TCPA consent.
How do I use it responsibly?
Carrier data helps you reach people who asked to hear from you. It doesn't give you permission to contact anyone. US and Canadian rules on calls and texts still apply, including consent and do-not-call obligations. The acceptable use policy forbids unsolicited bulk messaging and using results for credit, employment, housing or insurance decisions.
People can object to their number being checked through the opt-out form. Suppressed numbers are skipped and never charged.
Example request
Test keys are free and never reach a real network. The documented test numbers (+447700900001 to …006) work for this service in test mode. Any other non-US/CA number returns unsupported_country.
curl https://api.mobilevalidate.com/v1/jobs \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001","+447700900002","+447700900003"],"checks":["network.carrier_us"]}'// 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.jobs.create({
numbers: ["+447700900001", "+447700900002", "+447700900003"],
checks: ["network.carrier_us"],
});
if (error) console.error(error.code, error.message);
else console.dir(data, { 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/jobs", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({
numbers: ["+447700900001", "+447700900002", "+447700900003"],
checks: ["network.carrier_us"],
}),
});
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.jobs.create(
numbers=["+447700900001", "+447700900002", "+447700900003"],
checks=["network.carrier_us"],
)
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/jobs');
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', '+447700900002', '+447700900003'],
'checks' => ['network.carrier_us'],
]),
]);
$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","+447700900002","+447700900003"],"checks":["network.carrier_us"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/jobs", 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/jobs")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"numbers" => ["+447700900001", "+447700900002", "+447700900003"],
"checks" => ["network.carrier_us"]
})
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.
Response from GET /v1/jobs/{id}/results (excerpt, test mode: the first row):
{
"kind": "phone",
"input": "+44770*****01",
"e164": "+447700900001",
"country": "GB",
"number_status": "valid",
"checks": {
"network.carrier_us": {
"service": "network.carrier_us",
"status": "completed",
"registered": true,
"attributes": {
"line_type": "mobile",
"carrier": "Test Carrier"
},
"confidence": "high",
"confidence_score": 0.99,
"checked_at": "2026-09-25T14:25:31.310Z",
"cached": false,
"age_seconds": 0,
"billed": false,
"reason": null,
"poll_after_ms": null
}
},
"test": true
}Try it now
Bulk-only service: create a job via POST /v1/jobs, then read GET /v1/jobs/{id}/results. Every example runs as pasted: it uses the public sandbox key, which answers the test values for free. In the SDKs, omit the key to use MOBILEVALIDATE_API_KEY.
curl https://api.mobilevalidate.com/v1/jobs \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"checks":["network.carrier_us"]}'// 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.jobs.create({
numbers: ["+447700900001"],
checks: ["network.carrier_us"],
});
if (error) console.error(error.code, error.message);
else console.dir(data, { 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/jobs", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({
numbers: ["+447700900001"],
checks: ["network.carrier_us"],
}),
});
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.jobs.create(numbers=["+447700900001"], checks=["network.carrier_us"])
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/jobs');
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' => ['network.carrier_us'],
]),
]);
$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":["network.carrier_us"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/jobs", 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/jobs")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"numbers" => ["+447700900001"],
"checks" => ["network.carrier_us"]
})
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 network.carrier_us" → tool: create_lookup_jobThe 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.
Real test-mode result for this check
{
"service": "network.carrier_us",
"status": "completed",
"registered": true,
"attributes": {
"line_type": "mobile",
"carrier": "Test Carrier"
},
"confidence": "high",
"confidence_score": 0.99,
"checked_at": "2026-09-25T14:25:29.004Z",
"cached": false,
"age_seconds": 0,
"billed": false,
"reason": null,
"poll_after_ms": null
}What you get
Fields of checks["network.carrier_us"], 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.line_type | enum | Line type.mobile · fixed_line · fixed_line_or_mobile · voip · toll_free · premium_rate · shared_cost · personal · pager · uan · voicemail · unknown |
| attributes.carrier | string | Current carrier name. |
Frequently asked questions
Why does the prefix of a US number not tell me its carrier?
Since wireless number portability started in the US in November 2003, subscribers can keep their number when they switch carriers. The area code and exchange show where the number was first assigned, not where it is served today.
Can I use this lookup in real time?
No. It is available in bulk jobs only (POST /v1/jobs). A real-time request is refused with service_disabled. For real-time answers, use the general carrier lookup.
What happens to numbers from other +1 countries, such as Caribbean numbers?
They share the +1 country code but are not US or Canadian numbers. They come back as unsupported_country and are not charged.
Is this the same as checking whether a number was reassigned?
No. The lookup tells you the current carrier and line type. It does not tell you whether the number changed hands, so it is not a substitute for the FCC Reassigned Numbers Database.

