The Apple Account check indicates whether an Apple Account, formerly called Apple ID, is associated with a phone number. It runs in real time and in bulk jobs (mainland China numbers in real time only), and answers registered: true, false or null (unknown) with the time of the check. It never returns names, addresses or devices.
What does the Apple Account check tell you?
It tells you whether a phone number is linked to an Apple Account, the account people use for the App Store, iCloud and other Apple services. Apple began calling Apple ID "Apple Account" in 2024. A phone number can be linked in two main ways. It can serve as a trusted phone number for two-factor authentication, which Apple uses to send sign-in codes. In some regions, it can also be the account's primary identifier instead of an e-mail address.
registered: true: an Apple Account is associated with the number.registered: false: a conclusive "no".registered: null: no conclusive answer. Not charged.
The check covers the account, not iMessage. To find out whether a number is registered for iMessage, use the iMessage check. It runs in bulk jobs only. Our guide on how to check if a phone number has an Apple ID compares the two answers and shows when they differ.
Who uses it, and why?
Apple Accounts protect payment details and device access, so a phone number tied to one has usually been used for real sign-ins and verification codes.
- Account security. When a customer adds or changes a phone number on their account with you, a number linked to an Apple Account is less likely to be a disposable number. It is one input to a review, not proof. See account security.
- Sign-up protection. In a live sign-up, the real-time answer can sit next to a carrier lookup to separate everyday mobile numbers from numbers set up for a single use.
- Channel planning for expected messages. Combined with iMessage and RCS, it helps you estimate how many of your consented customers are on Apple devices.
What do you get back?
Each number gets one result under checks["apple.registered"], with no extra attributes.
| Field | Type | Meaning |
|---|---|---|
registered | boolean or null | true account associated, false none, null unknown |
status | enum | completed, pending, unknown, unsupported_country or failed |
confidence / confidence_score | enum / 0–1 | How sure the answer is |
checked_at | timestamp | When the answer was obtained |
cached / age_seconds | boolean / integer | Served from your account's cache, and its age |
billed | boolean | Whether this check was charged |
reason | string or null | Why an answer is not conclusive |
How is it billed?
You pay per number checked, and only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Real-time and bulk checks are priced separately. See pricing. Cache hits inside the freshness window are free, and max_age: 0 forces a fresh, billed check.
What are the limits?
- Real time (
POST /v1/lookup, up to 100 identifiers) for numbers from every country, and bulk jobs (POST /v1/jobs, up to 50,000 per job) for every country except mainland China (+86, CN). In a bulk job, mainland China numbers answerunsupported_countryand are free; the job estimate lists them before you start. Check them withPOST /v1/lookupinstead. - Up to 20 checks per request. Numbers × checks is capped at 2,000 per lookup and 100,000 per job.
- Requests that look like sequential number ranges or generated e-mail lists are rejected (
suspected_enumeration). - A daily cap per account applies (
GET /v1/limits). - Invalid, duplicate and suppressed numbers are reported per row and never checked.
How do I use it responsibly?
Use the answer to protect accounts and sign-ups, and only for numbers you have a lawful basis to process. Don't use it to profile people, to judge them by the devices they use, or to decide on eligibility for credit, jobs, housing or insurance. The acceptable use policy forbids these uses and unsolicited messaging. People can object at /opt-out.
Example request
With a test key, +447700900001 answers "registered". See test mode.
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"checks":["apple"]}'// 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: ["apple"],
});
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: ["apple"],
}),
});
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=["apple"])
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' => ['apple'],
]),
]);
$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":["apple"]}`)
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" => ["apple"]
})
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 (excerpt, test mode: the first item of results):
{
"kind": "phone",
"input": "+447700900001",
"e164": "+447700900001",
"country": "GB",
"number_status": "valid",
"checks": {
"apple.registered": {
"service": "apple.registered",
"status": "completed",
"registered": true,
"attributes": null,
"confidence": "high",
"confidence_score": 0.99,
"checked_at": "2026-09-25T14:25:31.114Z",
"cached": false,
"age_seconds": 0,
"billed": false,
"reason": null,
"poll_after_ms": null
}
},
"test": true
}Try it now
Run it here with a test value: the console calls the live API from your browser with the public sandbox key and shows the real JSON and a plain-English verdict.
POST /v1/lookup
Public sandbox key · test values only · never billed
Pick a test value or press Run
Press Run or pick a test value above: the console sends a real request to the API with the public sandbox key and shows the answer here.
No response body.curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"checks":["apple"]}'Check your own numbers: 20 phone numbers and 10 e-mail addresses are checked for real, free, after sign-up. Start free
checks: ["apple.registered"] · Realtime lookup via POST /v1/lookup. 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/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"checks":["apple.registered"]}'// 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: ["apple.registered"],
});
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: ["apple.registered"],
}),
});
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=["apple.registered"])
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' => ['apple.registered'],
]),
]);
$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":["apple.registered"]}`)
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" => ["apple.registered"]
})
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 apple.registered" → 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.
Real test-mode result for this check
{
"service": "apple.registered",
"status": "completed",
"registered": true,
"attributes": null,
"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["apple.registered"], generated from the public service catalog.
| Field | Type | Meaning |
|---|---|---|
| registered | boolean | null | true = found, false = not found, null = unknown (not charged). |
| status | enum | completed, pending, unknown, unsupported_country or failed. |
| checked_at | timestamp | When the answer was obtained. |
Frequently asked questions
Is this the same as the iMessage check?
No. This check answers whether an Apple Account is associated with the number. The iMessage check answers whether the number is registered for iMessage. They often agree, but not always, and iMessage runs in bulk jobs only.
Apple ID or Apple Account — which is it?
Both names mean the same account. Apple began calling Apple ID the Apple Account in 2024. The service code stays apple.registered.
Does a linked Apple Account mean the person uses an iPhone?
Not necessarily. Apple Accounts are also used on Mac, iPad, Windows and the web, for example for Apple Music or iCloud. It suggests the number belongs to someone who uses Apple services.
Can I check an e-mail address for an Apple Account?
Yes, with the separate apple.email service (alias apple.email). It also runs in real time.
Does the check return the Apple Account's name or devices?
No. It only indicates whether an account is associated with the number. No name, e-mail address, device list or location is returned.
Is there a free apple number check?
Yes, to try it on your own data: create a free account and check up to 20 phone numbers for real on the dashboard's Try it page, at no cost. Test mode with documented test values is free and unlimited. After that, a apple number check costs $0.001 per check in real time ($1.00 per 1,000) and $0.0005 in bulk jobs ($0.50 per 1,000), paid from prepaid credit. Unknown and other inconclusive results are always free.

