# MobileValidate — full text Generated from https://mobilevalidate.com/llms.txt. Each section below is also available at its own URL ending in .md. --- # Terms of Service > Draft terms for business customers of the MobileValidate API: accounts, credits, billing for conclusive results only, acceptable use and liability. Canonical: https://mobilevalidate.com/legal/terms · Last updated: 2026-09-25 > **Draft — pending legal review.** These terms govern the use of the MobileValidate website, API, SDK, command-line tool and MCP server (together, "the service"). They are an agreement between BroadNet Technologies Inc., 5th floor, Minkara Building, Clemenceau Street, Beirut, Lebanon ("we", "us") and the business that opens an account ("you"). This draft has not been reviewed by counsel yet and may change before public launch. ## Who can use the service? The service is for businesses only. You must act for a company, organisation or sole trader in the course of business, not as a consumer. The person accepting these terms confirms that they are authorised to bind that business. During the private preview, accounts are created only after we approve a request made through the [request-access form](/request-access). We may decline a request without giving reasons. You are responsible for everything done with your API keys, including keys you give to staff, contractors or AI agents. ## What does the service do? The service checks phone numbers and e-mail addresses that you submit. For each check it returns an answer such as registered / not registered / unknown, or data such as line type and carrier, together with the time of the check (`checked_at`). Platform and brand names are used only to describe which service a check refers to. We are not affiliated with, endorsed by or sponsored by those platforms, and all trademarks belong to their owners. Checks never return names, photos or profiles. ## How do credits and billing work? You buy prepaid credit, and each billable check draws from it at the price shown for that service and mode (real time or bulk) when the request is made. Prices are listed on the [pricing page](/pricing) or agreed with you in writing. Before a request runs, we reserve its maximum possible cost. We then charge only for conclusive answers and release the rest. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Cache hits served from your own account's cache are free. For some data services, "no data found" is itself a conclusive answer. The service documentation says where this applies. For example, `no_reports` from the spam-reputation check is billed. If you cancel a running bulk job, items that have not yet been submitted are released and not billed. Items already in progress will finish, be stored and be billed, because they cannot be recalled. [Refund terms for unused credit — pending]. [Taxes and invoicing — pending]. ## What is test mode? Test keys (`mv_test_…`) are free. They return fixed answers for documented test numbers and test e-mail addresses. They never contact any network or platform and are never billed. Test results are not real data, so don't use them for decisions about real people. Live keys cannot be used with test identifiers. ## What are your obligations? You must: - follow the [Acceptable Use Policy](/legal/acceptable-use), which forms part of these terms; - have a lawful basis for every phone number and e-mail address you submit, and give any notices the law requires to the people concerned; - keep API keys confidential, use IP allowlists where we require them, and revoke a key straight away if it may have been exposed; - not try to get around rate limits, daily caps, anti-enumeration controls or spend limits; - honour opt-outs. When a person objects through our [opt-out form](/opt-out), we suppress their identifier and it will no longer be checked for any customer. Where we process personal data for you, the [Data Processing Agreement](/legal/dpa) applies. ## How accurate are the results? Results are signals, not facts about a person. They show what could be observed at `checked_at`. Answers can change, can be out of date, or can be unknown. A number may have been reassigned, an account may be dormant, and a platform's privacy settings may affect what can be seen. Results are not proof of identity. Don't use them on their own for KYC or AML decisions, or for decisions about credit, employment, housing or insurance. The service is provided "as is", and we don't promise any particular accuracy, coverage or availability except as agreed in writing. [Service levels, if any — pending]. ## When can we suspend or end your access? We may suspend or restrict an account, a key or a service without notice when we reasonably suspect abuse. This includes enumeration, unsolicited messaging, use for tracking people, a security risk, non-payment, or a legal or regulatory requirement. We may also switch off a service for everyone, for example if a platform or data source changes. Where we can, we will tell you why and give you a chance to respond. You can close your account at any time. [Treatment of remaining credit on termination — pending]. ## Liability, changes and law [Limitation of liability — pending legal review]. [Indemnity — pending legal review]. We may change these terms. For material changes, we will give notice to the e-mail address on your account at least [notice period — pending] before they take effect. Governing law: [Governing law — pending]. Courts: [Jurisdiction — pending]. Contact: info@broadnet.me. --- # Privacy Policy > Draft privacy policy: what MobileValidate collects from customers and website visitors, why, how long it is kept and how to exercise your rights. Canonical: https://mobilevalidate.com/legal/privacy · Last updated: 2026-09-25 > **Draft — pending legal review.** This policy explains how BroadNet Technologies Inc., 5th floor, Minkara Building, Clemenceau Street, Beirut, Lebanon ("we") handles personal data about our business customers, the people who ask for access, and visitors to mobilevalidate.com. Phone numbers and e-mail addresses that customers submit for checking are covered separately in the [data-subject notice](/legal/data-subject-notice). ## What data do we collect? | Where it comes from | Data | Why | |---|---|---| | Request-access form | Name, work e-mail, company, website, intended use case, expected monthly volume, services of interest | To assess the request and set up an account | | Form security | A Cloudflare Turnstile token, and a keyed hash of your IP address (we don't store the IP address itself) | To block automated abuse and repeated submissions | | Customer account | Contact details, organisation name, API key metadata (prefix, scopes, IP allowlist, creation and last-use dates), credit balance and transactions | To provide and bill the service | | API use | Request metadata such as time, endpoint, service, counts, cost, request ID and masked identifiers | Operation, billing, support, security and abuse prevention | | Opt-out form | The number or e-mail address you submit, encrypted at rest, plus a masked copy for staff review | To handle your request | We only store API keys as keyed hashes. They are shown once, when created. Logs never contain full phone numbers or e-mail addresses. Numbers are masked, for example `+44770*****01`, and so are e-mail addresses, for example `re•••@example.com`. ## On what basis do we use it? - **Contract:** to set up and run customer accounts, bill for use and give support. - **Legitimate interests:** security, fraud and abuse prevention (including Turnstile and IP hashing), improving the service, and answering business enquiries. - **Legal obligation:** accounting, tax and responses to lawful requests. We don't sell personal data. We don't use analytics or advertising cookies on this website (see the [cookie notice](/legal/cookies)). ## How long do we keep it? - Request-access submissions: 90 days for rejected or spam requests. If a request is approved, the data becomes part of the customer account. - Account and billing records: for the life of the account and then as long as accounting law requires ([period — pending]). - API single-lookup inputs: 7 days. Bulk-job inputs and results: 30 days by default. Customers can set bulk retention from 1 day to 24 months, or delete a job at any time. - Audit and security logs: [period — pending]. ## Who receives it? Our service providers act for us under written agreements. By category, they are hosting and infrastructure, storage, e-mail delivery, payments, and data-verification partners that carry out the checks customers request. We publish only the categories. Customers who sign our [Data Processing Agreement](/legal/dpa) can get the named list under confidentiality. Some providers may process data outside your country. Where that happens, we use appropriate safeguards ([transfer mechanism — pending]). ## What are your rights? Depending on where you live, you may have the right to access, correct or delete your data, to restrict or object to its use, to data portability, and to complain to a data-protection supervisory authority. To use these rights, contact [privacy contact — pending]. We may need to confirm your identity first. If your phone number or e-mail address was checked by one of our customers, see the [data-subject notice](/legal/data-subject-notice) or use the [opt-out form](/opt-out). ## Changes and contact We will update this page when our practices change and show the date at the top. Controller: BroadNet Technologies Inc., 5th floor, Minkara Building, Clemenceau Street, Beirut, Lebanon, Lebanon. Contact: info@broadnet.me. Data protection contact or representative: [pending]. --- # Notice to people whose phone number or e-mail address is checked > Draft notice for people whose phone number or e-mail is checked through MobileValidate, or appears in spam-report data: what we hold and how to object. Canonical: https://mobilevalidate.com/legal/data-subject-notice · Last updated: 2026-09-25 > **Draft — pending legal review.** You may be reading this because a business used MobileValidate to check your phone number or e-mail address, or because your number appears in spam or nuisance-call report data. We did not collect this data from you directly, so this notice explains what we hold, where it comes from, what we use it for and how you can object. It follows the approach of Article 14 GDPR. The provider of the service is BroadNet Technologies Inc., 5th floor, Minkara Building, Clemenceau Street, Beirut, Lebanon. ## Why would your number or e-mail address be checked? Our customers are businesses. They send us phone numbers or e-mail addresses they already hold, usually from their own customers, sign-ups or contact forms. They do this for a few reasons: - to prevent fraud, for example fake sign-ups or one-time passcodes sent to numbers that can't receive them; - to improve deliverability, for example by fixing mistyped numbers and choosing the channel a message can reach; - to verify leads. Each check answers a narrow question. Does this number have an account on a given messaging app or online service? Does this mailbox exist? What line type and carrier does the number have? The answer is yes, no or unknown, or a small piece of network data. We never return your name, photo, profile, messages or location. We never contact you as part of a check. ## Who is responsible for the data? For most checks, we act as a **processor** for the customer who asked for the check. That customer is the controller, and you can also use your rights with them. For checks that show whether a number or address has an account on a platform, we **may act as controller** for some of the processing, for example our own caching of results (pending counsel review). For the spam-reputation data described below, we are the **controller**. ## What data do we hold about you? - **The identifier:** your phone number in international format, or your e-mail address. It is stored encrypted, with a keyed hash so it can be matched without being readable. - **Check results:** registered / not registered / unknown for each service checked, and when the check happened. - **Network data about the number:** line type (for example mobile or VoIP), the current carrier and the carrier originally assigned the number, and the country. - **Spam-reputation data** (US, Canadian and German numbers only): a risk level and score, which classes of report exist, the most frequent report category, and the months the number first and last appeared. We don't hold report texts or anyone's name. ## Where does spam-reputation data come from? For phone numbers from the United States, Canada and Germany, we compile reputation signals from four classes of source: - published actions by telecom regulators; - government nuisance-call complaint data; - community spam-report websites; - public listings of numbers offered for sale as unassigned, which can point to a spoofed caller ID. Our legal basis is legitimate interests: protecting people and businesses from spam and fraud calls. A reputation level is a signal and not a finding that anyone did something wrong. A number can appear in reports because someone else spoofed it. If you think this applies to your number, please object as described below. ## How long do we keep it? - Real-time lookups: 7 days. - Bulk jobs: 30 days by default. The customer can set a shorter or longer period, up to 24 months. - Cached results and observations used to avoid repeat checks: up to 400 days (this period is pending review). - Spam-reputation data: while the reports are current. Older reports carry less weight. [Maximum period — pending]. ## What rights do you have, and how do you use them? You can ask for access to your data, and for it to be corrected or erased. You can also object to its use. Use the [opt-out form](/opt-out) and enter the phone number or e-mail address concerned. Once your request is approved, the identifier goes on our suppression list. It will then no longer be checked for any customer, and we treat erasure requests the same way. For privacy reasons, the form's reply is always the same and never confirms whether we hold data about an identifier. We may ask for proof that you control the number or address before we release any data. You can also contact [privacy contact — pending]. You have the right to complain to a data-protection supervisory authority, in particular in the country where you live or work. --- # Acceptable Use Policy > Draft rules for using MobileValidate: fraud-prevention and deliverability uses, prohibited uses such as enumeration, stalking and spam, and enforcement. Canonical: https://mobilevalidate.com/legal/acceptable-use · Last updated: 2026-09-25 > **Draft — pending legal review.** This policy is part of the [Terms of Service](/legal/terms). It covers every use of the MobileValidate API, SDK, command-line tool, MCP server and any AI agent acting with your keys. We built the service for fraud prevention, deliverability, sign-up and passcode protection, and lead verification. Any use outside that purpose needs our written approval first. ## What is the service for? The service is for checking contact data you already hold for a legitimate business reason. Typical examples: - screening sign-ups and one-time-passcode requests for fake or unreachable numbers; - cleaning a customer list before a transactional or consented campaign; - choosing the channel a customer who opted in can actually receive; - checking that a web-form lead is plausible before a salesperson calls; - screening inbound or outbound call numbers against spam-reputation signals. ## What is prohibited? You must not use the service, or let anyone else use it, to: 1. **Send unsolicited messages or calls** in breach of the law, including marketing rules such as the TCPA, PECR, the ePrivacy rules, CAN-SPAM and their equivalents. The same applies to finding recipients for such messages. 2. **Enumerate identifiers.** This means checking sequential or generated number ranges, or generated lists of e-mail addresses, to discover who uses a platform. It includes attempts to "find all users" of any app or service. 3. **Scrape, harvest or resell results** to build contact databases, or offer the service or its results as a competing product. 4. **Stalk, harass, monitor or locate** a person, or find out whether a specific private individual uses an app without a legitimate business reason. 5. **Build profiles of private individuals**, or combine results with other data to identify people who have not dealt with you. 6. **Make eligibility decisions** about credit, employment, housing, insurance or similar matters, or use results as a consumer report. 7. **Discriminate** against anyone on the basis of results, or infer sensitive characteristics. 8. **Get around our controls**, including rate limits, daily caps, spend caps, anti-enumeration rules, suppression and opt-outs, or entitlement to services. This covers splitting requests or spreading them across keys or accounts to avoid limits. 9. **Test or attack** the service's security without written permission, or use test keys to probe live behaviour. ## What do you promise us? You confirm that: - you have a lawful basis for every phone number and e-mail address you submit; - you have given the people concerned any notice the law requires; - you will honour opt-outs and objections. You understand that results are signals observed at a point in time (`checked_at`). They are not proof of identity or ownership and cannot replace KYC or AML checks. A spam-reputation level of `no_reports` does not mean a number is safe. Checks never return names, photos or profiles, and you must not try to obtain them through the service. ## How do we enforce this policy? We monitor for abuse automatically. Requests that look like sequential number ranges or generated e-mail lists are rejected, and patterns such as repeated checks of the same person are reviewed. If we reasonably suspect a breach, we may: - ask you for information about your use case and lists; - reduce limits; - disable services for your account; - suspend or revoke API keys; - close the account. We may act without notice where there is a risk to other people. Serious or repeated breaches can lead to termination. We may also report unlawful activity to the authorities. Credit used on prohibited activity is not refunded ([refund terms — pending]). To report abuse of the service, contact info@broadnet.me. --- # Data Processing Agreement > Draft summary of the MobileValidate Data Processing Agreement (GDPR Art. 28), available on request, and how sub-processors are disclosed. Canonical: https://mobilevalidate.com/legal/dpa · Last updated: 2026-09-25 > **Draft — pending legal review.** When you send us phone numbers or e-mail addresses to check, we process personal data for you. Our Data Processing Agreement (DPA) sets out how we do that. It meets the requirements of Article 28 GDPR and the UK GDPR. It is available on request: mention it in the [request-access form](/request-access), or contact info@broadnet.me. The processor is BroadNet Technologies Inc., 5th floor, Minkara Building, Clemenceau Street, Beirut, Lebanon. ## What does the DPA cover? In summary, the DPA commits us to: - **Process on your instructions only:** checking the identifiers you submit for the services you request, and storing results for the retention period you set. - **Keep it confidential:** everyone who can access customer data is bound by confidentiality. - **Secure the data:** identifiers are sealed (encrypted) at rest and matched through keyed hashes, logs contain masked identifiers only, API keys are stored as keyed hashes, and all traffic is encrypted in transit. Details are on the [Trust page](/trust). - **Follow your retention settings:** real-time lookups are kept for 7 days and bulk jobs for 30 days by default. You can change the bulk period from 1 day to 24 months, or delete a job at any time. - **Help you:** with data-subject requests, data-protection impact assessments and consultations with authorities, as far as our role allows. - **Report breaches:** we notify you of a personal-data breach without undue delay ([notification period — pending]). - **Delete or return data at the end:** unless the law requires us to keep it. - **Support audits:** we provide the information needed to show compliance ([audit terms — pending]). ## How are sub-processors handled? We publish sub-processor **categories**: hosting and infrastructure, storage, e-mail delivery, payments, and data-verification partners that perform the checks you request. Every customer who signs the DPA can get the **named** list of sub-processors under confidentiality. We give at least 30 days' notice before adding or replacing a sub-processor. You may object on reasonable data-protection grounds. If we can't resolve the objection, you may end the affected service. ## International transfers Some sub-processors may process data outside the EEA or the UK. Where they do, the DPA relies on [transfer mechanism, e.g. Standard Contractual Clauses — pending], with supplementary measures where needed. ## Where are we controller rather than processor? For some processing, we may act as a controller in our own right: - our spam-reputation data, which is compiled from report data; - possibly parts of the account-presence checks (pending counsel review). The DPA and our [data-subject notice](/legal/data-subject-notice) explain these cases. Suppression requests from individuals apply across all customers. --- # Cookie notice > Draft cookie notice: mobilevalidate.com uses only strictly necessary cookies for preview access and form protection, with no analytics or advertising. Canonical: https://mobilevalidate.com/legal/cookies · Last updated: 2026-09-25 > **Draft — pending legal review.** This website sets only cookies and similar technologies that are strictly necessary to run it securely. We don't use analytics, advertising, social-media or tracking cookies, and we don't build visitor profiles. For that reason we don't show a consent banner (pending counsel review). ## Which cookies are used? | Name | Set by | When | Purpose | Duration | |---|---|---|---|---| | `CF_Authorization` | Cloudflare Access, for us | Only during the private preview, after an authorised person signs in | Proves that the visitor is allowed to see the preview site | Session length set in the access policy ([duration — pending]) | | Turnstile challenge data | Cloudflare Turnstile, for us | Only on pages with a form (request access, opt-out) | Tells people apart from automated abuse without a puzzle | Short-lived, for the challenge only | The preview cookie disappears once the site is public. Turnstile runs only where a form needs protection against automated submissions. ## What about local storage and server logs? The site does not use local storage or any other browser storage. When you submit a form, we store only a keyed hash of your IP address, not the address itself. The [privacy policy](/legal/privacy) has the details. ## How can you control cookies? You can block or delete cookies in your browser settings. If you block the Turnstile challenge, the request-access and opt-out forms can't be submitted. If you block the preview cookie, you can't see the preview. If we ever add non-essential cookies, we will update this notice and ask for consent first. Contact: info@broadnet.me. --- # MobileValidate API documentation > Developer docs for the MobileValidate API: check phone numbers and e-mail addresses in real time or in bulk jobs, with test mode, webhooks and SDKs. Canonical: https://mobilevalidate.com/docs · Last updated: 2026-09-25 ![A terminal sends an API request and a JSON response returns true, false and null values.](https://mobilevalidate.com/images/api-request-and-json-response.svg) *One REST call. Every answer is true, false or null (unknown).* Machine-readable: [Key facts (JSON)](https://mobilevalidate.com/facts.json) · [OpenAPI spec](https://mobilevalidate.com/openapi.yaml) The MobileValidate API checks phone numbers and e-mail addresses against a catalog of services. These include messaging-app registration, carrier and line type, spam reputation, and account and mailbox checks. One request can run several checks. Each check returns `registered: true`, `false` or `null` (unknown), or a short list of data attributes. You're not charged for inconclusive results. ## How is the API organised? It has three ways in, plus account endpoints: | Surface | Endpoint | Use it for | |---|---|---| | Real-time lookups | `POST /v1/lookup`, `GET /v1/lookups/{id}` | 1–100 numbers and/or e-mails; the request waits up to 30 s for answers | | Bulk jobs | `POST /v1/jobs/estimate`, `POST /v1/jobs`, `GET /v1/jobs/{id}`, `…/results`, `…/download` | Up to 50,000 identifiers per job, including services offered in bulk only | | Webhooks | `/v1/webhook_endpoints` | Signed notifications when lookups and jobs finish | | Account | `GET /v1/account`, `/v1/limits`, `/v1/usage`, `/v1/services` | Balance, limits, usage and the service catalog for your key | The base URL is `https://api.mobilevalidate.com/v1`. Requests and responses are JSON (UTF-8), timestamps are RFC 3339 in UTC, and money is a decimal string such as `{"amount": "0.0012", "currency": "USD"}`. Phone numbers and e-mail addresses go only in POST bodies, never in URLs or headers. ## What does every answer contain? Every identifier gets one result per requested service. The results sit in a `checks` map keyed by service code, such as `checks["telegram.registered"]`. Each result has the same fields: | Field | Meaning | |---|---| | `status` | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `registered` | `true` / `false` for a conclusive answer, `null` otherwise | | `attributes` | Service-specific data (e.g. `line_type`, `carrier`, `risk_level`), or `null` | | `confidence`, `confidence_score` | `high` / `medium` / `low` and a 0–1 score for conclusive answers | | `checked_at`, `cached`, `age_seconds` | When the answer was obtained and whether it came from your account's cache | | `billed` | Whether this check was charged | | `reason` | Why an answer is not conclusive (e.g. `UPSTREAM_TIMEOUT`, `UNSUPPORTED_COUNTRY`) | `false` is only ever a conclusive negative. Anything uncertain is `null`, and uncertain results are free. ## Where should I start? 1. Follow the [quickstart](/docs/quickstart). It uses the public sandbox key, takes about 30 seconds and costs nothing. 2. Read [authentication](/docs/authentication) to understand the three key types and IP allowlists. 3. Use the [test mode](/docs/test-mode) numbers and addresses to build every branch of your integration (registered, not registered, unknown, pending, errors). 4. Choose [real-time lookups](/docs/lookups) or [bulk jobs](/docs/bulk-jobs), and add [webhooks](/docs/webhooks) if you don't want to poll. 5. Handle [errors](/docs/errors) and read the [rate limits and anti-abuse rules](/docs/rate-limits-and-abuse) before going live. The [services reference](/docs/services) lists every check code, and the [API reference](/docs/api-reference) every endpoint. The [SDK](/docs/sdk), [CLI](/docs/cli) and [MCP server](/docs/mcp) wrap the same API. ## What does the API never return? The checks answer whether an account or mailbox exists, or give network and reputation facts about a number. They never return names, photos, profile data, report texts or location. Roaming is shown only as a country. Checking numbers one after another in sequence, or checking lists of made-up addresses, is refused. Everyone whose number or e-mail address is checked can object through the [opt-out form](/opt-out). ## Frequently asked questions ### Do I need an account to try the API? No. Every example in these docs uses the public sandbox key, which answers the documented test numbers and e-mail addresses for free. For your own data you need a personal test key, and live keys come with an approved access request. Test keys are free, never billed and never reach a real network. ### Which base URL do I use? All endpoints live under https://api.mobilevalidate.com/v1. Send JSON over HTTPS with your key in the Authorization header. ### Is there a machine-readable specification? Yes. The OpenAPI 3.1 file is published at /openapi.yaml (the API also serves it at /v1/openapi.yaml), and /docs/api-reference renders it as an interactive reference. --- # Quickstart > Make your first MobileValidate API call in 30 seconds with the public sandbox key, no signup: check a number on WhatsApp and Telegram and read the result. Canonical: https://mobilevalidate.com/docs/quickstart · Last updated: 2026-09-25 This guide takes you from nothing to a checked phone number in about 30 seconds, without signing up. You'll send one real-time lookup with the public sandbox key, read the answer, and then add more checks to the same request. You need `curl`, or one of the languages in the tabs below. ## Step 1: Which key do I use? Start with the **public sandbox key**. It is printed here on purpose: anyone can use it, it answers the documented [test values](/docs/test-values) only, and it is never billed. ```bash export MOBILEVALIDATE_API_KEY="mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" ``` `MOBILEVALIDATE_API_KEY` is the one variable name used everywhere: the SDKs, the CLI and the MCP server read it when you don't pass a key. Every request sends the key as a bearer token: `Authorization: Bearer $MOBILEVALIDATE_API_KEY`. The sandbox key is limited per IP address (30 requests per minute, 1,000 per day) and refuses any other number or address with `403 sandbox_magic_only`. When you want to test with your own data, get a personal test key (`mv_test_…`). Live keys (`mv_live_…`) come with an approved access request. Your own keys are shown only once, when they are created, so keep them in a secret manager and never commit them. See [authentication](/docs/authentication). ## Step 2: How do I check a number? Send the number in E.164 format (`+` followed by the country code and number) to `POST /v1/lookup` and name the checks you want. `+447700900001` is a test number that always answers "registered". ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["whatsapp"]}' ``` If you leave out `checks`, the API defaults to `["whatsapp"]`. Numbers in national format (`07700 900001`) work too if you add `"default_country": "GB"`. ## Step 3: How do I read the answer? The response has one item per number in `results`, in input order. Each item has a `checks` map with one result per service: ```json "checks": { "whatsapp.registered": { "service": "whatsapp.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.489Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } } ``` Branch on `registered`: `true` means an account exists, `false` means it doesn't, and `null` means unknown (read `status` and `reason`). `summary` gives the counts, and `billing` shows what the request cost. It is always zero in test mode. ## Step 4: How do I run several checks at once? List more codes or aliases in `checks`. Each number gets one result per service: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["whatsapp", "telegram", "carrier"]}' ``` In test mode, `…001` is registered, `…002` is not registered, and `…003` is unknown (`reason: "UPSTREAM_TIMEOUT"`) for every yes/no service. The carrier lookup returns data for `…001` and is unknown for `…002` (`NO_DATA`) and `…003`. That way you can exercise every branch of your code. `summary.by_service` gives counts per service. ## What next? - Try the other [test numbers and addresses](/docs/test-mode): pending, unsupported country, rate limited, no balance. - Browse every endpoint in the interactive [API reference](/docs/api-reference) and send test requests from the browser. - Prefer an HTTP client? Import the [Postman collection](/collections/mobilevalidate.postman_collection.json), the [Bruno collection](/collections/mobilevalidate.bruno.zip) or the [.http file](/collections/mobilevalidate.http). The sandbox key is already set. - Some services are offered in bulk only. Run those through a [bulk job](/docs/bulk-jobs). - Check e-mail addresses with the `emails` field. See [e-mail checks](/docs/emails). - Look up codes, aliases and outputs in the [services reference](/docs/services), or call `GET /v1/services` to get the list your key can use, with prices. ## Frequently asked questions ### Does the quickstart cost anything? No. It uses the public sandbox key, a test key that anyone can use. Test keys answer from fixed test data, never reach a real network and are never billed. ### Why does the sandbox key refuse my own number? The public sandbox key answers only the documented test numbers and e-mail addresses (403 sandbox_magic_only otherwise). Get a personal test key to try any input in test mode, and a live key for real checks. ### What do I change to go live? Swap the test key for your live key (mv_live_…) and use real numbers. The request and response shapes stay the same, but test numbers are rejected with test_number_only. --- # Test values > Every MobileValidate test phone number and e-mail address, the exact answer each returns, and the public sandbox key that runs them with no signup. Canonical: https://mobilevalidate.com/docs/test-values · Last updated: 2026-09-25 Test values are phone numbers and e-mail addresses with a fixed, documented answer. Use them to build and test every branch of your integration: registered, not registered, unknown, pending, unsupported and the request errors. They never reach a real network and are never billed. ## How do I try them without signing up? Use the public sandbox key. It is public on purpose and works in every example in these docs: ```bash curl https://api.mobilevalidate.com/v1/lookup -H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" -H "Content-Type: application/json" -d '{"numbers":["+447700900001"],"checks":["whatsapp","telegram","carrier"]}' ``` The sandbox key answers **only the test values below**. Any other number or address returns `403 sandbox_magic_only`. It allows 30 requests a minute and 1,000 a day per IP address, bulk jobs of at most 10 rows, and no webhooks. You can also run every value from the console on the [home page](/#try-it). ## Which test values can I use? The answer depends on the value and on the kind of check. Account checks answer yes or no (`registered`), the carrier lookup returns data (`attributes`), and every e-mail check follows the address table. ### Phone numbers | Number | Account checks (WhatsApp, Telegram, …) | `whatsapp.business` | |---|---|---| | `+447700900001` | `registered: true` | `registered: true`, `business: false` | | `+447700900002` | `registered: false` | `registered: false` | | `+447700900003` | `unknown` (`UPSTREAM_TIMEOUT`) | `unknown` (`UPSTREAM_TIMEOUT`) | | `+447700900004` | `pending` for about 5 s, then `registered: true` | `pending` for about 5 s, then `registered: true`, `business: false` | | `+447700900005` | `unsupported_country` | `unsupported_country` | | `+447700900006` | `registered: true` | `registered: true`, `business: true` | ### Carrier lookup | Number | `network.carrier` | |---|---| | `+447700900001` | `line_type: "mobile"`, `carrier: "Test Carrier"`, `country: "GB"` | | `+447700900002` | `unknown` (`NO_DATA`) | | `+447700900003` | `unknown` (`UPSTREAM_TIMEOUT`) | | `+447700900004` | `pending` for about 5 s, then `line_type: "mobile"`, `carrier: "Test Carrier"`, `country: "GB"` | | `+447700900005` | `unsupported_country` | | `+447700900006` | `line_type: "mobile"`, `carrier: "Test Carrier"`, `country: "GB"` | ### E-mail addresses | Address | Every e-mail check | |---|---| | `registered@test.mobilevalidate.com` | `registered: true` | | `not-registered@test.mobilevalidate.com` | `registered: false` | | `unknown@test.mobilevalidate.com` | `unknown` (`UPSTREAM_TIMEOUT`) | | `pending@test.mobilevalidate.com` | `pending` for about 5 s, then `registered: true` | | `unsupported@test.mobilevalidate.com` | `unknown` (`UNSUPPORTED_PROVIDER`) | ### Values that fail the whole request | Value | Response (for every check) | |---|---| | `+447700900429` | `429 rate_limited` with a `Retry-After` header | | `+447700900402` | `402 insufficient_balance` | | `rate-limited@test.mobilevalidate.com` | `429 rate_limited` with a `Retry-After` header | | `no-balance@test.mobilevalidate.com` | `402 insufficient_balance` | Services limited to some countries still answer the test numbers, so every check can be tested with them. ## What do other numbers and addresses return? - **Public sandbox key:** `403 sandbox_magic_only`, with a suggestion to use a test value. - **Your own test key** (`mv_test_…`): a stable, made-up answer derived from the number or address and the check. The same input always gives the same answer, marked `"test": true`. Nothing is sent to any network. - **Live keys** (`mv_live_…`): test values are refused with `400 test_number_only`, so test data never ends up in real billing. ## How do I test error handling? The values that fail the whole request return the real error shape, including `request_id` and the `Retry-After` header for `429`. Use them to test your retry and top-up paths. Every error code is described in [Errors](/docs/errors). ## What comes next? - [Get a free test key](/get-test-key) to try any number or address in test mode, run bulk jobs and receive webhooks. - Live keys follow after we review your use case: [request access](/request-access). - The full test-mode behaviour, including the spam reputation fixtures, is in [Test mode](/docs/test-mode). ## Frequently asked questions ### Do I need an account to use the test values? No. The public sandbox key in the examples answers every test value on this page. It is free, never billed and limited per IP address. ### Why does the sandbox key reject my own number? The sandbox key answers only the test values on this page and returns 403 sandbox_magic_only for anything else. Get a personal test key to try any number in test mode, or request access for live keys. ### Are the test numbers real phone numbers? No. They come from the +44 7700 900000–900999 range, which the UK regulator reserves for drama and never assigns to subscribers. The e-mail addresses use our own test domain. --- # Authentication > How MobileValidate API keys work: test, live and agent keys, scopes, IP allowlists for IPv4 and IPv6, and what a 401 means. Canonical: https://mobilevalidate.com/docs/authentication · Last updated: 2026-09-25 Every request authenticates with an API key sent as a bearer token: `Authorization: Bearer `. There are three kinds of key: test, live and agent. The key's prefix tells you which kind it is. Keys carry scopes and can be locked to IP addresses. Any key problem returns the same `401 unauthorized`. ## What kinds of key are there? | Prefix | Kind | What it does | |---|---|---| | `mv_test_` | Test | Answers from fixed [test data](/docs/test-mode). Never reaches a real network, never billed. Real numbers get deterministic fake answers. | | `mv_test_publicSandbox…` | Public sandbox | A test key published in these docs so every example runs as pasted. Only the documented test numbers and addresses (`403 sandbox_magic_only` otherwise), per-IP limits, jobs up to 10 rows, no webhooks. | | `mv_live_` | Live | Real checks, billed per conclusive answer. Test numbers and the test e-mail domain are refused (`test_number_only`). | | `mv_agent_` | Agent | For AI agents and the [MCP server](/docs/mcp). Only the scopes it was given, plus a daily spend cap. | A key is its prefix, 30 random base62 characters and a 6-character checksum. The checksum lets secret scanners and our API spot a mistyped or truncated key. We store only a keyed hash of each key, so a key is shown once, when it is created. If you lose a key, ask for a new one and revoke the old one. Keep keys on your servers. Don't put them in front-end code, mobile apps, URLs or logs. The SDKs, the CLI and the MCP server read `MOBILEVALIDATE_API_KEY` from the environment and never print it. It is the only variable name we use. ## What are scopes? Scopes limit what a key can do. A request outside a key's scopes gets `403 insufficient_scope`. | Scope | Allows | |---|---| | `lookup:write` | `POST /v1/lookup`, `GET /v1/lookups/{id}` | | `jobs:write` | `POST /v1/jobs/estimate`, `POST /v1/jobs`, `DELETE /v1/jobs/{id}` | | `jobs:read` | Read your own jobs, their results and downloads | | `jobs:read_all` | Read every job in the account (by default a key sees only the jobs it created). Any `lookup:write` key can read the account's lookups. | | `account:read` | `GET /v1/account`, `/v1/limits`, `/v1/usage` | | `webhooks:manage` | Create, change, test and delete webhook endpoints | Test and live keys without an explicit list get every scope except `webhooks:manage`, which has to be granted on purpose. The public sandbox key never has `webhooks:manage` or `jobs:read_all`. Agent keys have exactly the scopes they were issued with. `GET /v1/services` and `GET /v1/health` work without a key; with a key, `/v1/services` shows what your account can use, with your prices. An unknown path returns `404 not_found` before the key is checked. ## How do IP allowlists work? A key can be restricted to a list of IP addresses or CIDR ranges. Requests from anywhere else are refused with the same `401 unauthorized` as a bad key. Some live keys must have an allowlist, depending on your account. - Entries can be single addresses (`203.0.113.10`) or ranges (`203.0.113.0/24`, `2001:db8:1234::/48`). - IPv4 and IPv6 are both supported. An IPv4-mapped IPv6 address (`::ffff:203.0.113.10`) is matched as its IPv4 address. - **IPv6 note:** servers and networks with IPv6 often rotate their source address (privacy extensions, cloud NAT, container networking). A dual-stack client may also switch from IPv4 to IPv6 without warning. Allow your IPv6 prefix, usually the `/64` of the host or the `/56` or `/48` assigned to your network, rather than a single address. You can also force your client to use IPv4. ## What happens when authentication fails? A missing, malformed, unknown, revoked or expired key, or a key used from an address outside its allowlist, returns: ```json {"error": {"code": "unauthorized", "message": "…", "status": 401, "retryable": false, "param": null, "doc_url": "https://mobilevalidate.com/docs/errors#unauthorized", "request_id": "req_…"}} ``` The answer is the same in every case on purpose, so nobody can probe which keys exist. Retrying won't help. Check the prefix, check for a trailing space or line break, and check your server's outgoing IP address. Include the `request_id` when you contact support. ## Frequently asked questions ### Why does a wrong IP address give the same error as a wrong key? A missing, unknown, revoked, expired or wrong-IP key always returns the same 401 unauthorized, so the response never tells an attacker which part was wrong. Your account team can see the exact cause in the audit log. ### My allowlisted server suddenly gets 401. What changed? Often the server started connecting over IPv6, or its IPv6 address rotated. Add your IPv6 prefix (for example a /64) as well as your IPv4 address. ### Can I put the key in a query string? No. Keys go only in the Authorization header. Never embed a key in browser or mobile app code; call the API from your backend. --- # Test mode > Every MobileValidate test number and test e-mail address, and the exact answer each one returns with a test key: registered, unknown, pending, errors. Canonical: https://mobilevalidate.com/docs/test-mode · Last updated: 2026-09-25 Test keys (`mv_test_…`) give you free, predictable answers so you can build and test every branch of your integration. They never reach a real network and are never billed. They follow the same validation, request limits and request shapes as live keys; daily caps, spend caps and the daily e-mail-pattern rule apply only to live keys. The test numbers and addresses below always return the same result. The public sandbox key used in the examples on this site is a test key that accepts **only** these documented values. Any other input returns `403 sandbox_magic_only`. A personal test key accepts any number or address. ## Which test numbers can I use? The test numbers come from the `+44 7700 900000–900999` range, which the UK regulator reserves for drama and is never assigned to anyone. Live keys refuse the whole `+44 7700 9xxxxx` block. These answers apply to **every yes/no service** (WhatsApp, Telegram, Viber, account checks and so on): | Number | Result | |---|---| | `+447700900001` | `registered: true`, confidence high | | `+447700900002` | `registered: false` | | `+447700900003` | `unknown` (`registered: null`, `reason: "UPSTREAM_TIMEOUT"`) | | `+447700900004` | `pending` for about 5 s, then `registered: true` (real-time lookups only; in jobs it answers at once) | | `+447700900005` | `unsupported_country` | | `+447700900006` | `registered: true`; `business: true` for `whatsapp.business` | | `+447700900429` | the whole request fails with `429 rate_limited` | | `+447700900402` | the whole request fails with `402 insufficient_balance` | | any other valid number | a stable answer derived from the number (and service), marked `"test": true` (personal test keys only) | Services limited to some countries answer `unsupported_country` for other numbers. The test numbers above are exempt, so they work with every service. Bulk-only services are refused on `POST /v1/lookup` in test mode too, just as in live mode. ## What do the carrier lookups return in test mode? `network.carrier` and `network.carrier_us` return data rather than yes/no: | Number | Result | |---|---| | `+447700900001` | `{"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}` (`network.carrier_us` has no `country`) | | `+447700900002` | `unknown`, `reason: "NO_DATA"` | | `+447700900003` | `unknown`, `reason: "UPSTREAM_TIMEOUT"` | | `+447700900004` | `pending`, then the same data as `…001` | | `+447700900005` | `unsupported_country` | | other numbers | the same fake data, with the number's country (`network.carrier_us`: numbers outside US/CA → `unsupported_country`) | As in live mode, a conclusive data answer has `registered: true` ("data found"). A non-conclusive one has `registered: null`. ## What does spam reputation return in test mode? `number.spam` covers the US, Canada and Germany. In test mode the whole `+44 7700 9xxxxx` range is also allowed, so the documented numbers work: | Number | `number.spam` result | |---|---| | `+447700900001` | `risk_level: "high"`, `risk_score: 95`, `reason_regulator: true`, `reason_community: true`, `top_category: "robocall"`, `first_seen: "2025-11"`, `last_seen: "2026-08"`, `sources: 2` | | `+447700900002` | `risk_level: "no_reports"`, `risk_score: 0`, all reasons `false`, `sources: 0` (conclusive) | | `+447700900003` | `unknown`, `reason: "UPSTREAM_TIMEOUT"` | | `+447700900004` | `pending`, then `risk_level: "medium"`, `risk_score: 55`, `reason_community: true`, `top_category: "warranty"` | | `+447700900005` | `unsupported_country` | | other numbers in the test range, US, CA or DE | a stable level per number (`high`, `medium`, `low` or `no_reports`) with matching reasons | All spam test data is invented. None of it comes from real reports. ## What about live network status (HLR)? `number.hlr` is **coming soon**. While it is switched off, test keys get `403 service_disabled` just like live keys. When it launches, the test numbers will answer: `…001` reachable (`mcc_mnc: "23415"`, `country: "GB"`), `…002` unreachable, `…003` unknown, `…004` pending then reachable, `…005` unsupported country, `…006` reachable and `ported: true`. ## Which test e-mail addresses can I use? The domain is `test.mobilevalidate.com`. The part before the `@` picks the answer for **every e-mail service**: | Address | Result | |---|---| | `registered@test.mobilevalidate.com` | `registered: true` | | `not-registered@test.mobilevalidate.com` | `registered: false` | | `unknown@test.mobilevalidate.com` | `unknown`, `reason: "UPSTREAM_TIMEOUT"` | | `pending@test.mobilevalidate.com` | `pending` for about 5 s, then `true` (real-time lookups only) | | `unsupported@test.mobilevalidate.com` | `unknown`, `reason: "UNSUPPORTED_PROVIDER"` | | `rate-limited@test.mobilevalidate.com` | the request fails with `429 rate_limited` | | `no-balance@test.mobilevalidate.com` | the request fails with `402 insufficient_balance` | | any other address | a stable yes/no derived from the address and service | Live keys may not use this domain (`400 test_number_only`, "Test e-mail addresses can only be used with test keys."). ## What does a test error look like? The error test numbers return the real error shape, so you can test your retry and top-up handling: ```json { "error": { "code": "rate_limited", "message": "Too many requests (test mode).", "status": 429, "retryable": true, "param": null, "doc_url": "https://mobilevalidate.com/docs/errors#rate_limited", "request_id": "req_0VWF4BOF195D5GjKfh0P" } } ``` Test keys are rate limited like live keys (10 requests per second, burst 20). The public sandbox key has its own per-IP limits (30 requests per minute, 1,000 per day). A fast loop can therefore get a real `rate_limited` answer as well. With a test key, a pending lookup sends its `lookup.completed` webhook when the lookup is read again after the 5 seconds (the request's own `wait`, or a later `GET /v1/lookups/{id}`). The payload has the same fields as a live one, with `"livemode": false`. Test jobs finish immediately and send no job webhooks. ## Frequently asked questions ### Are test numbers real phone numbers? No. They come from the +44 7700 900000–900999 range, which the UK regulator reserves for drama and fiction and never assigns to subscribers. ### What happens if I send a real number with a test key? You get a fake but stable answer: a hash of the number (and service) decides it, so the same number always gives the same result. Nothing is sent to any network and nothing is billed. ### Can I use test numbers with a live key? No. A live key sending a test number or a test-domain address gets 400 test_number_only, so test data never ends up in real billing. --- # Real-time lookups > Check up to 100 phone numbers and e-mails in one request: multi-check, waiting and polling, the results shape, caching, max_cost and idempotency. Canonical: https://mobilevalidate.com/docs/lookups · Last updated: 2026-09-25 `POST /v1/lookup` checks 1–100 phone numbers and/or e-mail addresses against one or more services and waits up to 30 seconds for the answers. It returns `200` when every answer is ready. Otherwise it returns `202` with `status: "pending"` and a URL to poll. Use it for real-time decisions, such as a sign-up form or an OTP send. For lists, use [bulk jobs](/docs/bulk-jobs). ## What goes in the request? ```json { "numbers": ["+447700900001", "07700 900002"], "emails": ["registered@test.mobilevalidate.com"], "checks": ["whatsapp", "telegram", "email"], "default_country": "GB", "max_age": 604800, "wait": 10, "max_cost": {"amount": "0.05", "currency": "USD"}, "webhook_endpoint_id": "we_…", "metadata": {"crm_id": "C-1042"} } ``` | Field | Rules | |---|---| | `numbers`, `emails` | At least one; 1–100 identifiers in total. Numbers in any format; national formats need `default_country` | | `checks` | Service codes or aliases, up to 20 (default `["whatsapp"]`). Phone checks run on `numbers`, e-mail checks on `emails` | | `default_country` | ISO 3166-1 alpha-2, e.g. `GB` | | `max_age` | Seconds; accept a cached answer up to this age. `0` forces a fresh, billed check | | `wait` | 0–30 seconds to hold the request open (default 10) | | `max_cost` | Refuse with `402 cost_limit_exceeded` if the maximum possible cost is higher | | `webhook_endpoint_id` | A verified endpoint that receives `lookup.completed` | | `metadata` | Up to 20 string keys, values up to 500 characters; echoed back | Unknown fields are rejected (`400 invalid_request`), so typos don't pass silently. Every check must have identifiers of its type: `"checks": ["email"]` with only `numbers` is refused with `param: "checks[0]"`. Numbers × applicable checks may not exceed 2,000 per lookup. ## How do multi-check requests work? Every identifier gets one result per requested service of its input type. Numbers get phone checks and e-mails get e-mail checks. If you send e-mails without any e-mail check (or numbers without a phone check), the request is refused with `param: "checks"`. That way you never pay for a request that couldn't answer. `whatsapp.registered` and `whatsapp.business` together collapse into `whatsapp.business`, because it answers both questions. Some services are **bulk only**. On this endpoint they return `403 service_disabled`: "The check 'signal.registered' is available in bulk jobs only (POST /v1/jobs)." The [services reference](/docs/services) marks them. `GET /v1/services` returns `modes` for each service. ## What does the response look like? Real test-mode output for `{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["whatsapp", "telegram", "carrier"]}` (first item, then the totals): ```json { "object": "lookup", "id": "lkp_0VWF4BLkr4ltR4n4dawy", "status": "completed", "livemode": false, "created_at": "2026-09-25T14:23:15.712Z", "results": [ { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "whatsapp.registered": {"service": "whatsapp.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:23:15.717Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null}, "telegram.registered": {"service": "telegram.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:23:15.717Z", "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-09-25T14:23:15.717Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null} }, "whatsapp": {"service": "whatsapp.registered", "status": "completed", "registered": true, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:23:15.717Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null}, "test": true } ], "summary": { "total": 3, "registered": 1, "not_registered": 1, "unknown": 1, "pending": 0, "invalid": 0, "suppressed": 0, "by_service": { "whatsapp.registered": {"completed": 2, "registered": 1, "not_registered": 1, "unknown": 1, "pending": 0}, "telegram.registered": {"completed": 2, "registered": 1, "not_registered": 1, "unknown": 1, "pending": 0}, "network.carrier": {"completed": 1, "registered": 1, "not_registered": 0, "unknown": 2, "pending": 0} } }, "billing": {"billed_units": 0, "cost": {"amount": "0", "currency": "USD"}, "balance_after": {"amount": "9.97907", "currency": "USD"}}, "next": null, "request_id": "req_0VWF4BLivGQN76TzQcXl" } ``` ## How do I read a result item? - **Rows** come in input order: numbers first, then e-mails. `kind` is `phone` or `email`; treat a missing `kind` as `phone`. - **Phone rows** have `e164`, `country` and `number_status` (`valid`, `invalid_number`, `duplicate`, `suppressed`). E-mail rows have `email` and `email_status` instead (see [e-mail checks](/docs/emails)). - **`checks`** holds one result per service, keyed by code, in request order. It is missing for invalid, duplicate and suppressed rows, which are never checked or billed. - **`registered`** is `false` only for a conclusive negative. `null` means not conclusive. Data services (carrier, spam) put their answer in `attributes` and set `registered: true` when data was found. - **`whatsapp`** is a compatibility copy of the WhatsApp result in the original v1 shape, with `business` added when `whatsapp.business` was requested. It appears only when a `whatsapp.*` check was requested. New code should read `checks`. - **`summary`** counts rows for the first requested service of each row's kind. `summary.by_service` gives the counts per service. For data services, "registered" means data found. - **`billing`** shows the checks billed in this request, their cost and your balance afterwards. Clients should ignore fields they don't recognise and accept new enum values. ## How do waiting and polling work? The request waits up to `wait` seconds (default 10, maximum 30). If some answers are still outstanding, you get `202` with a `Location` header and: ```json "status": "pending", "next": {"poll_url": "/v1/lookups/lkp_0VWF4BNZB4tgTHFOXsUy", "poll_after_ms": 2000} ``` Poll `GET /v1/lookups/{id}?wait=30`. This is a long poll: it returns as soon as everything is done, or after 30 seconds. Pending checks carry `status: "pending"` and `poll_after_ms`. With `wait: 0` the request returns straight away, which suits webhook-driven flows. Test number `+447700900004` stays pending for about 5 seconds, so you can try this out. When you read a lookup later, identifiers in `input` are masked (for example `+44770*****04`). ## How do caching, costs and retries work? - **Cache.** Answers are cached per account, number (or address) and service. A request within `max_age` (default: each service's freshness window) is answered from cache with `cached: true`, the answer's age in `age_seconds`, and `billed: false`. `max_age: 0` forces a fresh check, which is billed and rate limited. - **Reservation.** Before checking, the API reserves the maximum possible cost of the request. Each check is then settled when its answer arrives. Inconclusive checks (unknown, unsupported country, timeout, invalid, duplicate) are released, and you're not charged for them. If the reservation doesn't fit your balance, you get `402 insufficient_balance`. If it exceeds `max_cost`, you get `402 cost_limit_exceeded`. - **Idempotency.** Send `Idempotency-Key: <1–255 printable ASCII characters, no spaces>` on any POST. The same key with the same body within 24 hours replays the stored response with `Idempotent-Replayed: true`. The same key with a different body gets `409 idempotency_key_reused`. A retry while the first request is still running gets `409 idempotency_request_in_progress` (retryable). Keys are scoped to your account. ## Frequently asked questions ### What happens if an answer takes longer than my wait time? The API returns 202 with status pending and a next.poll_url. Poll GET /v1/lookups/{id}?wait=30, or register a webhook for lookup.completed. ### Is a pending result billed? Only once it becomes conclusive. The price is reserved when you send the request and settled per check; unknown, unsupported, invalid and duplicate results are released and free. ### Can I send the same request twice safely? Yes, with an Idempotency-Key header. The same key and body within 24 hours replays the first response (Idempotent-Replayed: true) instead of checking again. ### Why is the whatsapp object missing from some results? The top-level whatsapp object is a compatibility copy that appears only when a whatsapp.* check was requested. Always read checks for new code. --- # E-mail checks > Check e-mail addresses with the MobileValidate API: which services exist, how addresses are normalized, result rows, and anti-enumeration rules. Canonical: https://mobilevalidate.com/docs/emails · Last updated: 2026-09-25 E-mail checks answer whether a mailbox exists, or whether an account on a platform is registered with an address. Send the addresses in `emails` (next to or instead of `numbers`) and request at least one e-mail check. Answers are `registered: true`, `false` or `null` only. They never include names, avatars or profile data. ## Which e-mail services are there? | Code | Alias | Answers | Modes | |---|---|---|---| | `email.valid` | `email` | The mailbox exists (major webmail providers) | real time + bulk | | `gmail.email` | `gmail` | A Gmail account exists | bulk only | | `outlook.email` | `outlook` | An Outlook account exists | bulk only | | `yahoo.email` | `yahoo` | A Yahoo account exists | bulk only | | `yandex.email` | `yandex` | A Yandex account exists | bulk only | | `mailru.email` | `mailru` | A Mail.ru account exists | bulk only | | `apple.email` | `apple.email` | An Apple Account uses this address | real time + bulk | | `amazon.email`, `facebook.email`, `instagram.email`, `netflix.email`, `spotify.email` | — | An account on that platform uses this address | real time + bulk | | `linkedin.email`, `x.email` | — | An account on that platform uses this address | bulk only | Bulk-only services work in [bulk jobs](/docs/bulk-jobs). On `POST /v1/lookup` they return `403 service_disabled`. `GET /v1/services` is the live list for your key (`input_type: "email"`). ## How do I send e-mails in a request? ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com", "unsupported@test.mobilevalidate.com"], "checks": ["email"]}' ``` Phone checks run on `numbers` and e-mail checks on `emails`, so one request can mix both, up to 100 identifiers in total. If you send e-mails without an e-mail check, the request is refused before anything is stored: `400 invalid_request`, `param: "checks"`, "No e-mail check was requested: add an e-mail check (e.g. email, gmail…) for the emails. See GET /v1/services." ## What does an e-mail result row look like? Real test-mode output, first row: ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "email.valid": { "service": "email.valid", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:23:15.781Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` E-mail rows have `kind: "email"`, the normalized `email` (`null` if invalid), and `email_status`: `valid`, `invalid_email`, `duplicate` or `suppressed`. `e164` and `country` are always `null`, and there is no `number_status` or `whatsapp` copy. Rows are ordered numbers first, then e-mails, in input order. The second test address above returns `status: "unknown"`, `reason: "UNSUPPORTED_PROVIDER"`, `billed: false`. ## How are addresses normalized? Addresses are trimmed and lowercased, and nothing else. We do not remove dots or `+tags`, and we do not map one provider domain to another, because such rewriting could merge addresses that belong to different people. An address that isn't syntactically valid gets `email_status: "invalid_email"` and no checks. A repeat of an earlier address in the same request is `duplicate`. An address on the suppression list is `suppressed`. None of these are billed. When you read results back later, `input` is masked (`re•••@test.mobilevalidate.com`) and `email` holds the normalized address. ## What are the anti-enumeration rules for e-mails? Requests that look like generated e-mail lists are rejected with `403 suspected_enumeration`, `param: "emails"`: - **Per request:** 20 or more distinct addresses on one domain whose local parts differ only by digits and/or separators. Every run of non-letters counts as one class, so `john1@`, `john.2@`, `john_3@`, `john-4@` and `john+5@` fall into one group, and `googlemail.com` is grouped with `gmail.com`. - **Per day (live keys):** 50 or more distinct addresses of one such pattern from the same account in one UTC day, across all requests. Splitting a generated list into small requests doesn't get around this. The account's daily cap counts e-mails the same way as numbers. E-mail checks are for fraud prevention and deliverability, such as screening sign-ups and keeping lists clean. They are not for discovering who owns an address. ## Frequently asked questions ### Do you send an e-mail to the address or log in to the mailbox? No. Nothing is sent to the address and no mailbox is read. The answer is only yes, no or unknown. ### Why is my company domain unknown for the email check? The email check covers major webmail providers. Addresses on other domains return unknown with reason UNSUPPORTED_PROVIDER, which is not billed. ### Do you treat john.smith@ and johnsmith@ as the same address? No. We only trim spaces and lowercase the address. Dots and plus tags are kept, because rewriting them could merge different people's addresses. --- # Bulk jobs > Run up to 50,000 phone numbers and e-mails through any service: free estimate, JSON or CSV input, progress, paginated results and CSV/NDJSON downloads. Canonical: https://mobilevalidate.com/docs/bulk-jobs · Last updated: 2026-09-25 A bulk job checks a list of up to 50,000 phone numbers and/or e-mail addresses against one or more services, in the background. You create it with `POST /v1/jobs` and follow its progress. When it is done you page through the results or download the whole file as CSV or NDJSON. Jobs use the lower bulk price and can run services that are offered in bulk only. ## How do I estimate a job first? `POST /v1/jobs/estimate` takes the same body as a job and returns counts and the maximum cost, without checking anything: ```bash curl https://api.mobilevalidate.com/v1/jobs/estimate \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900002", "+447700900003"], "checks": ["signal", "imessage"]}' ``` ```json {"object": "estimate", "total": 4, "valid": 3, "invalid": 0, "duplicate": 1, "cached": 0, "unsupported": 0, "suppressed": 0, "checks": ["signal.registered", "imessage.registered"], "checks_total": 8, "billable_max": 0, "max_cost": {"amount": "0", "currency": "USD"}} ``` `total`, `valid`, `invalid`, `duplicate`, `unsupported` and `suppressed` count rows (`unsupported` is currently always 0; unsupported countries show up per check in the results). `checks_total`, `cached` and `billable_max` count checks (rows × services). `billable_max` and `max_cost` are the worst case, and `max_cost` is exactly what the job reserves, so you can pass it as the job's `max_cost`. They include the checks counted in `cached`: a cached answer is free if it is still fresh when the job reaches it, so the final charge is often lower, and the unused part of the reservation is released. (Test keys always estimate zero.) ## How do I create a job? Send JSON with `numbers` and/or `emails` (up to 50,000 in total) and `checks`: ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: signal-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["signal", "imessage", "rcs"]}' ``` Or upload a CSV file as `multipart/form-data`. The `file` field holds the CSV. The optional fields are `checks` (comma-separated), `default_country`, `max_age`, `max_cost` (a decimal amount) and `webhook_endpoint_id`: ```bash title="Upload a CSV (leads.csv in the current directory)" curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -F file=@leads.csv -F checks=whatsapp,carrier,email -F default_country=GB ``` A `phone`, `number`, `msisdn`, `mobile`, `telephone` or `e164` column feeds `numbers`, and an `email` (or `e-mail`, `mail`) column feeds `emails`. A row with both contributes to both; empty cells are skipped. If the file contains e-mail addresses, request at least one e-mail check (as above), otherwise the job is refused with `400 invalid_request`; the same applies to numbers and phone checks. Without a recognised header, the first column is used, and cells containing `@` are treated as e-mails. The request body may be up to 4 MB. The API answers `201` with the job object and a `Location: /v1/jobs/{id}` header. Numbers are numbered first (rows 1..n), then e-mails. ## What limits apply to a job? - Up to **50,000 identifiers** (numbers + e-mails) per job. Above that: `400 too_many_numbers`. - Up to **20 checks** per request. - Up to **100,000 checks per job** (rows × applicable checks, counting every submitted row, invalid ones included). Above that: `400 invalid_request`, "Too many checks: numbers and e-mails × applicable checks must not exceed 100,000 per job. Send fewer identifiers or fewer checks." - The account's daily cap counts **identifiers** (valid numbers and e-mails), not checks. - Anti-enumeration rules apply as for lookups: requests that look like sequential number ranges or generated e-mail lists are rejected (see [rate limits and abuse](/docs/rate-limits-and-abuse)). All of these, plus `max_cost` and your balance, are checked before anything is stored. ## How do I follow progress? `GET /v1/jobs/{id}` returns the job, and `?wait=30` long-polls until the job finishes or 30 seconds pass. Live jobs move from `queued` to `running` and end as `completed` or `cancelled`. Test-mode jobs are `completed` as soon as they are created. `preflight`, `merging` and `failed` are reserved for future use, so handle them without breaking. ```json {"object": "job", "id": "job_0VWF4DPMNNZ10QNau3Pe", "status": "completed", "livemode": false, "checks": ["signal.registered", "imessage.registered", "rcs.registered"], "created_at": "2026-09-25T14:23:23.624Z", "completed_at": "2026-09-25T14:23:23.630Z", "progress": {"total": 3, "checks_total": 9, "done": 9, "conclusive": 6, "non_billable": 9}, "eta_seconds": null, "cost": {"estimated_max": {"amount": "0", "currency": "USD"}, "reserved": {"amount": "0", "currency": "USD"}, "charged": {"amount": "0", "currency": "USD"}, "released": {"amount": "0", "currency": "USD"}}, "retention_days": 30} ``` `progress.total` counts rows. `checks_total`, `done`, `conclusive` and `non_billable` count checks. `cost` shows the reserved, charged and released amounts. To be notified instead of polling, subscribe a [webhook](/docs/webhooks) to `job.completed` (sent for live jobs; `job.failed` and `job.progress` are subscribable, delivery is rolling out). ## How do I page through results? `GET /v1/jobs/{id}/results?limit=100&after=` returns `{object: "list", data, has_more, next_cursor}`. `limit` defaults to 100, with a maximum of 1,000. Each item is one **row**, with all its services in `checks`, in the same shape as a [lookup result](/docs/lookups). A row is never split across pages. The input is masked (`+44770*****01`), and `e164` holds the normalized number. Filters: `registered=true|false|null` and `status=` apply to one service per row, chosen by `service=` (default: the job's first check). With a filter, only rows of that service's input type are returned. For example, `?service=signal®istered=true` lists the numbers with a Signal account. A bad cursor returns `400 invalid_cursor`. ## How do I download the results? `GET /v1/jobs/{id}/download?format=csv` or `?format=ndjson` streams the whole file with a `Content-Disposition: attachment` header. There is one line per row, in row order. The first columns describe the row's primary service (its first requested check of that kind), except `billed`, which is `true` when any check on the row was billed: `row_no, input_masked, e164, country, number_status, service, status, registered, business, checked_at, cached, billed, kind, email, email_status` Then, for each service, the columns `.status`, `.registered` (yes/no services), `.` for every attribute, and `.billed`. They are empty for rows of the other kind. A real CSV line from a test job with `whatsapp`, `spam` and `email`: ```text row_no,input_masked,e164,country,number_status,service,status,registered,business,checked_at,cached,billed,kind,email,email_status,whatsapp.registered.status,whatsapp.registered.registered,whatsapp.registered.billed,number.spam.status,number.spam.risk_level,number.spam.risk_score,number.spam.reason_regulator,number.spam.reason_government,number.spam.reason_community,number.spam.reason_unassigned,number.spam.voip_range,number.spam.top_category,number.spam.first_seen,number.spam.last_seen,number.spam.sources,number.spam.billed,email.valid.status,email.valid.registered,email.valid.billed 1,"'+44770*****01",+447700900001,GB,valid,whatsapp.registered,completed,true,,2026-09-25T14:28:15.993Z,false,false,phone,,,completed,true,false,completed,high,95,true,false,true,false,false,robocall,2025-11,2026-08,2,false,,, ``` Values that a spreadsheet could read as a formula, such as a masked number starting with `+`, get a leading apostrophe. Booleans are `true`/`false`, and integers are plain numbers. In NDJSON, empty cells are `null` and integers are JSON numbers. ## How do I cancel a job or delete its data? `DELETE /v1/jobs/{id}` does one of two things: - **Running job:** cancels it. Checks not yet sent for processing are released and never billed. Checks already in progress finish, are stored and are billed if conclusive, because they can't be recalled. The job ends as `cancelled`. - **Finished job:** purges its per-row results now, instead of waiting for the retention period. The job object remains with `purged_at` set. Afterwards `GET /v1/jobs/{id}/results` returns an empty list (`data: []`), and download requests return `404 not_found` with the message "Results for this job were purged." Job results are kept for your account's retention setting (30 days by default) and then deleted automatically. ## Frequently asked questions ### Which services can run in a bulk job? Every active service, including those offered in bulk only (for example signal, imessage, rcs, gmail). The real-time flag does not matter for jobs. ### Is the estimate free? Yes. POST /v1/jobs/estimate only validates and counts; it checks nothing, stores nothing and charges nothing. ### What happens to results after a job finishes? They are kept for your account's retention period (30 days by default) and then deleted. You can delete them earlier with DELETE /v1/jobs/{id}. ### If I cancel a running job, am I charged for anything? Checks not yet sent for processing are released and never billed. Checks already in progress finish, are stored and are billed if conclusive, because they cannot be recalled. --- # Webhooks > Receive signed MobileValidate webhooks when lookups and jobs finish: endpoint verification, event types, Standard Webhooks signatures and retries. Canonical: https://mobilevalidate.com/docs/webhooks · Last updated: 2026-09-25 Webhooks notify your server when a lookup or job finishes, so you don't have to poll. Every delivery is an HTTPS POST signed with the Standard Webhooks scheme, an HMAC-SHA256 over the message id, timestamp and body. Endpoints must prove they are yours before they receive events. Failed deliveries are retried for up to 24 hours. ## How do I register an endpoint? Managing endpoints needs a key with the `webhooks:manage` scope. Keys don't get this scope by default, and the public sandbox key never has it (it answers `403 insufficient_scope`), so use your own key here. ```bash key=personal expect=403 curl https://api.mobilevalidate.com/v1/webhook_endpoints \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://hooks.example.com/mobilevalidate", "events": ["lookup.completed", "job.completed", "job.failed"]}' ``` The response (`201`) includes `id` (`we_…`), `status: "pending_verification"` and `secret` (`whsec_…`). **The secret is shown only this once.** Store it in your secret manager. - The URL must be `https://` and resolve to a public IP address. Private, loopback and link-local addresses are refused, and redirects are not followed. - Endpoints belong to a mode. An endpoint created with a test key is a test endpoint (`livemode: false`) and gets only test-mode events. One created with a live key is a live endpoint and gets only live events. Each key sees and manages only the endpoints of its own mode, so a test key can never change where your live events go. - An account can have up to 20 endpoints per mode. - `GET /v1/webhook_endpoints` lists them, `PATCH /v1/webhook_endpoints/{id}` changes `url` or `events` (a new URL must be verified again), and `DELETE` removes one. ## How does endpoint verification work? Within about 30 seconds of creating an endpoint, we send it a signed `webhook.verification` event, and resend it every hour until it is answered. When your endpoint answers with any `2xx` status, it becomes `active`. `webhook_endpoint_id` in a request must name a verified endpoint of the same mode as the key (`400 invalid_request` otherwise). If you later change an endpoint's URL, its events are held, not dropped, until the new URL is verified (they expire after 24 hours). This ownership challenge stops anyone from pointing our deliveries at a server they don't control. `POST /v1/webhook_endpoints/{id}/test` queues a signed `webhook.test` event (answers `202`). Use it to check your signature code. Test events are delivered even before verification. To have a lookup or job notify a particular endpoint, pass its id as `webhook_endpoint_id` in `POST /v1/lookup` or `POST /v1/jobs`. You can't pass an ad-hoc URL. ## Which events are there? | Event | When | Status | |---|---|---| | `lookup.completed` | A lookup that returned `pending` has finished | delivered | | `job.completed` | A bulk job finished | delivered (live jobs; test jobs finish at once and send none) | | `job.failed` | A bulk job failed | subscribable; delivery rolling out | | `job.progress` | Progress update for a running job (opt-in, at most once a minute) | subscribable; delivery rolling out | | `balance.low` | Your balance dropped below the low-balance threshold | subscribable; delivery rolling out | | `limits.cap_reached` | A daily or spend cap was reached | subscribable; delivery rolling out | | `webhook.verification`, `webhook.test` | Ownership challenge and test deliveries | always sent | Lookup and job events go to the endpoint named in that request's `webhook_endpoint_id`, provided the endpoint subscribes to the event. Every payload has the shape `{"type", "id", "created_at", "data": {…}}`. Job events carry a summary and a results URL, never phone numbers or e-mail addresses: ```json {"type": "job.completed", "id": "evt_…", "created_at": "2026-09-25T14:30:02Z", "data": {"object": "job", "id": "job_…", "status": "completed", "progress": {"total": 3, "checks_total": 9, "done": 9, "conclusive": 6, "non_billable": 3}, "results_url": "https://api.mobilevalidate.com/v1/jobs/job_…/results"}} ``` This is a job of 3 numbers with 3 checks each. The `progress` counters use two units: | Counter | Counts | In the example | |---|---|---| | `total` | **rows** (numbers and e-mails in the job) | 3 numbers | | `checks_total` | **checks** in the job (each row × the checks for its input type) | 3 × 3 = 9 | | `done` | **checks** finished | 9 | | `conclusive` | checks with a conclusive answer (billed) | 6 | | `non_billable` | checks that were not billed: unknown, unsupported, invalid, duplicate, cached | 9 − 6 = 3 | So `done` can be larger than `total`: compare it with `checks_total`, which it reaches when the job is finished. `GET /v1/jobs/{id}` returns the same counters. A `lookup.completed` event carries a summary in the same units: `total` counts rows, the other counters count checks. Test keys send the same fields with `"livemode": false`: ```json {"type": "lookup.completed", "id": "evt_…", "created_at": "2026-09-25T14:30:05Z", "data": {"object": "lookup", "id": "lkp_…", "status": "completed", "livemode": true, "summary": {"total": 1, "registered": 1, "not_registered": 0, "unknown": 0}}} ``` Fetch the rows with your key. Treat `id` as the idempotency key for your handler, because retries can deliver the same event more than once. ## How do I verify the signature? Each delivery has three headers: | Header | Content | |---|---| | `webhook-id` | The event id (same across retries) | | `webhook-timestamp` | Unix seconds when this attempt was sent | | `webhook-signature` | `v1,` of `${webhook-id}.${webhook-timestamp}.${raw body}` | The HMAC key is the base64-decoded part of the secret after `whsec_`. Use the **raw** request body, not re-serialised JSON. Reject timestamps more than 5 minutes from your clock, and compare signatures in constant time: ```js title="verify-webhook.mjs (Node.js 18+, ES module)" import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyWebhook(rawBody, headers, secret) { const id = headers["webhook-id"]; const ts = headers["webhook-timestamp"]; const sigHeader = headers["webhook-signature"] ?? ""; if (!id || !ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error("stale or missing headers"); const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = createHmac("sha256", key).update(`${id}.${ts}.${rawBody}`).digest(); const ok = sigHeader.split(" ").some((part) => { const [version, sig] = part.split(","); const got = Buffer.from(sig ?? "", "base64"); return version === "v1" && got.length === expected.length && timingSafeEqual(got, expected); }); if (!ok) throw new Error("invalid signature"); return JSON.parse(rawBody); } ``` The [SDK](/docs/sdk) ships the same check as `verifyWebhook` from `mobilevalidate/webhooks`. Any Standard Webhooks library works too. ## What happens when a delivery fails? Anything other than a `2xx` within 10 seconds counts as a failure. That includes timeouts, connection errors and redirects. We retry after about 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours and 10 hours. If the next retry would fall more than 24 hours after the event, the delivery is marked failed instead. The `webhook-id` stays the same across retries, and `webhook-timestamp` and the signature are new for each attempt. Answer quickly with `2xx`, then do the heavy work in the background. Responses larger than 64 KB are cut off, and their content is ignored. ## Frequently asked questions ### Why is my endpoint not receiving job events? New endpoints stay in pending_verification until they answer our signed webhook.verification event with a 2xx status. Until then, other events are held. Check that your endpoint returns 2xx quickly and is reachable over public HTTPS. ### Do webhook payloads contain phone numbers? No. Job events carry a summary and a results URL; fetch the rows with your API key. ### I lost my signing secret. Can I see it again? No. The secret is shown once when the endpoint is created. Delete the endpoint and create a new one. --- # Errors > Every MobileValidate API error code with its HTTP status, whether to retry, what causes it and how to fix it, plus the error response format. Canonical: https://mobilevalidate.com/docs/errors · Last updated: 2026-09-25 Machine-readable: [Error catalog (JSON)](https://mobilevalidate.com/errors.json) When a whole request can't be processed, the API returns a non-2xx status and one JSON error object. Each error has a stable `code` to branch on, a `retryable` flag, and a `request_id` to quote to support. Problems with single rows, such as invalid, duplicate, suppressed, unknown or unsupported numbers, are reported inside `results` instead. They never fail the request and are never billed. ## What does an error response look like? ```json { "error": { "code": "service_disabled", "message": "The check 'signal.registered' is available in bulk jobs only (POST /v1/jobs).", "status": 403, "retryable": false, "param": "checks[0]", "doc_url": "https://mobilevalidate.com/docs/errors#service_disabled", "request_id": "req_0VWF4BN8PIsgkUh3EvUj", "suggestion": "Create a bulk job (POST /v1/jobs) for this check, or remove it from checks." } } ``` - `code` is stable. Branch on it, not on `message`, whose wording may change. - `param` points to the field at fault (`checks[1]`, `numbers`, `emails[3]`, `max_cost.amount`), or is `null`. Your values are never echoed back. - `retryable` says whether sending the same request again later can succeed. - `request_id` also comes back as the `X-Request-Id` header on every response. - `suggestion` (optional) is a plain-English hint on how to fix the request, for example a missing country code or a likely typo in an e-mail domain. It never repeats the value you sent. Show it to developers; don't parse it. ## Which codes can the API return? | Code | HTTP | Retryable | |---|---|---| | `invalid_request` | 400 | no | | `too_many_numbers` | 400 | no | | `test_number_only` | 400 | no | | `invalid_cursor` | 400 | no | | `unauthorized` | 401 | no | | `insufficient_balance` | 402 | no | | `cost_limit_exceeded` | 402 | no | | `insufficient_scope` | 403 | no | | `service_disabled` | 403 | no | | `sandbox_magic_only` | 403 | no | | `suspected_enumeration` | 403 | no | | `not_found` | 404 | no | | `idempotency_key_reused` | 409 | no | | `idempotency_request_in_progress` | 409 | yes | | `test_key_exists` | 409 | no | | `payload_too_large` | 413 | no | | `rate_limited` | 429 | yes | | `daily_cap_reached` | 429 | yes (after the reset) | | `spend_cap_reached` | 429 | no | | `internal_error` | 500 | yes | | `temporarily_unavailable` | 503 | yes | ## What does each code mean? ### `invalid_request` **400, not retryable.** The body is malformed JSON, breaks the schema (missing or unknown field, wrong type), or uses an unknown check code. It is also returned when e-mails are sent without an e-mail check or numbers without a phone check (`param: "checks"`), for more than 20 checks, and when identifiers × applicable checks exceed 2,000 per lookup or 100,000 per job. **Fix:** read `param` and `message`, and correct that field. `GET /v1/services` lists valid codes. ### `too_many_numbers` **400, not retryable.** More than 100 numbers + e-mails on `POST /v1/lookup`, more than 50,000 on `POST /v1/jobs` (or in an uploaded CSV, `param: "file"`), or more than 10 in a job or estimate with the public sandbox key. **Fix:** split the list, or use a [bulk job](/docs/bulk-jobs) for anything over 100. ### `test_number_only` **400, not retryable.** A live key was used with a test number ("Test numbers can only be used with test keys.") or a `test.mobilevalidate.com` address ("Test e-mail addresses can only be used with test keys."). **Fix:** use your test key for [test data](/docs/test-mode) and real identifiers with live keys. ### `invalid_cursor` **400, not retryable.** The `after` cursor for job results is malformed. **Fix:** use `next_cursor` from the previous page exactly as returned, or start again without `after`. ### `unauthorized` **401, not retryable.** The key is missing, malformed, unknown, revoked or expired, or was used from an IP address outside its allowlist. All cases look the same on purpose. **Fix:** check the `Authorization: Bearer` header, the key prefix and your outgoing IP address, including IPv6 (see [authentication](/docs/authentication)). ### `insufficient_balance` **402, not retryable.** Your balance can't cover the maximum possible cost of the request, which is reserved before checking. **Fix:** top up, or send fewer identifiers or checks. Cached and inconclusive answers are released afterwards, so the final charge is often lower than the reservation. ### `cost_limit_exceeded` **402, not retryable.** The maximum possible cost is higher than the `max_cost` you sent. **Fix:** raise `max_cost` or shrink the request. `POST /v1/jobs/estimate` shows the maximum cost for free. ### `insufficient_scope` **403, not retryable.** The key doesn't have the scope this endpoint needs, for example `webhooks:manage` or `account:read`. **Fix:** use a key with that scope, or ask for one. ### `service_disabled` **403, not retryable.** One of the checks is switched off, isn't enabled for your account, or is bulk only and was sent to `POST /v1/lookup` ("… is available in bulk jobs only (POST /v1/jobs)"). `param` names the check (`checks[i]`); it is `null` when real-time checks are not enabled for the whole account. **Fix:** run bulk-only services in a job, or remove the check. Services marked coming soon, such as `number.hlr`, give this answer until they launch. ### `sandbox_magic_only` **403, not retryable.** The public sandbox key (the key in the docs examples) accepts only the documented [test numbers and e-mail addresses](/docs/test-values). `param` names the first other identifier (`numbers[2]`, `emails[0]`); an invalid number counts as another identifier. The sandbox's other limits have their own codes: jobs over 10 rows return `too_many_numbers`, and webhooks return `invalid_request`. **Fix:** use a test value, or get a personal test key to test your own data. The `suggestion` field links both. ### `suspected_enumeration` **403, not retryable.** The request looks like a scan: 20 or more consecutive numbers (`param: "numbers"`), 20 or more addresses on one domain differing only by digits or separators, or (live keys) 50 or more such addresses from your account in one UTC day (`param: "emails"`). **Fix:** only check identifiers you actually hold, such as customers, sign-ups and leads. See [rate limits and abuse](/docs/rate-limits-and-abuse). ### `not_found` **404, not retryable.** The id doesn't exist, belongs to another account, or its results were purged ("Results for this job were purged."). For privacy, we don't say which. An unknown path also returns `not_found`, before the key is checked; the `suggestion` names the closest endpoint. **Fix:** check the id and the path, and fetch results within your retention period. ### `idempotency_key_reused` **409, not retryable.** The `Idempotency-Key` was already used within 24 hours with a different request body. **Fix:** use a new key for each distinct request, and reuse a key only for exact retries. ### `idempotency_request_in_progress` **409, retryable.** The first request with this `Idempotency-Key` is still running. **Fix:** wait briefly and retry with the same key and body. You'll get the stored response. ### `test_key_exists` **409, not retryable.** `POST /v1/test-keys` (the "get a test key" form): no more test keys can be issued for this e-mail address right now (at most 3 in 30 days). Keys already issued are never shown again. **Fix:** use a key you received earlier, or request access and we'll set you up. ### `payload_too_large` **413, not retryable.** The body is larger than 1 MB (4 MB for `POST /v1/jobs` and `/v1/jobs/estimate`). **Fix:** split the list into several jobs. ### `rate_limited` **429, retryable.** More than 10 requests per second (burst 20) on this key, or, for the public sandbox key, more than 30 requests per minute or 1,000 per day from your IP address. **Fix:** wait for the `Retry-After` seconds, then retry with backoff. Batch numbers into one request instead of sending one request per number. ### `daily_cap_reached` **429, retryable after the reset.** Your account's daily cap on identifiers is used up. Caps reset at 00:00 UTC, and `Retry-After` counts down to it. **Fix:** retry after the reset shown in `GET /v1/limits`, or ask for a higher cap. ### `spend_cap_reached` **429, not retryable.** The key's daily spend cap (agent keys) has been reached. It resets at 00:00 UTC. **Fix:** wait for the reset, raise the cap or use another key. Retrying right away won't help. ### `internal_error` **500, retryable.** Something failed on our side. **Fix:** retry with backoff, using the same `Idempotency-Key` so the retry is safe. If it keeps happening, send us the `request_id`. ### `temporarily_unavailable` **503, retryable.** The service is overloaded or every route for a check is down. **Fix:** retry after `Retry-After`. A request that fails this way has not been charged. ## Are there codes outside the HTTP API? - **MCP server:** `confirmation_required` means a tool call would spend more than the confirmation threshold (default USD 1.00) or covers more than 100 numbers. The agent should show the amount to the user and call again with `confirm_max_cost`. See [MCP](/docs/mcp). - **SDK:** client-side codes `missing_api_key`, `connection_error`, `timeout`, `invalid_response` and `invalid_argument` never come from the API itself. See [SDK](/docs/sdk). ## Frequently asked questions ### Is an unknown result an error? No. Per-row problems (invalid, duplicate, suppressed, unknown, unsupported country) never fail the request and are never billed. Errors are only for problems with the request as a whole. ### Which errors should my client retry? Only those with retryable: true: rate_limited, daily_cap_reached (after the reset), idempotency_request_in_progress, internal_error and temporarily_unavailable. Honour Retry-After and use backoff with jitter. --- # Rate limits and anti-abuse rules > MobileValidate API limits: 10 requests/s per key, daily caps, spend caps, request sizes, and the anti-enumeration rules that block number and e-mail scans. Canonical: https://mobilevalidate.com/docs/rate-limits-and-abuse · Last updated: 2026-09-25 Machine-readable: [Limits in key facts (JSON)](https://mobilevalidate.com/facts.json) Every key is limited to 10 requests per second, with bursts of up to 20. Every account has a daily cap on the numbers and e-mails it can check. On top of that, requests that look like sequential number ranges or generated e-mail lists are rejected. These limits keep the service fast for everyone and stop the API from being used to find out who owns a number or address. ## How does the request rate limit work? Each key has a token bucket: 10 requests per second on average, with a burst of 20. When you go over, you get `429 rate_limited` with a `Retry-After` header. Every authenticated response carries the current state in IETF RateLimit headers: ```text RateLimit-Policy: "default";q=20;w=2 RateLimit: "default";r=19;t=1 ``` `q` is the bucket size, `w` the window in seconds, `r` the requests remaining, and `t` the seconds until the bucket is full again. The most effective way to stay under the limit is to batch: one lookup takes up to 100 identifiers, and one job takes up to 50,000. Test keys are limited in the same way. The public sandbox key is shared by everyone, so it is limited per IP address instead: 30 requests per minute and 1,000 per UTC day (policies `sandbox-minute` and `sandbox-day` in the same headers), and jobs of at most 10 rows. ## What are the daily and spend caps? | Limit | Where to see it | Resets | |---|---|---| | Daily identifiers (valid numbers + valid e-mails checked) | `GET /v1/limits` → `daily_numbers.cap`, `used`, `remaining` | 00:00 UTC | | Key daily spend (agent keys) | `GET /v1/limits` → `key_daily_spend` | 00:00 UTC | | Key daily spend cap reached | error `spend_cap_reached` | 00:00 UTC | ```json {"object": "limits", "rate_limit": {"requests_per_second": 10, "burst": 20}, "lookup": {"max_numbers": 100, "max_wait_seconds": 30, "realtime_enabled": true}, "jobs": {"max_numbers": 50000}, "daily_numbers": {"cap": 50000, "used": 19, "remaining": 49981}, "key_daily_spend": null, "resets_at": "2026-09-26T00:00:00.000Z"} ``` (Real test-mode output. Your values depend on your account.) The daily cap counts identifiers, not checks. A number checked against five services counts once. Hitting a cap returns `429 daily_cap_reached`. `max_cost` on each request gives you your own ceiling as well. ## What are the request size limits? | Limit | Value | |---|---| | Identifiers per lookup | 100 numbers + e-mails | | Identifiers per job | 50,000 numbers + e-mails | | Checks per request | 20 | | Identifiers × applicable checks | 2,000 per lookup, 100,000 per job | | Request body | 1 MB; 4 MB for `POST /v1/jobs` and `/v1/jobs/estimate` | | Long-poll `wait` | up to 30 s | | Webhook endpoints | 20 per account | All size limits, `max_cost`, caps and your balance are checked before any identifier is stored, so a refused request leaves no data behind. ## What counts as enumeration? Requests that look like sequential number ranges or generated e-mail lists are rejected with `403 suspected_enumeration`: - **Number ranges:** 20 or more numerically consecutive numbers in one request, for example `+447700900100` to `+447700900119`. - **Generated addresses in a request:** 20 or more distinct addresses on one domain whose local parts differ only by digits or separators (`john1@`, `john.2@`, `john_3@`…). `gmail.com` and `googlemail.com` count as one domain. - **Generated addresses across a day (live keys):** 50 or more distinct addresses of one such pattern from the same account in one UTC day, however they are split across requests. The rules exist because checking made-up ranges is how people try to build lists of platform users. Our [acceptable use policy](/legal/acceptable-use) forbids that. Checks are for identifiers you already hold, such as customers, sign-ups and leads, and for purposes like fraud prevention and deliverability. ## What else protects people whose data is checked? - **Suppression list:** people can object through the [opt-out form](/opt-out). Suppressed numbers and addresses are returned as `suppressed`, never checked and never billed. - **No personal data in answers:** checks return yes/no/unknown or network and reputation attributes, never names, photos, profiles or locations. - **Masking:** stored results and downloads show masked identifiers (`+44770*****01`, `re•••@…`), and logs never contain full numbers. - **Kill switch:** we can switch off a service for everyone within minutes if it is being abused. Repeated abuse can lead to suspension of the account under the acceptable use policy. ## Frequently asked questions ### Can I get higher limits? Yes. Daily caps and throughput are set per account and can be raised for established use cases. GET /v1/limits always shows your current values. ### Why was my list of customer numbers rejected as enumeration? 20 or more numerically consecutive numbers in one request trigger the rule, which can happen with test data or sorted internal ranges. Real customer lists rarely contain long consecutive runs; shuffle genuine data or contact us if your use case needs an exception. --- # Services reference > Reference of every MobileValidate check: service code, alias, input type, real-time or bulk-only, outputs and country coverage. Canonical: https://mobilevalidate.com/docs/services · Last updated: 2026-09-25 This page lists every check you can put in `checks`, with its code, alias, input type, mode (real time and bulk, or bulk only), outputs and country coverage. Codes and aliases are interchangeable in requests, and responses always use the full code. `GET /v1/services` returns the live list for your key, with prices. The site's [services page](/services) may show a live version of this table. ## How do I read this table? - **Input:** `phone` services run on `numbers`, `e-mail` services on `emails`. - **Modes:** "real time + bulk" works on `POST /v1/lookup` and in jobs. "Bulk only" works in [bulk jobs](/docs/bulk-jobs) only. - **Outputs:** `registered` means the answer is yes/no/unknown. Named attributes appear in `attributes` for conclusive answers. - **Countries:** "all" means any country. Numbers outside a service's countries return `unsupported_country` and are not billed. - Asking for `whatsapp.registered` and `whatsapp.business` together runs only `whatsapp.business`, because it answers both. ## Which phone services are there? | Code | Alias | Modes | Outputs | Countries | |---|---|---|---|---| | `whatsapp.registered` | `whatsapp` | real time + bulk | `registered` | all | | `whatsapp.business` | — | real time + bulk | `registered`, `business` | all | | `telegram.registered` | `telegram` | real time + bulk | `registered` | all | | `viber.registered` | `viber` | real time + bulk | `registered` | all | | `signal.registered` | `signal` | bulk only | `registered` | all | | `imessage.registered` | `imessage` | bulk only | `registered` | all | | `rcs.registered` | `rcs` | bulk only | `registered`, `device_os` | all | | `line.registered` | `line` | bulk only | `registered` | all | | `zalo.registered` | `zalo` | real time + bulk | `registered` | all | | `botim.registered` | `botim` | bulk only | `registered` | all | | `max.registered` | `max` | bulk only | `registered` | all | | `messenger.registered` | `messenger` | bulk only | `registered` | all | | `facebook.registered` | `facebook` | real time + bulk | `registered` | all | | `instagram.registered` | `instagram` | real time + bulk | `registered` | all | | `threads.registered` | `threads` | real time + bulk | `registered` | all | | `x.registered` | `x`, `twitter` | real time + bulk | `registered` | all | | `tiktok.registered` | `tiktok` | bulk only | `registered` | all | | `snapchat.registered` | `snapchat` | bulk only | `registered` | all | | `linkedin.registered` | `linkedin` | bulk only | `registered` | US, IN | | `vk.registered` | `vk` | real time + bulk | `registered` | RU | | `apple.registered` | `apple` | real time + bulk | `registered` | all | | `amazon.registered` | `amazon` | real time + bulk | `registered` | all | | `microsoft.registered` | `microsoft` | real time + bulk | `registered` | all | | `netflix.registered` | `netflix` | real time + bulk | `registered` | all | | `network.carrier` (beta) | `carrier` | real time + bulk | `line_type`, `carrier`, `original_carrier`, `country` | all (coverage varies) | | `network.carrier_us` | — | bulk only | `line_type`, `carrier` | US, CA | | `number.spam` | `spam` | real time + bulk | `risk_level`, `risk_score`, `reason_regulator`, `reason_government`, `reason_community`, `reason_unassigned`, `voip_range`, `top_category`, `first_seen`, `last_seen`, `sources` | US, CA, DE | | `number.hlr` (coming soon) | `hlr` | — | `status`, `ported`, `roaming`, `network`, `mcc_mnc`, `country` | — | `number.hlr` (live network status) is not available yet. Requests for it return `403 service_disabled`. Access to `number.spam` is limited while it is in review. Ask us if you need it. ## Which e-mail services are there? | Code | Alias | Modes | Outputs | |---|---|---|---| | `email.valid` | `email` | real time + bulk | `registered` (mailbox exists; major webmail providers, others → `UNSUPPORTED_PROVIDER`) | | `gmail.email` | `gmail` | bulk only | `registered` | | `outlook.email` | `outlook` | bulk only | `registered` | | `yahoo.email` | `yahoo` | bulk only | `registered` | | `yandex.email` | `yandex` | bulk only | `registered` | | `mailru.email` | `mailru` | bulk only | `registered` | | `apple.email` | `apple.email` | real time + bulk | `registered` | | `amazon.email` | — | real time + bulk | `registered` | | `facebook.email` | — | real time + bulk | `registered` | | `instagram.email` | — | real time + bulk | `registered` | | `netflix.email` | — | real time + bulk | `registered` | | `spotify.email` | — | real time + bulk | `registered` | | `linkedin.email` | — | bulk only | `registered` | | `x.email` | — | bulk only | `registered` | E-mail services have no country limits. See [e-mail checks](/docs/emails) for normalization and the anti-enumeration rules. ## What do the attribute values mean? | Attribute | Values | |---|---| | `business` | `true` / `false`: WhatsApp Business account | | `device_os` | `ios`, `android`, `unknown`: handset platform reported with RCS capability | | `line_type` | `mobile`, `fixed_line`, `fixed_line_or_mobile`, `voip`, `toll_free`, `premium_rate`, `shared_cost`, `personal`, `pager`, `uan`, `voicemail`, `unknown` | | `carrier` / `original_carrier` | Current carrier name / carrier the range was allocated to, when different | | `risk_level` | `high`, `medium`, `low`, `no_reports` (`no_reports` does not mean safe) | | `risk_score` | integer 0–100 | | `top_category` | `debt_relief`, `impersonation`, `robocall`, `medical`, `home_services`, `warranty`, `sms_spam`, `dialer`, `fraud_hacking`, `other` | | `first_seen` / `last_seen` | `YYYY-MM` | | `sources` | integer 0–10: number of independent signal classes | Each attribute's type (`string`, `boolean`, `enum`, `integer`), with enum values and integer bounds, is also returned by `GET /v1/services`. Enums may gain values, so treat unknown values as `unknown`. ## How do I get the live list? ```bash curl https://api.mobilevalidate.com/v1/services -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" ``` Each entry has `code`, `name`, `platform`, `family`, `input_type`, `result_kind`, `attributes`, `realtime`, `batch`, `modes`, `status`, `beta`, `countries` and `prices` (`realtime` and `batch`, as decimal strings per check). Without a key, you get the public catalog. Platform names are used only to describe which service a check refers to. ## Frequently asked questions ### Is this table always current? It reflects the catalog on the date shown. GET /v1/services is authoritative at run time and shows exactly what your key can use, with prices. ### What does bulk only mean? The service answers in bulk jobs (POST /v1/jobs) but not on the real-time endpoint, which refuses it with 403 service_disabled. --- # SDKs: Node.js and Python > Use the mobilevalidate SDKs for Node.js/TypeScript and Python: install, lookups with automatic waiting, bulk jobs, e-mail checks, errors and webhook verification. Canonical: https://mobilevalidate.com/docs/sdk · Last updated: 2026-09-25 The `mobilevalidate` package is a typed TypeScript client for the API. It waits for slow answers for you, retries safely and verifies webhooks. It has no runtime dependencies, ships as ES module and CommonJS, and runs on Node.js 18 or later, Bun, Deno and edge runtimes. By default, every call returns `{ data, error }` rather than throwing. A [Python SDK](#python) with the same methods is below. ## How do I install it? Install from npm ([`mobilevalidate`](https://www.npmjs.com/package/mobilevalidate)): ```bash tabs=off npm install mobilevalidate ``` For Python, install [`mobilevalidate-sdk`](https://pypi.org/project/mobilevalidate-sdk/) from PyPI (the import name is `mobilevalidate`): ```bash tabs=off pip install mobilevalidate-sdk ``` `new MobileValidate({ sandbox: true })` uses the public sandbox key, so you can try every example with the documented [test values](/docs/test-values) before you have a key of your own. You can also call the [HTTP API](/docs/lookups) directly. Every example on this page maps one-to-one to an endpoint. ## How do I check numbers? ```ts title="check.mjs" import { MobileValidate } from "mobilevalidate"; const mv = new MobileValidate(); // reads MOBILEVALIDATE_API_KEY const { data, error } = await mv.lookup(["+447700900001", "+447700900002"], { checks: ["whatsapp", "telegram", "carrier"], }); if (error) { console.error(error.code, error.message, error.requestId); } else { for (const item of data.results) { const wa = item.checks?.["whatsapp.registered"]; if (wa?.registered === true) { /* has WhatsApp */ } else if (wa?.registered === false) { /* no account: use another channel */ } else { /* unknown: wa.status / wa.reason explain why; not billed */ } console.log(item.e164, item.checks?.["network.carrier"]?.attributes); } console.log(data.summary.by_service); } ``` The examples use top-level `await`, so run them as an ES module: a `.mjs` file, `"type": "module"` in `package.json`, or TypeScript with ESM output. In CommonJS, wrap the code in an `async function main() { … }` and call `main()`. `lookup()` calls `POST /v1/lookup` and then long-polls `GET /v1/lookups/{id}` until everything is done or the wait budget (`waitTimeoutMs`, default 60 s) runs out. `mv.whatsapp.check()` is kept as an alias for WhatsApp-only integrations. `mv.services()` returns the catalog your key can use, with prices. ## How do I check e-mails? ```ts const { data } = await mv.lookup({ numbers: ["+447700900001"], emails: ["registered@test.mobilevalidate.com"], checks: ["whatsapp", "email"], }); for (const item of data?.results ?? []) { if (item.kind === "email") console.log(item.email, item.email_status, item.checks?.["email.valid"]?.registered); else console.log(item.e164, item.number_status, item.checks?.["whatsapp.registered"]?.registered); } ``` Phone checks run on numbers and e-mail checks on e-mails. Rows come back numbers first. See [e-mail checks](/docs/emails). ## How do I run bulk jobs? ```ts const { data: est } = await mv.jobs.estimate({ numbers: list, checks: ["whatsapp", "signal"] }); const { data: job } = await mv.jobs.create({ numbers: list, checks: ["whatsapp", "signal"], maxCost: "5.00" }); // your ceiling; see the estimate await mv.jobs.get(job!.id, { wait: 30 }); // long-poll status for await (const item of mv.jobs.results(job!.id, { service: "signal", registered: true })) { console.log(item.e164); // async iterator over every page } ``` `jobs.resultsPage()` fetches a single page, and `jobs.cancel(id)` cancels or purges a job. `jobs.download(id, { format: "csv" | "ndjson" })` returns the whole result file as a stream: read it with `text()`, pipe `body` (a web `ReadableStream`), or, for NDJSON, iterate `rows()`: ```ts const { data: file } = await mv.jobs.download(job!.id); // CSV (default) await writeFile("results.csv", await file!.text()); // import { writeFile } from "node:fs/promises" const { data: nd } = await mv.jobs.download(job!.id, { format: "ndjson" }); for await (const row of nd!.rows()) console.log(row.row_no, row.e164); // one object per line, in input order ``` ## What options does the client take? ```ts new MobileValidate({ apiKey: "mv_test_...", // default: env MOBILEVALIDATE_API_KEY baseUrl: "https://api.mobilevalidate.com", // default; env MOBILEVALIDATE_BASE_URL also honoured timeoutMs: 30_000, // per HTTP request waitTimeoutMs: 60_000, // overall wait budget for lookup() maxRetries: 2, // retryable errors only throwOnError: false, // true → throw MobileValidateError }); ``` Money is always a decimal string. `maxCost` also accepts `"0.05"` or `0.05`. `maxAge` takes seconds or a string such as `"30m"`, `"24h"` or `"7d"`, and `0` forces a fresh, billed check. `wait: 0` returns at once, possibly with `pending` results. ## How are errors handled? `error` is a `MobileValidateError` with `code`, `status`, `retryable`, `requestId`, `param` and `retryAfterMs`. Its codes match the [API error codes](/docs/errors). The SDK adds a few client-side codes: `missing_api_key`, `connection_error`, `timeout`, `invalid_response` and `invalid_argument`. Retries use exponential backoff with jitter and honour `Retry-After`. Per-row outcomes such as invalid, duplicate or unknown are not errors. They appear in `number_status` and `checks[code].status`. ## How do I verify webhooks? ```ts import { verifyWebhook } from "mobilevalidate/webhooks"; export async function POST(req: Request) { const raw = await req.text(); // raw body, not re-serialised JSON const event = await verifyWebhook(raw, req.headers, process.env.MV_WEBHOOK_SECRET!); // throws on failure if (event.type === "job.completed") { /* fetch results */ } return new Response(null, { status: 204 }); } ``` It checks the Standard Webhooks signature with a 5-minute timestamp tolerance and a constant-time comparison. See [webhooks](/docs/webhooks). ## Python The Python package has the same methods, a synchronous `MobileValidate` client and an asynchronous `AsyncMobileValidate` client (on httpx). It is typed (TypedDicts, `py.typed`), raises one exception class per API error code, retries safely and verifies webhooks with the standard library. Python 3.9 or later. Install [`mobilevalidate-sdk`](https://pypi.org/project/mobilevalidate-sdk/) from PyPI: ```bash tabs=off pip install mobilevalidate-sdk ``` ```python title="check.py" from mobilevalidate import MobileValidate mv = MobileValidate(sandbox=True) # the public sandbox key; MobileValidate() reads MOBILEVALIDATE_API_KEY lookup = mv.lookup(["+447700900001", "+447700900002", "+447700900003"], checks=["whatsapp"]) for row in lookup["results"]: answer = row["checks"]["whatsapp.registered"] print(row["e164"], answer["registered"], answer["status"]) ``` ```python title="check_async.py" import asyncio from mobilevalidate import AsyncMobileValidate async def main(): async with AsyncMobileValidate() as mv: lookup = await mv.lookup("+447700900001", checks=["whatsapp", "carrier"]) print(lookup["results"][0]["checks"]) asyncio.run(main()) ``` In Python, `mv.jobs.download(job_id, format="csv")` returns the whole result file as text, and `mv.jobs.download_to(job_id, "results.csv")` streams it to disk. ## Frequently asked questions ### Which runtimes does the SDK support? Node.js 18 or later (ES modules and CommonJS), Bun, Deno and edge runtimes. It has no runtime dependencies and uses fetch and Web Crypto. The Python SDK needs Python 3.9 or later. ### Does the SDK retry failed requests? Yes, but only retryable errors (rate_limited, 5xx, network errors, idempotency in progress), and it doesn't wait out a Retry-After longer than 60 seconds (such as a daily cap). Every POST gets an automatic Idempotency-Key, so retries never double-charge. --- # Command-line tool > Use the mobilevalidate CLI to check phone numbers and e-mails, run bulk jobs from files, and read results as tables, JSON or NDJSON. Canonical: https://mobilevalidate.com/docs/cli · Last updated: 2026-09-25 The `mobilevalidate` command-line tool comes with the [SDK](/docs/sdk). You can use it to check numbers and e-mail addresses, run bulk jobs from files and read results without writing code. In a terminal it prints tables. When piped, it prints NDJSON or JSON, so it fits into shell scripts and data pipelines. ## How do I run it? Run it with `npx`, no install needed. `--sandbox` uses the public sandbox key, so this works without signing up: ```bash tabs=off npx mobilevalidate check +447700900001 +447700900002 registered@test.mobilevalidate.com --sandbox ``` Without `--sandbox`, the key comes from `MOBILEVALIDATE_API_KEY` or `--api-key`. The API URL comes from `--base-url` or `MOBILEVALIDATE_BASE_URL` (default `https://api.mobilevalidate.com`). The CLI needs Node 18 or later and never prints your key. ## Which commands are there? ```text mobilevalidate check [--checks whatsapp,telegram,email] [--country GB] [--max-age 7d] [--wait 60] [--max-cost 1.00] [--json] mobilevalidate check-email [--checks email,gmail] mobilevalidate services [--json] mobilevalidate lookup [--wait 30] mobilevalidate jobs create --file list.csv [--checks whatsapp,email] [--wait [seconds]] mobilevalidate jobs get [--wait 30] mobilevalidate jobs results [--registered true|false|null] [--limit 500] mobilevalidate jobs download [--format csv|ndjson] [--output results.csv] mobilevalidate jobs cancel mobilevalidate webhooks verify --secret whsec_… --file body.json --headers headers.txt mobilevalidate webhooks sign --secret whsec_… --file body.json mobilevalidate webhooks list | test mobilevalidate account | limits ``` - `check` takes numbers and e-mail addresses together. The default checks are `whatsapp` for numbers and `email` for addresses. With several `--checks` you get one table column per service; `spam` adds `SPAM RISK` and `SPAM SCORE`. - `jobs create --wait` waits for the job (up to 600 s by default), then prints its results. - `jobs download` writes the whole result file (CSV by default) to stdout, or streams it to `--output `. - `check-email` runs a job for bulk-only e-mail checks such as `gmail` and waits for it within `--wait`. - `webhooks sign` prints signed headers for a body, so you can test your receiver locally; `webhooks verify` checks a delivery you captured. - On an API error the CLI prints the code and message, then the API's `suggestion`, the docs link and the request ID. ## How do I feed it lists? Use `-` to read from stdin, one identifier per line (commas and tabs also work): ```bash cat numbers.txt | mobilevalidate check - --checks whatsapp,carrier mobilevalidate jobs create --file leads.csv --wait ``` For CSV files, the CLI uses the `phone`, `number`, `msisdn`, `mobile` or `e164` column and/or the `email` column. If none of these exists, it uses the first column, and cells containing `@` are sent as e-mails. Lists longer than 100 entries belong in `jobs create`. Requests that look like sequential number ranges or generated e-mail lists are rejected by the API, whichever client sends them. ## What do the exit codes mean? | Code | Meaning | |---|---| | `0` | Success, all results conclusive | | `1` | The request failed (API error, network), the job failed, or a webhook signature is invalid | | `2` | Usage error (bad flags or arguments) | | `3` | At least one result is not conclusive: unknown, pending, unsupported country, invalid, duplicate or suppressed | Exit code `3` lets a script tell "everything answered" apart from "some rows need another look" without parsing the output. Output formats: a table on a terminal, NDJSON (one result per line) when piped, and one JSON document with `--json`. ## Frequently asked questions ### Does the CLI log the numbers I check? No. Pass lists on stdin or from a file rather than as arguments if your shell history is shared. The CLI never prints your API key. ### How do I use the CLI in a script? Pipe its output: when stdout is not a terminal it prints NDJSON, one result per line, or a single JSON document with --json. Exit code 3 means at least one result was unknown, pending or invalid. --- # MCP server for AI agents > Connect Claude, Cursor and other MCP clients to MobileValidate: tools, remote and local setup, agent keys and spend confirmation. Canonical: https://mobilevalidate.com/docs/mcp · Last updated: 2026-09-25 Machine-readable: [MCP server card (JSON)](https://mobilevalidate.com/.well-known/mcp/server-card.json) **Status:** the hosted MCP server at `https://mcp.mobilevalidate.com/mcp` is live for customers with an agent key or a test key. The local package [`@mobilevalidate/mcp`](https://www.npmjs.com/package/@mobilevalidate/mcp) runs the same tools over stdio. The MobileValidate MCP server lets AI agents run checks on phone numbers and e-mail addresses through the Model Context Protocol. Agents can check registration on messaging apps, carrier and line type, spam reputation, and mailbox or account existence. It is a thin client of the public API with its own spend safeguards, and it accepts only agent keys and test keys. ## Which tools does it offer? | Tool | What it does | Spends credit | |---|---|---| | `normalize_numbers` | Formats numbers as E.164 locally; flags ambiguous inputs and duplicates | no | | `estimate_cost` | Free pre-flight for numbers and/or e-mails: valid, invalid, duplicate and cached counts plus maximum cost | no | | `lookup_numbers` | Checks up to 100 numbers (optionally with e-mails) against one or more real-time services | yes | | `lookup_emails` | Checks up to 100 e-mail addresses in real time (default check `email`) | yes | | `check_spam_reputation` | Spam reputation for up to 100 US, CA or DE numbers: level, score, reasons, counts per level | yes | | `create_lookup_job` | Bulk job with up to 50,000 numbers and/or e-mails, for any active service including bulk-only ones | yes | | `get_lookup_job` | Job status plus a filtered, paginated page of results | no | | `list_services` | Services the key can use: input type, real time or bulk only, attributes, countries, prices | no | | `get_account` | Balance, reserved credit, today's usage, limits | no | Each tool declares a title, annotations (read-only, idempotent), an input schema and an output schema. It returns structured content plus a one-line summary. `checks` takes codes or aliases such as `whatsapp`, `telegram`, `carrier` or `spam`. Live network status (`hlr`) is coming soon and not offered yet. ## How do I connect a remote client? The hosted server speaks Streamable HTTP at `https://mcp.mobilevalidate.com/mcp`. Send your agent or test key as a bearer token. The key is passed on to the API for that request only and is never stored. One-click install links for Cursor and VS Code are in the **Copy page** menu at the top of every docs page. ```bash tabs=off claude mcp add --transport http mobilevalidate https://mcp.mobilevalidate.com/mcp \ --header "Authorization: Bearer $MOBILEVALIDATE_API_KEY" ``` `.mcp.json` / Cursor equivalent: ```json { "mcpServers": { "mobilevalidate": { "type": "http", "url": "https://mcp.mobilevalidate.com/mcp", "headers": { "Authorization": "Bearer ${MOBILEVALIDATE_API_KEY}" } } } } ``` ## How do I run it locally (stdio)? Run the [`@mobilevalidate/mcp`](https://www.npmjs.com/package/@mobilevalidate/mcp) package with `npx`: ```bash tabs=off claude mcp add mobilevalidate --env MOBILEVALIDATE_API_KEY=mv_test_... -- npx -y @mobilevalidate/mcp ``` For Claude Desktop (`claude_desktop_config.json`) or Cursor (`~/.cursor/mcp.json`), use: ```json { "mcpServers": { "mobilevalidate": { "command": "npx", "args": ["-y", "@mobilevalidate/mcp"], "env": { "MOBILEVALIDATE_API_KEY": "mv_agent_..." } } } } ``` Then ask: *"Check +447700900001, +447700900002 and +447700900003 on WhatsApp."* With a test key, you get registered, not registered and unknown, at no cost. ## How does spend confirmation work? Before any tool that spends credit (`lookup_numbers`, `lookup_emails`, `check_spam_reputation`, `create_lookup_job`) runs, the server gets the free estimate. The real-time tools price it at your key's real-time prices, and `create_lookup_job` at bulk prices. Because the estimate is `POST /v1/jobs/estimate`, an agent key needs `jobs:write` for every spending tool, plus `lookup:write` for the three real-time tools. It refuses the call with `confirmation_required` if the maximum cost is above the threshold (default USD 1.00), or if a job has more than 100 numbers and e-mails. The message states the amount. The agent should ask the user, then call again with `confirm_max_cost` set to that amount. The confirmed amount is sent to the API as `max_cost`, so the request can never cost more. This confirmation goes through the agent, so the server can't prove a human approved it. The hard limits are the agent key's daily spend cap and its scopes, which is why live keys (`mv_live_…`) are rejected. Other safeguards: tools have no webhook URL parameter, request metadata is never echoed into tool output, and the anti-enumeration rules (20 or more consecutive numbers, generated e-mail lists) apply exactly as in the API. ## Frequently asked questions ### Can I use my live key with the MCP server? No. The MCP server accepts only agent keys (mv_agent_…, scoped and spend-capped) and test keys. Live keys are rejected so an agent can never spend without a cap. ### Can an agent spend a lot of credit by mistake? Calls above the confirmation threshold (default USD 1.00) or with more than 100 numbers are refused until the agent repeats them with the confirmed amount, and the API enforces that amount as max_cost. The agent key's daily spend cap is the hard limit. ### Does the agent get names or profiles? No. Tools return yes/no/unknown per service, carrier and line type, or spam-reputation attributes. They never return names, photos or profiles. --- # Recipes > Copy-paste recipes for phone and e-mail checks: an OTP and sign-up guard, a CSV list cleaner, a webhook receiver, WhatsApp vs SMS channel choice and an e-mail check at sign-up. Canonical: https://mobilevalidate.com/docs/recipes · Last updated: 2026-09-25 Recipes are small, complete programs for common jobs. Each one uses only the [test values](/docs/test-values), so you can run it with the public sandbox key before you have an account. The full source and tests are in the [examples repository](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples). ## Which recipes are there? | Recipe | Language | What it shows | |---|---|---| | [OTP and sign-up guard](/docs/recipes/otp-signup-guard) | Node (Express, Next.js) | Fix invalid numbers, send the code on WhatsApp or SMS, extra checks for VoIP numbers | | [CSV list cleaner](/docs/recipes/csv-list-cleaner) | Python | Deduplication, bulk jobs, automatic pagination and a verdict per row | | [Webhook receiver](/docs/recipes/webhook-receiver) | Node, Python | Signature check on the raw body, fast replies, duplicate handling | | [Choose the channel](/docs/recipes/choose-channel) | Node | WhatsApp, Telegram, Viber or SMS from one lookup | | [E-mail check at sign-up](/docs/recipes/email-signup-check) | Node, Python | Typos, missing mailboxes and unknown answers | ## How do I run them? Each recipe installs with `npm install` ([`mobilevalidate`](https://www.npmjs.com/package/mobilevalidate)) or `pip install -r requirements.txt` ([`mobilevalidate-sdk`](https://pypi.org/project/mobilevalidate-sdk/)). You can also call the [HTTP API](/docs/lookups) directly; every recipe step maps to one endpoint. Every recipe reads the key from `MOBILEVALIDATE_API_KEY`. If it isn't set, the recipe uses the public sandbox key, which only answers the test values. For any other input, [get a personal test key](/get-test-key). ## Are there instructions for AI coding agents? Yes. The examples repository has an `AGENTS.md` file and an agent skill (`skills/mobilevalidate/SKILL.md`). They cover the result model, test values, error handling and common mistakes, such as logging full phone numbers. ## Frequently asked questions ### Do the recipes need an account? No. Every recipe uses only the documented test values, so it runs as-is with the public sandbox key. Set MOBILEVALIDATE_API_KEY to use your own key. ### Are the recipes tested? Yes. Each recipe has tests that run offline against a mock of the API, and the same tests can run against the real API with the sandbox key. --- # Choose the channel > Pick the best channel for a message with one MobileValidate lookup: the first registered channel in your preference order, SMS as the fallback, voice for landlines. Canonical: https://mobilevalidate.com/docs/recipes/choose-channel · Last updated: 2026-09-25 This recipe picks a channel for each number with one lookup and several checks. It chooses the first channel in your preference order where the number is known to be registered, and falls back to SMS. The full code is in [`choose-channel.mjs`](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples/choose-channel). ## How does it choose? ```js const ORDER = [ { channel: "whatsapp", service: "whatsapp.registered" }, { channel: "telegram", service: "telegram.registered" }, { channel: "viber", service: "viber.registered" }, ]; export function chooseChannel(row, order = ORDER) { if (row.number_status !== "valid") return { channel: "none", suggestion: row.suggestion }; const unknown = []; for (const { channel, service } of order) { const c = row.checks?.[service]; if (c?.registered === true) return { channel, unknown }; if (!c || c.registered === null) unknown.push(channel); // unknown is not "no" } if (row.checks?.["network.carrier"]?.attributes?.line_type === "fixed_line") return { channel: "voice", unknown }; return { channel: "sms", unknown }; } const { data } = await mv.lookup(numbers, { checks: ORDER.map((o) => o.service) }); const choices = data.results.map((row) => chooseChannel(row)); ``` Put the channels in your own order, for example cheapest first. The unknown channels are reported separately, so you can retry later instead of recording "not registered". ## How do I test it? ```bash node choose-channel.mjs +447700900001 +447700900002 +447700900003 # +44••••••••01 → whatsapp # +44••••••••02 → sms # +44••••••••03 → sms (unknown: whatsapp, telegram, viber) ``` These are [test values](/docs/test-values), so the command works with the public sandbox key. Platform names are used descriptively only; MobileValidate is not affiliated with these platforms. ## Frequently asked questions ### How many numbers can I check at once? Up to 100 numbers per lookup. For bigger lists, use a bulk job. ### Can I use this for marketing campaigns? Use it to route messages people asked for, such as one-time codes, order updates and reminders. MobileValidate is not meant for unsolicited bulk messaging. --- # CSV list cleaner > Deduplicate and validate a CSV of phone numbers and e-mail addresses with the MobileValidate Python SDK: bulk job, automatic pagination and a verdict per row. Canonical: https://mobilevalidate.com/docs/recipes/csv-list-cleaner · Last updated: 2026-09-25 This recipe cleans a contact list before a CRM import or a transactional send. It reads a CSV, removes duplicates, checks each distinct value once and writes the rows back with a verdict. The full script is [`clean_list.py`](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples/csv-list-cleaner). ## How does it work? 1. It finds a `phone`, `number`, `mobile`, `msisdn` or `e164` column and/or an `email` column. 2. It sends each distinct value once: one lookup for up to 100 values, otherwise a [bulk job](/docs/bulk-jobs). 3. It writes `cleaned.csv` with three new columns: `verdict`, `reachable_on` and `suggestion`. ```python from mobilevalidate import MobileValidate mv = MobileValidate() # reads MOBILEVALIDATE_API_KEY; MobileValidate(sandbox=True) for the test values job = mv.jobs.create(numbers=numbers, emails=emails, checks=["whatsapp", "email"]) job = mv.jobs.wait(job["id"], wait_timeout=600) for row in mv.jobs.results(job["id"]): # pages through all results registered = row.get("checks", {}).get("whatsapp.registered", {}).get("registered") # True / False / None ``` ## What verdicts can a row get? | Verdict | Meaning | |---|---| | `ok` | Registered on at least one requested service, or the mailbox exists | | `not_reachable` | Every requested check answered "not registered" | | `check_address` | The mailbox was not found | | `unknown` | No conclusive answer (never billed) | | `invalid` | Not a valid number or address; `suggestion` says how to fix it | | `duplicate` | The value already appeared in an earlier row | ## How do I try it? ```bash pip install -r requirements.txt python clean_list.py list.csv cleaned.csv ``` The sample `list.csv` contains only [test values](/docs/test-values), so it runs with the public sandbox key. Sandbox jobs are limited to 10 rows; for bigger files, use a [personal test key](/get-test-key) or a live key. The script prints counts only. It never prints numbers or addresses. ## Frequently asked questions ### Are duplicates billed twice? No. The script sends each distinct value once and marks repeated rows as duplicate in the output. ### Should I delete contacts with an unknown verdict? No. Unknown means the check could not give a conclusive answer. It is never billed and says nothing about the contact. --- # E-mail check at sign-up > Catch typos and missing mailboxes at sign-up with the MobileValidate e-mail check: fix invalid addresses, confirm unknown mailboxes and allow uncertain answers. Node and Python. Canonical: https://mobilevalidate.com/docs/recipes/email-signup-check · Last updated: 2026-09-25 This recipe checks an e-mail address before you send the confirmation e-mail. It catches typos and mailboxes that don't exist, without blocking real users on uncertain answers. There is a [Node version and a Python version](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples/email-signup-check). ## What does it decide? | Result | Decision | |---|---| | `email_status` is not `valid` | Fix: show the API's `suggestion`, such as a mistyped domain | | `registered: false` | Confirm: "We couldn't find this mailbox. Is the address spelled correctly?" | | `registered: true` | Allow | | `registered: null` (unknown) | Allow | | The check fails | Allow, and log the error code and request id | ## What does the code look like? ```js const { data, error } = await mv.lookup({ emails: [email], checks: ["email"], wait: 5, waitTimeoutMs: 8_000 }); if (error) return { decision: "allow" }; // fail open const row = data.results[0]; if (row.email_status !== "valid") return { decision: "fix", message: row.suggestion }; const registered = row.checks["email.valid"].registered; if (registered === false) return { decision: "confirm" }; return { decision: "allow" }; // true, or null (unknown) ``` ```python lookup = mv.lookup(emails=[email], checks=["email"], wait=5, wait_timeout=8) row = lookup["results"][0] registered = row.get("checks", {}).get("email.valid", {}).get("registered") # True / False / None ``` ## How do I test it? Use the test addresses on `test.mobilevalidate.com` with the public sandbox key: `registered@` allows, `not-registered@` asks to confirm, `unknown@` allows and `pending@` allows after about 5 seconds. See [test values](/docs/test-values). Log the error code and `requestId`, never the address itself. ## Frequently asked questions ### Should I block sign-ups when the mailbox is not found? No. Ask the user to confirm the spelling and let them continue if they insist. Block only addresses that are invalid. ### Does the e-mail check return names or profiles? No. E-mail answers are yes, no or unknown only. --- # OTP and sign-up guard > Check a phone number before sending a one-time code: fix invalid numbers, send on WhatsApp when registered, fall back to SMS, and add friction for VoIP numbers. Express and Next.js. Canonical: https://mobilevalidate.com/docs/recipes/otp-signup-guard · Last updated: 2026-09-25 This recipe checks a phone number once, before you send a one-time code. It tells you whether to ask the user to fix the number, send the code on WhatsApp, or send it by SMS. There are two variants with the same rules: an [Express app](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples/otp-signup-guard/express) and a [Next.js route handler](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples/otp-signup-guard/nextjs). ## What does the guard decide? | Result | Action | |---|---| | `number_status` is not `valid` | Ask the user to fix the number and show the API's `suggestion` (HTTP 422) | | WhatsApp `registered: true` | Send the code on WhatsApp | | `registered: false` | Send the code by SMS | | `registered: null` (unknown) | Send the code by SMS, and record "unknown", not "no" | | Carrier `line_type` is `voip`, `premium_rate`, `toll_free` or `shared_cost` | Add extra verification, such as a CAPTCHA | | Temporary error | Fail open: send by SMS | | Other error | Return the error code and its `suggestion` | ## What does the code look like? The decision is a pure function, so you can unit-test it without the API: ```js export function decideOtpChannel(row) { if (row.number_status !== "valid") { return { action: "fix_number", suggestion: row.suggestion ?? "Enter your number with the country code." }; } const lineType = row.checks?.["network.carrier"]?.attributes?.line_type; const extraVerification = ["voip", "premium_rate", "toll_free", "shared_cost"].includes(lineType); const wa = row.checks?.["whatsapp.registered"]; if (wa?.registered === true) return { action: "send_whatsapp", extraVerification }; if (wa?.registered === false) return { action: "send_sms", reason: "whatsapp_not_registered", extraVerification }; return { action: "send_sms", reason: "whatsapp_unknown", extraVerification }; // null is not "no" } ``` The Next.js route handler calls the API once and applies the decision: ```ts // app/api/otp/route.ts import { MobileValidate } from "mobilevalidate"; import { decideOnError, decideOtpChannel } from "../../../lib/otp-decision.ts"; const mv = process.env.MOBILEVALIDATE_API_KEY ? new MobileValidate() : new MobileValidate({ sandbox: true }); export async function POST(req: Request) { const { phone, country } = await req.json(); const { data, error } = await mv.lookup(phone, { checks: ["whatsapp", "carrier"], defaultCountry: country, wait: 5, waitTimeoutMs: 8_000, }); const decision = error ? decideOnError(error) : decideOtpChannel(data.results[0]); return Response.json(decision, { status: decision.action === "fix_number" ? 422 : 200 }); } ``` Keep the key on the server. Never put it in a `NEXT_PUBLIC_` variable or a browser bundle. ## How do I test it? Use the [test values](/docs/test-values) with the public sandbox key: | Input | Decision | |---|---| | `+447700900001` | WhatsApp | | `+447700900002` | SMS (not registered) | | `+447700900003` | SMS (unknown) | | `+447700900004` | WhatsApp, after about 5 seconds of pending | | `7700 900001` with a [personal test key](/get-test-key) | Fix the number (missing country code) | The sandbox key refuses any other number with `403 sandbox_magic_only` and a suggestion. ## What should I log? Log the decision, the `requestId` and a masked number such as `+44••••••••01`. Never log full phone numbers. ## Frequently asked questions ### What happens when the WhatsApp answer is unknown? The guard sends the code by SMS. Unknown (registered: null) is never billed, and you should record it as unknown rather than as not registered. ### Should a VoIP number be blocked? No. A VoIP line type is a reason for extra verification, such as a CAPTCHA, not a block on its own. The carrier check is in beta. ### What if the check fails? The guard fails open for temporary errors (retryable errors, timeouts, network problems) and sends the code by SMS, so sign-ups are never blocked by an unavailable check. --- # Webhook receiver > A webhook receiver in Node and Python (Flask) that verifies Standard Webhooks signatures on the raw body, replies fast, ignores duplicates and fetches job results. Canonical: https://mobilevalidate.com/docs/recipes/webhook-receiver · Last updated: 2026-09-25 This recipe receives `job.completed` and `lookup.completed` events and verifies them before trusting them. There is a [Node version](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples/webhook-receiver/node) with no framework and a [Python version](https://github.com/brntech/mobilevalidate-sdk/tree/main/examples/webhook-receiver/python) with Flask. ## What rules does the receiver follow? | Rule | Why | |---|---| | Verify the signature on the raw body | Re-serialized JSON changes the bytes, so the signature no longer matches | | Reply with 2xx quickly, then do the work | Slow or failing endpoints are retried | | Ignore repeated `webhook-id` values | A delivery can arrive more than once | | Fetch job results with your API key | Events never contain phone numbers or e-mail addresses | ## What does verification look like? ```js import { verifyWebhook } from "mobilevalidate"; const raw = Buffer.concat(chunks); // the exact bytes received const event = await verifyWebhook(raw, req.headers, process.env.MOBILEVALIDATE_WEBHOOK_SECRET); res.writeHead(204).end(); // reply first if (event.type === "job.completed") { for await (const row of mv.jobs.results(event.data.id)) { /* … */ } } ``` ```python from mobilevalidate import verify_webhook, WebhookVerificationError @app.post("/webhooks/mobilevalidate") def receive(): try: event = verify_webhook(request.get_data(), request.headers, SECRET) except WebhookVerificationError as e: return str(e), 400 return "", 204 ``` `verifyWebhook` throws when the signature doesn't match or the timestamp is more than 5 minutes off. See [Webhooks](/docs/webhooks) for the signature scheme and retry schedule. ## How do I test it locally? Sign a sample event with the CLI and send it with curl. No API key is needed for this: ```bash export MOBILEVALIDATE_WEBHOOK_SECRET=whsec_$(printf 'local-test-secret' | base64) npx mobilevalidate webhooks sign --secret "$MOBILEVALIDATE_WEBHOOK_SECRET" --file event.json > headers.txt curl -i localhost:3000/webhooks/mobilevalidate -H 'content-type: application/json' \ -H "$(sed -n 1p headers.txt)" -H "$(sed -n 2p headers.txt)" -H "$(sed -n 3p headers.txt)" \ --data-binary @event.json ``` To receive real events, register an endpoint with a [personal test key](/get-test-key) or a live key. ## Frequently asked questions ### Why does my signature check fail? Usually because the body was parsed and re-serialized before verification. Verify the raw bytes exactly as received, with the endpoint's own whsec_ secret. ### Can I use webhooks with the sandbox key? No. The public sandbox key has no webhooks. Use a personal test key to register a test endpoint, or sign a sample event yourself with the CLI. --- # OpenAPI specification > Download the MobileValidate OpenAPI 3.1 specification to generate clients, import into Postman or Insomnia, or give AI coding agents the full API contract. Canonical: https://mobilevalidate.com/docs/openapi · Last updated: 2026-09-25 The full contract of the MobileValidate API is published as an OpenAPI 3.1 document at [/openapi.yaml](/openapi.yaml). It describes every public v1 endpoint, request body, response shape and error code. Use it to generate a client in your language, import the API into an HTTP tool, or give a coding agent the exact contract. ## Where can I get it? | URL | Notes | |---|---| | `https://mobilevalidate.com/openapi.yaml` | Published with this site | | `https://api.mobilevalidate.com/v1/openapi.yaml` | Served by the API itself | ```bash tabs=off curl -O https://mobilevalidate.com/openapi.yaml ``` Both files describe the same contract. To explore it interactively, open the [API reference](/docs/api-reference): every endpoint with examples, and test requests from your browser with the public sandbox key filled in. ## What can I do with it? - **Generate a client:** any OpenAPI 3.1 generator works, for example `openapi-generator` or `openapi-typescript`. For TypeScript, the [SDK](/docs/sdk) is the ready-made option. - **Import into a tool:** Postman, Insomnia, Bruno and similar tools import the file directly. Set the `Authorization: Bearer` header to your test key. - **Give it to an agent:** coding agents can read the file to write correct integrations. For agents that should run checks, use the [MCP server](/docs/mcp). ## How is it versioned? The API is versioned in the path (`/v1`). Within v1, changes are additive only: new endpoints, new optional fields and new enum values. Your client should ignore fields it doesn't recognise and tolerate new enum values, such as a new `status`, `reason` or service code. A breaking change would ship as `/v2`, with at least 12 months of overlap and `Deprecation` and `Sunset` headers on the old version. The full policy is on the [versioning](/docs/versioning) page, and notable changes are listed in the [changelog](/changelog). ## Frequently asked questions ### Which OpenAPI version is it? OpenAPI 3.1, in YAML. It covers every public v1 endpoint, request and response schema, and error code. ### Will the specification change? Only additively within /v1 — new fields, endpoints and enum values. Breaking changes would ship as /v2 with at least 12 months of overlap. --- # Versioning and deprecation > How the MobileValidate API changes: v1 is stable, changes within v1 are additive only, and breaking changes come in a new major version with at least 12 months' notice. Canonical: https://mobilevalidate.com/docs/versioning · Last updated: 2026-09-25 The MobileValidate API is versioned in the URL path. **v1** (`https://api.mobilevalidate.com/v1`) is the current, stable version. This page explains which changes can happen within v1, what your client must tolerate, and how we retire anything. ## What changes can happen within v1? Within v1, changes are **additive only**. We may, without notice: - add new endpoints, and new HTTP methods on existing paths; - add optional request fields, query parameters and headers (existing requests keep the same meaning); - add fields to response objects and webhook payloads, including nested objects; - add new error codes, new values to enums such as `status`, `reason` and attribute values, and new check codes in `GET /v1/services`; - add new webhook event types (you only receive the types your endpoint subscribes to); - change human-readable text: error `message` and `suggestion`, descriptions and documentation; - change the order of fields in JSON objects, and the format of opaque IDs (treat IDs as strings); - raise limits. Lowering a documented limit counts as a breaking change. Every such change is listed in the [changelog](/changelog). ## What must my client tolerate? To keep working through additive changes, a v1 client should: - **ignore unknown fields** in responses and webhook payloads, rather than failing to parse them. Don't use strict deserialization that rejects extra properties; - **handle unknown enum values**. For example, treat an unknown `status` like a non-conclusive result, and an unknown attribute value as "unknown"; - **branch on `error.code`, never on `error.message`**, and fall back to the HTTP status for codes it doesn't know (see [errors](/docs/errors)); - **ignore webhook event types it doesn't handle**, returning `2xx` so they aren't retried; - **not depend on field order** or on the length or format of IDs. ## What counts as a breaking change? Removing or renaming an endpoint, field, parameter or enum value; changing a field's type or meaning; making an optional request field required; changing authentication; changing an HTTP status for an existing situation; or lowering a documented limit. Breaking changes only happen in a **new major version** (for example `/v2`). v1 keeps working unchanged for **at least 12 months** after the new version and the v1 retirement date are announced, and both versions run side by side during that time. ## How are deprecations announced? When we deprecate an endpoint, field or parameter: 1. We publish a changelog entry with the tag "API" (also in the [RSS feed](/changelog/rss.xml)), naming what is deprecated, what replaces it and the sunset date. The sunset date is at least 12 months after the announcement. 2. We e-mail the contacts of the accounts that use it. 3. The documentation marks it as deprecated. 4. Responses that use it carry the standard headers: ```http Deprecation: @1790812800 Sunset: Fri, 01 Oct 2027 00:00:00 GMT Link: ; rel="deprecation"; type="text/html" ``` `Deprecation` ([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)) gives the date the deprecation took effect as a Unix timestamp. `Sunset` ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)) gives the date after which the endpoint or field may stop working. The values above are an example: deprecated on 2026-10-01, sunset 12 months later. Log these headers in your client so a deprecation never goes unnoticed. Until the sunset date, deprecated endpoints and fields keep working exactly as before. ## Are there exceptions? If a change is required to fix a security vulnerability or to comply with the law, we may have to act with less than 12 months' notice. In that case we give as much notice as we can and explain the reason in the changelog. ## How are the SDKs, CLI and OpenAPI file versioned? - The SDKs, the CLI and the MCP server follow [semantic versioning](https://semver.org). A new major version of a package is released only for breaking changes to the package itself, and each release lists its changes. - The [OpenAPI specification](/docs/openapi) carries the API version in `info.version` (`1.x.y` for v1). A minor or patch increase never contains a breaking change. ## Where can I see the current state? - [Changelog](/changelog) and its [RSS feed](/changelog/rss.xml) for every API, SDK and documentation change. - [System status](/status) for availability and incident reports. ## Frequently asked questions ### Will my v1 integration break? Not because of a change on our side. Within v1 we only add things: new endpoints, optional fields, response fields, error codes and enum values. Clients that ignore unknown fields and handle unknown values keep working. ### How much notice do I get before something is removed? At least 12 months. Removals and other breaking changes only happen in a new major version, announced in the changelog, and deprecated endpoints send Deprecation and Sunset headers during that period. ### How do I find out about changes? Follow the changelog or its RSS feed at /changelog/rss.xml. Deprecations are also e-mailed to the contacts of affected accounts. --- # Changelog > The MobileValidate API changelog lives at /changelog, with an RSS feed: dated changes to the API, SDKs, MCP server and docs. Canonical: https://mobilevalidate.com/docs/changelog · Last updated: 2026-09-25 The changelog has its own page: **[mobilevalidate.com/changelog](/changelog)**. It lists every change to the API, SDKs, CLI, MCP server and docs that affects developers, newest first, and you can follow it by [RSS](/changelog/rss.xml). Within `/v1`, changes are additive: existing requests keep working and responses only gain fields. See [versioning](/docs/versioning) for the full policy. --- # API reference > Every MobileValidate API endpoint, from the OpenAPI document. Interactive version (with the public sandbox key): https://mobilevalidate.com/docs/api-reference Canonical: https://mobilevalidate.com/docs/api-reference Base URL: `https://api.mobilevalidate.com/v1` · OpenAPI 1.0.0 document: https://mobilevalidate.com/openapi.yaml ## Lookups ### POST /lookup Check up to 100 numbers and e-mail addresses Runs every requested check (see GET /v1/services) for every identifier of the check's input type: phone checks for `numbers`, e-mail checks for `emails`. Result rows come back in order: numbers first, then e-mails. The call waits up to `wait` seconds (default 10) for all answers; if some are still running it answers `202` with `status: pending` and a `next.poll_url` (or use a webhook). Checks without real-time support are refused with 403 service_disabled (param `checks[i]`) — send them in a bulk job. E-mail checks answer only whether an account or mailbox exists — never names, photos or profiles. - `Idempotency-Key` (header): Any unique string (1–255 printable ASCII) for safe retries: a retry with the same key and body within 24 hours returns the stored response (header Idempotent-Replayed: true) instead of running the request again. Responses: 200 All checks completed.; 202 Some checks are still running; poll `next.poll_url` (the Location header) or wait for lookup.completed.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### GET /lookups/{id} Get a lookup Returns a lookup created by POST /v1/lookup, optionally waiting up to `wait` seconds for pending checks. `input` is masked on later reads; `e164` and `email` are returned. Lookups expire after the account's retention period (404 not_found afterwards). - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). - `wait` (query): Seconds (0–30) to wait for pending checks before answering. Responses: 200 The lookup (completed or still pending).; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ## Bulk jobs ### POST /jobs/estimate Estimate a bulk job for free Validates and counts a list without storing it, calling a data source or charging: valid, invalid and duplicate rows, cached answers, and the maximum possible cost (`max_cost`). Same body as POST /v1/jobs. Responses: 200 The estimate.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### POST /jobs Create a bulk job Checks up to 50,000 numbers and e-mail addresses (JSON body, or a CSV upload as multipart/form-data). The maximum possible cost is reserved up front; cached, inconclusive, invalid and duplicate rows are released when the job finishes. Poll GET /v1/jobs/{id} (or subscribe to job.completed), then page through GET /v1/jobs/{id}/results or download the whole file. Test-key jobs complete immediately. - `Idempotency-Key` (header): Any unique string (1–255 printable ASCII) for safe retries: a retry with the same key and body within 24 hours returns the stored response (header Idempotent-Replayed: true) instead of running the request again. Responses: 201 Job created. `estimate` shows how the list was counted.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### GET /jobs/{id} Get a job Status, progress and cost of a job. `wait` (0–30 s) holds the request until the job reaches a final status (completed, failed or cancelled) or the time runs out. Keys with `jobs:read` see their own jobs; `jobs:read_all` sees every job of the account. - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). - `wait` (query): Seconds (0–30) to wait for pending checks before answering. Responses: 200 The job.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### DELETE /jobs/{id} Cancel a running job or delete a finished job's results Running job → cancelled: rows not yet sent to a data source are released and never charged; rows already sent finish and are billed as usual. Finished job → its per-row results are deleted now instead of at the end of the retention period (`purged_at` is set). - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). Responses: 200 The job after cancelling or deleting results.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### GET /jobs/{id}/results List a job's results One item per row (number or e-mail address) in input order, with every check of the row. Filter by `registered` / `status` of one `service`; page with `limit` and `after` = the previous page's `next_cursor`. - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). - `registered` (query): Only rows whose answer for `service` is true / false / null. - `status` (query): Only rows whose `service` check has this status. - `service` (query): Service (code or alias) the registered/status filters apply to; default = the job's first check. With a filter, only rows of that service's input type are returned. - `limit` (query): Rows (numbers and e-mails) per page. A row's checks are never split across pages. - `after` (query): Cursor from the previous page (`next_cursor`). Responses: 200 A page of result items.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### GET /jobs/{id}/download Download a job's results as CSV or NDJSON Streams the whole result file, one line per row (numbers, then e-mails) in input order. CSV columns: row_no, input_masked, e164, country, number_status, service, status, registered, business, checked_at, cached, billed (for the row's primary check = first requested check of its kind), then kind, email, email_status, then per service `.status`, `.registered`, `.`, `.billed` (empty for rows of the other kind). NDJSON lines have the same shape as result items. - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). - `format` (query, required): File format. Responses: 200 The result file (Content-Disposition attachment).; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ## Account ### GET /account Get the account balance Balance, reserved amount and today's usage (UTC) of the key's account. Needs `account:read`. Responses: 200 The account.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### GET /limits Get limits and remaining allowances Request rate, request sizes, the daily identifiers cap and the key's daily spend cap. Daily counters reset at 00:00 UTC. Responses: 200 Current limits.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### GET /usage Get usage by day or by check Checks, conclusive answers, cached answers, billed units and cost for an inclusive UTC date range (at most 366 days), for the key's mode (live or test). Rows past the retention period are no longer counted. - `from` (query, required): First day (UTC). - `to` (query, required): Last day (UTC), inclusive. - `group_by` (query): One row per day or per check. Responses: 200 Usage rows.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ## Webhooks ### GET /webhook_endpoints List webhook endpoints Every endpoint of the account that is not deleted and has the same mode as the key (test keys list test endpoints, live keys live endpoints). Needs `webhooks:manage`. Responses: 200 Endpoints.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### POST /webhook_endpoints Create a webhook endpoint Registers a public HTTPS URL (at most 20 per account). The response contains the signing `secret` — shown only once. The endpoint stays `pending_verification` until it answers our signed `webhook.verification` event with a 2xx status. Not available with the public sandbox key. Responses: 201 Endpoint created; `secret` is shown only this once.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### PATCH /webhook_endpoints/{id} Update a webhook endpoint Changes the URL and/or the subscribed events. A new URL must be verified again (`pending_verification`). - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). Responses: 200 The updated endpoint.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### DELETE /webhook_endpoints/{id} Delete a webhook endpoint Nothing is delivered to a deleted endpoint. - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). Responses: 204 Deleted.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ### POST /webhook_endpoints/{id}/test Send a test event Queues a signed `webhook.test` event to the endpoint (delivered even before verification). Use it to check your signature code. - `id` (path, required): Object id (`lkp_…`, `job_…` or `we_…`). Responses: 202 Test event queued.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ## Service catalog ### GET /services List checks and prices Without a key: the public catalog. With a key: every check the key's account may use, at the account's prices. Lists active phone and e-mail checks; switched-off checks (e.g. number.hlr, coming soon) are not listed. Responses: 200 Service catalog with prices by mode.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ## Keys ### POST /test-keys Get a personal test key Issues a personal test key (`mv_test_…`) for a work e-mail address, protected by a Cloudflare Turnstile token from the form at https://mobilevalidate.com/get-test-key. The key works in test mode with any number (never billed, never reaches a data source), including bulk jobs and test-mode webhooks. It is shown **once** and never returned again. Each address can receive at most 3 test keys in 30 days; after that the answer is 409 test_key_exists. At most 3 requests per network per day, whatever their result (IPv6 networks count per /64). The request also asks for live access, which we review by hand. No authentication. Responses: 201 The key (shown only once).; 409 No more test keys can be issued for this e-mail address right now (at most 3 in 30 days). Keys already issued are never shown again.; default Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. ## System ### GET /health Check that the API is up No authentication. 200 when the API can reach its database, 503 otherwise. Responses: 200 Healthy.; 503 The API cannot reach its database; retry after Retry-After. ## Webhook events - `lookup.completed`: A pending lookup finished - `job.completed`: A bulk job finished - `job.failed`: A bulk job failed (subscribable; delivery rolling out) - `job.progress`: Progress of a running job (opt-in; delivery rolling out) - `balance.low`: The balance fell below the low-balance threshold (delivery rolling out) - `limits.cap_reached`: A daily identifiers cap or spend cap was reached (delivery rolling out) - `webhook.verification`: Ownership challenge for a new or changed endpoint - `webhook.test`: Test event from POST /v1/webhook_endpoints/{id}/test --- # All services > Every phone and e-mail check with realtime and bulk prices. Canonical: https://mobilevalidate.com/services ![How a lookup works: one request, checks run in parallel, one answer per check (yes, no or unknown), and only conclusive answers are charged.](https://mobilevalidate.com/images/how-a-lookup-works.svg) *Checks run in parallel; unknown answers are free.* ## Messaging apps - [WhatsApp number check](https://mobilevalidate.com/services/whatsapp-number-check.md) — `whatsapp.registered`: Whether the phone number has a WhatsApp account. (realtime $0.0018 · bulk $0.00012 per check) - [WhatsApp Business account check](https://mobilevalidate.com/services/whatsapp-business-check.md) — `whatsapp.business`: Whether the phone number has a WhatsApp account, and whether it is a WhatsApp Business account. (realtime $0.0018 · bulk $0.00015 per check) - [Telegram number check](https://mobilevalidate.com/services/telegram-number-check.md) — `telegram.registered`: Whether the phone number has a Telegram account. (realtime $0.0005 · bulk $0.0003 per check) - [Viber number check](https://mobilevalidate.com/services/viber-number-check.md) — `viber.registered`: Whether the phone number has a Viber account. (realtime $0.00015 · bulk $0.00008 per check) - [Signal number check](https://mobilevalidate.com/services/signal-number-check.md) — `signal.registered`: Whether the phone number has a Signal account. (bulk only · bulk $0.0003 per check) - [iMessage number check](https://mobilevalidate.com/services/imessage-number-check.md) — `imessage.registered`: Whether the phone number has a iMessage account. (bulk only · bulk $0.0003 per check) - [RCS capability check](https://mobilevalidate.com/services/rcs-capability-check.md) — `rcs.registered`: Whether the phone number can receive RCS messages. (bulk only · bulk $0.0003 per check) - [LINE number check](https://mobilevalidate.com/services/line-number-check.md) — `line.registered`: Whether the phone number has a LINE account. (bulk only · bulk $0.0008 per check) - [Zalo number check](https://mobilevalidate.com/services/zalo-number-check.md) — `zalo.registered`: Whether the phone number has a Zalo account. (realtime $0.0005 · bulk $0.0003 per check) - [Botim number check](https://mobilevalidate.com/services/botim-number-check.md) — `botim.registered`: Whether the phone number has a Botim account. (bulk only · bulk $0.0008 per check) - [MAX number check](https://mobilevalidate.com/services/max-number-check.md) — `max.registered`: Whether the phone number has a MAX account. (bulk only · bulk $0.0015 per check) - [Facebook Messenger number check](https://mobilevalidate.com/services/facebook-messenger-number-check.md) — `messenger.registered`: Whether the phone number has a Facebook Messenger account. (bulk only · bulk $0.0005 per check) ## Social networks - [Facebook number check](https://mobilevalidate.com/services/facebook-number-check.md) — `facebook.registered`: Whether the phone number has a Facebook account. (realtime $0.00008 · bulk $0.00005 per check) - [Instagram number check](https://mobilevalidate.com/services/instagram-number-check.md) — `instagram.registered`: Whether the phone number has a Instagram account. (realtime $0.00008 · bulk $0.00005 per check) - [Threads number check](https://mobilevalidate.com/services/threads-number-check.md) — `threads.registered`: Whether the phone number has a Threads account. (realtime $0.0002 · bulk $0.00015 per check) - [X (Twitter) number check](https://mobilevalidate.com/services/x-twitter-number-check.md) — `x.registered`: Whether the phone number has a X (Twitter) account. (realtime $0.0003 · bulk $0.00015 per check) - [TikTok number check](https://mobilevalidate.com/services/tiktok-number-check.md) — `tiktok.registered`: Whether the phone number has a TikTok account. (bulk only · bulk $0.0015 per check) - [Snapchat number check](https://mobilevalidate.com/services/snapchat-number-check.md) — `snapchat.registered`: Whether the phone number has a Snapchat account. (bulk only · bulk $0.003 per check) - [LinkedIn number check](https://mobilevalidate.com/services/linkedin-number-check.md) — `linkedin.registered`: Whether the phone number has a LinkedIn account. (bulk only · bulk $0.0008 per check) - [VK number check](https://mobilevalidate.com/services/vk-number-check.md) — `vk.registered`: Whether the phone number has a VK account. (realtime $0.0015 · bulk $0.0008 per check) ## Consumer apps & accounts - [Apple number check](https://mobilevalidate.com/services/apple-id-number-check.md) — `apple.registered`: Whether the phone number has a Apple account. (realtime $0.0005 · bulk $0.0003 per check) - [Amazon number check](https://mobilevalidate.com/services/amazon-number-check.md) — `amazon.registered`: Whether the phone number has a Amazon account. (realtime $0.0005 · bulk $0.0003 per check) - [Microsoft number check](https://mobilevalidate.com/services/microsoft-account-number-check.md) — `microsoft.registered`: Whether the phone number has a Microsoft account. (realtime $0.0005 · bulk $0.0003 per check) - [Netflix number check](https://mobilevalidate.com/services/netflix-number-check.md) — `netflix.registered`: Whether the phone number has a Netflix account. (realtime $0.002 · bulk $0.0015 per check) ## Network & risk - [Carrier lookup](https://mobilevalidate.com/services/carrier-lookup.md) — `network.carrier`: Line type and carrier of the phone number (coverage varies by country; no data → unknown, not billed). (realtime $0.0005 · bulk $0.0003 per check) - [US/CA carrier lookup](https://mobilevalidate.com/services/us-carrier-lookup.md) — `network.carrier_us`: Line type and current carrier for US and Canadian numbers. (bulk only · bulk $0.003 per check) - [Spam reputation](https://mobilevalidate.com/services/spam-reputation.md) — `number.spam`: Whether the number appears in spam and nuisance-call reports (regulator actions, government complaint data, community reports) and whether it looks unassigned. Explainable signals, no report texts. (realtime $0.0008 · bulk $0.0004 per check) - [Live network status (HLR)](https://mobilevalidate.com/services/hlr-lookup.md) — `number.hlr`: Live home-network query: whether the mobile number is reachable, ported or roaming, and its current network. (coming soon per check) ## E-mail - [E-mail verification](https://mobilevalidate.com/services/email-verification.md) — `email.valid`: Whether the e-mail mailbox exists (major webmail providers). (realtime $0.002 · bulk $0.0015 per check) - [Gmail account check by e-mail](https://mobilevalidate.com/services/gmail-email-check.md) — `gmail.email`: Whether the e-mail address has a Gmail account. (bulk only · bulk $0.0008 per check) - [Outlook account check by e-mail](https://mobilevalidate.com/services/outlook-email-check.md) — `outlook.email`: Whether the e-mail address has a Outlook account. (bulk only · bulk $0.0003 per check) - [Yahoo account check by e-mail](https://mobilevalidate.com/services/yahoo-email-check.md) — `yahoo.email`: Whether the e-mail address has a Yahoo account. (bulk only · bulk $0.0003 per check) - [Yandex account check by e-mail](https://mobilevalidate.com/services/yandex-email-check.md) — `yandex.email`: Whether the e-mail address has a Yandex account. (bulk only · bulk $0.0005 per check) - [Mail.ru account check by e-mail](https://mobilevalidate.com/services/mailru-email-check.md) — `mailru.email`: Whether the e-mail address has a Mail.ru account. (bulk only · bulk $0.00008 per check) - [Apple account check by e-mail](https://mobilevalidate.com/services/apple-id-email-check.md) — `apple.email`: Whether the e-mail address has a Apple account. (realtime $0.0005 · bulk $0.0003 per check) - [Amazon account check by e-mail](https://mobilevalidate.com/services/amazon-email-check.md) — `amazon.email`: Whether the e-mail address has a Amazon account. (realtime $0.0005 · bulk $0.0003 per check) - [Facebook account check by e-mail](https://mobilevalidate.com/services/facebook-email-check.md) — `facebook.email`: Whether the e-mail address has a Facebook account. (realtime $0.003 · bulk $0.0015 per check) - [Instagram account check by e-mail](https://mobilevalidate.com/services/instagram-email-check.md) — `instagram.email`: Whether the e-mail address has a Instagram account. (realtime $0.003 · bulk $0.0015 per check) - [Netflix account check by e-mail](https://mobilevalidate.com/services/netflix-email-check.md) — `netflix.email`: Whether the e-mail address has a Netflix account. (realtime $0.002 · bulk $0.0015 per check) - [Spotify account check by e-mail](https://mobilevalidate.com/services/spotify-email-check.md) — `spotify.email`: Whether the e-mail address has a Spotify account. (realtime $0.0003 · bulk $0.00015 per check) - [LinkedIn account check by e-mail](https://mobilevalidate.com/services/linkedin-email-check.md) — `linkedin.email`: Whether the e-mail address has a LinkedIn account. (bulk only · bulk $0.00005 per check) - [X (Twitter) account check by e-mail](https://mobilevalidate.com/services/x-twitter-email-check.md) — `x.email`: Whether the e-mail address has a X (Twitter) account. (bulk only · bulk $0.00015 per check) You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). --- # Pricing > Per-check prices for every service, real-time and bulk. Unknown, unsupported, timed-out, invalid and duplicate results are never charged. Canonical: https://mobilevalidate.com/pricing · Last updated: 2026-09-25 ![How a lookup works: one request, checks run in parallel, one answer per check (yes, no or unknown), and only conclusive answers are charged.](https://mobilevalidate.com/images/how-a-lookup-works.svg) *Checks run in parallel; unknown answers are free.* You pay per check, and only when the check gives a conclusive answer. Each service has one price for real-time lookups and a lower price for bulk jobs. The table on this page shows current prices per check and per 1,000 checks. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## How is a check counted? A check is one identifier (a phone number or an e-mail address) run against one service. A lookup of 10 numbers with `checks: ["whatsapp", "telegram"]` contains 20 checks. Each check is billed on its own, and only when its answer is conclusive: - **Account checks** (for example "registered on Telegram"): billed for `registered: true` or `false`. Not billed for `null`. - **Data checks** (for example the carrier lookup): billed when the answer carries the service's main data, such as a carrier. "No data" comes back as `unknown` and is free. - **Spam reputation:** every level, including `no_reports`, is a conclusive answer and is billed. Unknown and unsupported-country results are free. Invalid numbers, invalid e-mail addresses, duplicates within a request and suppressed identifiers are never checked, so they never cost anything. Each result shows `billed: true` or `false`, so you can reconcile every charge. ## How does the balance work? Your account has a prepaid USD balance. When you send a request, we reserve the most it could cost, meaning every billable check answering conclusively. As answers arrive, we charge only the conclusive ones and give the rest back to your available balance. For a bulk job you can follow this in the job's `cost` object: `estimated_max`, `reserved`, `charged` and `released`. If your balance can't cover the maximum cost, the request is refused with `insufficient_balance` before anything is checked. `GET /v1/account` shows your balance, the amount currently reserved and today's usage. ## How do I control spend? - **`max_cost`** on any lookup or job: if the most the request could cost is higher, it's refused with `cost_limit_exceeded` and nothing is charged. - **Free estimates:** `POST /v1/jobs/estimate` returns the maximum cost of a list before you commit. - **Caching:** repeat checks inside the freshness window come from your account's cache and are free (`cached: true`, `billed: false`). Send `max_age: 0` only when you need a fresh answer. - **Daily caps:** each account has a daily cap on numbers, and agent keys can have a daily spend cap. `GET /v1/limits` shows both. - **Test keys:** build and test your integration for free with the [test numbers](/docs/test-mode). ## Real time or bulk: which price applies? The price depends on how you send the check. `POST /v1/lookup` uses the real-time price. `POST /v1/jobs` uses the bulk price, whatever the service. Some services, such as the Signal, iMessage and RCS checks, are available in bulk jobs only. For those, the pricing table shows only the bulk price. Use real time when a person or a process is waiting for the answer, for example during sign-up. Use bulk for lists, scheduled refreshes and anything that can wait a few minutes. ## Are there volume prices? For large or committed volumes, tell us your expected monthly volume and services when you [request access](/request-access). We will discuss committed-volume pricing with you. Prices shown during the private preview may change before public launch. The price that applies to a request is fixed when the request is accepted. ## Frequently asked questions ### What counts as a conclusive answer? A completed yes or no for account checks, or a completed answer with data for data checks such as the carrier lookup. Anything else, such as unknown, unsupported country, timeout, invalid or duplicate, is free. ### Why is bulk cheaper than real time? Bulk jobs can be scheduled and batched, so they cost less to run. Real-time lookups answer within the request, which costs more. Every active service is available in bulk; some are available in bulk only. ### How do I know what a job will cost before it runs? Call POST /v1/jobs/estimate. It is free and returns the maximum cost of the job, along with counts of invalid, duplicate, cached and unsupported rows. ### Is a spam reputation answer of no_reports charged? Yes. For spam reputation every level, including no_reports, is a conclusive answer. Unknown and unsupported-country results are free. ### Do repeat checks cost money? Not inside the freshness window. A repeat check of the same number or address for the same service can be served from your account's cache, and cache hits are free. Sending max_age 0 forces a fresh, billed check. ### Are test keys charged? No. Test keys only answer for the documented test numbers and addresses, never reach a real network and are never billed. ## Price table (live, USD per conclusive check) | Service | Code | Realtime / check | Realtime / 1,000 | Bulk / check | Bulk / 1,000 | |---|---|---|---|---|---| | WhatsApp number check | `whatsapp.registered` | $0.0018 | $1.80 | $0.00012 | $0.12 | | WhatsApp Business account check | `whatsapp.business` | $0.0018 | $1.80 | $0.00015 | $0.15 | | Telegram number check | `telegram.registered` | $0.0005 | $0.50 | $0.0003 | $0.30 | | Viber number check | `viber.registered` | $0.00015 | $0.15 | $0.00008 | $0.08 | | Signal number check | `signal.registered` | bulk only | — | $0.0003 | $0.30 | | iMessage number check | `imessage.registered` | bulk only | — | $0.0003 | $0.30 | | RCS capability check | `rcs.registered` | bulk only | — | $0.0003 | $0.30 | | LINE number check | `line.registered` | bulk only | — | $0.0008 | $0.80 | | Zalo number check | `zalo.registered` | $0.0005 | $0.50 | $0.0003 | $0.30 | | Botim number check | `botim.registered` | bulk only | — | $0.0008 | $0.80 | | MAX number check | `max.registered` | bulk only | — | $0.0015 | $1.50 | | Facebook Messenger number check | `messenger.registered` | bulk only | — | $0.0005 | $0.50 | | Facebook number check | `facebook.registered` | $0.00008 | $0.08 | $0.00005 | $0.05 | | Instagram number check | `instagram.registered` | $0.00008 | $0.08 | $0.00005 | $0.05 | | Threads number check | `threads.registered` | $0.0002 | $0.20 | $0.00015 | $0.15 | | X (Twitter) number check | `x.registered` | $0.0003 | $0.30 | $0.00015 | $0.15 | | TikTok number check | `tiktok.registered` | bulk only | — | $0.0015 | $1.50 | | Snapchat number check | `snapchat.registered` | bulk only | — | $0.003 | $3.00 | | LinkedIn number check | `linkedin.registered` | bulk only | — | $0.0008 | $0.80 | | VK number check | `vk.registered` | $0.0015 | $1.50 | $0.0008 | $0.80 | | Apple number check | `apple.registered` | $0.0005 | $0.50 | $0.0003 | $0.30 | | Amazon number check | `amazon.registered` | $0.0005 | $0.50 | $0.0003 | $0.30 | | Microsoft number check | `microsoft.registered` | $0.0005 | $0.50 | $0.0003 | $0.30 | | Netflix number check | `netflix.registered` | $0.002 | $2.00 | $0.0015 | $1.50 | | Carrier lookup | `network.carrier` | $0.0005 | $0.50 | $0.0003 | $0.30 | | US/CA carrier lookup | `network.carrier_us` | bulk only | — | $0.003 | $3.00 | | Spam reputation | `number.spam` | $0.0008 | $0.80 | $0.0004 | $0.40 | | E-mail verification | `email.valid` | $0.002 | $2.00 | $0.0015 | $1.50 | | Gmail account check by e-mail | `gmail.email` | bulk only | — | $0.0008 | $0.80 | | Outlook account check by e-mail | `outlook.email` | bulk only | — | $0.0003 | $0.30 | | Yahoo account check by e-mail | `yahoo.email` | bulk only | — | $0.0003 | $0.30 | | Yandex account check by e-mail | `yandex.email` | bulk only | — | $0.0005 | $0.50 | | Mail.ru account check by e-mail | `mailru.email` | bulk only | — | $0.00008 | $0.08 | | Apple account check by e-mail | `apple.email` | $0.0005 | $0.50 | $0.0003 | $0.30 | | Amazon account check by e-mail | `amazon.email` | $0.0005 | $0.50 | $0.0003 | $0.30 | | Facebook account check by e-mail | `facebook.email` | $0.003 | $3.00 | $0.0015 | $1.50 | | Instagram account check by e-mail | `instagram.email` | $0.003 | $3.00 | $0.0015 | $1.50 | | Netflix account check by e-mail | `netflix.email` | $0.002 | $2.00 | $0.0015 | $1.50 | | Spotify account check by e-mail | `spotify.email` | $0.0003 | $0.30 | $0.00015 | $0.15 | | LinkedIn account check by e-mail | `linkedin.email` | bulk only | — | $0.00005 | $0.05 | | X (Twitter) account check by e-mail | `x.email` | bulk only | — | $0.00015 | $0.15 | --- # Security and data handling > How MobileValidate protects the phone numbers and e-mail addresses you check: encryption, masking, retention defaults, suppression and sub-processors. Canonical: https://mobilevalidate.com/trust · Last updated: 2026-09-25 ![A security seal with a shield, an encrypted-storage lock, a masked phone number and a retention timer.](https://mobilevalidate.com/images/data-protection-and-masking.svg) *Numbers are encrypted at rest, masked in logs and screens, and kept only as long as needed.* Phone numbers and e-mail addresses are personal data, and we handle them that way. Identifiers are encrypted when stored and masked wherever people see them. They are kept only for short default periods, and your cache is never shared with other customers. Anyone can object to being checked. This page describes these controls in plain terms. It is not a certification. ## How are phone numbers and e-mails stored? Identifiers are sealed with public-key encryption as soon as they are stored. The services that take requests can encrypt, but they can't decrypt. Decryption keys are only held by the internal component that has to run the check. To find repeat checks and apply the suppression list without decrypting anything, we use a keyed blind index. That is a one-way keyed hash of the normalized identifier, and without the secret key it can't be reversed or recomputed. Results are sealed per item as well. Before anything is stored, phone numbers are normalized to E.164 and e-mail addresses are trimmed and lowercased. This means one identifier maps to exactly one record. Connections to the API and the website use TLS. ## Who can see the data? Almost no one sees full identifiers. Logs, the admin panel, job results and downloads show masked values, such as `+44770*****01` for a number or `re•••@example.com` for an address. We never put phone numbers or e-mail addresses in URLs or request headers, because those end up in access logs. The API only accepts them in request bodies. API keys are stored as keyed hashes and shown only once, when they are created. You can limit a key to certain scopes, and you can restrict it to your own IP addresses with an allowlist that accepts IPv4 and IPv6 ranges. Unknown, revoked, expired and wrong-IP keys all get the same `unauthorized` answer, so the error doesn't reveal which case applies. Staff access to the admin panel goes through an identity-aware access gateway and every action is audited. ## How long is data kept? | Data | Default retention | |---|---| | Real-time API lookups (identifiers and results) | 7 days | | Bulk jobs (inputs, results, downloads) | 30 days, configurable per account | | Operational logs | Masked identifiers only | You can delete a finished job's data before the retention period ends with `DELETE /v1/jobs/{id}`. Cached answers live in your account's cache only and are never shared across customers. Billing records keep counts and amounts, not identifiers. ## How can people object to being checked? People whose number or e-mail address may be checked, and people whose number may appear in spam-report data, can use the [opt-out form](/opt-out). We review every request. An approved objection puts the identifier on a suppression list, which is checked before every lookup. A suppressed identifier is never checked and shows up in results as `suppressed`, free of charge. The form's reply is the same in every case, so it never confirms whether we hold data about someone. Requests for access or erasure are handled as described in the [data-subject notice](/legal/data-subject-notice). ## Which sub-processors do you use? We use sub-processors in these categories: - hosting and infrastructure - storage and backups - e-mail delivery - payments - data-verification partners that answer individual checks The named list is given under confidentiality to every customer who signs our [data processing agreement](/legal/dpa). Changes are notified in advance and you can object to them. Partners receive only what a check needs, which is the identifier and the service, and never your account details. ## What else protects the service? - **Abuse controls.** Requests that look like sequential number ranges or generated e-mail lists are rejected, and each account has a daily cap. See [rate limits and abuse](/docs/rate-limits-and-abuse). - **No profiling outputs.** Checks answer yes, no or unknown, plus defined data fields. We never return names, photos or profiles. - **Signed webhooks.** Every delivery is signed and only goes to public HTTPS endpoints that you have verified. - **Cookies.** The site uses no analytics or advertising cookies. See the [cookie notice](/legal/cookies). To report a vulnerability, use the contact in `/.well-known/security.txt`. ## Frequently asked questions ### How long do you keep the numbers and e-mails I check? By default 7 days for API lookups and 30 days for bulk jobs. Your account's job retention can be configured, and you can delete a finished job's data at any time with DELETE /v1/jobs/{id}. ### Do other customers benefit from my checks? No. The cache is per account. A result obtained for your account is never served to another customer. ### Who are your sub-processors? We publish the categories: hosting, storage, e-mail, payments and data-verification partners. Customers who sign our data processing agreement receive the named list under confidentiality, with notice of changes. ### Do you use analytics or advertising cookies? No. The site uses no analytics or advertising cookies. See the cookie notice for the strictly necessary ones. ### How do I report a security issue? Use the contact given in /.well-known/security.txt. Please don't include real personal data in your report. ### Do you hold security certifications? Not at this time. We describe our controls here and don't display certification badges we don't hold. --- # Changelog > Changes to the MobileValidate API, SDKs, MCP server and documentation, newest first. Within v1, changes are additive. Canonical: https://mobilevalidate.com/changelog · RSS: https://mobilevalidate.com/changelog/rss.xml ## 2026-09-26: SDKs, CLI and MCP package published Tags: SDK & CLI - **Node.js SDK and CLI:** `npm install mobilevalidate`, or `npx mobilevalidate check +447700900001 --sandbox` with no install. See [SDKs](/docs/sdk) and the [CLI](/docs/cli). - **Python SDK:** `pip install mobilevalidate-sdk` (import `mobilevalidate`), sync and async clients. See [SDKs](/docs/sdk#python). - **MCP server:** `npx -y @mobilevalidate/mcp` runs the same tools as the hosted server over stdio. See [MCP](/docs/mcp). - Job result downloads stream CSV or NDJSON in both SDKs and the CLI (`jobs download`), and job estimates now return the same maximum cost that the job reserves. ## 2026-09-25: Developer experience: try the API without signing up Tags: API, SDK & CLI, Docs - **Live API console** on the home page and in the docs: pick a test value, run a real request and see the response, an explained verdict and the same call in seven languages. - **Sandbox key in the docs**: every example works as pasted with a public sandbox key that only accepts [test values](/docs/test-values). Never billed. - **[Instant test key](/get-test-key)**: get a personal test key in a minute, shown once on screen, without waiting for approval. Live keys still need an approved request. - **Error suggestions**: errors can include `error.suggestion`, a short hint on how to fix the request (for example a missing country code). Unknown endpoints now return `404 not_found` with the closest match. - **[Interactive API reference](/docs/api-reference)** generated from OpenAPI 1.0.0, plus request collections for Postman, Bruno and `.http` files. - **Code examples** in cURL, Node.js, fetch, Python, PHP, Go and Ruby, with syntax highlighting and copy buttons; [recipes](/docs/recipes) for common integrations. - **Python SDK** and an updated Node.js SDK and CLI (retries, typed errors, sandbox mode): coming soon on PyPI and npm. - **[Status page](/status)** with 90-day uptime per component, this **changelog** with an [RSS feed](/changelog/rss.xml), and a [versioning and deprecation policy](/docs/versioning). ## 2026-09-25: Website and developer documentation Tags: Docs, Website - [mobilevalidate.com](/) is public, with live [pricing](/pricing) and a page for every check. - [Developer documentation](/docs): quickstart, authentication, lookups, e-mail checks, bulk jobs, webhooks, errors, limits, SDK, CLI and MCP. - The [OpenAPI specification](/docs/openapi) is published at `/openapi.yaml`, and the error catalog at `/errors.json`. - Every documentation page has a Markdown version (add `.md` to the URL), and `/llms.txt` lists them for AI tools. - The remote MCP endpoint is `https://mcp.mobilevalidate.com/mcp`. ## 2026-09-25: Spam reputation and carrier lookup Tags: API, SDK & CLI, MCP - **Spam reputation** (`number.spam`) for US, Canadian and German numbers: a risk level, a 0–100 score and the reasons behind them. Limited access: available to approved customers on request. MCP tool: `check_spam_reputation`. - **Carrier lookup** (`network.carrier`, beta): line type, current carrier, original carrier and country. The US/CA carrier lookup (`network.carrier_us`) runs in bulk jobs. - **Live network status** (`number.hlr`) is listed as coming soon. ## 2026-09-25: Request-size limits and anti-enumeration Tags: API - A lookup may contain up to **2,000 checks** and a job up to **100,000 checks** (identifiers × checks). Larger requests get `400 invalid_request` with a clear message before anything is processed or charged. - Sequential number ranges and generated e-mail lists are rejected with `suspected_enumeration`. Checking lists of your own customers or sign-ups is unaffected. See [rate limits and abuse protection](/docs/rate-limits-and-abuse) and the [error reference](/docs/errors). ## 2026-09-25: E-mail checks Tags: API, SDK & CLI, MCP - New `emails` field on `POST /v1/lookup` and `POST /v1/jobs`, alongside `numbers`. - **Mailbox validation** (`email.valid`) and **account checks by e-mail** for major mail providers and consumer services. Some run in real time, others in bulk jobs only; `GET /v1/services` shows which. - Result rows carry `kind`, `email` and `email_status`. Bulk CSV uploads accept an `email` column. - Test mode includes [test addresses](/docs/test-mode) on a reserved test domain. - MCP server: new `lookup_emails` tool. See [e-mail checks](/docs/emails). ## 2026-09-25: Full check catalog and multi-check requests Tags: API, SDK & CLI, MCP - **Catalog**: messaging apps (WhatsApp, WhatsApp Business, Telegram, Viber, Signal, iMessage, RCS, LINE, Zalo, Botim, MAX, Messenger), social networks and consumer app accounts. The [services reference](/docs/services) lists every code and alias. - **Multi-check requests**: up to 20 checks per request. Each identifier gets one result per service in a `checks` map, plus a `summary.by_service` count. The original `whatsapp` object is kept for compatibility. - **`GET /v1/services`** returns the catalog your key can use, including each attribute's type (string, boolean or integer). - SDK, CLI and MCP server: `services` / `list_services` and the `checks` option. ## 2026-09-25: API version 1: real-time lookups, bulk jobs, test mode and webhooks Tags: API, SDK & CLI The MobileValidate API is available at `https://api.mobilevalidate.com/v1` for approved customers. - **Real-time lookups** with `POST /v1/lookup`: send up to 100 phone numbers and get one result per number, with `registered`, `status` and `checked_at`. - **Bulk jobs** with `POST /v1/jobs` for lists, with paginated results and CSV or NDJSON downloads. - **Test mode**: `mv_test_…` keys return fixed answers for the [test numbers](/docs/test-mode), are never billed and never reach a real network. - **Webhooks** for finished lookups and jobs, with endpoint verification and Standard Webhooks signatures. See [webhooks](/docs/webhooks). - **Idempotency keys** on every `POST`: a retry with the same key and body within 24 hours replays the first response instead of checking again. - **Fair billing**: inconclusive results (`unknown`, unsupported country, timeout, invalid, duplicate) are never charged. - TypeScript SDK and CLI (`mobilevalidate`). The npm release is coming soon. --- # MobileValidate facts > MobileValidate is a phone-number and e-mail intelligence API: one request checks whether a phone number or e-mail address is registered on messaging apps and online services, and returns carrier, line type and (for approved customers) spam reputation, each as registered / not registered / unknown with a checked_at timestamp. Canonical: https://mobilevalidate.com/facts · JSON: https://mobilevalidate.com/facts.json (schema 1.0: https://mobilevalidate.com/schemas/facts.schema.json) · Last updated: 2026-09-26T06:32:48.834Z ## Company - Brand: MobileValidate — Know before you send. - Operated by: BroadNet Technologies Inc. (BroadNet, https://www.broadnet.me) - Address: 5th floor, Minkara Building, Clemenceau Street, Beirut, Lebanon - Contact: info@broadnet.me · Security: noc@broadnet.me · https://mobilevalidate.com/contact - Independent service, not affiliated with, endorsed or sponsored by the platforms it checks; platform names are used descriptively. ## Billing - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). - Unit: per conclusive check (one identifier × one service), in USD. Cache hits and test keys are not charged; suppressed identifiers are never checked or billed. - Prepaid balance. The maximum possible cost is reserved before checking; conclusive results are settled and everything else (inconclusive, cached, cancelled before submission) is released. ## Limits - 10 requests per second per key (burst 20); caps reset at 00:00 UTC. - 100 identifiers (numbers + e-mails) per lookup, 50,000 per bulk job. - 20 checks per request; identifiers × applicable checks ≤ 2,000 per lookup and 100,000 per job. - Lookup `wait`: default 10 s, max 30 s. Body ≤ 1 MB (4 MB for jobs). Job results page ≤ 1,000 rows. - Per-account daily identifiers cap and, for agent keys, a daily spend cap; current values in GET /v1/limits. - 403 suspected_enumeration for 20+ consecutive numbers, 20+ generated addresses on one domain in a request, or (live keys) 50+ such addresses per account per UTC day. ## Services (40 live: 22 realtime + bulk, 18 bulk only) | Service | Code | Status | Modes | Realtime price | Bulk price | Coverage | |---|---|---|---|---|---|---| | WhatsApp number check | `whatsapp.registered` | live | realtime + bulk | $0.0018 ($1.80 / 1,000) | $0.00012 ($0.12 / 1,000) | worldwide | | WhatsApp Business account check | `whatsapp.business` | live | realtime + bulk | $0.0018 ($1.80 / 1,000) | $0.00015 ($0.15 / 1,000) | worldwide | | Telegram number check | `telegram.registered` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | Viber number check | `viber.registered` | live | realtime + bulk | $0.00015 ($0.15 / 1,000) | $0.00008 ($0.08 / 1,000) | worldwide | | Signal number check | `signal.registered` | live | bulk only | — | $0.0003 ($0.30 / 1,000) | worldwide | | iMessage number check | `imessage.registered` | live | bulk only | — | $0.0003 ($0.30 / 1,000) | worldwide | | RCS capability check | `rcs.registered` | live | bulk only | — | $0.0003 ($0.30 / 1,000) | worldwide | | LINE number check | `line.registered` | live | bulk only | — | $0.0008 ($0.80 / 1,000) | worldwide | | Zalo number check | `zalo.registered` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | Botim number check | `botim.registered` | live | bulk only | — | $0.0008 ($0.80 / 1,000) | worldwide | | MAX number check | `max.registered` | live | bulk only | — | $0.0015 ($1.50 / 1,000) | worldwide | | Facebook Messenger number check | `messenger.registered` | live | bulk only | — | $0.0005 ($0.50 / 1,000) | worldwide | | Facebook number check | `facebook.registered` | live | realtime + bulk | $0.00008 ($0.08 / 1,000) | $0.00005 ($0.05 / 1,000) | worldwide | | Instagram number check | `instagram.registered` | live | realtime + bulk | $0.00008 ($0.08 / 1,000) | $0.00005 ($0.05 / 1,000) | worldwide | | Threads number check | `threads.registered` | live | realtime + bulk | $0.0002 ($0.20 / 1,000) | $0.00015 ($0.15 / 1,000) | worldwide | | X (Twitter) number check | `x.registered` | live | realtime + bulk | $0.0003 ($0.30 / 1,000) | $0.00015 ($0.15 / 1,000) | worldwide | | TikTok number check | `tiktok.registered` | live | bulk only | — | $0.0015 ($1.50 / 1,000) | worldwide | | Snapchat number check | `snapchat.registered` | live | bulk only | — | $0.003 ($3.00 / 1,000) | worldwide | | LinkedIn number check | `linkedin.registered` | live | bulk only | — | $0.0008 ($0.80 / 1,000) | US, IN | | VK number check | `vk.registered` | live | realtime + bulk | $0.0015 ($1.50 / 1,000) | $0.0008 ($0.80 / 1,000) | RU | | Apple number check | `apple.registered` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | Amazon number check | `amazon.registered` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | Microsoft number check | `microsoft.registered` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | Netflix number check | `netflix.registered` | live | realtime + bulk | $0.002 ($2.00 / 1,000) | $0.0015 ($1.50 / 1,000) | worldwide | | Carrier lookup | `network.carrier` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | US/CA carrier lookup | `network.carrier_us` | live | bulk only | — | $0.003 ($3.00 / 1,000) | US, CA | | Spam reputation | `number.spam` | limited access | realtime + bulk | $0.0008 ($0.80 / 1,000) | $0.0004 ($0.40 / 1,000) | US, CA, DE | | Live network status (HLR) | `number.hlr` | coming soon | — | — | — | worldwide | | E-mail verification | `email.valid` | live | realtime + bulk | $0.002 ($2.00 / 1,000) | $0.0015 ($1.50 / 1,000) | worldwide | | Gmail account check by e-mail | `gmail.email` | live | bulk only | — | $0.0008 ($0.80 / 1,000) | worldwide | | Outlook account check by e-mail | `outlook.email` | live | bulk only | — | $0.0003 ($0.30 / 1,000) | worldwide | | Yahoo account check by e-mail | `yahoo.email` | live | bulk only | — | $0.0003 ($0.30 / 1,000) | worldwide | | Yandex account check by e-mail | `yandex.email` | live | bulk only | — | $0.0005 ($0.50 / 1,000) | worldwide | | Mail.ru account check by e-mail | `mailru.email` | live | bulk only | — | $0.00008 ($0.08 / 1,000) | worldwide | | Apple account check by e-mail | `apple.email` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | Amazon account check by e-mail | `amazon.email` | live | realtime + bulk | $0.0005 ($0.50 / 1,000) | $0.0003 ($0.30 / 1,000) | worldwide | | Facebook account check by e-mail | `facebook.email` | live | realtime + bulk | $0.003 ($3.00 / 1,000) | $0.0015 ($1.50 / 1,000) | worldwide | | Instagram account check by e-mail | `instagram.email` | live | realtime + bulk | $0.003 ($3.00 / 1,000) | $0.0015 ($1.50 / 1,000) | worldwide | | Netflix account check by e-mail | `netflix.email` | live | realtime + bulk | $0.002 ($2.00 / 1,000) | $0.0015 ($1.50 / 1,000) | worldwide | | Spotify account check by e-mail | `spotify.email` | live | realtime + bulk | $0.0003 ($0.30 / 1,000) | $0.00015 ($0.15 / 1,000) | worldwide | | LinkedIn account check by e-mail | `linkedin.email` | live | bulk only | — | $0.00005 ($0.05 / 1,000) | worldwide | | X (Twitter) account check by e-mail | `x.email` | live | bulk only | — | $0.00015 ($0.15 / 1,000) | worldwide | ## Policies - Data retention: realtime lookups 7 days; bulk jobs 30 days by default (1 day to 24 months per account). DELETE /v1/jobs/{id} removes a finished job's data at any time. - Checks return registered / not registered / unknown or network and reputation attributes, never names, photos, profiles or locations. Sensitive network identifiers are never returned. - Opt-out: Anyone can object to checks of their phone number or e-mail address; suppressed identifiers are never checked and never billed. https://mobilevalidate.com/opt-out - Acceptable use (https://mobilevalidate.com/legal/acceptable-use): for fraud prevention (sign-up and one-time-passcode screening); deliverability and channel selection for contacts who opted in; list hygiene before transactional or consented messaging; lead verification; call screening with spam-reputation signals. Prohibited: unsolicited messages or calls; enumerating number ranges or generated e-mail lists; scraping, harvesting or reselling results; stalking, harassment, monitoring or locating people; profiling private individuals; eligibility decisions (credit, employment, housing, insurance); circumventing rate limits, caps, anti-enumeration or opt-outs. - Terms: https://mobilevalidate.com/legal/terms · Privacy: https://mobilevalidate.com/legal/privacy · DPA: https://mobilevalidate.com/legal/dpa · Trust: https://mobilevalidate.com/trust ## Test mode - Keys starting `mv_test_` never reach upstream and are never billed. Test numbers: +447700900001, +447700900002, +447700900003, +447700900004, +447700900005, +447700900006. https://mobilevalidate.com/docs/test-mode ## Endpoints - website: https://mobilevalidate.com/ - api_base: https://api.mobilevalidate.com/v1 - openapi: https://mobilevalidate.com/openapi.yaml - docs: https://mobilevalidate.com/docs - docs_markdown: https://mobilevalidate.com/docs.md - llms_txt: https://mobilevalidate.com/llms.txt - llms_full_txt: https://mobilevalidate.com/llms-full.txt - mcp: https://mcp.mobilevalidate.com/mcp - mcp_server_card: https://mcp.mobilevalidate.com/mcp/server-card - api_catalog: https://mobilevalidate.com/.well-known/api-catalog - ai_catalog: https://mobilevalidate.com/.well-known/ai-catalog.json - errors: https://mobilevalidate.com/errors.json - facts_json: https://mobilevalidate.com/facts.json - facts_markdown: https://mobilevalidate.com/facts.md - facts_schema: https://mobilevalidate.com/schemas/facts.schema.json - pricing: https://mobilevalidate.com/pricing - api_status: https://api.mobilevalidate.com/healthz --- # Check if a phone number is registered on WhatsApp > Find out whether a phone number has a WhatsApp account before you pick a channel. Answers yes, no or unknown with a timestamp; unknowns are free. Canonical: https://mobilevalidate.com/services/whatsapp-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The WhatsApp check answers one question: does this phone number have a WhatsApp account right now? You get `registered: true`, `false` or `null` (unknown), with the time we checked. It works for numbers in any country, in real time or in bulk jobs. No message is sent, and no name, photo or profile is returned. ## What does the WhatsApp check tell you? It tells you whether a WhatsApp account is associated with the phone number. WhatsApp ties each account to one phone number and verifies that number with a code at sign-up, so an account is a useful sign that the number was active on a smartphone at some point. - `registered: true`: an account exists for the number. - `registered: false`: we got a conclusive answer and there is no account. - `registered: null`: we could not get a conclusive answer (`status` and `reason` tell you why). You are not charged for it. The answer describes the account, not the person. A number can be reassigned by the carrier, moved to a new phone or left dormant. WhatsApp's help center says accounts are generally deleted after 120 days of inactivity, so a number that changed hands recently can still show the previous owner's account for a while. Always read `checked_at` together with `registered`. ## Who uses it, and why? Most teams use the WhatsApp check to choose a channel before they send a message people expect, such as a one-time passcode, an order update or an appointment reminder. - **OTP and sign-up flows.** If a number has no WhatsApp account, sending a verification code there will fail, so the flow can go straight to SMS or voice. Where WhatsApp is common, this can reduce SMS spend. See [SMS cost reduction](/use-cases/sms-cost-reduction). - **Deliverability.** A number with a WhatsApp account passed WhatsApp's own sign-up verification at some point. That makes it a useful extra signal next to a [carrier lookup](/services/carrier-lookup). - **Lead verification.** A form entry with a WhatsApp account is less likely to be a typo or a made-up number. It is not proof of identity. Usage differs a lot between markets. WhatsApp announced two billion users in February 2020 (WhatsApp blog). It is the default messenger in many countries, while other markets rely on [Telegram](/services/telegram-number-check), [Viber](/services/viber-number-check), [LINE](/services/line-number-check) or [iMessage](/services/imessage-number-check). A multi-check request covers several of them at once. ## What do you get back? Each number gets one result per requested check in `checks`. Because WhatsApp was the first service, WhatsApp results also appear in the older top-level `whatsapp` object, so existing integrations keep working. | Field | Type | Meaning | |---|---|---| | `checks["whatsapp.registered"].registered` | boolean or null | `true` account exists, `false` no account, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…confidence` / `…confidence_score` | enum / 0–1 | How sure the answer is; `null` when not conclusive | | `…checked_at` | timestamp | When the answer was obtained | | `…cached` / `…age_seconds` | boolean / integer | Whether it came from your account's cache, and how old it is | | `…billed` | boolean | Whether this check was charged | | `…reason` | string or null | Why an answer is not conclusive, e.g. `UPSTREAM_TIMEOUT` | | `whatsapp` | object | The same result in the v1 shape (back-compatibility) | The service has no extra attributes. For the business flag, use the [WhatsApp Business check](/services/whatsapp-business-check). ## 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 lookups and bulk jobs have separate per-check prices. Bulk is the cheaper option when you don't need the answer straight away. See [pricing](/pricing) for current rates. Repeat checks of the same number inside the freshness window can come from your account's cache. Cache hits are free and marked `cached: true, billed: false`. Send `max_age: 0` to force a fresh check. A fresh check is billed and counts against your rate limits. `max_cost` puts a ceiling on a request, and the API refuses the request if the most it could cost is higher. ## What are the limits? The WhatsApp check is available in real time (`POST /v1/lookup`, up to 100 numbers per request) and in bulk jobs (`POST /v1/jobs`, up to 50,000 numbers and e-mails per job). It covers numbers from every country. Numbers in national format need `default_country`, so the API can convert them to [E.164](/glossary/e164). - A request can hold up to 20 checks. The total of 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. For numbers, that means 20 or more consecutive numbers in one request, which is refused with `suspected_enumeration`. - Each account has a daily cap on the numbers it can check. `GET /v1/limits` shows the cap and how much of it is left. - Invalid, duplicate and suppressed numbers are reported per row and never checked. Most answers arrive within the default 10-second wait. The rest come back as `pending`, and you can poll for them or receive them by webhook. ## How do I use it responsibly? Check only numbers you have a lawful reason to process, such as your own customers and people who signed up or asked to be contacted. Use the result to pick a channel for messages people expect. Don't use it to start conversations nobody asked for. WhatsApp's business messaging policy requires opt-in before businesses message people. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging, building profiles of individuals and checking ranges of numbers to find out who uses WhatsApp. Anyone whose number was checked can object through the [opt-out form](/opt-out). Numbers on the suppression list are skipped (`number_status: suppressed`) and never charged. ## Example request Test keys (`mv_test_…`) are free and never reach a real network. `+447700900001` always answers "registered". See [test mode](/docs/test-mode) for the other test numbers. ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["whatsapp"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "whatsapp.registered": { "service": "whatsapp.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.489Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "whatsapp": { "service": "whatsapp.registered", "status": "completed", "registered": true, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.489Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null }, "test": true } ``` ## Frequently asked questions ### Does the check send a message to the number or notify its owner? No. The check only answers whether a WhatsApp account is associated with the number. Nothing is sent to the number and the result contains no name, photo or profile. ### Can a number show as registered even though its owner stopped using WhatsApp? Yes, for a while. WhatsApp says in its help center that accounts are generally deleted after 120 days of inactivity, so a recently abandoned or recycled number can still show an account. Use checked_at and a short max_age when freshness matters. ### What is the difference between this check and the WhatsApp Business check? This check answers only whether an account exists. The WhatsApp Business check answers the same question and also reports whether the account is a WhatsApp Business account. If you request both, the API runs only the business check because it answers both. ### Why do some numbers come back as unknown? Unknown means we could not get a conclusive answer, for example because the check timed out. Unknown results have registered set to null and are not charged. ### Can I use the result to start WhatsApp marketing to people who never opted in? No. The acceptable use policy forbids unsolicited messaging, and WhatsApp's own business messaging policy requires opt-in before a business messages someone. Use the check to choose a channel for people who have already agreed to hear from you. ## Service code and modes - Code: `whatsapp.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0018 per check ($1.80 per 1,000) - Bulk (POST /v1/jobs): $0.00012 per check ($0.12 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is a WhatsApp Business account > See whether a phone number has a WhatsApp account and whether it is a WhatsApp Business account, in real time or in bulk. Unknowns are free. Canonical: https://mobilevalidate.com/services/whatsapp-business-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The WhatsApp Business check answers two questions about a phone number: does it have a WhatsApp account, and is that account a WhatsApp Business account? You get `registered` (true, false or unknown) plus a `business` flag, with the time we checked. It works worldwide, in real time and in bulk jobs, and never returns profile details. ## What does the WhatsApp Business check tell you? It reports whether a WhatsApp account is associated with the number and, if so, whether it was set up as a business account. WhatsApp launched the separate WhatsApp Business app for small businesses in January 2018 (WhatsApp blog). Larger companies reach customers through the WhatsApp business platform instead. Either way, a number carries one WhatsApp account at a time, so a business number is not also a personal account. - `registered: true` and `attributes.business: true`: a WhatsApp Business account exists. - `registered: true` and `business: false`: a regular WhatsApp account exists. - `registered: true` and `business: null`: an account exists, but the business flag could not be determined this time. - `registered: false`: conclusive answer, no account. `registered: null`: unknown, not charged. The flag describes how the account is configured. It says nothing about who owns the number or whether the business is genuine. ## Who uses it, and why? The business flag helps where the difference between a consumer and a company matters. - **B2B lead verification.** A sales lead that gives a company mobile number with a business account on WhatsApp is consistent with its claim. A lead claiming to be a company with no account at all may deserve a closer look. See [lead verification](/use-cases/lead-verification). - **Marketplace and merchant onboarding.** Platforms that onboard sellers, drivers or service providers can record whether the contact number is run as a business account, as one input to their review. - **Impersonation and fraud review.** Scammers often pose as a company's support line. Knowing whether a number that contacted your customers is a business account helps a fraud team decide how to triage reports, together with a [spam reputation](/services/spam-reputation) check where it is available. - **Channel planning for consented messages.** Messages from your own business account to a customer's business account behave like any other WhatsApp chat, but many businesses route business contacts to e-mail or account managers instead. ## What do you get back? One result per number in `checks["whatsapp.business"]`. The older top-level `whatsapp` object mirrors it and adds a `business` field, so v1 integrations keep working. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account exists, `false` no account, `null` unknown | | `attributes.business` | boolean | `true` business account, `false` regular account; absent when not determined | | `status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `confidence` / `confidence_score` | enum / 0–1 | How sure the registration answer is | | `checked_at` | timestamp | When the answer was obtained | | `cached` / `age_seconds` / `billed` | boolean / integer / boolean | Cache and billing details for this check | | `reason` | string or null | Why an answer is not conclusive | | `whatsapp.business` | boolean or null | Same flag in the v1 shape; `null` when not determined | If you request `whatsapp` and `whatsapp.business` together, the two collapse into `whatsapp.business`, because it answers both. ## How is it billed? You pay per number, for conclusive answers only. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). A result with a conclusive `registered` value counts as conclusive even when `business` is `null`. The business check has its own real-time and bulk prices, which differ from the plain registration check, so see [pricing](/pricing) for current rates. Repeat checks of the same number within the freshness window can be answered from your account's cache for free (`cached: true`, `billed: false`). Send `max_age: 0` to force a fresh, billed check, and `max_cost` to cap what a request may cost. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers per request) and in bulk jobs (`POST /v1/jobs`, up to 50,000 numbers and e-mails per job) for numbers in every country. - The business flag is filled more reliably in bulk jobs. Real-time answers may return `business: null` next to a conclusive `registered` value. If the flag is essential, run the list as a job. - Up to 20 checks per request; numbers × checks may not exceed 2,000 per lookup or 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected (`suspected_enumeration`). - A daily cap applies per account; `GET /v1/limits` shows what is left today. ## How do I use it responsibly? Check numbers you have a lawful reason to process: leads who contacted you, sellers applying to your platform, numbers reported to your fraud team. Don't use the flag to compile lists of businesses to message cold. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging and range scanning, and WhatsApp's own business messaging rules require opt-in before a business messages someone. People whose numbers were checked can object via the [opt-out form](/opt-out). ## Example request `+447700900006` is the test number for a registered business account. The other test numbers behave as documented in [test mode](/docs/test-mode); for them `business` is `false` whenever the answer is conclusive. ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900006"], "checks": ["whatsapp.business"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900006", "e164": "+447700900006", "country": "GB", "number_status": "valid", "checks": { "whatsapp.business": { "service": "whatsapp.business", "status": "completed", "registered": true, "attributes": { "business": true }, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.519Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "whatsapp": { "service": "whatsapp.business", "status": "completed", "registered": true, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.519Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null, "business": true }, "test": true } ``` ## Frequently asked questions ### What is the difference between a WhatsApp account and a WhatsApp Business account? Both are tied to one phone number. A WhatsApp Business account is created with the separate WhatsApp Business app or through the WhatsApp business platform, and it can show a business profile. This check reports registration and, separately, whether the account is a business account. ### Why is business null when registered is true? The account check was conclusive but the business flag could not be determined for that answer. This happens more often on real-time lookups. The answer is still a conclusive registration result. ### Do I need to request whatsapp and whatsapp.business together? No. The business check already answers whether an account exists. If you request both, the API runs only whatsapp.business and you pay for one check per number. ### Does a business account prove that a number belongs to a real company? No. Anyone can install the WhatsApp Business app, so the flag says how the account was set up, not who owns it. Treat it as one signal next to your own verification steps. ### Is the business name or profile returned? No. The result contains only registered and the business flag. Names, photos, descriptions and other profile details are never returned. ## Service code and modes - Code: `whatsapp.business` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0018 per check ($1.80 per 1,000) - Bulk (POST /v1/jobs): $0.00015 per check ($0.15 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | | attributes.business | boolean | Account is a WhatsApp Business account. | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on Telegram > Find out whether a phone number has a Telegram account, in real time or in bulk. Yes, no or unknown with a timestamp; unknown answers are free. Canonical: https://mobilevalidate.com/services/telegram-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The Telegram check tells you whether a phone number has a Telegram account. You get `registered: true`, `false` or `null` (unknown) with the time of the check, in real time or in a bulk job, for numbers from any country. The check sends nothing to the number and returns no username, name or photo. ## What does the Telegram check tell you? It answers whether a Telegram account is associated with the phone number. Telegram asks for a phone number when someone signs up and confirms it with a code, so every account starts from a number. After that, Telegram is built around usernames: people can chat without ever sharing their number, and the privacy settings let them choose who can see their number and who can find them by it. That design matters for how you read the answer: - `registered: true`: an account is associated with the number and could be found. - `registered: false`: a conclusive answer that no account was found for the number. - `registered: null`: we could not get a conclusive answer. `status` and `reason` explain why, and you are not charged. Because discoverability is under the account holder's control, a `false` on Telegram is a weaker "no" than on a platform where every account can be found by number. Treat it as "not reachable by this number", which is usually what a channel decision needs. ## Who uses it, and why? Telegram's founder announced in March 2025 that the app had passed one billion monthly active users. Its use is uneven: it is a main messenger in parts of Eastern Europe, Central Asia and the Middle East, and a secondary app in many markets where WhatsApp leads. - **Verification codes.** Telegram offers a paid gateway that lets businesses deliver verification codes to users inside Telegram. Teams that use it check registration first, so codes go to Telegram only when the number has an account and the rest go to SMS. - **Channel selection.** Support and notification teams in Telegram-heavy markets check whether a customer who opted in can be reached there. See [channel selection](/use-cases/channel-selection). - **Sign-up screening.** A new sign-up whose number has neither Telegram nor [WhatsApp](/services/whatsapp-number-check) nor [Viber](/services/viber-number-check) is not necessarily fake, but it gives fraud rules a data point. Combine it with a [carrier lookup](/services/carrier-lookup). ## What do you get back? One result per number in `checks["telegram.registered"]`. The service has no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account found, `false` none found, `null` unknown | | `status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `confidence` / `confidence_score` | enum / 0–1 | How sure the answer is; `null` if not conclusive | | `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 the answer is not conclusive, e.g. `UPSTREAM_TIMEOUT` | ## How is it billed? You pay per number checked, 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; bulk is cheaper when the answer can wait. See [pricing](/pricing). A repeat check of the same number within the freshness window can come from your account's cache at no charge (`cached: true`, `billed: false`). `max_age: 0` forces a fresh check, which is billed. `max_cost` stops a request that could cost more than you set. ## What are the limits? Telegram is available in real time (`POST /v1/lookup`, 1–100 numbers, waits up to 30 seconds, default 10) and in bulk jobs (`POST /v1/jobs`, up to 50,000 numbers and e-mails). There are no country restrictions. - Up to 20 checks per request; numbers × checks 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 with `suspected_enumeration`. - A daily number cap applies per account (`GET /v1/limits`). - Unknown answers mostly come from timeouts. Privacy settings can also make an account impossible to find by number. ## How do I use it responsibly? Check the numbers of people who gave them to you and agreed to be contacted, and use the answer to pick a channel they expect. Telegram's reach through public groups and channels makes it attractive for spam, and our [acceptable use policy](/legal/acceptable-use) forbids using checks to find people to message unsolicited or to work out who is on Telegram by scanning number ranges. Respect the fact that many Telegram users hide their number on purpose. People can object to checks through the [opt-out form](/opt-out). ## Example request With a test key, `+447700900001` answers "registered", `+447700900002` "not registered" and `+447700900003` "unknown". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["telegram"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "telegram.registered": { "service": "telegram.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.556Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Telegram users can hide their phone number. Does that affect the check? It can. Telegram lets people limit who can find them by their number. When an account cannot be found through its number, the check may answer not registered or unknown even though the account exists. ### Does the check return the Telegram username? No. The check only answers whether an account is associated with the number. Usernames, names, photos and bios are never returned. ### Is Telegram a good channel for one-time passcodes? In markets where Telegram is common it can be, provided the person agreed to receive codes there. Checking registration first avoids sending a code to a channel the person does not use. ### Can I check Telegram in real time? Yes. The Telegram check works with POST /v1/lookup for up to 100 numbers per request and in bulk jobs for larger lists. ### Is anything sent to the person on Telegram? No. Nothing is sent, and the person is not notified by us. ## Service code and modes - Code: `telegram.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on Viber > Find out whether a phone number has a Viber account before you send a code or notification. Real time or bulk; unknown answers are free. Canonical: https://mobilevalidate.com/services/viber-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The Viber check answers whether a phone number has a Viber account. It returns `registered: true`, `false` or `null` (unknown) with the time of the check. It runs in real time or in bulk jobs, for numbers from any country. Nothing is sent to the number, and no name, photo or online status is returned. ## What does the Viber check tell you? It tells you whether a Viber account is associated with the number. Viber identifies users by their phone number: you install the app on a phone, confirm the number, and your contacts see you under that number. There is no username layer on top, so the number is the account. - `registered: true`: a Viber account exists for the number. - `registered: false`: a conclusive answer that there is no account. - `registered: null`: no conclusive answer; `status` and `reason` explain why. It is not charged. Viber launched in 2010 and has been owned by the Japanese company Rakuten since 2014 (Rakuten press release, February 2014). Accounts can outlive the phone they were created on, and numbers get recycled by carriers, so read `registered` together with `checked_at`, and keep `max_age` short where freshness matters. ## Who uses it, and why? Viber's footprint is regional. In several countries of Eastern Europe, the Balkans, the Middle East and Southeast Asia it is one of the most-used messengers. Elsewhere it plays a smaller part. - **Cheaper delivery of expected messages.** Viber offers paid business messaging with a branded sender, which many companies in Viber-heavy markets use for transactional messages and codes, falling back to SMS when the person has no account. Checking first keeps undeliverable Viber attempts, and the delay they cause, out of the flow. See [SMS cost reduction](/use-cases/sms-cost-reduction). - **Channel order per market.** Teams operating across countries check [WhatsApp](/services/whatsapp-number-check), [Telegram](/services/telegram-number-check) and Viber in one request and choose the channel the customer opted into and actually has. - **Deliverability signal.** A Viber account shows the number passed a phone-based activation at some point. Together with a [carrier lookup](/services/carrier-lookup), this helps separate live mobile numbers from typos and landlines. ## What do you get back? One result per number in `checks["viber.registered"]`; no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account exists, `false` no account, `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 | Cache details | | `billed` | boolean | Whether the check was charged | | `reason` | string or null | Why the answer is not conclusive | ## How is it billed? Per number, only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Real-time and bulk checks have separate prices. See [pricing](/pricing). Within the freshness window a repeat check can be answered from your account's cache, free of charge (`cached: true`, `billed: false`). Use `max_age: 0` for a fresh, billed answer and `max_cost` to cap a request. ## What are the limits? The Viber check works in real time (`POST /v1/lookup`, up to 100 numbers, wait up to 30 seconds) and in bulk jobs (`POST /v1/jobs`, up to 50,000 numbers and e-mails). All countries are accepted. - Up to 20 checks per request, and numbers × checks up to 2,000 per lookup or 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected (`suspected_enumeration`). - Each account has a daily number cap (`GET /v1/limits`). - Numbers written without a country code need `default_country` to be read correctly. ## How do I use it responsibly? Use the check to decide how to reach customers who asked to hear from you, not to find new people to message. Viber's business messaging requires approved senders and opted-in recipients, and our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging and scanning number ranges. Keep a lawful basis for every list you check. People whose numbers were checked can object via the [opt-out form](/opt-out). ## Example request Test keys are free and never reach a real network. `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["viber"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "viber.registered": { "service": "viber.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.589Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### In which countries is the Viber check most useful? The check works for numbers from any country. It is most useful where Viber is a leading messenger, which includes several countries in Eastern Europe, the Balkans, the Middle East and Southeast Asia. ### Does a Viber account mean the number is a mobile number? Usually. Viber is registered on a phone with the number activated by a code, so an account is a good sign the number was used on a smartphone. It is not proof of the current owner. ### Can I use the result to send Viber business messages? Only to people who agreed to receive them. Viber business messaging has its own rules and approval process; our check only tells you whether the number has an account. ### What makes a Viber answer unknown? Mostly a check that did not complete in time. Unknown answers have registered set to null and are never charged. ### Is the Viber name or photo returned? No. Only whether an account is associated with the number. Names, photos and online status are never returned. ## Service code and modes - Code: `viber.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.00015 per check ($0.15 per 1,000) - Bulk (POST /v1/jobs): $0.00008 per check ($0.08 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on Signal > Check in bulk whether phone numbers have a Signal account. Yes, no or unknown per number; you are only charged for conclusive answers. Canonical: https://mobilevalidate.com/services/signal-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The Signal check tells you whether phone numbers in a list have a Signal account. It runs in bulk jobs only and answers `registered: true`, `false` or `null` (unknown) per number, with the time of the check. It covers numbers from every country, sends nothing to anyone and returns no username or profile data. ## What does the Signal check tell you? It answers whether a Signal account can be associated with the number. Signal still requires a phone number to create an account, confirmed by a code. Since early 2024, Signal has also offered usernames, hidden phone numbers by default, and added a setting that lets people choose whether anyone can find them by their number. - `registered: true`: an account is associated with the number and discoverable through it. - `registered: false`: a conclusive answer that no discoverable account exists for the number. - `registered: null`: no conclusive answer; not charged. Because people can switch off discovery by number, a `false` means "not findable through this number" rather than "this person has never used Signal". Signal is run by a non-profit foundation and collects very little data about its users, so there is less to observe than on other platforms. Expect a higher share of `false` and unknown answers than for WhatsApp. ## Who uses it, and why? Signal is not a marketing or customer-service channel. It has no business messaging API. Its value in our catalog is as evidence that a number is in real use. - **Sign-up and OTP fraud screening.** Numbers bought in bulk to open fake accounts are rarely set up on several privacy-focused messengers. A Signal account, along with [WhatsApp](/services/whatsapp-number-check) and [Telegram](/services/telegram-number-check) results, raises confidence that the number belongs to a real, active phone. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **List hygiene.** Batch-checking an older contact list for messenger presence helps flag numbers that seem to have gone dead since collection. - **Research on your own customers.** Product teams sometimes want to know which messengers their opted-in users have, in aggregate, before adding a channel. ## What do you get back? Each row of the job carries `checks["signal.registered"]`. There are no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account found, `false` none found, `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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | Downloads (CSV or NDJSON) add `signal.registered.status`, `signal.registered.registered` and `signal.registered.billed` columns. ## How is it billed? Per number, for conclusive answers only, at the bulk price. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The job reserves the maximum possible cost when it starts and releases what is not used when it finishes. Call `POST /v1/jobs/estimate` first to see the maximum cost for free. See [pricing](/pricing). Numbers checked recently by your account can be answered from its cache at no cost. `max_age: 0` forces fresh, billed checks. ## What are the limits? Signal runs in bulk jobs only. `POST /v1/lookup` refuses it with `403 service_disabled` ("The check 'signal.registered' is available in bulk jobs only (POST /v1/jobs)."). - Up to 50,000 numbers and e-mails per job, 20 checks per request, and 100,000 number × check pairs per job. - Results arrive as the job progresses. Follow `GET /v1/jobs/{id}?wait=30` or a `job.completed` [webhook](/docs/webhooks), then page through results or download them. See [bulk jobs](/docs/bulk-jobs). - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily number cap applies per account. ## How do I use it responsibly? People choose Signal for privacy, and many turn off discovery by number on purpose. Check only numbers you hold for a legitimate reason, such as your own users or applicants, and never to find out whether a specific person uses Signal. Our [acceptable use policy](/legal/acceptable-use) forbids stalking, profiling individuals and scanning number ranges. People can object to checks through the [opt-out form](/opt-out). ## Example request Test jobs finish at once. The first three test numbers answer registered, not registered and unknown. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: signal-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["signal"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Job (excerpt, test mode): ```json { "object": "job", "id": "job_0VWF4kBdVZK0W6fJs7sS", "status": "completed", "livemode": false, "checks": [ "signal.registered" ], "created_at": "2026-09-25T14:25:29.625Z", "completed_at": "2026-09-25T14:25:29.628Z", "progress": { "total": 3, "checks_total": 3, "done": 3, "conclusive": 2, "non_billable": 3 }, "retention_days": 30 } ``` First row from `GET /v1/jobs/{id}/results` (test mode): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "signal.registered": { "service": "signal.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.628Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Why is the Signal check only available in bulk jobs? Signal answers are gathered in batches, so the service runs in POST /v1/jobs rather than POST /v1/lookup. A real-time request for signal is refused with service_disabled and a message pointing to bulk jobs. ### Signal users can turn off discovery by phone number. What happens then? Signal lets people choose that nobody can find them by their number. Such accounts cannot be confirmed through the number, so the check may answer not registered or unknown for them. ### Can I message Signal users from my business after the check? Signal does not offer a business messaging API, so the check is not a way to open a business channel. It is more useful as a sign that a number is in active use on a smartphone. ### Does the check reveal the Signal username? No. Only whether an account can be associated with the number. Usernames, names and profile photos are never returned. ## Service code and modes - Code: `signal.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered for iMessage > Check in bulk whether phone numbers are registered for Apple's iMessage, which indicates use on an Apple device. Unknown answers are free. Canonical: https://mobilevalidate.com/services/imessage-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The iMessage check tells you whether phone numbers are registered for iMessage, Apple's messaging service. Because iMessage exists only on Apple devices, a registered number almost always means an iPhone. The check runs in bulk jobs, returns `registered: true`, `false` or `null` (unknown) per number, and sends nothing to anyone. ## What does the iMessage check tell you? It answers whether the number is registered for iMessage. Apple introduced iMessage in 2011. When someone activates an iPhone with a SIM, iMessage normally registers that phone number, and it can also register e-mail addresses from the person's Apple Account. Messages between registered users go through Apple. Messages to anyone else fall back to SMS, and since iOS 18 in 2024 also to RCS. - `registered: true`: the number is registered for iMessage on an Apple device. - `registered: false`: a conclusive answer that it is not registered. - `registered: null`: no conclusive answer, not charged. This is a device-platform signal, not only a "has an app" signal. Nobody installs iMessage separately and there is no Android version. So `true` is strong evidence of an Apple device and `false` is common for Android users. One caveat: registration can survive a move to Android until the person deregisters the number, which Apple lets people do online. ## Who uses it, and why? - **Choosing the fallback path.** A message to an iPhone user without iMessage-level features lands as SMS or RCS. Knowing the device platform helps teams decide between [RCS](/services/rcs-capability-check), SMS and app push for messages customers opted into. See [channel selection](/use-cases/channel-selection). - **Rich-link and media planning.** Marketing and product teams sometimes segment their opted-in audience by device platform before choosing message formats, for example whether media will render inline. - **Fraud signals.** Numbers from SIM farms and virtual number services are rarely activated on iPhones. A number presented as a personal mobile that shows iMessage registration is consistent with that claim. It is useful next to a [carrier lookup](/services/carrier-lookup) and the [Apple ID check](/services/apple-id-number-check). ## What do you get back? Each job row carries `checks["imessage.registered"]`; there are no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` registered, `false` not registered, `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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | ## How is it billed? Per number, for conclusive answers only, at the bulk price. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Use the free `POST /v1/jobs/estimate` to see the maximum cost first, and set `max_cost` to cap the job. See [pricing](/pricing). Numbers your account checked recently can be answered from its cache without charge. `max_age: 0` forces fresh, billed checks. ## What are the limits? iMessage runs in bulk jobs only. `POST /v1/lookup` answers `403 service_disabled` with "The check 'imessage.registered' is available in bulk jobs only (POST /v1/jobs)." - Up to 50,000 numbers and e-mails per job, up to 20 checks per request, 100,000 number × check pairs per job. - All countries are accepted; registration is most common where iPhones have a large share of the market. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily number cap applies per account. Track progress with `GET /v1/jobs/{id}?wait=30` or a [webhook](/docs/webhooks). ## How do I use it responsibly? Device platform is personal information. Check only numbers of people who gave them to you, and use the result to deliver messages they expect in a format that works. Don't use it to profile or target individuals, and don't treat it as a proxy for income or any other personal trait. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited messaging, profiling and range scanning. People can object to checks via the [opt-out form](/opt-out). ## Example request In test mode the job completes at once. `+447700900001` answers registered, `…002` not registered and `…003` unknown. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: imessage-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["imessage"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Job (excerpt, test mode): ```json { "object": "job", "id": "job_0VWF4kDgF4FJ2IXyZNL8", "status": "completed", "livemode": false, "checks": [ "imessage.registered" ], "created_at": "2026-09-25T14:25:29.753Z", "completed_at": "2026-09-25T14:25:29.756Z", "progress": { "total": 3, "checks_total": 3, "done": 3, "conclusive": 2, "non_billable": 3 }, "retention_days": 30 } ``` First row from `GET /v1/jobs/{id}/results` (test mode): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "imessage.registered": { "service": "imessage.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.756Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does an iMessage registration mean the person has an iPhone? It means the number has been registered for iMessage on an Apple device, which is almost always an iPhone. It does not tell you whether that device is still in use. ### Why might a number that moved to Android still show as registered? iMessage registration can outlast the switch until it is removed. Apple offers a way to deregister a number for exactly this reason. Until then, the number can still appear as registered. ### Can my business start iMessage conversations after the check? No. Apple's business messaging is designed so that customers start the conversation. The check helps you understand your customers' devices; it does not open an outbound channel. ### Why is iMessage bulk only? iMessage answers are gathered in batches, so the check runs in POST /v1/jobs. POST /v1/lookup refuses it with service_disabled. ### Is anything sent to the number? No. No message is sent and the person is not notified by us. ## Service code and modes - Code: `imessage.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number can receive RCS messages > Check in bulk whether phone numbers can receive RCS messages, with the handset platform when reported. Only conclusive answers are charged. Canonical: https://mobilevalidate.com/services/rcs-capability-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The RCS check tells you whether phone numbers can currently receive RCS messages: the richer successor to SMS built into phone messaging apps. It runs in bulk jobs and answers `registered: true`, `false` or `null` (unknown) per number. When the handset platform is reported, it adds `device_os` (`ios`, `android` or `unknown`). Nothing is sent to the number. ## What does the RCS check tell you? It answers whether the number is RCS-capable right now. Unlike app-based messengers, RCS is not an account someone signs up for. It is a feature of the carrier network and the phone's default messaging app, standardised by the GSMA as the Universal Profile. On Android it runs in the messaging app; Apple added RCS to iOS 18 in 2024. It works only where the carrier supports it and the user has not turned it off. - `registered: true`: the number can receive RCS messages. `attributes.device_os` may say whether the handset runs iOS or Android. - `registered: false`: a conclusive answer that the number is not RCS-capable now. Messages to it would be delivered as SMS or MMS. - `registered: null`: no conclusive answer; not charged. Capability changes more often than messenger registrations do: a new phone, a disabled setting or a carrier change can flip it. Keep `max_age` short when you use the answer to route messages. ## Who uses it, and why? - **RCS business messaging rollouts.** Brands adopting RCS for verified, branded messages check which opted-in customers can receive them and send SMS to the rest. See [SMS cost reduction](/use-cases/sms-cost-reduction). - **Channel selection.** RCS can carry rich cards, suggested replies and read receipts for messages people asked for, such as delivery updates or appointment confirmations. See [channel selection](/use-cases/channel-selection). - **Device platform awareness.** `device_os` tells teams whether a customer is on iOS or Android without asking, which helps choose between app push, [iMessage](/services/imessage-number-check)-friendly formats and RCS. - **Deliverability signal.** RCS capability shows a live number on a smartphone with a supporting carrier, which complements a [carrier lookup](/services/carrier-lookup). ## What do you get back? Each job row carries `checks["rcs.registered"]`. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` RCS-capable, `false` not capable, `null` unknown | | `attributes.device_os` | enum `ios`, `android`, `unknown` | Handset platform reported with the capability, when available | | `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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | Downloads include an `rcs.registered.device_os` column. ## How is it billed? Per number, for conclusive answers only, at the bulk price. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The free `POST /v1/jobs/estimate` shows the maximum cost before you start. See [pricing](/pricing). Answers your account received recently can be served from its cache at no charge. Because capability changes, consider a shorter `max_age` than for messenger checks. `max_age: 0` forces fresh, billed checks. ## What are the limits? RCS runs in bulk jobs only. `POST /v1/lookup` refuses it with `403 service_disabled` ("The check 'rcs.registered' is available in bulk jobs only (POST /v1/jobs)."). - Up to 50,000 numbers and e-mails per job, 20 checks per request, 100,000 number × check pairs per job. - All countries are accepted, but `true` answers only occur where carriers support RCS. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily number cap applies per account. Follow the job with `GET /v1/jobs/{id}?wait=30` or a [webhook](/docs/webhooks). ## How do I use it responsibly? Use RCS capability to deliver messages people agreed to receive in a format their phone supports. Carrier and industry rules for RCS business messaging require verified senders and consent, and our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging and range scanning. Don't use `device_os` to infer anything about a person beyond the handset platform. People can object to checks via the [opt-out form](/opt-out). ## Example request In test mode `+447700900001` answers capable, `…002` not capable and `…003` unknown. Test answers do not include `device_os`. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: rcs-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["rcs"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Job (excerpt, test mode): ```json { "object": "job", "id": "job_0VWF4kFfnr2scbsj4bNU", "status": "completed", "livemode": false, "checks": [ "rcs.registered" ], "created_at": "2026-09-25T14:25:29.875Z", "completed_at": "2026-09-25T14:25:29.879Z", "progress": { "total": 3, "checks_total": 3, "done": 3, "conclusive": 2, "non_billable": 3 }, "retention_days": 30 } ``` First row from `GET /v1/jobs/{id}/results` (test mode): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "rcs.registered": { "service": "rcs.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:29.879Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### What is RCS? RCS (Rich Communication Services) is the carrier messaging standard that adds typing indicators, read receipts, high-resolution media and branded business messages to the phone's built-in messaging app. It is defined by the GSMA Universal Profile. ### Do iPhones support RCS? Yes, since iOS 18 in 2024, where the user's carrier supports it. That is why the result can report device_os ios as well as android. ### Why can RCS capability change from one day to the next? Capability depends on the carrier, the handset, the messaging app and whether the user has RCS chats turned on. A SIM moved to another phone, or a setting changed, can switch it on or off. ### Does RCS capability mean I can send RCS business messages? Not by itself. Business messaging over RCS needs a verified sender and carrier approval, and it may only be used for messages the recipient agreed to receive. ### Why is device_os missing from some answers? device_os is included only when it is reported with the capability answer. When it is not, the key is absent or unknown. Test-mode answers do not include it. ## Service code and modes - Code: `rcs.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | | attributes.device_os | enum (ios, android, unknown) | Handset platform reported with the RCS capability. | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on LINE > Check in bulk whether phone numbers have a LINE account, useful where LINE is the main messenger. Yes, no or unknown; unknowns are free. Canonical: https://mobilevalidate.com/services/line-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The LINE check tells you whether phone numbers have a LINE account. LINE is the everyday messenger in Japan, Taiwan and Thailand, so the check matters most for customers there. It runs in bulk jobs, answers `registered: true`, `false` or `null` (unknown) per number, and returns no LINE ID, name or photo. ## What does the LINE check tell you? It answers whether a LINE account is associated with the number. LINE launched in Japan in 2011 and is now run by LY Corporation, which was formed in 2023 by merging LINE with Yahoo Japan's operator. Accounts are usually registered with a phone number, confirmed by a code. Contacts connect by phone number, by LINE ID, by QR code or through friend lists. - `registered: true`: a LINE account is associated with the number. - `registered: false`: a conclusive answer that none is. - `registered: null`: no conclusive answer; not charged. Because LINE also lets people connect through IDs and QR codes, some accounts are not easy to find by number. A `false` means "not reachable through this number". It does not prove the person has no LINE account. ## Who uses it, and why? LINE is more than a messenger in its core markets. It is also where people follow brands, get coupons and receive service notices, and businesses reach their followers through official accounts. - **Channel planning for Japanese, Taiwanese and Thai customers.** Companies expanding into these markets check which opted-in customers are on LINE before investing in an official account, instead of assuming WhatsApp coverage. See [channel selection](/use-cases/channel-selection). - **Customer-contact hygiene.** A regional contact list can be batch-checked to see which numbers still show messenger activity. Numbers without LINE, [WhatsApp](/services/whatsapp-number-check) or [Telegram](/services/telegram-number-check) may need re-verification. - **Sign-up screening in LINE markets.** For a Japanese or Thai mobile number, the absence of a LINE account is unusual enough to feed into fraud scoring. It is not proof of fraud. Combine it with a [carrier lookup](/services/carrier-lookup). ## What do you get back? Each job row carries `checks["line.registered"]`; the service has no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account found, `false` none found, `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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | ## How is it billed? Per number, for conclusive answers only, at the bulk price. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Run `POST /v1/jobs/estimate` for a free maximum-cost preview, and set `max_cost` to cap the job. See [pricing](/pricing). Numbers your account checked recently can be answered from its cache free of charge. `max_age: 0` forces fresh, billed checks. ## What are the limits? LINE runs in bulk jobs only. `POST /v1/lookup` refuses it with `403 service_disabled` ("The check 'line.registered' is available in bulk jobs only (POST /v1/jobs)."). - Up to 50,000 numbers and e-mails per job, 20 checks per request, 100,000 number × check pairs per job. - Numbers from every country are accepted. Japanese numbers written in national format (starting with 0) need `default_country: "JP"`. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily number cap applies per account. Follow progress with `GET /v1/jobs/{id}?wait=30` or a [webhook](/docs/webhooks). ## How do I use it responsibly? Check numbers of customers who gave them to you and agreed to be contacted. In LINE markets, people expect businesses to reach them through official accounts they chose to follow, not through unsolicited messages. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging, building profiles of people and scanning number ranges. Japan and Thailand have their own data protection laws. Make sure you have a lawful basis for each list you check. People can object via the [opt-out form](/opt-out). ## Example request Test jobs complete at once. `+447700900001` answers registered, `…002` not registered and `…003` unknown. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: line-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["line"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Job (excerpt, test mode): ```json { "object": "job", "id": "job_0VWF4kHhV5Pc5QNT3EAJ", "status": "completed", "livemode": false, "checks": [ "line.registered" ], "created_at": "2026-09-25T14:25:30.001Z", "completed_at": "2026-09-25T14:25:30.005Z", "progress": { "total": 3, "checks_total": 3, "done": 3, "conclusive": 2, "non_billable": 3 }, "retention_days": 30 } ``` First row from `GET /v1/jobs/{id}/results` (test mode): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "line.registered": { "service": "line.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.005Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Where is the LINE check most useful? In markets where LINE is the main messenger, above all Japan, Taiwan and Thailand. The check accepts numbers from any country, but outside those markets most answers will be not registered. ### Can a LINE account exist without a phone number? LINE accounts are normally registered with a phone number, but people can also sign in with other login methods, and their number settings can differ. An account that is not linked to the number cannot be found through it. ### Why is LINE bulk only? LINE answers are gathered in batches, so the check runs in POST /v1/jobs. POST /v1/lookup refuses it with service_disabled. ### Does the check return the LINE ID or display name? No. Only whether an account is associated with the number. LINE IDs, display names and profile images are never returned. ## Service code and modes - Code: `line.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0008 per check ($0.80 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on Zalo > Find out whether a phone number has a Zalo account, the leading messenger in Vietnam. Real time or bulk; unknown answers are free. Canonical: https://mobilevalidate.com/services/zalo-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The Zalo check tells you whether a phone number has a Zalo account. Zalo is the most-used messenger in Vietnam, so for Vietnamese customers it is often the check that matters most. It runs in real time and in bulk jobs, answers `registered: true`, `false` or `null` (unknown), and returns no name or avatar. ## What does the Zalo check tell you? It answers whether a Zalo account is associated with the number. Zalo was built in Vietnam by the technology group VNG and launched in 2012. Accounts are tied to a phone number confirmed by a code, and people usually find each other through their phone contacts. - `registered: true`: a Zalo account exists for the number. - `registered: false`: a conclusive answer that there is none. - `registered: null`: no conclusive answer; not charged. Zalo users can limit who finds them by phone number, so a small share of existing accounts may not be confirmable through the number. For Vietnamese mobile numbers, a `true` is the expected answer for most active smartphone users. A `false` on a Vietnamese mobile number is therefore more telling than the same answer on a messenger that is rarely used in that country. ## Who uses it, and why? Zalo matters for any business with customers in Vietnam, where it is used for chats with friends, family, shops and service providers. - **Delivering codes and transactional messages.** Businesses in Vietnam send order confirmations, delivery updates and verification codes to customers through Zalo official accounts. The approach usually costs less than SMS and falls back to SMS where there is no account. Checking registration first avoids failed attempts. See [SMS cost reduction](/use-cases/sms-cost-reduction). - **Market-aware channel choice.** International companies that default to WhatsApp can check [WhatsApp](/services/whatsapp-number-check) and Zalo together for Vietnamese numbers and use the one each opted-in customer actually has. See [channel selection](/use-cases/channel-selection). - **Sign-up screening in Vietnam.** A Vietnamese mobile number without a Zalo account is uncommon enough to feed into a fraud score. It is not a verdict on its own; combine it with a [carrier lookup](/services/carrier-lookup). ## What do you get back? One result per number in `checks["zalo.registered"]`; no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account exists, `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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | ## How is it billed? Per number, for conclusive answers only. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Real-time lookups and bulk jobs are priced separately. See [pricing](/pricing). Repeat checks within the freshness window can be answered from your account's cache at no charge (`cached: true`, `billed: false`). `max_age: 0` forces a fresh, billed check, and `max_cost` caps a request. ## What are the limits? Zalo is available in real time (`POST /v1/lookup`, up to 100 numbers, wait up to 30 seconds) and in bulk jobs (`POST /v1/jobs`, up to 50,000 numbers and e-mails). Numbers from every country are accepted, but the platform is used mainly in Vietnam. - Up to 20 checks per request; numbers × checks up to 2,000 per lookup or 100,000 per job. - Vietnamese numbers in national format (starting with 0) need `default_country: "VN"`. - Requests that look like sequential number ranges or generated e-mail lists are rejected (`suspected_enumeration`). - A daily number cap applies per account (`GET /v1/limits`). ## How do I use it responsibly? Check numbers of customers who gave them to you for the service you provide, and use Zalo only for messages they expect. Vietnam has its own personal data protection rules. Make sure you have a lawful basis for processing each number. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited messaging, profiling individuals and scanning number ranges. People can object to checks via the [opt-out form](/opt-out). ## Example request `+447700900001` answers "registered" with a test key. See [test mode](/docs/test-mode) for the other test numbers. ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["zalo"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "zalo.registered": { "service": "zalo.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.136Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Why check Zalo rather than WhatsApp for Vietnamese numbers? Zalo, not WhatsApp, is the main messenger in Vietnam. For Vietnamese customers, a Zalo check usually tells you more about how to reach them than a WhatsApp check does. ### Can I check Zalo in real time? Yes. The Zalo check works with POST /v1/lookup for up to 100 numbers per request, as well as in bulk jobs. ### Can I send Zalo notifications to anyone who has an account? No. Business notifications on Zalo go through official accounts under Zalo's own rules, and they must be messages the person expects, such as an order update or a code they requested. Our policy forbids unsolicited messaging. ### Do Vietnamese numbers need a country code? Send numbers in international format (+84…), or send the national format with default_country set to VN so the API can convert them to E.164. ### Does the check return the Zalo name or avatar? No. Only whether an account is associated with the number. ## Service code and modes - Code: `zalo.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on Botim > Check in bulk whether phone numbers have a Botim account, the calling and messaging app widely used in the UAE. Unknown answers are free. Canonical: https://mobilevalidate.com/services/botim-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The Botim check tells you whether phone numbers have a Botim account. Botim is a calling and messaging app that is especially common in the United Arab Emirates. The check runs in bulk jobs and answers `registered: true`, `false` or `null` (unknown) per number, with the time of the check. It returns no name, photo or location. ## What does the Botim check tell you? It answers whether a Botim account is associated with the number. Botim accounts are created on a phone and verified by the phone number. Calling is the main use, and messaging and other services sit alongside it. - `registered: true`: a Botim account exists for the number. - `registered: false`: a conclusive answer that there is none. - `registered: null`: no conclusive answer; not charged. Botim's popularity is tied to the UAE. For many years, voice and video calls over several well-known internet apps were restricted there, and Botim became one of the apps residents use for calls. Many people use it together with WhatsApp messaging rather than instead of it. ## Who uses it, and why? - **Lead and contact verification in the Gulf.** For a UAE mobile number, a Botim account alongside [WhatsApp](/services/whatsapp-number-check) is a sign the number is in active use on a smartphone. See [lead verification](/use-cases/lead-verification). - **Deliverability and list hygiene.** Businesses serving UAE residents batch-check older contact lists and flag numbers without messenger activity for re-verification before a consented campaign. - **Understanding reach for consented contact.** Teams deciding how to support UAE customers by voice or chat use aggregate Botim coverage of their opted-in customer base as one input. ## What do you get back? Each job row carries `checks["botim.registered"]`; no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account exists, `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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | ## How is it billed? Per number, for conclusive answers only, at the bulk price. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The free `POST /v1/jobs/estimate` shows the maximum cost before you start. See [pricing](/pricing). Numbers your account checked recently can be answered from its cache at no charge. `max_age: 0` forces fresh, billed checks. ## What are the limits? Botim runs in bulk jobs only. `POST /v1/lookup` answers `403 service_disabled` ("The check 'botim.registered' is available in bulk jobs only (POST /v1/jobs)."). - Up to 50,000 numbers and e-mails per job, 20 checks per request, 100,000 number × check pairs per job. - Numbers from every country are accepted. UAE numbers in national format need `default_country: "AE"`. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily number cap applies per account. Follow the job with `GET /v1/jobs/{id}?wait=30` or a [webhook](/docs/webhooks). ## How do I use it responsibly? Check only numbers you hold for a legitimate reason and a clear purpose. Never use a Botim result to guess someone's country of residence, nationality or community, and never to find people to message. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited messaging, profiling and range scanning. People can object to checks via the [opt-out form](/opt-out). ## Example request Test jobs complete at once. `+447700900001` answers registered, `…002` not registered and `…003` unknown. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: botim-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["botim"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Job (excerpt, test mode): ```json { "object": "job", "id": "job_0VWF4kKQgsOwDE5GVOfI", "status": "completed", "livemode": false, "checks": [ "botim.registered" ], "created_at": "2026-09-25T14:25:30.171Z", "completed_at": "2026-09-25T14:25:30.174Z", "progress": { "total": 3, "checks_total": 3, "done": 3, "conclusive": 2, "non_billable": 3 }, "retention_days": 30 } ``` First row from `GET /v1/jobs/{id}/results` (test mode): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "botim.registered": { "service": "botim.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.174Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### What is Botim? Botim is a messaging and internet calling app that is widely used in the United Arab Emirates, including by people who call family and friends abroad. ### Why is Botim bulk only? Botim answers are gathered in batches, so the check runs in POST /v1/jobs. POST /v1/lookup refuses it with service_disabled. ### Does a Botim account tell me where the person lives? No. The check only answers whether an account is associated with the number. It returns no location, name or profile, and it must not be used to infer where someone lives. ### Are UAE numbers required? No. Numbers from any country are accepted. Many Botim users in the UAE register with a UAE number, but not all do. ## Service code and modes - Code: `botim.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0008 per check ($0.80 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on MAX > Check in bulk whether phone numbers have an account on MAX, a newer Russian messenger. Yes, no or unknown; unknowns are free. Canonical: https://mobilevalidate.com/services/max-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The MAX check tells you whether phone numbers have an account on MAX, a newer Russian messenger. It runs in bulk jobs, answers `registered: true`, `false` or `null` (unknown) per number, and returns nothing beyond that: no name, photo or profile. It is most relevant for Russian mobile numbers. ## What does the MAX check tell you? It answers whether a MAX account is associated with the number. MAX is a newer app, developed by the company behind the social network [VK](/services/vk-number-check). It brings chats, calls and other everyday services into one app, and accounts are registered with a phone number confirmed by a code. - `registered: true`: a MAX account exists for the number. - `registered: false`: a conclusive answer that there is none. - `registered: null`: no conclusive answer; not charged. Because the app is young and its user base is growing, answers for the same number can change faster than on long-established messengers. Read `checked_at` with every answer and keep `max_age` short if you rely on the result. ## Who uses it, and why? - **Channel coverage for Russian customers.** Messenger use in Russia is spread across several apps, including [Telegram](/services/telegram-number-check), [WhatsApp](/services/whatsapp-number-check) and MAX. Businesses serving Russian customers check all three in one job to see which channel each opted-in customer can use. See [channel selection](/use-cases/channel-selection). - **Contact-list hygiene.** For lists of Russian numbers collected with consent, messenger presence is one way to tell live mobile numbers from stale ones. - **Tracking adoption in your own base.** Product teams may want aggregate figures on how many of their existing customers have moved to a new messenger before they support it. ## What do you get back? Each job row carries `checks["max.registered"]`; the service has no extra attributes. | Field | Type | Meaning | |---|---|---| | `registered` | boolean or null | `true` account exists, `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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | ## How is it billed? Per number, for conclusive answers only, at the bulk price. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The free `POST /v1/jobs/estimate` shows the maximum cost first. See [pricing](/pricing). Numbers your account checked recently can be answered from its cache at no charge. `max_age: 0` forces fresh, billed checks. ## What are the limits? MAX runs in bulk jobs only. `POST /v1/lookup` answers `403 service_disabled` ("The check 'max.registered' is available in bulk jobs only (POST /v1/jobs)."). - Up to 50,000 numbers and e-mails per job, 20 checks per request, 100,000 number × check pairs per job. - Numbers from every country are accepted. Russian numbers in national format (8 9xx …) need `default_country: "RU"`. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily number cap applies per account. Follow the job with `GET /v1/jobs/{id}?wait=30` or a [webhook](/docs/webhooks). ## How do I use it responsibly? Check numbers of customers who gave them to you, for messages they expect. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited messaging, profiling individuals, and checking ranges of numbers to find out who uses a particular app. Russian data protection law has its own requirements for processing personal data of people in Russia, so make sure your processing is lawful. People can object to checks via the [opt-out form](/opt-out). ## Example request Test jobs complete at once. `+447700900001` answers registered, `…002` not registered and `…003` unknown. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: max-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["max"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Job (excerpt, test mode): ```json { "object": "job", "id": "job_0VWF4kMMCloqoEvo1IlO", "status": "completed", "livemode": false, "checks": [ "max.registered" ], "created_at": "2026-09-25T14:25:30.291Z", "completed_at": "2026-09-25T14:25:30.295Z", "progress": { "total": 3, "checks_total": 3, "done": 3, "conclusive": 2, "non_billable": 3 }, "retention_days": 30 } ``` First row from `GET /v1/jobs/{id}/results` (test mode): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "max.registered": { "service": "max.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.295Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### What is MAX? MAX is a newer Russian messenger that combines chats, calls and access to other services. It is registered with a phone number. ### Which numbers is the MAX check useful for? Mainly Russian numbers, because that is where the app is used. The check accepts numbers from any country, but outside Russia most answers will be not registered. ### Why is MAX bulk only? MAX answers are gathered in batches, so the check runs in POST /v1/jobs. POST /v1/lookup refuses it with service_disabled. ### Does the check return names or profile information? No. Only whether an account is associated with the number. ## Service code and modes - Code: `max.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0015 per check ($1.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is registered on Facebook Messenger > Check in bulk whether a phone number is associated with a Facebook Messenger account. Yes, no or unknown per number; unknown answers are free. Canonical: https://mobilevalidate.com/services/facebook-messenger-number-check · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* The Messenger check tells you whether phone numbers are associated with a Facebook Messenger account. It runs in bulk jobs and answers `registered: true`, `false` or `null` (unknown) per number, with the time of the check. It works for numbers from any country and never returns names, photos or profile links. ## What does the Messenger check tell you? It answers whether a Messenger account can be associated with the phone number. Messenger is Meta's chat app, built on Facebook accounts. Unlike WhatsApp, where the phone number is the account, a Messenger user is identified by the account itself. A phone number is linked only if the person added it, for example for login, account recovery or finding contacts. - `registered: true`: a Messenger account is associated with the number. - `registered: false`: a conclusive answer that no account can be associated with it. - `registered: null`: no conclusive answer; not charged. Because the number is optional and discoverability can be limited, `false` is common even for active users. Read the answer as "a Messenger account is linked to this number", not as a complete picture of the person's Meta usage. ## Who uses it, and why? - **Account security and recovery flows.** Services that let users recover access by phone sometimes check whether a number is also linked to major consumer accounts, as one sign that it is a long-held personal number rather than a throwaway. See [account security](/use-cases/account-security). - **Fraud screening.** A number with no link to any mainstream consumer account, including Messenger, [Facebook](/services/facebook-number-check) or [Instagram](/services/instagram-number-check), is a weak signal of a newly acquired or virtual number. Combine it with a [carrier lookup](/services/carrier-lookup). - **Support-channel planning.** Businesses that answer customer questions on Messenger can see, in aggregate, how many of their customers are linked to the app before staffing that channel. Messenger is not an outbound channel you can open with a phone number. On Meta's platform, businesses reply to people who contacted them first. ## What do you get back? Each job row carries `checks["messenger.registered"]`; 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` / `billed` | boolean / integer / boolean | Cache and billing details | | `reason` | string or null | Why the answer is not conclusive | ## How is it billed? Per number, for conclusive answers only, at the bulk price. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Use the free `POST /v1/jobs/estimate` to see the maximum cost first, and `max_cost` to cap the job. See [pricing](/pricing). Numbers your account checked recently can be answered from its cache at no charge. `max_age: 0` forces fresh, billed checks. ## What are the limits? Messenger runs in bulk jobs only. `POST /v1/lookup` answers `403 service_disabled` ("The check 'messenger.registered' is available in bulk jobs only (POST /v1/jobs)."). - Up to 50,000 numbers and e-mails per job, 20 checks per request, 100,000 number × check pairs per job. - Numbers from every country are accepted. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily number cap applies per account. Follow the job with `GET /v1/jobs/{id}?wait=30` or a [webhook](/docs/webhooks). ## How do I use it responsibly? Check only numbers you hold for a legitimate purpose, such as your own users or people reported to your fraud team. Never use the check to find a person's social media presence or to look someone up. It returns no profile, and our [acceptable use policy](/legal/acceptable-use) forbids stalking, profiling, unsolicited messaging and range scanning. People can object to checks via the [opt-out form](/opt-out). ## Example request Test jobs complete at once. `+447700900001` answers registered, `…002` not registered and `…003` unknown. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: messenger-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["messenger"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Job (excerpt, test mode): ```json { "object": "job", "id": "job_0VWF4kOJtJr7IyMjsM9T", "status": "completed", "livemode": false, "checks": [ "messenger.registered" ], "created_at": "2026-09-25T14:25:30.412Z", "completed_at": "2026-09-25T14:25:30.416Z", "progress": { "total": 3, "checks_total": 3, "done": 3, "conclusive": 2, "non_billable": 3 }, "retention_days": 30 } ``` First row from `GET /v1/jobs/{id}/results` (test mode): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "messenger.registered": { "service": "messenger.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.416Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### How is this different from the Facebook check? Both relate to Meta accounts, but they answer separately: the Facebook check asks whether a Facebook account is associated with the number, this one whether a Messenger account is. The Facebook check also works in real time; Messenger is bulk only. ### Can my business message a person on Messenger after a positive result? Not on that basis. Businesses can reply on Messenger when a person contacts their Page, within the time windows and message tags that Meta's platform rules allow. A registration result does not open a conversation. ### Why do many people not show up by phone number? A phone number is optional on the underlying account, and people control who can find them by it. Accounts without a findable number answer not registered or unknown. ### Why is Messenger bulk only? Messenger answers are gathered in batches, so the check runs in POST /v1/jobs. POST /v1/lookup refuses it with service_disabled. ### Are names or profile photos returned? No. Only whether an account is associated with the number. ## Service code and modes - Code: `messenger.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0005 per check ($0.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has a Facebook account > See whether a Facebook account is associated with a phone number, in real time, for sign-up and fraud checks. Yes, no or unknown; unknowns are free. Canonical: https://mobilevalidate.com/services/facebook-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The Facebook check indicates whether a Facebook account is associated with a phone number. You get `registered: true`, `false` or `null` (unknown), plus the time of the check, in real time or in bulk jobs, for numbers in any country. Nothing is sent to the number, and no name, photo or profile is returned. ## What does the Facebook check tell you? It tells you whether the number is linked to a Facebook account. On Facebook the phone number is optional. People can sign up with a mobile number or an e-mail address, and they can add a number later to log in, recover a locked account or receive two-factor codes. A `true` answer therefore means the number was attached to an account at some point and is still attached. - `registered: true`: a Facebook account is associated with the number. - `registered: false`: a conclusive "no". This happens often for genuine people who registered with e-mail only. - `registered: null`: no conclusive answer. `status` and `reason` explain why, and you are not charged. Because the number is optional, `false` is weak evidence on its own. A `true` answer carries more weight: it means the number took part in Facebook's own verification when it was added. ## Who uses it, and why? Teams mostly use the Facebook check as one fraud-prevention signal among several. They rarely use it as a channel decision. - **Sign-up and OTP protection.** Throwaway numbers used for fake sign-ups tend to have no history on large consumer platforms. A number with an associated Facebook account is less likely to be freshly generated. Combine the check with a [carrier lookup](/services/carrier-lookup) to see the line type. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Lead verification.** For consumer lead forms, an associated account suggests the number is a real, used mobile number rather than a typo. - **Account-security reviews.** Changes to a recovery phone number are a common step in account takeover. Checking the new number's history is one input to a manual review. Meta also runs Instagram, Threads and Messenger. People often link these accounts through Meta's Accounts Center, but each service has its own check. Request [Instagram](/services/instagram-number-check), [Threads](/services/threads-number-check) or [Messenger](/services/facebook-messenger-number-check) in the same request. ## What do you get back? Each number gets one result under `checks["facebook.registered"]`. The service has 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; `null` when not conclusive | | `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, e.g. `UPSTREAM_TIMEOUT` | ## 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 lookups and bulk jobs are priced separately. See [pricing](/pricing) for current rates. Repeat checks of the same number inside the freshness window can come from your account's cache. Cache hits are free and marked `cached: true, billed: false`. Send `max_age: 0` to force a fresh check, which is billed. Use `max_cost` to cap what a request may cost. ## What are the limits? The Facebook check runs in real time (`POST /v1/lookup`, up to 100 numbers per request) and in bulk jobs (`POST /v1/jobs`, up to 50,000 numbers and e-mails per job), for numbers from any country. - A request can hold up to 20 checks. The total of 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`). - Each account has a daily cap on the numbers it can check. `GET /v1/limits` shows what is left. - Invalid, duplicate and suppressed numbers are reported per row and not checked. An answer can be unknown when the check does not complete in time. Answers that take longer than your `wait` come back as `pending`, and you can poll for them or receive them by webhook. ## How do I use it responsibly? Check only numbers you have a lawful reason to process, such as sign-ups, customers and consented leads. Treat the result as a risk signal, not a verdict on a person. Never use it to decide eligibility for credit, jobs, housing or insurance. The [acceptable use policy](/legal/acceptable-use) forbids profiling individuals, working through number ranges to see who has an account, and unsolicited messaging. Anyone whose number was checked can object through the [opt-out form](/opt-out). Suppressed numbers are skipped and never charged. ## Example request Test keys (`mv_test_…`) are free and never reach a real network. `+447700900001` always answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["facebook"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "facebook.registered": { "service": "facebook.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.545Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the result include the person's name or profile? No. The check only indicates whether a Facebook account is associated with the number. No name, photo, profile link or activity is returned. ### Is a Facebook account on a number proof of who the person is? No. It is a signal that the number has been used to create or secure a Facebook account at some point. It is not identity verification and must not be used for eligibility decisions. ### Why can a real, active person come back as not registered? Facebook lets people sign up with an e-mail address instead of a phone number, and adding a number later is optional. Many genuine users simply never attached their number to the account. ### Is the Messenger check the same as this one? They are separate services. The Messenger check is available in bulk jobs only, while the Facebook check also works in real time. Request both in one job if you need both answers. ### Is the check available for every country? Yes, numbers from any country can be checked. National-format numbers need default_country so they can be converted to E.164. ## Service code and modes - Code: `facebook.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.00008 per check ($0.08 per 1,000) - Bulk (POST /v1/jobs): $0.00005 per check ($0.05 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has an Instagram account > Check in real time whether an Instagram account is associated with a phone number. Useful for sign-up and lead checks. Yes, no or unknown. Canonical: https://mobilevalidate.com/services/instagram-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The Instagram check indicates whether an Instagram account is associated with a phone number. It answers `registered: true`, `false` or `null` (unknown) with the time of the check. It works in real time and in bulk jobs, for numbers from any country. It never returns usernames, names, photos or any profile content. ## What does the Instagram check tell you? It tells you whether the number is linked to an Instagram account. Instagram lets people sign up with either a mobile number or an e-mail address. A number can also be added later for login, account recovery or two-factor codes. A `true` answer means an account is associated with the number today. - `registered: true`: an Instagram account is associated with the number. - `registered: false`: a conclusive "no". This is common for people who use Instagram with an e-mail address only. - `registered: null`: no conclusive answer (`status`, `reason`). You are not charged. Instagram is one of the few services here with both a phone check and an [e-mail check](/services/instagram-email-check). If your sign-up form collects both, you can check each identifier with the matching service in a single request. ## Who uses it, and why? The Instagram check is mostly a fraud-prevention and lead-quality signal for consumer businesses. - **Sign-up protection.** Accounts on large consumer apps build up over years. A number with an associated Instagram account is less likely to have been bought minutes ago for a single sign-up. Pair the check with a [carrier lookup](/services/carrier-lookup) to see the line type. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Lead verification.** For consumer brands whose customers are active on Instagram, an associated account supports the case that a submitted number is real and in use. - **Account-security reviews.** When a customer changes a recovery number, a number with no footprint on any consumer platform can be flagged for a second look. Instagram shares an owner with Facebook, Threads and Messenger, and people can link these accounts in Meta's Accounts Center. The Threads link is especially close: at its July 2023 launch, Threads profiles were created from existing Instagram accounts. Request [Threads](/services/threads-number-check) in the same call when you need both answers. ## What do you get back? Each number gets one result under `checks["instagram.registered"]`. The service has 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; `null` when not conclusive | | `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 have separate prices. See [pricing](/pricing). Repeat checks inside the freshness window can come from your account's cache for free (`cached: true, billed: false`). `max_age: 0` forces a fresh, billed check. `max_cost` caps the most a request may cost. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails per request) and in bulk jobs (`POST /v1/jobs`, up to 50,000 per job). It covers every country. - A request can hold up to 20 checks. 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`). - Your account's daily cap and what is left of it are shown by `GET /v1/limits`. - Invalid, duplicate and suppressed numbers are reported per row and never checked. Timeouts return `unknown` with `reason: UPSTREAM_TIMEOUT`. Slow answers return `pending` first. ## How do I use it responsibly? Only check numbers you have a lawful reason to process, and use the answer to protect your service or clean data you already hold. Don't use it to build profiles, research individuals or test lists of numbers to see who uses Instagram. Our [acceptable use policy](/legal/acceptable-use) forbids all of these, as well as unsolicited messaging. People can object at [/opt-out](/opt-out), and suppressed numbers are skipped without charge. ## Example request With a test key, `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["instagram"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "instagram.registered": { "service": "instagram.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.579Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Do I get the Instagram username or profile? No. The check only indicates whether an Instagram account is associated with the number. Usernames, names, photos, followers and posts are never returned. ### Why do Instagram and Threads answers often match? Threads launched in July 2023 with sign-up through an existing Instagram account, so many numbers linked to Instagram also sit behind a Threads profile. They are still separate checks and can differ. ### Can I check an e-mail address instead of a number? Yes. Use the separate Instagram-by-e-mail check (instagram.email) with the emails field. It answers the same yes, no or unknown question for an address. ### Does a missing Instagram account mean the number is fake? No. Many people never use Instagram, and many who do signed up with an e-mail address. Treat false as neutral unless other signals point the same way. ### Is the check free in test mode? Yes. Test keys never reach a real network and are never billed. +447700900001 answers registered, +447700900002 not registered and +447700900003 unknown. ## Service code and modes - Code: `instagram.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.00008 per check ($0.08 per 1,000) - Bulk (POST /v1/jobs): $0.00005 per check ($0.05 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has a Threads account > Check in real time whether a Threads account is associated with a phone number. Yes, no or unknown with a timestamp; unknowns are never charged. Canonical: https://mobilevalidate.com/services/threads-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The Threads check indicates whether a Threads account is associated with a phone number. It answers `registered: true`, `false` or `null` (unknown) with the time of the check. It works in real time and in bulk jobs, for numbers from any country. No handle, name, photo or post is ever returned. ## What does the Threads check tell you? It tells you whether the number is linked to a Threads account. Threads is Meta's text-based social app. It launched on 5 July 2023 (Meta announcement), and sign-up went through an existing Instagram account. For most people, the Threads account therefore shares its login details, including any phone number, with their Instagram account. - `registered: true`: a Threads account is associated with the number. - `registered: false`: a conclusive "no". - `registered: null`: no conclusive answer (see `status` and `reason`). You are not charged. Because of this Instagram link, Threads answers are strongly correlated with the [Instagram check](/services/instagram-number-check). They are not identical. Someone can have Instagram without ever opening Threads, and Threads rules on how accounts are created and linked have changed since launch. ## Who uses it, and why? Threads is a younger network than Facebook or Instagram. Teams use the check mainly as an extra signal next to them. - **Sign-up protection.** An account on a new app shows recent activity by the person behind the number, which a number bought yesterday for one sign-up usually won't have. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Consumer lead checks.** Brands whose audience is active on Meta apps can add Threads to a multi-check with Instagram and Facebook to see how much history a number has. - **Data hygiene.** Along with other checks, it helps separate live, used numbers from dead entries in older lists you are allowed to process. Threads also differs from other Meta apps in one technical way: Meta has started connecting it to the fediverse through the ActivityPub protocol. That affects how posts are shared. It does not affect what this check returns, which is only whether an account exists. ## What do you get back? Each number gets one result under `checks["threads.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; `null` when not conclusive | | `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](/pricing). Repeat checks inside the freshness window are served free from your account's cache (`cached: true, billed: false`). Use `max_age: 0` to force a fresh check, which is billed. ## What are the limits? The Threads check runs in real time (`POST /v1/lookup`, up to 100 identifiers) and in bulk jobs (`POST /v1/jobs`, up to 50,000 per job). It accepts numbers from every country. - A request can hold up to 20 checks. 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. - 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 only to protect your service or to check data you have a lawful reason to hold. Don't research people, build profiles or run through lists of numbers to see who uses Threads. The [acceptable use policy](/legal/acceptable-use) forbids these uses and unsolicited messaging. People can object through the [opt-out form](/opt-out). ## Example request With a test key, `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["threads"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "threads.registered": { "service": "threads.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.627Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### How is Threads related to Instagram? Threads launched in July 2023 as a Meta app where people signed up through their Instagram account. Because of that link, Threads and Instagram answers for a number often match, but they are separate checks and can differ. ### Does the check return the Threads profile or posts? No. It only indicates whether a Threads account is associated with the number. Handles, names, photos and posts are never returned. ### Should I request Threads if I already check Instagram? Only if the Threads answer changes your decision. For most sign-up and lead checks the Instagram answer is enough. Threads adds a second signal on the same account family. ### Is Threads available in every country? Threads launched outside the EU first and reached EU users in December 2023. Our check accepts numbers from any country, but how common Threads accounts are still varies by market. ### What happens if the check does not answer in time? The result is unknown, with registered set to null and a reason such as UPSTREAM_TIMEOUT. Unknown results are not charged. ## Service code and modes - Code: `threads.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0002 per check ($0.20 per 1,000) - Bulk (POST /v1/jobs): $0.00015 per check ($0.15 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has an X (Twitter) account > Check in real time whether an X (formerly Twitter) account is associated with a phone number. Yes, no or unknown; unknown results are free. Canonical: https://mobilevalidate.com/services/x-twitter-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The X check indicates whether an account on X, the platform formerly called Twitter, is associated with a phone number. It answers `registered: true`, `false` or `null` (unknown) with the time of the check. It runs in real time and in bulk jobs, for numbers from any country, and returns no handle, name, photo or post. ## What does the X check tell you? It tells you whether the phone number is linked to an X account. On X the phone number is optional. People can register with an e-mail address and add a number later to sign in, recover the account or pass a security check. A `true` answer means an account is associated with the number now. - `registered: true`: an X account is associated with the number. - `registered: false`: a conclusive "no". - `registered: null`: no conclusive answer (`status`, `reason`). You are not charged. Two platform changes affect how you should read the answer. Twitter was renamed X in July 2023. Aliases `x` and `twitter` therefore both work, and older integrations that send `twitter` keep running. And since 20 March 2023, text-message two-factor authentication has been available only to paying subscribers (X/Twitter announcement, February 2023). Fewer people have a reason to attach a number than before, so a `false` answer is common and fairly neutral. ## Who uses it, and why? Teams use the X check as a supporting signal for account security and sign-up quality, usually in a multi-check with other platforms. - **Account-security reviews.** When a user changes the phone number on their account with you, a number with some history on large platforms is a mildly reassuring sign. A number with no footprint anywhere may deserve a second look. See [account security](/use-cases/account-security). - **Sign-up protection.** Together with [Instagram](/services/instagram-number-check), [Facebook](/services/facebook-number-check) and a [carrier lookup](/services/carrier-lookup), it helps separate numbers people actually use from numbers set up for a single sign-up. - **Data hygiene.** Along with other checks, it helps you judge whether old contact numbers you are allowed to process are still in use. If you collect e-mail addresses rather than numbers, the [X e-mail check](/services/x-twitter-email-check) answers the same question for an address. It runs in bulk jobs only. ## What do you get back? Each number gets one result under `checks["x.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; `null` when not conclusive | | `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](/pricing). Cache hits inside the freshness window are free, and `max_age: 0` forces a fresh, billed check. ## What are the limits? The X check runs in real time (`POST /v1/lookup`, up to 100 identifiers) and in bulk jobs (`POST /v1/jobs`, up to 50,000 per job), for numbers from every country. - A request can hold up to 20 checks. 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? X is widely used under pseudonyms, and people have good reasons for that. Never use this check to link a number to a pseudonymous account or to find out who is behind one. We don't support that: there are no reverse lookups, and the answer is only yes, no or unknown. Check only numbers you have a lawful reason to process. The [acceptable use policy](/legal/acceptable-use) forbids stalking, harassment, profiling and unsolicited messaging. Anyone can object at [/opt-out](/opt-out). ## Example request With a test key, `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["x"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "x.registered": { "service": "x.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.665Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Should I use the code x or twitter? Either one. Both aliases map to the service code x.registered. Results always use the full code, so they appear under checks["x.registered"]. ### Why do many X accounts have no phone number? X accepts sign-up with an e-mail address, and adding a phone number is optional. Since March 2023, text-message two-factor authentication has been limited to paying subscribers, so fewer accounts need a number for 2FA. ### Does the check reveal the X handle? No. It only indicates whether an X account is associated with the number. Handles, names, photos and posts are never returned. ### Can I check e-mail addresses for X accounts? Yes, with the separate x.email service. It runs in bulk jobs only (POST /v1/jobs), while this phone check also works in real time. ### Can I use this to find out who is behind an anonymous account? No. There are no reverse lookups: we never map a number to an account or an account to a person. The acceptable use policy forbids de-anonymisation, stalking and harassment. ## Service code and modes - Code: `x.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0003 per check ($0.30 per 1,000) - Bulk (POST /v1/jobs): $0.00015 per check ($0.15 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has a TikTok account > Check in bulk whether a TikTok account is associated with phone numbers you already hold. Yes, no or unknown per number; unknowns are free. Canonical: https://mobilevalidate.com/services/tiktok-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The TikTok check indicates whether a TikTok account is associated with a phone number. It runs in bulk jobs only and answers `registered: true`, `false` or `null` (unknown) for each number, with the time of the check. No username, name, photo or video is ever returned. ## What does the TikTok check tell you? It tells you whether the number is linked to a TikTok account. TikTok offers sign-up with a phone number and a one-time code, with an e-mail address, or through other sign-in providers. A number is therefore attached to many accounts, but not all. - `registered: true`: a TikTok account is associated with the number. - `registered: false`: a conclusive "no", which includes people who signed up another way. - `registered: null`: no conclusive answer. You are not charged. ## Who uses it, and why? Consumer businesses whose customers skew young use the TikTok check alongside [Instagram](/services/instagram-number-check) and [Snapchat](/services/snapchat-number-check) to judge whether numbers in a list they hold are real and used. - **Lead-list hygiene.** Checking consented leads in bulk before a campaign shows which numbers have any footprint on large consumer apps. See [lead verification](/use-cases/lead-verification). - **Sign-up review queues.** Batches of new sign-ups can be checked overnight to flag numbers with no platform history for review. Because it runs as a batch, this check is not built for decisions that must happen during a live sign-up. For those, use real-time services such as [Instagram](/services/instagram-number-check) or [Facebook](/services/facebook-number-check). ## What do you get back? Each number gets one result under `checks["tiktok.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` / `billed` | boolean | Served from cache; charged or not | | `reason` | string or null | Why an answer is not conclusive | Job downloads (CSV or NDJSON) add `tiktok.registered.status`, `tiktok.registered.registered` and `tiktok.registered.billed` columns. ## How is it billed? You pay the bulk price per number, and only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). `POST /v1/jobs/estimate` is free and shows the maximum cost before you start. See [pricing](/pricing). ## What are the limits? - Bulk only: `POST /v1/jobs`, up to 50,000 numbers and e-mails per job, and at most 100,000 numbers × checks. - Numbers from any country. National formats need `default_country`. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily cap per account applies (`GET /v1/limits`). Job data is kept for 30 days by default. ## How do I use it responsibly? Only check lists you have a lawful basis to process, such as your own customers or consented leads. Never use the answers to contact people who did not ask to hear from you, or to work out who uses TikTok. The [acceptable use policy](/legal/acceptable-use) forbids both. People can object at [/opt-out](/opt-out). ## Example request Create a job with test numbers, then read its results. Test keys are free. See [test mode](/docs/test-mode) and [bulk jobs](/docs/bulk-jobs). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: tiktok-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["tiktok"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Result row (excerpt, test mode: the first item of `data` from `GET /v1/jobs/{id}/results`): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "tiktok.registered": { "service": "tiktok.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.701Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Can I check TikTok in real time? Not at the moment. The TikTok check runs in bulk jobs only (POST /v1/jobs). A request to POST /v1/lookup returns 403 service_disabled with a message pointing to bulk jobs. ### How long does a TikTok bulk job take? It depends on the job's size and current load. GET /v1/jobs/{id} shows progress and an estimate, and you can long-poll it with wait=30 or receive a job.completed webhook. ### Does the check return the TikTok username or videos? No. It only indicates whether a TikTok account is associated with the number. Usernames, names, photos and content are never returned. ### Why would a real TikTok user come back as not registered? TikTok also accepts sign-up with e-mail or through other sign-in providers, so not every account has a phone number attached. ## Service code and modes - Code: `tiktok.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0015 per check ($1.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has a Snapchat account > Check in bulk whether a Snapchat account is associated with phone numbers you already hold. Yes, no or unknown per number; unknowns are free. Canonical: https://mobilevalidate.com/services/snapchat-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The Snapchat check indicates whether a Snapchat account is associated with a phone number. It runs in bulk jobs only and answers `registered: true`, `false` or `null` (unknown) per number, with the time of the check. It never returns usernames, names, avatars or locations. ## What does the Snapchat check tell you? It tells you whether the phone number is linked to a Snapchat account. Snapchat accounts are built around a username. People can add a phone number for login and recovery, and they choose whether others can find them by that number. The answer tells you whether a number is associated. It says nothing about how the account is used. - `registered: true`: a Snapchat account is associated with the number. - `registered: false`: a conclusive "no". Because of the username model and privacy options, this is weaker evidence than a `true`. - `registered: null`: no conclusive answer. You are not charged. ## Who uses it, and why? Businesses with a young consumer audience use the Snapchat check in bulk, together with [TikTok](/services/tiktok-number-check) and [Instagram](/services/instagram-number-check), to judge whether numbers in consented lists are real and used. - **Lead-list hygiene** before a consented campaign. See [lead verification](/use-cases/lead-verification). - **Batch review of new sign-ups**, to flag numbers with no platform footprint for a closer look. Decisions during a live sign-up need an instant answer, and this check can't give one. Use a real-time service such as [Instagram](/services/instagram-number-check) for those. ## What do you get back? Each number gets one result under `checks["snapchat.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` / `billed` | boolean | Served from cache; charged or not | | `reason` | string or null | Why an answer is not conclusive | ## How is it billed? You pay the bulk price per number, only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). See [pricing](/pricing), and use the free `POST /v1/jobs/estimate` to see the maximum cost first. ## What are the limits? - Bulk only: `POST /v1/jobs`, up to 50,000 numbers and e-mails per job, and at most 100,000 numbers × checks. - Any country. National formats need `default_country`. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily cap per account applies (`GET /v1/limits`). Job data is kept for 30 days by default. ## How do I use it responsibly? Snapchat's audience includes many young people. Be especially careful to check only numbers you have a lawful basis to process, and never use results to contact people who did not ask to hear from you. The [acceptable use policy](/legal/acceptable-use) forbids unsolicited messaging, profiling and working through number lists to find out who uses an app. Objections go through [/opt-out](/opt-out). ## Example request See [test mode](/docs/test-mode) and [bulk jobs](/docs/bulk-jobs). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: snapchat-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["snapchat"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Result row (excerpt, test mode: the first item of `data` from `GET /v1/jobs/{id}/results`): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "snapchat.registered": { "service": "snapchat.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.824Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Is the Snapchat check available in real time? No, it runs in bulk jobs only (POST /v1/jobs). POST /v1/lookup refuses it with 403 service_disabled and points you to bulk jobs. ### Does the check return a Snapchat username? No. It only indicates whether a Snapchat account is associated with the number. No username, name, Bitmoji, photo or location is returned. ### Can privacy settings affect the answer? Snapchat is built around usernames, and people control whether others can find them by phone number. Settings like these may make an account harder to detect, so treat false as a weak signal. ### What do I pay for a job? The bulk price per conclusive answer. Unknown, unsupported-country, invalid and duplicate rows are free. POST /v1/jobs/estimate shows the maximum cost before you start. ## Service code and modes - Code: `snapchat.registered` (input: phone number) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.003 per check ($3.00 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has a LinkedIn account > Check in bulk whether a LinkedIn account is associated with US or Indian phone numbers, for B2B lead verification. Other countries are free. Canonical: https://mobilevalidate.com/services/linkedin-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The LinkedIn check indicates whether a LinkedIn account is associated with a phone number from the United States or India. It runs in bulk jobs only and answers `registered: true`, `false` or `null` (unknown) per number, with the time of the check. It never returns profiles, names, job titles or employers. ## What does the LinkedIn check tell you? It tells you whether a US or Indian phone number is linked to a LinkedIn account. LinkedIn is Microsoft's professional network (Microsoft completed the acquisition in December 2016). Members can add a phone number for sign-in, account recovery and two-step verification. Most people join with an e-mail address, and the number is optional. - `registered: true`: a LinkedIn account is associated with the number. - `registered: false`: a conclusive "no". Common, because many members never add a number. - `registered: null`: no conclusive answer. Not charged. - `status: unsupported_country`: the number is not from the US or India. Not charged. Of the social checks in the catalog, this is the only one limited to these two markets. Coverage outside them is not offered, because we only sell a check where conclusive answers are available at a useful rate. ## Who uses it, and why? The LinkedIn check is mainly a **B2B lead-verification** signal. When a sales team receives a US or Indian lead through a demo request or event sign-up, a number with a LinkedIn account behind it is more likely to belong to a working professional than to be a typo or a throwaway number. - **Lead scoring for inbound forms.** Run consented leads through a nightly job and add the result as one scoring input. Pair it with the [US carrier lookup](/services/us-carrier-lookup) or the [carrier lookup](/services/carrier-lookup) to see whether the line is mobile, fixed or VoIP. See [lead verification](/use-cases/lead-verification). - **CRM hygiene.** Bulk-check contact numbers you already hold for existing business relationships to spot dead entries. For addresses rather than numbers, use the [LinkedIn e-mail check](/services/linkedin-email-check). It has no country limit and also runs in bulk. ## What do you get back? Each number gets one result under `checks["linkedin.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` / `billed` | boolean | Served from cache; charged or not | | `reason` | string or null | Why an answer is not conclusive, e.g. `UNSUPPORTED_COUNTRY` | ## How is it billed? You pay the bulk price per number, only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). A mixed international list therefore costs nothing for its non-US, non-Indian rows. The free `POST /v1/jobs/estimate` counts them under `unsupported` and shows the maximum cost. See [pricing](/pricing). ## What are the limits? - Bulk only: `POST /v1/jobs`, up to 50,000 numbers and e-mails per job, and at most 100,000 numbers × checks. `POST /v1/lookup` answers `403 service_disabled`. - Countries: US and IN only. - Requests that look like sequential number ranges or generated e-mail lists are rejected. - A daily cap per account applies (`GET /v1/limits`). Job data is kept for 30 days by default. ## How do I use it responsibly? Business contacts are still people with privacy rights. Check only leads and contacts you have a lawful basis to process. Don't use the answer to add numbers to cold-outreach lists. Never try to match numbers to profiles: we don't support it, and our [acceptable use policy](/legal/acceptable-use) forbids profiling and unsolicited messaging. People can object at [/opt-out](/opt-out). ## Example request See [test mode](/docs/test-mode) and [bulk jobs](/docs/bulk-jobs). The test numbers work here even though they are UK numbers. ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: linkedin-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["linkedin"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Result row (excerpt, test mode: the first item of `data` from `GET /v1/jobs/{id}/results`): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "linkedin.registered": { "service": "linkedin.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:30.952Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Which countries does the LinkedIn check cover? United States (+1 numbers assigned to the US) and India (+91). Numbers from any other country come back as unsupported_country and are not charged. ### Can I run it in real time during a form submission? No. The LinkedIn check runs in bulk jobs only (POST /v1/jobs). For an instant signal during a sign-up, combine real-time checks such as the carrier lookup instead. ### Do I get the person's LinkedIn profile, job title or employer? No. The check only indicates whether a LinkedIn account is associated with the number. No profile URL, name, photo, job title or employer is returned, and we never enrich leads with profile data. ### Is there a LinkedIn check for e-mail addresses? Yes. The separate linkedin.email service checks an address and runs in bulk jobs only. It does not have the phone check's country limit. ### Can a test key check non-US numbers? Yes. In test mode the documented test numbers (+44 7700 900xxx) work for every service, including country-limited ones. Other non-US, non-Indian numbers answer unsupported_country, as they do in live mode. ## Service code and modes - Code: `linkedin.registered` (input: phone number) - Modes: bulk only · countries: US, IN ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0008 per check ($0.80 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a Russian phone number has a VK account > Check in real time whether a VK account is associated with a Russian (+7) mobile number. Yes, no or unknown; other countries are never charged. Canonical: https://mobilevalidate.com/services/vk-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The VK check indicates whether a VK account is associated with a Russian phone number. It runs in real time and in bulk jobs, and answers `registered: true`, `false` or `null` (unknown) with the time of the check. Numbers outside Russia return `unsupported_country` and are not charged. No profile data is ever returned. ## What does the VK check tell you? It tells you whether a Russian mobile number is linked to an account on VK, the Russian social network run by VK Company. VK uses the phone number as the main sign-in identifier through VK ID, a single account used across several of the company's services. A number with an associated account has usually been confirmed with a code on VK. - `registered: true`: a VK account is associated with the number. - `registered: false`: a conclusive "no". - `registered: null`: no conclusive answer. Not charged. - `status: unsupported_country`: the number is not Russian. Not charged. Here the phone number is how people sign in, not an optional extra added for recovery. That makes a `true` answer on VK more informative than on platforms where most people register with an e-mail address. ## Who uses it, and why? The VK check suits businesses that serve customers in Russia and need a quick signal on a Russian number. - **Sign-up and OTP protection.** In a live sign-up, a number with a VK account is more likely to be a person's everyday mobile number than a number bought for a single registration. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Channel choice for expected messages.** Combine with [Telegram](/services/telegram-number-check) and [MAX](/services/max-number-check) to see which messengers a consenting customer is likely to use. - **Lead verification** for Russian consumer leads. For e-mail addresses on the same market, see the [Mail.ru](/services/mailru-email-check) and [Yandex](/services/yandex-email-check) e-mail checks. ## What do you get back? Each number gets one result under `checks["vk.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, e.g. `UNSUPPORTED_COUNTRY` | ## 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](/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) and bulk jobs (`POST /v1/jobs`, up to 50,000 per job). - Russia only. The +7 country code is shared with Kazakhstan, so numbers are checked against the Russian numbering plan, not just the prefix. - 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. - A daily cap per account applies (`GET /v1/limits`). ## How do I use it responsibly? Check only numbers you have a lawful basis to process under the laws that apply to you and to the people concerned. Use results for fraud prevention and for choosing a channel for messages people expect. Don't use them for profiling or unsolicited contact, which our [acceptable use policy](/legal/acceptable-use) forbids. People can object at [/opt-out](/opt-out). ## Example request With a test key, `+447700900001` answers "registered" (test numbers bypass the country limit). See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["vk"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "vk.registered": { "service": "vk.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.077Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Which numbers does the VK check accept? Russian numbers only. Numbers from other countries, including other +7 numbers that are not Russian, come back as unsupported_country and are not charged. ### Why is the phone number a strong signal on VK? VK signs people in with a phone number through its VK ID account, so an associated account usually means the number was verified with a code on VK. Treat it as a signal, not as identity proof. ### Is the answer instant? Usually. The VK check is available in real time on POST /v1/lookup and waits up to 10 seconds by default. Slower answers come back as pending. ### Does the check return the VK profile? No. It only indicates whether a VK account is associated with the number. Names, photos, profile pages and friends are never returned. ### Can I test it without a Russian number? Yes. With a test key the documented test numbers (+44 7700 900xxx) work for every service, including VK. ## Service code and modes - Code: `vk.registered` (input: phone number) - Modes: realtime and bulk · countries: RU ## Price (live) - Realtime (POST /v1/lookup): $0.0015 per check ($1.50 per 1,000) - Bulk (POST /v1/jobs): $0.0008 per check ($0.80 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number is linked to an Apple Account (Apple ID) > Check in real time whether an Apple Account (Apple ID) is associated with a phone number. Yes, no or unknown per number; unknowns are free. Canonical: https://mobilevalidate.com/services/apple-id-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* 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, for numbers from any country, 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](/services/imessage-number-check). It runs in bulk jobs only. ## 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](/use-cases/account-security). - **Sign-up protection.** In a live sign-up, the real-time answer can sit next to a [carrier lookup](/services/carrier-lookup) to separate everyday mobile numbers from numbers set up for a single use. - **Channel planning for expected messages.** Combined with [iMessage](/services/imessage-number-check) and [RCS](/services/rcs-capability-check), 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](/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) and bulk jobs (`POST /v1/jobs`, up to 50,000 per job), for numbers from every country. - 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](/legal/acceptable-use) forbids these uses and unsolicited messaging. People can object at [/opt-out](/opt-out). ## Example request With a test key, `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["apple"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "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 } ``` ## 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. ## Service code and modes - Code: `apple.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has an Amazon account > Check in real time whether an Amazon customer account is associated with a phone number. Yes, no or unknown per number; unknowns are free. Canonical: https://mobilevalidate.com/services/amazon-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The Amazon check indicates whether an Amazon customer account is associated with a phone number. It runs in real time and in bulk jobs, for numbers from any country, and answers `registered: true`, `false` or `null` (unknown) with the time of the check. It never returns names, addresses, orders or payment details. ## What does the Amazon check tell you? It tells you whether a phone number is linked to an Amazon account. Amazon lets customers sign in with a mobile number, and in some marketplaces an account can be created with a mobile number instead of an e-mail address. The same customer account is typically used across Amazon's shopping sites and its other consumer services. - `registered: true`: an Amazon account is associated with the number. - `registered: false`: a conclusive "no", including customers who registered with e-mail only. - `registered: null`: no conclusive answer. Not charged. ## Who uses it, and why? Online shops, marketplaces and delivery services use the Amazon check as one fraud signal among several. - **Checkout and sign-up protection.** A number that has been used as a shopping login has probably received delivery notifications and one-time codes, which suggests a person's everyday number. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Promotion and trial abuse.** Batches of new accounts whose numbers have no history on large consumer platforms can be sent for review, with the [carrier lookup](/services/carrier-lookup) added for line type. For addresses rather than numbers, use the [Amazon e-mail check](/services/amazon-email-check). ## What do you get back? Each number gets one result under `checks["amazon.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` / `billed` | boolean | Served from cache; charged or not | | `reason` | string or null | Why an answer is not conclusive | ## How is it billed? You pay per number checked, 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](/pricing). Cache hits are free, and `max_age: 0` forces a fresh, billed check. ## What are the limits? - Real time (`POST /v1/lookup`, up to 100 identifiers) and bulk jobs (`POST /v1/jobs`, up to 50,000 per job), for numbers from every country. - 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. - A daily cap per account applies (`GET /v1/limits`). ## How do I use it responsibly? Treat the answer as a risk signal about a number, not a judgement about a person. Never use it for eligibility decisions. Check only numbers you have a lawful basis to process. The [acceptable use policy](/legal/acceptable-use) forbids profiling, working through number lists and unsolicited messaging. People can object at [/opt-out](/opt-out). ## Example request With a test key, `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["amazon"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "amazon.registered": { "service": "amazon.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.154Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check reveal orders, addresses or payment data? No. It only indicates whether an Amazon account is associated with the number. No name, address, order history or payment information is returned. ### Why would a regular Amazon customer come back as not registered? Amazon accounts can be created with an e-mail address, and a phone number is not always attached. False means no account is associated with this particular number. ### Is there an e-mail version? Yes. The amazon.email service answers the same question for an e-mail address, also in real time. ### Can I check sellers or business accounts separately? No. The check does not distinguish account types. It answers only whether an Amazon account is associated with the number. ## Service code and modes - Code: `amazon.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has a Microsoft account > Check in real time whether a personal Microsoft account is associated with a phone number. Yes, no or unknown per number; unknowns are free. Canonical: https://mobilevalidate.com/services/microsoft-account-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The Microsoft account check indicates whether a personal Microsoft account is associated with a phone number. It runs in real time and in bulk jobs, for numbers from any country, and answers `registered: true`, `false` or `null` (unknown) with the time of the check. It never returns names, gamertags or e-mail addresses. ## What does the Microsoft account check tell you? It tells you whether a phone number is linked to a Microsoft account. That is the personal account used for Windows sign-in, Outlook.com, OneDrive, Xbox and other Microsoft consumer services. Microsoft accepts a phone number as the sign-in name, and numbers are also added as security contacts to receive verification codes. - `registered: true`: a Microsoft account is associated with the number. - `registered: false`: a conclusive "no". - `registered: null`: no conclusive answer. Not charged. Microsoft runs two separate account systems. Personal Microsoft accounts are created by individuals. Work or school accounts are created and managed by organizations. This check is about the personal kind, so it tells you nothing about someone's employer or their company's systems. ## Who uses it, and why? A single Microsoft account often covers a person's PC, e-mail, cloud storage and games console. Numbers linked to one have usually been used for sign-ins and verification codes for years. - **Account security.** When a customer changes the recovery number on their account with you, a number linked to a Microsoft account is less likely to be temporary. See [account security](/use-cases/account-security). - **Sign-up protection.** The real-time answer fits into a live sign-up next to a [carrier lookup](/services/carrier-lookup), with [Apple Account](/services/apple-id-number-check) as a second large-platform signal. - **Data hygiene.** It helps you judge whether old numbers in consented customer records are still in use. For e-mail addresses, use the [Outlook e-mail check](/services/outlook-email-check). It runs in bulk jobs only. ## What do you get back? Each number gets one result under `checks["microsoft.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, 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](/pricing). Cache hits are free, and `max_age: 0` forces a fresh, billed check. ## What are the limits? - Real time (`POST /v1/lookup`, up to 100 identifiers) and bulk jobs (`POST /v1/jobs`, up to 50,000 per job), for numbers from every country. - 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. - A daily cap per account applies (`GET /v1/limits`). ## How do I use it responsibly? Use the answer to protect accounts and sign-ups, only for numbers you have a lawful basis to process. Don't build profiles or run through number lists to see who has an account. The [acceptable use policy](/legal/acceptable-use) forbids both, as well as unsolicited messaging. People can object at [/opt-out](/opt-out). ## Example request With a test key, `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["microsoft"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "microsoft.registered": { "service": "microsoft.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.192Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check cover work or school accounts? The check is about personal Microsoft accounts, the kind used for Outlook.com, Xbox, OneDrive and signing in to Windows. Work or school accounts are managed by each organization in a separate system, so don't read the answer as a statement about them. ### Can a Microsoft account use a phone number as its username? Yes. Microsoft lets people sign in with a phone number as well as with an e-mail address. A number can also be added as a security contact for verification codes. ### Is there an Outlook e-mail check too? Yes. The outlook.email service checks whether an Outlook address exists. It runs in bulk jobs only, while this phone check also works in real time. ### Does the result include the account's name or Xbox gamertag? No. It only indicates whether a Microsoft account is associated with the number. No name, gamertag, e-mail address or device information is returned. ## Service code and modes - Code: `microsoft.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if a phone number has a Netflix account > Check in real time whether a Netflix account is associated with a phone number, e.g. to spot trial and promotion abuse. Unknowns are free. Canonical: https://mobilevalidate.com/services/netflix-number-check · Last updated: 2026-09-25 ![A phone number checked against a grid of generic account tiles, each marked registered, not registered or unknown.](https://mobilevalidate.com/images/account-presence-check.svg) *Account checks say whether an account exists. They never say whose it is.* The Netflix check indicates whether a Netflix account is associated with a phone number. It runs in real time and in bulk jobs, for numbers from any country, and answers `registered: true`, `false` or `null` (unknown) with the time of the check. It never returns profiles, viewing history or plan details. ## What does the Netflix check tell you? It tells you whether a phone number is linked to a Netflix account. Netflix accounts are paid subscriptions held by one member and shared within a household. A phone number can be added for sign-in and account recovery, but most accounts are created with an e-mail address. - `registered: true`: a Netflix account is associated with the number. - `registered: false`: a conclusive "no". Common, because many accounts have no phone number and many people use someone else's account. - `registered: null`: no conclusive answer. Not charged. Netflix reported more than 300 million paid memberships in its fourth-quarter 2024 shareholder letter (January 2025). Accounts are spread across most countries, but one account usually covers a whole household. So `true` says something about the account holder's number, not about each person who watches. ## Who uses it, and why? A paid subscription is harder to fake than a free sign-up, so a number linked to one is a useful signal that the number is established and in everyday use. - **Trial and promotion abuse.** Subscription businesses see repeated free-trial sign-ups from freshly obtained numbers. A number with no history on large consumer services can be routed to extra verification, and one with history can pass straight through. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Account security.** When a recovery number changes, history on other paid services is one mild reassurance. Netflix tightened account-sharing rules in 2023, which pushed more people to hold their own accounts. That trend may make the signal slightly more useful over time, but it is not a reason to rely on it alone. ## What do you get back? Each number gets one result under `checks["netflix.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` / `billed` | boolean | Served from cache; charged or not | | `reason` | string or null | Why an answer is not conclusive | ## How is it billed? You pay per number checked, 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](/pricing). Cache hits are free, and `max_age: 0` forces a fresh, billed check. ## What are the limits? - Real time (`POST /v1/lookup`, up to 100 identifiers) and bulk jobs (`POST /v1/jobs`, up to 50,000 per job), for numbers from every country. - 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. - A daily cap per account applies (`GET /v1/limits`). ## How do I use it responsibly? Use the answer to protect sign-ups and offers, only for numbers you have a lawful basis to process. Don't use it to infer how people spend their money or to decide on eligibility for anything. The [acceptable use policy](/legal/acceptable-use) forbids profiling and unsolicited messaging. People can object at [/opt-out](/opt-out). ## Example request With a test key, `+447700900001` answers "registered". See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["netflix"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "netflix.registered": { "service": "netflix.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.231Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check say who in a household uses Netflix? No. A Netflix account is shared by a household, and the check only indicates whether an account is associated with the number. It returns no names, profiles, viewing history or plan details. ### Why is this useful outside the streaming business? A paid subscription account linked to a number suggests a long-used, everyday number. Subscription and promotion businesses use it as one signal against sign-ups from throwaway numbers. ### Can I check an e-mail address instead? Yes. The netflix.email service answers the same question for an e-mail address, in real time. ### Is a number without a Netflix account suspicious? No. Most accounts are created with an e-mail address, and many households share one account. On its own, false says very little. ### Which countries are covered? Numbers from any country can be checked. Netflix is available in most countries, though not all. ## Service code and modes - Code: `netflix.registered` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.002 per check ($2.00 per 1,000) - Bulk (POST /v1/jobs): $0.0015 per check ($1.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Carrier and line type lookup > Look up the line type and current carrier of a phone number, plus the original carrier when it differs. Beta, real time or bulk; no data is free. Canonical: https://mobilevalidate.com/services/carrier-lookup · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* The carrier lookup tells you what kind of line a phone number is (mobile, fixed line, VoIP, toll-free and others) and which network serves it. When the number has moved away from the network its range was allocated to, it also returns that original carrier. It covers numbers worldwide, in real time or in bulk jobs, and is currently in beta. ## What does the carrier lookup tell you? It answers two practical questions about a number: *can it receive SMS at all*, and *which network is it on*. The first comes from `line_type`, the second from `carrier`. A `line_type` of `mobile` means the number belongs to a mobile range. `fixed_line`, `toll_free`, `premium_rate`, `shared_cost` or `uan` usually mean SMS will fail or cost money for nothing. `voip` means the number is served by an internet-telephony provider. In some countries mobile and fixed ranges cannot be told apart, and the answer is then `fixed_line_or_mobile`. The [line type glossary entry](/glossary/line-type) explains each value. `carrier` is the current network name. `original_carrier` appears only when it differs from `carrier`, and a difference is a strong hint that the number was [ported](/glossary/mobile-number-portability). `country` is the ISO country of the number. This is data about the number, not about a person or a handset. It does not tell you whether the phone is switched on. That needs a live query to the home network, which the [HLR lookup](/services/hlr-lookup) will provide when it launches. ## Who uses it, and why? Teams that send SMS, place calls or accept phone numbers on forms use the carrier lookup as the first filter. - **SMS cost control.** Sending a text to a landline or a toll-free number wastes the message fee and skews delivery reports. Filter by `line_type` before you send. See [SMS cost reduction](/use-cases/sms-cost-reduction). - **Routing.** Some messaging providers price or route by destination network. The current `carrier`, not the one the number prefix suggests, is what counts once a number has been ported. - **Sign-up and OTP fraud.** A burst of sign-ups from `voip` numbers or a single small carrier is a common pattern in fake account creation and [SMS pumping](/glossary/sms-pumping). The line type is a signal for an extra verification step. - **Lead verification.** A form entry with a `fixed_line` number in a "mobile" field, or a number whose country doesn't match the address, is worth a second look. Unlike the platform checks, this service returns data rather than a yes/no answer. A conclusive answer has `registered: true`, which means "data found". ## 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) | | `attributes.original_carrier` | string | Carrier the number range was allocated to, only when different | | `attributes.country` | string | ISO 3166-1 alpha-2 country of the number | | `registered` | boolean or null | `true` when data was found; `null` when not conclusive | | `status` / `reason` | enum / string | `completed`, `unknown` (e.g. `NO_DATA`, `UPSTREAM_TIMEOUT`), `pending`, `unsupported_country` | | `checked_at`, `cached`, `billed` | — | When the answer was obtained, whether it came from cache, whether it was charged | An answer is conclusive only when `carrier` is present. Attributes we don't have are left out rather than guessed. ## How is it billed? You pay per number, and only when we return carrier data. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). A number we hold no data for comes back as `unknown` with reason `NO_DATA` and is free. Real-time and bulk checks have separate prices. See [pricing](/pricing). Repeat checks of the same number inside the freshness window can come from your account's cache. Cache hits are free (`cached: true, billed: false`). Send `max_age: 0` to force a fresh, billed check. Use `max_cost` to cap what a single request can cost. ## What are the limits? The carrier lookup runs in real time (`POST /v1/lookup`, up to 100 numbers) and in bulk jobs (`POST /v1/jobs`, up to 50,000 numbers and e-mails). It accepts numbers from every country, but **coverage varies by country**. That is why the service is labelled beta, and why `NO_DATA` answers are free. - 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 (20 or more consecutive numbers → `suspected_enumeration`). - A daily numbers cap applies per account (`GET /v1/limits`). - Carrier names are returned as we hold them. Brand names, mergers and MVNOs (virtual operators that use another network) can make the name differ from what a subscriber sees on their bill. ## How do I use it responsibly? Line type and carrier are facts about a number, but the number usually belongs to a person. Check only numbers you have a lawful reason to process, such as customers, sign-ups and leads who gave you their number. Don't use the lookup to screen or profile people for eligibility decisions like credit, housing or employment. The [acceptable use policy](/legal/acceptable-use) forbids that. A `voip` answer is not evidence of fraud on its own. Many legitimate users have VoIP numbers. People can object to their number being checked through the [opt-out form](/opt-out). Suppressed numbers are skipped and never charged. ## Example request With a test key, `+447700900001` returns fixed test data, `…002` returns `unknown` (`NO_DATA`) and `…003` returns `unknown` (`UPSTREAM_TIMEOUT`). See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["carrier"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "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-09-25T14:25:31.270Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### What is the difference between carrier and original_carrier? carrier is the network the number is served by today, as far as our data shows. original_carrier is the network the number range was first allocated to, and is only returned when it differs, which usually means the number was ported. ### Does a carrier lookup tell me whether the phone is switched on? No. It describes the number (line type and network), not the handset. Live reachability needs a live network query, which our HLR lookup will provide when it launches. ### Why is the service marked beta? Coverage and data depth still vary by country. When we hold no data for a number, the result is unknown with reason NO_DATA and you are not charged. ### Should I use this or the US/CA carrier lookup for North American numbers? For US and Canadian numbers where porting is common, the US/CA carrier lookup is the more specific option. It runs in bulk jobs only. The general carrier lookup covers all countries and also works in real time. ### Can I block every VoIP number based on line_type? You can, but many real customers use VoIP numbers. Most teams treat voip as a reason for an extra step, such as another verification method, rather than an automatic block. ## Service code and modes - Code: `network.carrier` (input: phone number) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true when data was found; null when unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | | attributes.line_type | enum (mobile, fixed_line, fixed_line_or_mobile, voip, toll_free, premium_rate, shared_cost, personal, pager, uan, voicemail, unknown) | Line type. | | attributes.carrier | string | Current carrier name. | | attributes.original_carrier | string | Carrier the number range was allocated to, when different. | | attributes.country | string | ISO 3166-1 alpha-2 country of the number. | --- # US and Canada carrier lookup > Current carrier and line type for US and Canadian numbers, including ported numbers. Runs in bulk jobs; numbers outside US/CA are free. Canonical: https://mobilevalidate.com/services/us-carrier-lookup · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* 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](/services/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](/use-cases/call-center-screening). - **Lead verification.** A lead form in the US that returns a `voip` or `toll_free` line 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](/services/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](/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/lookup` refuses the check with `403 service_disabled`. Use `POST /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](/legal/acceptable-use) 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](/opt-out). 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`. ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: us-carrier-demo-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["network.carrier_us"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response from `GET /v1/jobs/{id}/results` (excerpt, test mode: the first row): ```json { "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 } ``` ## 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. ## Service code and modes - Code: `network.carrier_us` (input: phone number) - Modes: bulk only · countries: US, CA ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.003 per check ($3.00 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true when data was found; null when unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | | attributes.line_type | enum (mobile, fixed_line, fixed_line_or_mobile, voip, toll_free, premium_rate, shared_cost, personal, pager, uan, voicemail, unknown) | Line type. | | attributes.carrier | string | Current carrier name. | --- # Phone number spam reputation > See whether a US, Canadian or German number appears in spam and nuisance-call reports, with a level, a 0–100 score and the reasons behind it. Canonical: https://mobilevalidate.com/services/spam-reputation · Last updated: 2026-09-25 ![A shield and a risk gauge pointing into the high range, with an incoming call flagged as risky.](https://mobilevalidate.com/images/spam-reputation-risk-score.svg) *Spam reputation gives a risk level and the reasons behind it, where the service is enabled.* **Limited access:** this service is available to internal customers only for now. The spam reputation check tells you whether a phone number appears in spam and nuisance-call reports, and why. Every answer includes a level (`high`, `medium`, `low` or `no_reports`), a 0–100 score and the classes of signal behind it. It covers numbers from the United States, Canada and Germany, in real time or in bulk. ## What does the spam reputation check tell you? It tells you whether a number has a record of complaints, and how strong that record is. It is a **reputation signal, not a verdict**. It summarizes what others reported about a number. It doesn't prove who is calling. The answer is built from these classes of signal, described here by type only: - **Regulator actions**: a telecom regulator took action against the number. - **Government complaint data**: the number appears in government nuisance-call complaint data. - **Community reports**: people reported the number on public spam-report sites. - **Unassigned-number signal**: the number was recently offered for sale as unassigned. This is a sign of a spoofed caller ID or a made-up lead. - **VoIP-range hint**: the number range belongs to a VoIP carrier. This is shown for context only and adds no points. Reports are refreshed daily. Report texts, reporter details and names are never returned. ## How are levels and scores decided? The `risk_score` runs from 0 to 100. More reports and stronger reports raise it. Reports last seen more than 12 months ago count half, so a number that was abused years ago and has been quiet since slowly drifts down. | Level | Rule | |---|---| | `high` | Score 80 or more **and** either a regulator action or at least two independent signal classes | | `medium` | Score 50–79 | | `low` | Score 20–49 | | `no_reports` | We hold no reports for the number | `sources` counts the independent signal classes behind the answer. `top_category` names the most frequent kind of report: `debt_relief`, `impersonation`, `robocall`, `medical`, `home_services`, `warranty`, `sms_spam`, `dialer`, `fraud_hacking` or `other`. `first_seen` and `last_seen` give the months (`YYYY-MM`) when the number first and last appeared in our data. **`no_reports` is not a guarantee a number is safe.** It means we have no negative signals, nothing more. ## Who uses it, and why? - **Call centers and inbound teams** use it to decide whether to answer, flag or route an incoming call, and to check that their own outbound numbers haven't collected complaints. See [call-center screening](/use-cases/call-center-screening). - **Lead verification.** A web lead whose phone number was recently offered as unassigned (`reason_unassigned`) is likely fake. That is worth knowing before a sales team spends time on it. See [lead verification](/use-cases/lead-verification). - **Fraud teams** add the level to sign-up and payment risk scoring. A `high` number with an `impersonation` or `fraud_hacking` category deserves a manual review. Coverage is limited to the US, Canada and Germany because report data is dense enough there to give meaningful answers. We don't offer the check in countries where a thin data set would produce misleading `no_reports` answers. ## What do you get back? | Field | Type | Meaning | |---|---|---| | `attributes.risk_level` | enum | `high`, `medium`, `low` or `no_reports` | | `attributes.risk_score` | integer 0–100 | Higher means more and stronger reports | | `attributes.reason_regulator` | boolean | A telecom regulator took action against the number | | `attributes.reason_government` | boolean | Listed in government nuisance-call complaint data | | `attributes.reason_community` | boolean | Reported on community spam-report sites | | `attributes.reason_unassigned` | boolean | Recently offered for sale as an unassigned number | | `attributes.voip_range` | boolean | Hint only: the range belongs to a VoIP carrier (no points) | | `attributes.top_category` | enum | Most frequent report category (absent when none) | | `attributes.first_seen` / `last_seen` | `YYYY-MM` | First and last month the number appeared in our data | | `attributes.sources` | integer 0–10 | Number of independent signal classes | | `registered` | boolean or null | `true` for every conclusive answer ("data found"); `null` otherwise | In summaries, `high`, `medium` and `low` count as "registered" (reports found) and `no_reports` counts as "not registered". ## How is it billed? Every conclusive answer is billed, **including `no_reports`**. The check was carried out and answered. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). An `unknown` answer, for example while reference data is temporarily unavailable, is free, and so is any number outside the US, Canada and Germany. See [pricing](/pricing). Answers come from our own daily-refreshed reference data rather than a live call to another service, so real-time answers are fast. Repeat checks of the same number within 24 hours are served from your account's cache and are free. ## What are the limits? - **Countries:** US, CA and DE. Other numbers return `unsupported_country`, which is free. - **Modes:** real time (`POST /v1/lookup`, up to 100 numbers) and bulk jobs (`POST /v1/jobs`, up to 50,000). The MCP tool `check_spam_reputation` takes up to 100 numbers per call. - 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 (20 or more consecutive numbers → `suspected_enumeration`). - Scores change over time. Store `checked_at` with any decision you make. ## How do I use it responsibly? Treat the level as one signal among several. Don't treat it as proof that a person is a spammer. A number can be spoofed by someone else, and a number reported years ago can have a new owner. Don't use the result to deny someone credit, a job, housing or insurance. The [acceptable use policy](/legal/acceptable-use) forbids that. We process report data about numbers as a controller. If your number appears in our data and you think it shouldn't, or you want it removed, use the [opt-out form](/opt-out). The [data-subject notice](/legal/data-subject-notice) explains your rights. ## Example request With a test key, the whole test range `+44 7700 9xxxxx` works for this service, although live checks cover only US, CA and DE. `+447700900001` returns `high`, `…002` returns `no_reports`, `…003` returns `unknown`, `…004` is pending and then `medium`, and `…005` returns `unsupported_country`. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["spam"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "phone", "input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "number.spam": { "service": "number.spam", "status": "completed", "registered": true, "attributes": { "risk_level": "high", "risk_score": 95, "reason_regulator": true, "reason_government": false, "reason_community": true, "reason_unassigned": false, "voip_range": false, "top_category": "robocall", "first_seen": "2025-11", "last_seen": "2026-08", "sources": 2 }, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.435Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Who can use the spam reputation check today? It is in limited access: available to internal customers only for now. If you need it, say so in your access request and we will let you know when it opens up. ### Does no_reports mean the number is safe? No. no_reports means we hold no reports for the number. A new number, a rarely used number or a spoofed caller ID can still be abusive. Treat it as the absence of negative signals and combine it with other checks. ### Why is a no_reports answer charged? Because the check was carried out and answered conclusively. Every risk level, including no_reports, is billed. Unknown answers and numbers outside the US, Canada and Germany are free. ### Can I see the report texts or who reported a number? No. The check returns levels, reasons and categories only. Report texts, reporter details and names are never returned. ### Can a number's score change? Yes. Scores are recomputed as new reports arrive and old ones age. Reports last seen more than 12 months ago count half. checked_at shows when an answer was computed. ## Service code and modes - Code: `number.spam` (input: phone number) - Modes: realtime and bulk · countries: US, CA, DE - Status: limited access ## Price (live) - Realtime (POST /v1/lookup): $0.0008 per check ($0.80 per 1,000) - Bulk (POST /v1/jobs): $0.0004 per check ($0.40 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true when data was found; null when unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | | attributes.risk_level | enum (high, medium, low, no_reports) | Overall reputation level. | | attributes.risk_score | integer | Score 0–100 (higher = more reports). | | attributes.reason_regulator | boolean | A telecom regulator took action against the number. | | attributes.reason_government | boolean | Listed in government nuisance-call complaint data. | | attributes.reason_community | boolean | Reported on community spam-report sites. | | attributes.reason_unassigned | boolean | Recently offered for sale as an unassigned number (possible spoofed caller ID or fake lead). | | attributes.voip_range | boolean | Hint only: the number range belongs to a VoIP carrier (not a risk by itself). | | attributes.top_category | enum (debt_relief, impersonation, robocall, medical, home_services, warranty, sms_spam, dialer, fraud_hacking, other) | Most frequent report category. | | attributes.first_seen | string | Month the number first appeared in our data (YYYY-MM). | | attributes.last_seen | string | Month the number last appeared in our data (YYYY-MM). | | attributes.sources | integer | Number of independent signal classes. | --- # HLR lookup: live network status (coming soon) > Coming soon: a live query to a mobile number's home network showing whether it is reachable, ported or roaming, and its current network. Canonical: https://mobilevalidate.com/services/hlr-lookup · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* **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](/glossary/hlr-lookup) 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](/services/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 `unreachable` to offer another verification channel straight away instead of waiting for an SMS that won't arrive. - **Fraud teams** can compare `ported` and the current network with what a customer told them. - **Routing** will use `mcc_mnc` and `network`, which show the network actually serving a ported number. See [number reachability](/glossary/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](/glossary/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](/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](/docs/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](/legal/acceptable-use) applies, and people can object through the [opt-out form](/opt-out). ## What does a request return today? A request made now with a test key gets the real current answer: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["hlr"]}' ``` ```json { "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" } } ``` ## 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. ## Service code and modes - Code: `number.hlr` (input: phone number) - Modes: realtime and bulk · worldwide - Status: coming soon ## Price (live) Coming soon: prices will be published at launch. ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true when data was found; null when unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | | attributes.status | enum (reachable, unreachable, invalid, unknown) | Reachability from the home network. | | 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. | --- # Check if an e-mail mailbox exists > Find out whether an e-mail address has a live mailbox at a major webmail provider before you send a code or accept a sign-up. Unknowns are free. Canonical: https://mobilevalidate.com/services/email-verification · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The mailbox check answers one question: does this e-mail address have a working mailbox at its provider? You get `registered: true`, `false` or `null` (unknown), plus the time we checked. It covers major webmail providers, runs in real time or in bulk jobs, and never sends mail to the address or reads the mailbox. ## What does the mailbox check tell you? It tells you whether the mailbox behind an address exists at the provider that hosts it. A syntax check only proves that an address is well formed. `jane.doe@gmail.com` and `jane.dooe@gmail.com` both pass syntax, but only one of them may exist. - `registered: true`: the provider has a mailbox at this address. - `registered: false`: we got a conclusive answer, and there is no such mailbox. - `registered: null`: no conclusive answer. `status` and `reason` explain why, and you are not charged. Coverage is intentionally narrow. The check answers only for **major webmail providers**, where most consumer sign-ups come from. Company and custom domains run their own mail servers with their own rules, so they answer `unknown` with `reason: UNSUPPORTED_PROVIDER`. That result is free. Addresses on a typo domain, such as a misspelled provider name, also fall outside coverage. A format check or a domain allow-list in your form catches those. ## Who uses it, and why? Teams use the mailbox check at the moment an address first enters their system. That is when a mistake or a fake is cheapest to catch. - **Sign-up and OTP protection.** Fake accounts are often created with made-up addresses at big webmail providers. A `false` answer before you send a confirmation link keeps them out. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Deliverability.** Sending to mailboxes that don't exist produces hard bounces, which hurt sender reputation. Checking first keeps receipts and password-reset mail flowing. - **Lead verification.** A form lead whose address has no mailbox is either a typo you can fix while the person is still on the page, or a lead that isn't worth a sales call. See [lead verification](/use-cases/lead-verification). The per-provider account checks, such as the [Gmail check](/services/gmail-email-check), answer a different question: whether an account exists at that one provider. The mailbox check is the general-purpose, real-time option. ## What do you get back? Every address becomes one result row with `kind: "email"`. Each requested check gets one entry in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | The normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["email.valid"].registered` | boolean or null | `true` mailbox exists, `false` no mailbox, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | e.g. `UNSUPPORTED_PROVIDER`, `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Whether it came from your cache, and whether it was charged | `e164` and `country` are always `null` on e-mail rows. When you read results back later, the input is masked (for example `re•••@test.mobilevalidate.com`). ## How is it billed? You pay per address checked, and only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). That covers addresses outside provider coverage too. They answer `unknown` with `UNSUPPORTED_PROVIDER`, so a list full of company domains doesn't cost you anything for those rows. Real-time and bulk have separate per-check prices. See [pricing](/pricing). Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free and marked `cached: true, billed: false`. `max_age: 0` forces a fresh check, which is billed. `max_cost` puts a ceiling on a request. ## What are the limits? The mailbox check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails together) and in bulk jobs (`POST /v1/jobs`, up to 50,000 per job). Put addresses in `emails`, not `numbers`. A request with e-mails but no e-mail check is refused with `invalid_request`. - Up to 20 checks per request. The total of identifiers × applicable 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. For e-mail, that means 20 or more addresses in one request on one domain whose local parts differ only by digits or separators (`john1@`, `john.2@`, `john_3@`). Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - The account's daily cap counts e-mail addresses the same way as phone numbers (see `GET /v1/limits`). - Test-domain addresses work only with test keys. Live keys get `test_number_only`. ## How do I use it responsibly? Check addresses that people gave you, or that you otherwise have a lawful reason to process. Examples are a sign-up form, a checkout or an existing customer record. We never read mailboxes and never send e-mail to the address. The answer is only yes, no or unknown, and never includes a name, avatar or profile. Don't use the check to guess addresses, to test variations of a name, or to build lists for unsolicited e-mail. The [acceptable use policy](/legal/acceptable-use) forbids all three, and the anti-enumeration rules above block the most common patterns. Anyone can object to having their address checked through the [opt-out form](/opt-out). Suppressed addresses answer `email_status: suppressed` and are never charged. ## Example request With a test key (`mv_test_…`), addresses on `test.mobilevalidate.com` return fixed answers: `registered@` answers yes, `unsupported@` shows the `UNSUPPORTED_PROVIDER` case. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com"], "checks": ["email"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "email.valid": { "service": "email.valid", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.473Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check send an e-mail to the address? No. Nothing is sent to the address and no mailbox is opened or read. The check only answers whether the mailbox exists: yes, no or unknown. ### Which addresses can be checked? Addresses at major consumer webmail providers. Addresses on other domains, such as company or custom domains, come back as unknown with the reason UNSUPPORTED_PROVIDER, and you are not charged for them. ### Is this the same as checking the address format? No. A format check only tells you an address could exist. This check tells you whether the mailbox does exist at the provider. Addresses with an invalid format are marked invalid_email and are never checked or charged. ### Do you treat j.doe@ and jdoe@ as the same address? No. We only trim spaces and lowercase the address. We never remove dots or plus tags, because on many providers those are different mailboxes that can belong to different people. ### Can I use the result to build a mailing list? No. The acceptable use policy forbids unsolicited bulk messaging and list building. Use the check to protect sign-up forms and to keep transactional e-mail deliverable for people who gave you their address. ## Service code and modes - Code: `email.valid` (input: e-mail address) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.002 per check ($2.00 per 1,000) - Bulk (POST /v1/jobs): $0.0015 per check ($1.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | --- # Check if an e-mail address has a Gmail account > Check in bulk whether gmail.com and googlemail.com addresses have a Gmail account. Yes, no or unknown per address; unknowns are free. Canonical: https://mobilevalidate.com/services/gmail-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Gmail check answers whether an e-mail address has a Gmail account. It is meant for `gmail.com` and `googlemail.com` addresses and runs in bulk jobs. Each address gets `registered: true`, `false` or `null` (unknown), with the time we checked. We never send mail to the address, open the mailbox or return anything about the account holder. ## What does the Gmail check tell you? It tells you whether Google has a Gmail account at the address, exactly as written. That is the right question for lists and sign-up data where Gmail makes up a large share of consumer addresses. - `registered: true`: a Gmail account exists at this address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer (`status` and `reason` say why), and you are not charged. Two Gmail behaviours matter here. Google's help center explains that Gmail ignores dots in the part before the `@` (`j.doe` and `jdoe` reach the same inbox) and that `name+tag@gmail.com` delivers to `name@gmail.com`. We deliberately do **not** rewrite addresses. We only trim and lowercase them. So `j.doe@gmail.com` and `jdoe@gmail.com` are separate rows and are not treated as duplicates. If you want one row per inbox, collapse the spellings before you send them. ## Who uses it, and why? Gmail is used by consumers worldwide, so fake and mistyped Gmail addresses show up in almost every sign-up funnel. - **Cleaning sign-up and CRM data.** A batch run over last month's sign-ups shows which Gmail addresses never existed. Those are usually typos, or accounts created with invented addresses. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Deliverability before a transactional send.** Removing Gmail addresses without an account prevents hard bounces, which count against your sender reputation with large mailbox providers. - **Lead scoring.** A lead with a real Gmail address is more likely to be reachable. A non-existent one is usually not worth a follow-up. See [lead verification](/use-cases/lead-verification). `googlemail.com` still appears in older records. Google used it in some countries, notably Germany and the UK, while it could not use the Gmail name there. Both domains reach Gmail, so both belong in the same check. For a real-time answer at sign-up, use the [mailbox check](/services/email-verification). ## What do you get back? Every address becomes a row with `kind: "email"` and one entry per check in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | Normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["gmail.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | Why an answer is not conclusive, e.g. `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | In job results and downloads, the input is masked (`re•••@test.mobilevalidate.com`). CSV and NDJSON downloads add `gmail.email.status`, `gmail.email.registered` and `gmail.email.billed` columns. ## How is it billed? You pay the bulk price per address, only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). `POST /v1/jobs/estimate` is free. It shows how many rows are valid, invalid, duplicate or already cached, and the most the job could cost. Pass that figure as `max_cost` to cap the job. See [pricing](/pricing). Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free (`cached: true, billed: false`). ## What are the limits? The Gmail check is **bulk only**. `POST /v1/lookup` refuses it with `403 service_disabled` ("The check 'gmail.email' is available in bulk jobs only (POST /v1/jobs)."). A job holds up to 50,000 numbers and e-mails. You can send them as JSON `emails`, or as a CSV upload with an `email` column. - Up to 20 checks per request. The total of identifiers × applicable checks is capped at 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more addresses in one request whose local parts differ only by digits or separators are refused with `suspected_enumeration`. `googlemail.com` counts as `gmail.com` for this rule. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - The daily cap counts e-mail addresses like phone numbers (see `GET /v1/limits`). - Job data is kept for 30 days by default, and you can purge a job earlier with `DELETE /v1/jobs/{id}`. ## How do I use it responsibly? Check addresses people gave you: customers, sign-ups and leads who asked to be contacted. We never read mailboxes or send e-mail to the address. The answer is yes, no or unknown, and never includes a name, avatar or profile. Don't generate address variations to find out whether someone has a Gmail account, and don't build lists for unsolicited mail. The [acceptable use policy](/legal/acceptable-use) forbids both. Anyone can object to having their address checked through the [opt-out form](/opt-out). Suppressed addresses are skipped and never charged. ## Example request Test keys return fixed answers for addresses on `test.mobilevalidate.com` (see [test mode](/docs/test-mode)). This job checks one registered address, one unregistered address and one address that answers unknown. ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: gmail-demo-001" \ -d '{"emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com", "unknown@test.mobilevalidate.com"], "checks": ["gmail"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response of `GET /v1/jobs/{id}/results` (excerpt, test mode: the first item of `data`): ```json { "kind": "email", "input": "re•••@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "gmail.email": { "service": "gmail.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.513Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check send an e-mail or sign in to the account? No. Nothing is sent to the address and no mailbox is opened. The check only answers whether a Gmail account exists for the address: yes, no or unknown. ### Gmail ignores dots in addresses. Do you merge j.doe@gmail.com and jdoe@gmail.com? No. We only trim spaces and lowercase the address. Each address is checked as you sent it, so two spellings are two rows. If you want to merge them, normalize them yourself before sending. ### Why is the Gmail check only available in bulk jobs? The Gmail check is offered in bulk jobs only for now. Send it with POST /v1/jobs. For a real-time answer at sign-up, use the mailbox check (email). GET /v1/services always shows the current modes. ### Does googlemail.com count as Gmail? Yes, both domains are Gmail. For anti-enumeration, googlemail.com addresses are grouped with gmail.com addresses, so splitting a generated list across the two domains does not get around the limit. ### What happens to addresses that are not on Gmail? The check is meant for gmail.com and googlemail.com addresses. Answers for other domains may be unknown, and unknown answers are not charged. ## Service code and modes - Code: `gmail.email` (input: e-mail address) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0008 per check ($0.80 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has an Outlook account > Check in bulk whether outlook.com, hotmail.com and live.com addresses have a Microsoft consumer mailbox. Yes, no or unknown; unknowns are free. Canonical: https://mobilevalidate.com/services/outlook-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Outlook check answers whether an e-mail address has an account on Microsoft's consumer mail service. That covers `outlook.com` and the older `hotmail.com`, `live.com` and `msn.com` addresses. It runs in bulk jobs and returns `registered: true`, `false` or `null` (unknown) for each address, with the time we checked. Nothing is sent to the address and no mailbox is read. ## What does the Outlook check tell you? It tells you whether Microsoft has a consumer mailbox at the address. Outlook.com is where Microsoft's free web mail lives today. Microsoft launched it in 2012 and moved Hotmail users onto it in 2013, keeping their addresses. As a result, a single list often mixes `@outlook.com`, `@hotmail.com`, `@hotmail.co.uk`, `@live.com` and `@msn.com`, all served by the same system. - `registered: true`: an account exists at this address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer, with `status` and `reason` explaining why. You are not charged. Old Hotmail and Live addresses make this check useful. Some of them are still active after twenty years, and many were abandoned long ago. The check tells the two apart without sending anything. Work and school mailboxes on Microsoft 365 use the organisation's own domain and are managed by that organisation. They are not consumer Outlook.com accounts. ## Who uses it, and why? Outlook-family domains are common in older customer databases, which makes them a typical source of bounces and stale records. - **Re-activating old customer data.** Before a legitimate transactional or service mailing to customers you already have, a batch run shows which `hotmail.com` and `live.com` addresses no longer have an account. - **Deliverability.** Large mailbox providers watch bounce rates. Removing addresses that no longer exist protects the reputation that your receipts and password resets depend on. - **Sign-up checks after the fact.** A periodic job over new registrations flags Outlook addresses that never existed. That is typical of throwaway sign-ups. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). For an answer while the user is still on the form, use the real-time [mailbox check](/services/email-verification). For phone numbers linked to a Microsoft account, see the [Microsoft account number check](/services/microsoft-account-number-check). ## What do you get back? Each address is a row with `kind: "email"` and one entry per requested check in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | Normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["outlook.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | e.g. `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | Downloads add the columns `outlook.email.status`, `outlook.email.registered` and `outlook.email.billed`. Inputs are masked in stored results. ## How is it billed? You pay the bulk price per address with a conclusive answer. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The free `POST /v1/jobs/estimate` call counts valid, invalid, duplicate and cached rows and returns the maximum cost before you commit. See [pricing](/pricing) for current rates. Repeat checks of the same address inside the freshness window can come from your account's cache, and cache hits are free. If you cancel a running job, rows that have not started are released and not billed. ## What are the limits? The Outlook check is **bulk only**. `POST /v1/lookup` answers `403 service_disabled` with "The check 'outlook.email' is available in bulk jobs only (POST /v1/jobs)." A job accepts up to 50,000 numbers and e-mails, as JSON `emails` or as a CSV upload with an `email` column. - Up to 20 checks per request. Identifiers × applicable checks may not exceed 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more addresses in one request on one domain whose local parts differ only by digits or separators are refused with `suspected_enumeration`. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - Addresses are only trimmed and lowercased, never rewritten. - The daily cap counts e-mail addresses like numbers (see `GET /v1/limits`). ## How do I use it responsibly? Check addresses that belong to your own customers, users or leads. We never open mailboxes and never send e-mail to the address. The answer is yes, no or unknown, and never a name, avatar or profile. A "yes" does not mean the person agreed to hear from you. Don't use the check to build or "clean" lists for unsolicited e-mail. The [acceptable use policy](/legal/acceptable-use) forbids that, along with guessing addresses. Anyone can object through the [opt-out form](/opt-out). Suppressed addresses are skipped and free. ## Example request With a test key, `test.mobilevalidate.com` addresses give fixed answers (see [test mode](/docs/test-mode)). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: outlook-demo-001" \ -d '{"emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com", "unknown@test.mobilevalidate.com"], "checks": ["outlook"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response of `GET /v1/jobs/{id}/results` (excerpt, test mode: the first item of `data`): ```json { "kind": "email", "input": "re•••@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "outlook.email": { "service": "outlook.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:53.015Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Which addresses does the Outlook check cover? Microsoft's consumer mail domains, such as outlook.com, hotmail.com, live.com and msn.com, including country versions like hotmail.co.uk. Work and school mailboxes that run on Microsoft 365 under a company's own domain are a different product and are not what this check is for. ### Does the check send an e-mail or open the mailbox? No. Nothing is sent to the address and no mailbox is opened or read. You get yes, no or unknown, and never a name, avatar or profile. ### Is a Hotmail address still valid today? It can be. Hotmail addresses were moved into Outlook.com and many still work. The check tells you whether a given hotmail.com address still has an account. ### Why can't I run the Outlook check in real time? The Outlook check is offered in bulk jobs only for now. POST /v1/lookup refuses it with service_disabled. Use POST /v1/jobs, or the real-time mailbox check (email) at sign-up. ### Is an Outlook account the same as a Microsoft account? An Outlook.com address is normally also the sign-in for a Microsoft account, but a Microsoft account can use any e-mail address or a phone number. To check a phone number for a Microsoft account, use the Microsoft account number check. ## Service code and modes - Code: `outlook.email` (input: e-mail address) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has a Yahoo account > Check in bulk whether Yahoo Mail addresses such as yahoo.com, ymail.com and rocketmail.com have an account. Yes, no or unknown; unknowns are free. Canonical: https://mobilevalidate.com/services/yahoo-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Yahoo check answers whether an e-mail address has a Yahoo Mail account. It covers `yahoo.com`, Yahoo's country domains and the older `ymail.com` and `rocketmail.com` addresses. It runs in bulk jobs and returns `registered: true`, `false` or `null` (unknown) for each address, with the time we checked. Nothing is sent to the address and no mailbox is read. ## What does the Yahoo check tell you? It tells you whether Yahoo has an account at the exact address you send. Yahoo Mail is one of the oldest free web mail services, and over the years it has handed out addresses under several domains. Customer databases therefore contain `@yahoo.com`, country versions like `@yahoo.co.uk`, `@yahoo.fr` or `@yahoo.de`, and the alternative `@ymail.com` and `@rocketmail.com` domains. - `registered: true`: a Yahoo account exists at this address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer. `status` and `reason` say why, and you are not charged. Many Yahoo addresses were created years ago and never used again. Yahoo's own terms allow it to close accounts that stay inactive, so an address that worked in an old export may no longer exist. The check answers that question for today, and `checked_at` records when. ## Who uses it, and why? Yahoo addresses are common among long-standing consumers, which makes them important in older data and in some regional markets. - **Hygiene for older customer records.** Before a service or account notice to existing customers, a batch run finds Yahoo addresses that no longer have an account. Updating them through another channel beats watching the message bounce. - **Deliverability.** Large mailbox providers track how often a sender hits non-existent addresses. Removing dead Yahoo addresses protects delivery of receipts, alerts and password resets. - **Sign-up review.** A nightly job over new sign-ups flags Yahoo addresses that never existed. That is a typical trait of scripted registrations. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). For a real-time answer while the user is on the form, use the [mailbox check](/services/email-verification). To cover a whole list across providers, combine Yahoo with the [Gmail](/services/gmail-email-check) and [Outlook](/services/outlook-email-check) checks in one job. ## What do you get back? Each address becomes a row with `kind: "email"` and one entry per check in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | Normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["yahoo.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | e.g. `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | CSV and NDJSON downloads keep your original row order and add `yahoo.email.status`, `yahoo.email.registered` and `yahoo.email.billed` columns. ## How is it billed? You pay the bulk price per address with a conclusive answer. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Run the free `POST /v1/jobs/estimate` first to see valid, duplicate and cached counts and the maximum cost, then pass that figure as `max_cost`. See [pricing](/pricing). Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free. ## What are the limits? The Yahoo check is **bulk only**. `POST /v1/lookup` refuses it with `403 service_disabled` ("The check 'yahoo.email' is available in bulk jobs only (POST /v1/jobs)."). A job takes up to 50,000 numbers and e-mails, as JSON or as a CSV upload with an `email` column. - Up to 20 checks per request. Identifiers × applicable checks are capped at 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more addresses in one request on one domain whose local parts differ only by digits or separators are refused with `suspected_enumeration`. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - Addresses are trimmed and lowercased, never rewritten. - The daily cap counts e-mail addresses like numbers (see `GET /v1/limits`). ## How do I use it responsibly? Check addresses from your own customers, users and leads, where you have a lawful reason to process them. We never open mailboxes and never send e-mail to the address. The answer is yes, no or unknown, and never includes personal details. An existing account does not mean consent. Don't use the check to prepare unsolicited mailings, and don't test address variations to see whether someone has a Yahoo account. The [acceptable use policy](/legal/acceptable-use) forbids both. People can object through the [opt-out form](/opt-out). Suppressed addresses are skipped and never charged. ## Example request Test keys return fixed answers for `test.mobilevalidate.com` addresses (see [test mode](/docs/test-mode)). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: yahoo-demo-001" \ -d '{"emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com", "unknown@test.mobilevalidate.com"], "checks": ["yahoo"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response of `GET /v1/jobs/{id}/results` (excerpt, test mode: the first item of `data`): ```json { "kind": "email", "input": "re•••@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "yahoo.email": { "service": "yahoo.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:31.802Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Which domains belong to Yahoo Mail? yahoo.com and Yahoo's country domains (for example yahoo.co.uk or yahoo.fr), plus the older ymail.com and rocketmail.com addresses that Yahoo Mail still serves. Send them all to the Yahoo check. ### Does the check send an e-mail or open the mailbox? No. Nothing is sent to the address and no mailbox is opened or read. The answer is yes, no or unknown, never a name, avatar or profile. ### Why is the Yahoo check only in bulk jobs? The Yahoo check is offered in bulk jobs only for now. POST /v1/lookup refuses it with service_disabled; use POST /v1/jobs. For real-time checks at sign-up, use the mailbox check (email). ### What does an unknown answer cost? Nothing. Unknown answers have registered set to null and are not charged, like every other inconclusive result. ## Service code and modes - Code: `yahoo.email` (input: e-mail address) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has a Yandex account > Check in bulk whether Yandex Mail addresses such as yandex.ru, yandex.com and ya.ru have an account. Yes, no or unknown per address; unknowns are free. Canonical: https://mobilevalidate.com/services/yandex-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Yandex check answers whether an e-mail address has a Yandex Mail account. It covers `yandex.ru`, `yandex.com`, the short `ya.ru` and older country domains. It runs in bulk jobs and returns `registered: true`, `false` or `null` (unknown) for each address, with the time we checked. Nothing is sent to the address and no mailbox is read. ## What does the Yandex check tell you? It tells you whether Yandex has a mail account at the address you send. Yandex Mail is one of the main consumer e-mail services in Russia and is widely used by Russian-speaking people in neighbouring countries. Users can receive mail under several domain names. `yandex.ru` is the classic one, `ya.ru` is a short alias, `yandex.com` is the international variant, and older accounts also used domains such as `yandex.by`, `yandex.kz` or `yandex.ua`. - `registered: true`: a Yandex account exists at this address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer. `status` and `reason` explain it, and you are not charged. The alias domains matter for data quality. The same person may appear as `name@yandex.ru` in one system and `name@ya.ru` in another. We don't rewrite addresses, so each spelling is checked and billed as its own row. Merge them before you submit if you want one answer per person. ## Who uses it, and why? The Yandex check is for businesses whose customers include Russian-speaking users. Those users often sign up with Yandex addresses, and generic webmail checks cover them less well. - **Sign-up and promo abuse.** Throwaway registrations for bonuses and trials often use invented addresses at the largest local provider. A batch run over new sign-ups flags Yandex addresses that never existed. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Deliverability for transactional mail.** Removing Yandex addresses without an account before you send receipts or security notices protects your sender reputation with the provider. - **CRM hygiene.** Mixed databases collect both `yandex.ru` and `ya.ru` spellings. Checking them shows which records still reach a real mailbox. The [Mail.ru check](/services/mailru-email-check) covers the other large Russian-language provider. Run both in one job to cover a list with Russian-speaking customers. For phone-based checks in the same region, see the [VK check](/services/vk-number-check). ## What do you get back? Each address becomes a row with `kind: "email"` and one entry per check in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | Normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["yandex.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | e.g. `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | Downloads add `yandex.email.status`, `yandex.email.registered` and `yandex.email.billed` columns in your original row order. ## How is it billed? You pay the bulk price per address with a conclusive answer. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The free `POST /v1/jobs/estimate` call returns row counts and the maximum cost first. See [pricing](/pricing) for current rates. Repeat checks of the same address inside the freshness window can come from your account's cache, and cache hits are free. ## What are the limits? The Yandex check is **bulk only**. `POST /v1/lookup` answers `403 service_disabled` ("The check 'yandex.email' is available in bulk jobs only (POST /v1/jobs)."). A job accepts up to 50,000 numbers and e-mails. - Up to 20 checks per request. Identifiers × applicable checks are capped at 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more addresses in one request on one domain whose local parts differ only by digits or separators are refused with `suspected_enumeration`. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - The daily cap counts e-mail addresses like numbers (see `GET /v1/limits`). - Job results are kept for 30 days by default and can be purged earlier. ## How do I use it responsibly? Check addresses of your own customers, users and leads, where you have a lawful basis. We never read mailboxes or send e-mail to the address. The answer is yes, no or unknown and contains no personal details. Don't use the check to find out whether a particular person uses Yandex, and don't use it to prepare unsolicited mailings. The [acceptable use policy](/legal/acceptable-use) forbids both. Anyone can object through the [opt-out form](/opt-out). Suppressed addresses are skipped and free. ## Example request Test keys return fixed answers for `test.mobilevalidate.com` addresses (see [test mode](/docs/test-mode)). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: yandex-demo-001" \ -d '{"emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com", "unknown@test.mobilevalidate.com"], "checks": ["yandex"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response of `GET /v1/jobs/{id}/results` (excerpt, test mode: the first item of `data`): ```json { "kind": "email", "input": "re•••@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "yandex.email": { "service": "yandex.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:57.575Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Which domains does Yandex Mail use? The main ones are yandex.ru, yandex.com and the short ya.ru. Some older addresses use country domains such as yandex.by or yandex.kz. Yandex treats these as alternative names for the same mailbox, but we check each address exactly as you send it. ### Does the check send an e-mail or read the mailbox? No. Nothing is sent to the address and no mailbox is opened. You get yes, no or unknown, never a name, avatar or profile. ### Is the Yandex check available in real time? No, it is offered in bulk jobs only for now. POST /v1/lookup refuses it with service_disabled; use POST /v1/jobs. ### Do you merge name@yandex.ru and name@ya.ru into one row? No. We only trim and lowercase addresses, so the two spellings are separate rows and are each checked. Merge them yourself first if you want one row per mailbox. ## Service code and modes - Code: `yandex.email` (input: e-mail address) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.0005 per check ($0.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has a Mail.ru account > Check in bulk whether mail.ru, inbox.ru, list.ru, bk.ru and internet.ru addresses have a Mail.ru account. Yes, no or unknown; unknowns are free. Canonical: https://mobilevalidate.com/services/mailru-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Mail.ru check answers whether an e-mail address has a Mail.ru account. It covers `mail.ru` and the provider's other domains: `inbox.ru`, `list.ru`, `bk.ru` and `internet.ru`. It runs in bulk jobs and returns `registered: true`, `false` or `null` (unknown) for each address, with the time we checked. Nothing is sent to the address and no mailbox is read. ## What does the Mail.ru check tell you? It tells you whether Mail.ru has a mailbox at the exact address you send. Mail.ru is one of the large Russian-language web mail services. Since 2021 it has been part of the VK group, after Mail.ru Group renamed itself VK. When people register, Mail.ru offers several domains. So a list with Russian-speaking customers typically contains `@mail.ru`, `@inbox.ru`, `@list.ru`, `@bk.ru` and `@internet.ru`. Those domains work differently from Yandex's aliases. Each one is its own address space, so `name@bk.ru` and `name@list.ru` can be two different people. That is one more reason we never rewrite or merge addresses. - `registered: true`: a Mail.ru mailbox exists at this address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer, with `status` and `reason` explaining why. You are not charged. ## Who uses it, and why? The Mail.ru check matters for businesses whose sign-ups include users in Russia and other Russian-speaking markets, where Mail.ru and [Yandex](/services/yandex-email-check) carry a large share of consumer e-mail. - **Bonus and trial abuse.** Mass registrations often use invented addresses on any of the provider's domains. A batch run over new accounts flags addresses that never existed. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Deliverability.** Dead Mail.ru addresses cause hard bounces. Removing them keeps receipts and security notices reaching the rest of your users. - **Lead and CRM quality.** A form lead on `@bk.ru` or `@list.ru` looks as plausible as one on `@mail.ru`. The check shows which of them are real mailboxes. Run Mail.ru and Yandex together in one job to cover a Russian-language list. For phone-based checks in the same region, see the [VK check](/services/vk-number-check). ## What do you get back? Each address becomes a row with `kind: "email"` and one entry per check in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | Normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["mailru.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | e.g. `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | Downloads (CSV or NDJSON) add `mailru.email.status`, `mailru.email.registered` and `mailru.email.billed` columns. ## How is it billed? You pay the bulk price per address with a conclusive answer. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Run `POST /v1/jobs/estimate` first. It's free and shows the maximum cost, which you can then pass as `max_cost`. See [pricing](/pricing). Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free. ## What are the limits? The Mail.ru check is **bulk only**. `POST /v1/lookup` refuses it with `403 service_disabled` ("The check 'mailru.email' is available in bulk jobs only (POST /v1/jobs)."). A job takes up to 50,000 numbers and e-mails. - Up to 20 checks per request. Identifiers × applicable checks are capped at 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more addresses in one request on one domain whose local parts differ only by digits or separators are refused with `suspected_enumeration`. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - The daily cap counts e-mail addresses like numbers (see `GET /v1/limits`). ## How do I use it responsibly? Check addresses of people who gave them to you, or that you otherwise have a lawful reason to process. We never read mailboxes or send e-mail to the address. The answer is yes, no or unknown and contains no personal details. Don't use the check to try the same name across `mail.ru`, `bk.ru`, `list.ru` and the other domains to find a person, and don't use it to prepare unsolicited mail. The [acceptable use policy](/legal/acceptable-use) forbids both. Anyone can object through the [opt-out form](/opt-out). Suppressed addresses are skipped and free. ## Example request Test keys return fixed answers for `test.mobilevalidate.com` addresses (see [test mode](/docs/test-mode)). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: mailru-demo-001" \ -d '{"emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com", "unknown@test.mobilevalidate.com"], "checks": ["mailru"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response of `GET /v1/jobs/{id}/results` (excerpt, test mode: the first item of `data`): ```json { "kind": "email", "input": "re•••@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "mailru.email": { "service": "mailru.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:50.198Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Which domains belong to Mail.ru? Mail.ru offers addresses on mail.ru and on its alternative domains inbox.ru, list.ru, bk.ru and internet.ru. Unlike aliases, these are separate addresses: name@bk.ru and name@list.ru can belong to different people. ### Does the check send an e-mail or read the mailbox? No. Nothing is sent to the address and no mailbox is opened. The result is yes, no or unknown, never a name, avatar or profile. ### Can I run the Mail.ru check in real time? Not yet. It is offered in bulk jobs only, and POST /v1/lookup refuses it with service_disabled. Use POST /v1/jobs. ### What happens with invalid or duplicate addresses in my file? They are marked email_status invalid_email or duplicate, are never checked and are never charged. ## Service code and modes - Code: `mailru.email` (input: e-mail address) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.00008 per check ($0.08 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address is an Apple Account > Find out in real time whether an e-mail address is used as an Apple Account (formerly Apple ID) sign-in. Yes, no or unknown; unknowns are free. Canonical: https://mobilevalidate.com/services/apple-id-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Apple Account e-mail check answers whether an e-mail address is used to sign in to an Apple Account (called Apple ID until 2024). It runs in real time and in bulk jobs, and returns `registered: true`, `false` or `null` (unknown) with the time we checked. Nothing is sent to the address, and no name, device or profile is returned. ## What does the Apple Account e-mail check tell you? It tells you whether Apple has an account whose sign-in is this e-mail address. Every iPhone, iPad and Mac user signs in with an Apple Account, and that sign-in is usually an e-mail address. It doesn't have to be an Apple address: people often use their existing Gmail, Outlook or work address. Apple renamed the account from "Apple ID" to "Apple Account" with its 2024 software releases. - `registered: true`: the address is the sign-in of an Apple Account. - `registered: false`: a conclusive "no Apple Account with this address". - `registered: null`: no conclusive answer. `status` and `reason` explain why, and you are not charged. One Apple feature affects how you read a "no". Hide My Email and Sign in with Apple let people give apps a random forwarding address instead of their real one. Those relay addresses forward mail, but they are generally not Apple Account sign-ins. A `false` for a relay address is expected, and it doesn't mean the address is fake. ## Who uses it, and why? Because an Apple Account comes with nearly every Apple device, a "yes" suggests that the address belongs to someone who has actually set up an Apple device or service. Scripted sign-ups rarely go that far. - **Sign-up and trial protection.** In real time at registration, an address with an Apple Account is a positive signal alongside the [mailbox check](/services/email-verification). A made-up address rarely has one. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Account security reviews.** When a user changes the e-mail address on an existing account, checking the new one is one input into an account-takeover risk score. See [account security](/use-cases/account-security). - **App and subscription businesses** serving iOS users use it to spot mismatches between the platform a customer claims and the address they give. For phone numbers, use the [Apple Account number check](/services/apple-id-number-check). To choose between iMessage and SMS for consented messages, see the [iMessage check](/services/imessage-number-check). ## What do you get back? Each address becomes a row with `kind: "email"` and one entry per check in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | Normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["apple.email"].registered` | boolean or null | `true` Apple Account sign-in, `false` none, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | e.g. `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | The service has no extra attributes. It never returns the account holder's name, devices or anything else about the account. ## How is it billed? You pay per address with a conclusive answer. Real-time and bulk have separate prices. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). See [pricing](/pricing) for current rates. Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free (`cached: true, billed: false`). `max_age: 0` forces a fresh, billed check, and `max_cost` caps what a request can spend. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails together, waiting up to 30 seconds) and in bulk jobs (`POST /v1/jobs`, up to 50,000). Its code is `apple.email`, which also works as its alias. Plain `apple` means the phone-number check. - Up to 20 checks per request. Identifiers × applicable checks are 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. Twenty or more addresses in one request on one domain whose local parts differ only by digits or separators are refused with `suspected_enumeration`. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - Addresses are trimmed and lowercased, never rewritten. - The daily cap counts e-mail addresses like numbers (see `GET /v1/limits`). ## How do I use it responsibly? Check addresses that your own users and customers gave you, for fraud prevention and account protection. We never read mailboxes, never send e-mail to the address and never return a name, avatar or profile. The answer is only yes, no or unknown. Don't use the check to find out whether a specific person uses Apple products, and don't use it to build target lists. The [acceptable use policy](/legal/acceptable-use) forbids that. Anyone can object through the [opt-out form](/opt-out). Suppressed addresses are skipped and never charged. ## Example request With a test key, `registered@test.mobilevalidate.com` always answers yes (see [test mode](/docs/test-mode)). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com"], "checks": ["apple.email"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "apple.email": { "service": "apple.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:32.184Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Is Apple ID the same as Apple Account? Yes. Apple renamed Apple ID to Apple Account in 2024. The check answers whether an e-mail address is used as the sign-in for one. ### Does it only work for iCloud addresses? No. An Apple Account can be created with almost any e-mail address, not only icloud.com, me.com or mac.com. The check answers for the address you send, whatever its domain. ### What about Hide My Email or Sign in with Apple relay addresses? Those are forwarding addresses that Apple creates so people don't have to share their real address. They are usually not the sign-in of an Apple Account, so a no answer for such an address is expected and says nothing about the person. ### Does the check contact the person or reveal anything about the account? No. Nothing is sent to the address, and no name, device, photo or profile is returned. The answer is yes, no or unknown. ### Can I check a phone number instead? Yes, use the Apple Account phone number check, which answers the same question for a phone number. ## Service code and modes - Code: `apple.email` (input: e-mail address) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has an Amazon account > Find out in real time whether an e-mail address is used for an Amazon customer account. Yes, no or unknown per address; unknowns are free. Canonical: https://mobilevalidate.com/services/amazon-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Amazon e-mail check answers whether an e-mail address is used for an Amazon customer account. It runs in real time and in bulk jobs, and returns `registered: true`, `false` or `null` (unknown) for each address, with the time we checked. Nothing is sent to the address, and no name, order history or profile is returned. ## What does the Amazon e-mail check tell you? It tells you whether Amazon has a customer account that signs in with this address. Amazon accounts can use either an e-mail address or a mobile number as their sign-in. This check covers the e-mail side. The [Amazon number check](/services/amazon-number-check) covers phone numbers. - `registered: true`: an Amazon account is associated with the address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer. `status` and `reason` explain why, and you are not charged. A "yes" shows that someone once completed Amazon's sign-up with this address. That is a small but real sign that the address is in active use. It says nothing about who holds the account. ## Who uses it, and why? - **E-commerce and marketplace sign-ups.** Online shops and marketplaces use it as one extra signal at registration or checkout, next to the [mailbox check](/services/email-verification). A fabricated address rarely has an account at a large retailer. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Account security.** When a customer changes the e-mail on an existing account, the check is one input into a risk score. See [account security](/use-cases/account-security). ## What do you get back? | Field | Type | Meaning | |---|---|---| | `email`, `email_status` | string, enum | Normalized address; `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["amazon.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status`, `…reason` | enum, string | Outcome, and why an answer is not conclusive | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | ## How is it billed? You pay per address with a conclusive answer. Real-time and bulk have separate prices, shown on [pricing](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Cache hits inside the freshness window are free. `max_age: 0` forces a fresh, billed check. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails) and in bulk (`POST /v1/jobs`, up to 50,000). Use the code `amazon.email`. The alias `amazon` means the phone-number check. A request can hold up to 20 checks. Requests that look like sequential number ranges or generated e-mail lists are rejected: 20 or more addresses in one request on one domain that differ only by digits or separators, or, with live keys, 50 or more of one such pattern per account per UTC day. The daily cap counts e-mails like numbers. ## How do I use it responsibly? Check addresses of your own customers and users, for fraud prevention and account protection. We never read mailboxes or send e-mail to the address, and the answer is only yes, no or unknown. Don't use the check to find out whether a particular person shops on Amazon, and don't use it to build marketing lists. The [acceptable use policy](/legal/acceptable-use) forbids both. Anyone can object through the [opt-out form](/opt-out). ## Example request ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com"], "checks": ["amazon.email"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "amazon.email": { "service": "amazon.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:45.701Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check reveal anything about the Amazon account? No. It only answers whether an Amazon account is associated with the address: yes, no or unknown. No name, order history, address or profile is returned, and nothing is sent to the address. ### Can I check phone numbers too? Yes. Amazon accounts can also use a mobile number as their sign-in. Use the Amazon number check for phone numbers. ### Is the check available in real time? Yes. Send it with POST /v1/lookup using the code amazon.email. It also runs in bulk jobs. ### What does an unknown answer cost? Nothing. Unknown answers have registered set to null and are not charged. ## Service code and modes - Code: `amazon.email` (input: e-mail address) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0005 per check ($0.50 per 1,000) - Bulk (POST /v1/jobs): $0.0003 per check ($0.30 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has a Facebook account > Find out in real time whether an e-mail address is associated with a Facebook account. Yes, no or unknown; no names or profiles; unknowns are free. Canonical: https://mobilevalidate.com/services/facebook-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Facebook e-mail check answers whether an e-mail address is associated with a Facebook account. It runs in real time and in bulk jobs, and returns `registered: true`, `false` or `null` (unknown), with the time we checked. Nothing is sent to the address, and no name, photo or profile is ever returned. ## What does the Facebook e-mail check tell you? It tells you whether Facebook has an account that uses this e-mail address, either for sign-in or as a contact address. Facebook lets people register and sign in with an e-mail address or a mobile number. This page covers e-mail. The [Facebook number check](/services/facebook-number-check) covers phone numbers. - `registered: true`: a Facebook account is associated with the address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer (`status` and `reason` say why), not charged. Messenger conversations run on Facebook accounts. Instagram accounts are separate, even when they are linked through Meta's Accounts Center, so Instagram has its own [e-mail check](/services/instagram-email-check). ## Who uses it, and why? - **Sign-up and fake-account screening.** Communities, marketplaces and apps use a "yes" as a sign that an address has a history outside their own service. A fabricated address usually doesn't. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Lead verification.** A form lead whose address has no mailbox and no account on any large platform is often invented. See [lead verification](/use-cases/lead-verification). ## What do you get back? | Field | Type | Meaning | |---|---|---| | `email`, `email_status` | string, enum | Normalized address; `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["facebook.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status`, `…reason` | enum, string | Outcome, and why an answer is not conclusive | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | ## How is it billed? You pay per address with a conclusive answer. Real-time and bulk have separate prices, shown on [pricing](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Cache hits inside the freshness window are free. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails) and in bulk jobs (`POST /v1/jobs`, up to 50,000). Use the code `facebook.email`. The alias `facebook` means the phone-number check. A request can hold up to 20 checks. Requests that look like sequential number ranges or generated e-mail lists are rejected: 20 or more addresses in one request on one domain that differ only by digits or separators, or, with live keys, 50 or more of one such pattern per account per UTC day. ## How do I use it responsibly? Use the check for fraud prevention and data quality on addresses your users gave you. We never read mailboxes or send e-mail to the address, and the answer is only yes, no or unknown. Don't use it to find someone's social media presence or to build audiences. The [acceptable use policy](/legal/acceptable-use) forbids profiling individuals and unsolicited messaging. People can object through the [opt-out form](/opt-out). ## Example request ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com"], "checks": ["facebook.email"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "facebook.email": { "service": "facebook.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:46.234Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check return the Facebook profile? No. It only answers whether a Facebook account is associated with the address: yes, no or unknown. No name, photo, profile link or friends list is ever returned. ### Does the person get notified? No. Nothing is sent to the address and no action is taken on the account. ### Is a Facebook account the same as a Messenger or Instagram account? Messenger uses Facebook accounts. Instagram accounts are separate, even though they can be linked to a Facebook account in Meta's Accounts Center. Use the Instagram e-mail check for Instagram. ### What does an unknown answer cost? Nothing. Inconclusive answers are never charged. ## Service code and modes - Code: `facebook.email` (input: e-mail address) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.003 per check ($3.00 per 1,000) - Bulk (POST /v1/jobs): $0.0015 per check ($1.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has an Instagram account > Find out in real time whether an e-mail address is associated with an Instagram account. Yes, no or unknown; no usernames or profiles; unknowns free. Canonical: https://mobilevalidate.com/services/instagram-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Instagram e-mail check answers whether an e-mail address is associated with an Instagram account. It runs in real time and in bulk jobs, and returns `registered: true`, `false` or `null` (unknown), with the time we checked. Nothing is sent to the address, and no username, photo or profile is returned. ## What does the Instagram e-mail check tell you? It tells you whether Instagram has an account that uses this e-mail address. Instagram accounts can be registered with an e-mail address or a mobile number. For numbers, use the [Instagram number check](/services/instagram-number-check). Instagram is owned by Meta but keeps its own accounts. Linking an Instagram account to a Facebook account in Meta's Accounts Center does not merge them, so the [Facebook e-mail check](/services/facebook-email-check) can give a different answer for the same address. - `registered: true`: an Instagram account is associated with the address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer, with `status` and `reason` explaining why. Not charged. ## Who uses it, and why? - **Creator and community platforms.** Services whose users are active on Instagram use a "yes" as one signal that a new sign-up is a real person rather than a script. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Lead verification.** Combined with the [mailbox check](/services/email-verification), it helps separate real consumer leads from invented addresses. See [lead verification](/use-cases/lead-verification). ## What do you get back? | Field | Type | Meaning | |---|---|---| | `email`, `email_status` | string, enum | Normalized address; `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["instagram.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status`, `…reason` | enum, string | Outcome, and why an answer is not conclusive | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | ## How is it billed? You pay per address with a conclusive answer. Real-time and bulk have separate prices, shown on [pricing](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Cache hits inside the freshness window are free. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails) and in bulk jobs (`POST /v1/jobs`, up to 50,000). Use the code `instagram.email`. The alias `instagram` means the phone-number check. A request can hold up to 20 checks. Requests that look like sequential number ranges or generated e-mail lists are rejected: 20 or more addresses in one request on one domain that differ only by digits or separators, or, with live keys, 50 or more of one such pattern per account per UTC day. ## How do I use it responsibly? Use the check on addresses your users gave you, for fraud prevention and data quality. We never read mailboxes or send e-mail to the address, and the answer is only yes, no or unknown. Don't use it to look up someone's social media presence or to build audiences for unsolicited messages. The [acceptable use policy](/legal/acceptable-use) forbids both. People can object through the [opt-out form](/opt-out). ## Example request ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com"], "checks": ["instagram.email"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "instagram.email": { "service": "instagram.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:32.298Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check return the Instagram username or profile? No. It only answers whether an Instagram account is associated with the address: yes, no or unknown. No username, photo or profile is returned. ### Is this the same as the Facebook check? No. Instagram accounts are separate from Facebook accounts, even when they are linked in Meta's Accounts Center. Use both checks if you need both answers. ### Does the person find out? No. Nothing is sent to the address and nothing happens on the account. ### What does an unknown answer cost? Nothing. Inconclusive answers are never charged. ## Service code and modes - Code: `instagram.email` (input: e-mail address) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.003 per check ($3.00 per 1,000) - Bulk (POST /v1/jobs): $0.0015 per check ($1.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has a Netflix account > Find out in real time whether an e-mail address is used for a Netflix account. Yes, no or unknown per address; no account details; unknowns are free. Canonical: https://mobilevalidate.com/services/netflix-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Netflix e-mail check answers whether an e-mail address is used for a Netflix account. It runs in real time and in bulk jobs, and returns `registered: true`, `false` or `null` (unknown), with the time we checked. Nothing is sent to the address, and no plan, payment or viewing information is returned. ## What does the Netflix e-mail check tell you? It tells you whether Netflix has an account that uses this address. Netflix accounts are created with an e-mail address, and a phone number can be added for sign-in and recovery. For numbers, use the [Netflix number check](/services/netflix-number-check). A "yes" says nothing about whether the account is paid, paused or cancelled. It only shows that the address was used to create an account. Netflix reported more than 300 million paid memberships in its Q4 2024 shareholder letter, so a "no" for a consumer address is common and is not a warning sign by itself. - `registered: true`: a Netflix account is associated with the address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer (`status` and `reason` say why). Not charged. ## Who uses it, and why? - **Subscription and trial businesses.** Services that fight repeated free-trial sign-ups use a "yes" at a large consumer subscription service as a sign that an address is in real, long-term use. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Consumer lead quality.** Next to the [mailbox check](/services/email-verification), it helps tell real consumer addresses from invented ones. ## What do you get back? | Field | Type | Meaning | |---|---|---| | `email`, `email_status` | string, enum | Normalized address; `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["netflix.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status`, `…reason` | enum, string | Outcome, and why an answer is not conclusive | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | ## How is it billed? You pay per address with a conclusive answer. Real-time and bulk have separate prices, shown on [pricing](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Cache hits inside the freshness window are free. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails) and in bulk jobs (`POST /v1/jobs`, up to 50,000). Use the code `netflix.email`. The alias `netflix` means the phone-number check. A request can hold up to 20 checks. Requests that look like sequential number ranges or generated e-mail lists are rejected: 20 or more addresses in one request on one domain that differ only by digits or separators, or, with live keys, 50 or more of one such pattern per account per UTC day. ## How do I use it responsibly? Check addresses of your own users and customers, for fraud prevention and data quality. We never read mailboxes or send e-mail to the address, and the answer is only yes, no or unknown. Don't use it to find out what services a specific person subscribes to, and don't use it for unsolicited marketing. The [acceptable use policy](/legal/acceptable-use) forbids both. People can object through the [opt-out form](/opt-out). ## Example request ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com"], "checks": ["netflix.email"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "netflix.email": { "service": "netflix.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:51.333Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check reveal the subscription or plan? No. It only answers whether a Netflix account is associated with the address: yes, no or unknown. No plan, payment status, profile or viewing data is returned. ### Does a yes mean the address belongs to a paying subscriber? No. It means an account uses the address. The account may be active, paused or cancelled. ### Can I check phone numbers as well? Yes. Netflix accounts can also have a phone number for sign-in and recovery. Use the Netflix number check for numbers. ### What does an unknown answer cost? Nothing. Inconclusive answers are never charged. ## Service code and modes - Code: `netflix.email` (input: e-mail address) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.002 per check ($2.00 per 1,000) - Bulk (POST /v1/jobs): $0.0015 per check ($1.50 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has a Spotify account > Find out in real time whether an e-mail address is used for a Spotify account. Yes, no or unknown; no profile or listening data; unknowns are free. Canonical: https://mobilevalidate.com/services/spotify-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The Spotify e-mail check answers whether an e-mail address is used for a Spotify account. It runs in real time and in bulk jobs, and returns `registered: true`, `false` or `null` (unknown), with the time we checked. Nothing is sent to the address, and no profile, playlist or listening data is returned. ## What does the Spotify e-mail check tell you? It tells you whether Spotify has an account associated with the address you send. Spotify offers a free, ad-supported tier alongside paid plans, so an account says nothing about payment. It only shows that someone created a Spotify account with the address. Spotify reported more than 600 million monthly active users in its Q4 2024 results, which makes a "yes" common for addresses in daily consumer use. Some people sign up through another account provider, such as Google, Apple or Facebook, and the account's e-mail may then be a different address. A "no" for an address therefore doesn't mean the person doesn't use Spotify. It only means this address isn't linked to an account. - `registered: true`: a Spotify account is associated with the address. - `registered: false`: a conclusive "no account" for this address. - `registered: null`: no conclusive answer (`status`, `reason`). Not charged. ## Who uses it, and why? - **Consumer app sign-ups.** A "yes" at a widely used free service is a light signal that an address is in real use. It is most useful combined with the [mailbox check](/services/email-verification). See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Trial abuse screening** for subscription businesses, together with the [Netflix e-mail check](/services/netflix-email-check). ## What do you get back? | Field | Type | Meaning | |---|---|---| | `email`, `email_status` | string, enum | Normalized address; `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["spotify.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status`, `…reason` | enum, string | Outcome, and why an answer is not conclusive | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | ## How is it billed? You pay per address with a conclusive answer. Real-time and bulk have separate prices, shown on [pricing](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Cache hits inside the freshness window are free. ## What are the limits? The check runs in real time (`POST /v1/lookup`, up to 100 numbers and e-mails) and in bulk jobs (`POST /v1/jobs`, up to 50,000). Use the code `spotify.email`. There is no short alias. It is an e-mail-only check, and there is no Spotify phone-number check. A request can hold up to 20 checks. Requests that look like sequential number ranges or generated e-mail lists are rejected: 20 or more addresses in one request on one domain that differ only by digits or separators, or, with live keys, 50 or more of one such pattern per account per UTC day. ## How do I use it responsibly? Check addresses your own users and customers gave you, for fraud prevention and data quality. We never read mailboxes or send e-mail to the address, and the answer is only yes, no or unknown. Don't use it to profile a person's services or interests, or for unsolicited marketing. The [acceptable use policy](/legal/acceptable-use) forbids both. People can object through the [opt-out form](/opt-out). ## Example request ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["registered@test.mobilevalidate.com"], "checks": ["spotify.email"]}' ``` Response (excerpt, test mode: the first item of `results`): ```json { "kind": "email", "input": "registered@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "spotify.email": { "service": "spotify.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:32.383Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check return the Spotify profile or playlists? No. It only answers whether a Spotify account is associated with the address: yes, no or unknown. No username, profile, playlists or listening data is returned. ### Does a yes mean the person pays for Spotify? No. Spotify has free and paid plans. A yes only means an account uses the address. ### Why might an existing Spotify user show as no? People can sign up for Spotify through another account provider, such as Google, Apple or Facebook, or with a different e-mail address. The check answers only for the exact address you send. ### What does an unknown answer cost? Nothing. Inconclusive answers are never charged. ## Service code and modes - Code: `spotify.email` (input: e-mail address) - Modes: realtime and bulk · worldwide ## Price (live) - Realtime (POST /v1/lookup): $0.0003 per check ($0.30 per 1,000) - Bulk (POST /v1/jobs): $0.00015 per check ($0.15 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has a LinkedIn account > Check in bulk whether an e-mail address is associated with a LinkedIn account, to qualify B2B leads. Yes, no or unknown; no profiles; unknowns free. Canonical: https://mobilevalidate.com/services/linkedin-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The LinkedIn e-mail check answers whether an e-mail address is associated with a LinkedIn account. It runs in bulk jobs and returns `registered: true`, `false` or `null` (unknown) for each address, with the time we checked. It is built for qualifying B2B leads. Nothing is sent to the address, and no name, employer, job title or profile is returned. ## What does the LinkedIn e-mail check tell you? It tells you whether LinkedIn has an account that uses the address, as the sign-in or as one of the addresses on the account. LinkedIn, owned by Microsoft since 2016, is the main professional network. Its accounts can carry more than one e-mail address, typically a personal one and sometimes a work one. That last point is the key to reading the result: - `registered: true`: an account is associated with the address. For a work address, that is a useful sign the address belongs to a real professional who uses it. - `registered: false`: a conclusive "no account for this address". For **work addresses** this is common and neutral, because many people keep LinkedIn on a personal address. - `registered: null`: no conclusive answer (`status` and `reason` say why). Not charged. ## Who uses it, and why? The LinkedIn check is a B2B tool. It helps sales and marketing teams qualify people who have already come to them. - **Inbound lead qualification.** Demo requests, trial sign-ups and gated-content forms fill up with fake and throwaway entries. A lead whose address has a mailbox and a LinkedIn account is more likely to be a real professional. See [lead verification](/use-cases/lead-verification). - **CRM hygiene.** A batch run over older B2B contacts, together with the [mailbox check](/services/email-verification) or [Outlook check](/services/outlook-email-check), shows which addresses are still in use. - **Trial abuse in B2B software.** Repeated free trials from invented addresses rarely come with a LinkedIn account. Since "no" is normal for many work addresses, use the result to prioritise leads, not to reject them. For phone numbers, the [LinkedIn number check](/services/linkedin-number-check) covers US and Indian numbers. ## What do you get back? Each address becomes a row with `kind: "email"` and one entry per check in `checks`. | Field | Type | Meaning | |---|---|---| | `email` | string or null | Normalized address (trimmed, lowercased); `null` when invalid | | `email_status` | enum | `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["linkedin.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status` | enum | `completed`, `pending`, `unknown`, `unsupported_country` or `failed` | | `…reason` | string or null | e.g. `UPSTREAM_TIMEOUT` | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | Downloads add `linkedin.email.status`, `linkedin.email.registered` and `linkedin.email.billed` columns in your original row order. ## How is it billed? You pay the bulk price per address with a conclusive answer. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The free `POST /v1/jobs/estimate` call shows the maximum cost before you start. See [pricing](/pricing). Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free. ## What are the limits? The LinkedIn e-mail check is **bulk only**. `POST /v1/lookup` refuses it with `403 service_disabled` ("The check 'linkedin.email' is available in bulk jobs only (POST /v1/jobs)."). Use the code `linkedin.email`. The alias `linkedin` means the phone-number check. A job takes up to 50,000 numbers and e-mails, as JSON or as a CSV upload with an `email` column. - Up to 20 checks per request. Identifiers × applicable checks are capped at 100,000 per job. - Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more addresses in one request on one domain whose local parts differ only by digits or separators are refused with `suspected_enumeration`. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - The daily cap counts e-mail addresses like numbers (see `GET /v1/limits`). ## How do I use it responsibly? Check the addresses of leads and customers who contacted you or signed up. Never use the check to guess work addresses (`firstname.lastname@company`) or to find people for cold outreach. That is list building, and the [acceptable use policy](/legal/acceptable-use) forbids it. We never read mailboxes or send e-mail to the address, and the answer is only yes, no or unknown, never a profile. Anyone can object through the [opt-out form](/opt-out). ## Example request Test keys return fixed answers for `test.mobilevalidate.com` addresses (see [test mode](/docs/test-mode)). ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: linkedin-email-demo-001" \ -d '{"emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com", "unknown@test.mobilevalidate.com"], "checks": ["linkedin.email"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response of `GET /v1/jobs/{id}/results` (excerpt, test mode: the first item of `data`): ```json { "kind": "email", "input": "re•••@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "linkedin.email": { "service": "linkedin.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:47.920Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check return the LinkedIn profile, name or job title? No. It only answers whether a LinkedIn account is associated with the address: yes, no or unknown. No profile, name, employer or job title is ever returned. ### A real business contact came back as no. Is the lead fake? Not necessarily. Many people register LinkedIn with a personal address rather than their work address, so a work address often has no account even for a genuine professional. Treat no as neutral for work addresses. ### Why is the LinkedIn e-mail check only in bulk jobs? It is offered in bulk jobs only for now. POST /v1/lookup refuses it with service_disabled; use POST /v1/jobs. GET /v1/services always shows the current modes. ### Can I check phone numbers for LinkedIn? Yes, with the LinkedIn number check, which covers numbers from the US and India only. ### Can I use it to find people for outreach? No. The check qualifies leads who already came to you. Using it to discover people or build outreach lists breaks the acceptable use policy. ## Service code and modes - Code: `linkedin.email` (input: e-mail address) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.00005 per check ($0.05 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Check if an e-mail address has an X (Twitter) account > Check in bulk whether an e-mail address is associated with an X (formerly Twitter) account. Yes, no or unknown; no usernames or profiles; unknowns free. Canonical: https://mobilevalidate.com/services/x-twitter-email-check · Last updated: 2026-09-25 ![An e-mail envelope routed to mail servers found in DNS; two mailboxes are confirmed and one answer is unknown.](https://mobilevalidate.com/images/email-mailbox-verification.svg) *E-mail checks look up the domain's mail servers, then whether the mailbox exists.* The X e-mail check answers whether an e-mail address is associated with an X account (the service was called Twitter until 2023). It runs in bulk jobs and returns `registered: true`, `false` or `null` (unknown) for each address, with the time we checked. Nothing is sent to the address, and no username, handle or profile is returned. ## What does the X e-mail check tell you? It tells you whether X has an account that uses this e-mail address. X accounts can be registered with an e-mail address or a phone number. For numbers, use the [X number check](/services/x-twitter-number-check). Accounts created before the 2023 rename are the same accounts, so the name makes no difference to the answer. - `registered: true`: an X account is associated with the address. - `registered: false`: a conclusive "no account". - `registered: null`: no conclusive answer (`status` and `reason` explain it). Not charged. ## Who uses it, and why? - **Sign-up review in batches.** A periodic job over new registrations uses an account at a large public platform as one signal that an address is in real use, next to the [mailbox check](/services/email-verification). See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Lead data quality.** It works together with the [LinkedIn e-mail check](/services/linkedin-email-check) when cleaning older contact records. ## What do you get back? | Field | Type | Meaning | |---|---|---| | `email`, `email_status` | string, enum | Normalized address; `valid`, `invalid_email`, `duplicate` or `suppressed` | | `checks["x.email"].registered` | boolean or null | `true` account exists, `false` none, `null` unknown | | `…status`, `…reason` | enum, string | Outcome, and why an answer is not conclusive | | `…confidence`, `…checked_at` | enum, timestamp | How sure the answer is, and when it was obtained | | `…cached`, `…billed` | boolean | Cache hit, and whether it was charged | ## How is it billed? You pay the bulk price per address with a conclusive answer, shown on [pricing](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The free `POST /v1/jobs/estimate` call shows the maximum cost first, and cache hits are free. ## What are the limits? The check is **bulk only**. `POST /v1/lookup` answers `403 service_disabled` ("The check 'x.email' is available in bulk jobs only (POST /v1/jobs)."). Use the code `x.email`. The aliases `x` and `twitter` mean the phone-number check. A job takes up to 50,000 numbers and e-mails, and a request up to 20 checks. Requests that look like sequential number ranges or generated e-mail lists are rejected: 20 or more addresses in one request on one domain that differ only by digits or separators, or, with live keys, 50 or more of one such pattern per account per UTC day. ## How do I use it responsibly? Check addresses your users and customers gave you, for fraud prevention and data quality. We never read mailboxes or send e-mail to the address, and the answer is only yes, no or unknown. Don't use it to find someone's social media account or to build audiences. The [acceptable use policy](/legal/acceptable-use) forbids both. People can object through the [opt-out form](/opt-out). ## Example request ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: x-email-demo-001" \ -d '{"emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com", "unknown@test.mobilevalidate.com"], "checks": ["x.email"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` Response of `GET /v1/jobs/{id}/results` (excerpt, test mode: the first item of `data`): ```json { "kind": "email", "input": "re•••@test.mobilevalidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "x.email": { "service": "x.email", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:25:55.285Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, "test": true } ``` ## Frequently asked questions ### Does the check return the X username or profile? No. It only answers whether an X account is associated with the address: yes, no or unknown. No username, handle, photo or profile is returned. ### Is X the same as Twitter? Yes. Twitter was renamed X in 2023. Accounts created under either name are covered. ### Why is the X e-mail check only in bulk jobs? It is offered in bulk jobs only for now. POST /v1/lookup refuses it with service_disabled; use POST /v1/jobs. ### What does an unknown answer cost? Nothing. Inconclusive answers are never charged. ## Service code and modes - Code: `x.email` (input: e-mail address) - Modes: bulk only · worldwide ## Price (live) - Realtime: not available (bulk only) - Bulk (POST /v1/jobs): $0.00015 per check ($0.15 per 1,000) - You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Response fields (from the public catalog) | Field | Type | Meaning | |---|---|---| | registered | boolean or null | true = found, false = not found, null = unknown (not charged) | | status | enum | completed, pending, unknown, unsupported_country, failed | | checked_at | timestamp | When the answer was obtained | _Platform and brand names are used only to describe which service a check refers to. MobileValidate is not affiliated with, endorsed by or sponsored by any of these companies; all trademarks belong to their owners._ --- # Carrier and line type lookup, explained > What carrier and line type data is, allocated vs current network, how porting changes it, and how to read every field of a carrier lookup answer. Canonical: https://mobilevalidate.com/blog/carrier-and-line-type-lookup-explained · Last updated: 2026-09-25 ![Cover: Carrier and line type lookup, explained](https://mobilevalidate.com/og/blog/carrier-and-line-type-lookup-explained.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Carrier lookup, Line type, Mobile number portability, SMS routing, Data quality A carrier and line type lookup tells you two things about a phone number: what kind of line it is (mobile, landline, VoIP, toll-free and so on) and which network serves it today. The important word is *today*. A number's prefix only shows the network its range was originally allocated to. After porting, the current network can be different, and so can the line type. This guide explains where that data comes from, how to read each field, and how MobileValidate's carrier lookups answer. ## What is carrier and line type data? It is descriptive data about a number, not about a person or a handset. Two fields do most of the work: - **Line type** says what kind of service the number belongs to. It decides whether an SMS can arrive, what a call costs and which rules apply. The [line type glossary entry](/glossary/line-type) lists every value. - **Carrier** names the network that serves the number. It decides how messages are routed and priced, and it's a useful signal for fraud and data quality. Neither field tells you whether the phone is switched on, who owns the number or whether it has been reassigned to a new subscriber. Those are different questions with different checks, as our guide on [how to check if a number is active](/blog/how-to-check-if-a-phone-number-is-active) explains. ## Where does carrier and line type information come from? From three kinds of source, each with a different view of the number. **Numbering plans.** The international structure of phone numbers is defined in ITU-T Recommendation E.164, currently in its February 2026 edition ([ITU, 2026](https://www.itu.int/rec/T-REC-E.164)). Within that structure, each country's regulator allocates blocks of numbers to operators and designates which ranges are mobile, geographic, toll-free, premium rate and so on. Offline libraries such as [libphonenumber](https://github.com/google/libphonenumber) encode this: its `getNumberType` distinguishes fixed-line, mobile, toll-free, premium rate, shared cost, VoIP, personal numbers, UAN, pager and voicemail "whenever feasible". That's the range, not today's service. **Network identifiers.** Mobile networks are identified by a mobile country code and a mobile network code under ITU-T E.212 ([ITU, 2024](https://www.itu.int/rec/T-REC-E.212)). Together they form the [MCC-MNC](/glossary/mcc-mnc), which pins down a specific network rather than a brand name. **Porting records.** In countries with number portability, operators keep track of numbers that moved to another network, so calls and messages can still be routed. Those records are what turn "allocated to network A" into "served by network B today". A carrier lookup combines these views. The result is only as good as the underlying data for that country, which is why coverage varies and why a lookup should say "no data" rather than fall back to a guess based on the prefix. ## What is the difference between the allocated carrier and the current one? The allocated carrier is the network a number's range was originally given to. The current carrier is the one serving the number now. Before portability they were the same. Today they often aren't. In the US, the FCC's deadline for wireless local number portability was 24 November 2003. The FCC describes wireless LNP as a consumer's ability to change service providers within the same local area and keep the same number, and notes that it also allows moving a number from a wireline phone to a wireless phone in some cases ([FCC](http://web.archive.org/web/20251124041815/https://www.fcc.gov/general/wireless-local-number-portability-wlnp)). So a US number's area code and exchange tell you where it was first assigned, not which carrier or even which *kind* of line it is on now. That has two practical effects: - **Carrier can change** without any change to the digits. Routing and pricing by prefix then go wrong. - **Line type can change too**, for example from landline to mobile, or from mobile to a VoIP service. Our post on [why carrier lookups can be wrong after porting](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong) covers porting mechanics and failure modes in depth. ## What does each line type mean for your decision? The value matters less than what you do with it. A practical reading: | `line_type` | SMS | Voice | At sign-up | |---|---|---|---| | `mobile` | Send | Call | Normal | | `fixed_line` | Don't send; offer a voice code | Call | Normal for voice-based flows | | `fixed_line_or_mobile` | Plan doesn't separate them; send and watch delivery reports | Call | Normal; consider another signal | | `voip` | Sometimes works | Call | A signal for extra verification, not a block | | `toll_free` | Rarely relevant for consumers | Call | Worth a second look | | `premium_rate`, `shared_cost` | Don't send | Avoid | Very unlikely for a genuine sign-up | | `personal`, `uan`, `pager`, `voicemail` | Don't send | Depends | Worth a second look | | `unknown` | Keep your default | Keep your default | Treat as missing data | VoIP deserves a note. It is a legitimate service used by many real people, and NIST's current authentication guidance removed its earlier prohibition on VoIP numbers for out-of-band authentication ([NIST, 2025](https://pages.nist.gov/800-63-4/sp800-63b.html)). Our [VoIP detection guide](/blog/voip-number-detection-for-signups) explains how to use it proportionately. ## How does the MobileValidate carrier lookup respond? The [carrier lookup](/services/carrier-lookup) (`network.carrier`, alias `carrier`) works for numbers in every country, in real time and in bulk jobs. It is labelled **beta** because coverage varies by country. It returns data in `attributes`: | Attribute | Meaning | |---|---| | `line_type` | One of the values in the table above | | `carrier` | Current carrier name, as far as our data shows | | `original_carrier` | Carrier the range was allocated to; only present when it differs from `carrier` | | `country` | ISO 3166-1 alpha-2 country of the number | An answer is conclusive only when `carrier` is present. Attributes we don't hold are left out rather than guessed. Here is a real test-mode request with four numbers: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003", "+14155552671"], "checks": ["carrier"], "wait": 5}' ``` The four `network.carrier` results, trimmed: ```json {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "billed": false, "reason": null} {"status": "unknown", "registered": null, "attributes": null, "billed": false, "reason": "NO_DATA"} {"status": "unknown", "registered": null, "attributes": null, "billed": false, "reason": "UPSTREAM_TIMEOUT"} {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "US"}, "billed": false, "reason": null} ``` Three things to notice. A conclusive data answer has `registered: true`, which here means "data found", not "registered on an app". A number we hold nothing for comes back `unknown` with `reason: "NO_DATA"`, and a slow source gives `UPSTREAM_TIMEOUT`. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Test data never includes a port, so `original_carrier` doesn't appear here. In live answers it appears when the number has moved. ## How should you read carrier names? As labels, not as keys. Carrier names come with some traps: - **Virtual operators.** Many retail brands run on another company's network. The lookup can return the network operator's name, which differs from the brand on the customer's bill. - **Mergers and rebrands.** Operator names change over time. Two spellings can refer to the same network. - **Resellers of VoIP numbers.** The name may be a wholesale provider rather than the app the person uses. So avoid hard-coding exact string matches in routing or fraud rules. Group names into your own normalized list, review unknown names regularly, and rely on `line_type` for decisions about what kind of line it is. The [HLR lookup](/services/hlr-lookup), coming soon, will add the current network's `mcc_mnc`, which is a stable code rather than a name. ## When should you use the US/CA premium lookup? When most of your numbers are in the United States or Canada and you work with lists. There, porting between landline, wireless and VoIP services is common, and the [US and Canada carrier lookup](/services/us-carrier-lookup) (`network.carrier_us`) is the more specific option. It returns `line_type` and the current `carrier`, and runs in **bulk jobs only**. A real-time request is refused. This is the real test-mode error: ```json {"error": {"code": "service_disabled", "status": 403, "param": "checks[0]", "message": "The check 'network.carrier_us' is available in bulk jobs only (POST /v1/jobs)."}} ``` As a job, with a US number, a Canadian number, a Dominican Republic number (also `+1`) and a test number with no data: ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: carrier-us-demo-001" \ -d '{"numbers": ["+12025550143", "+14165550123", "+18095550100", "+447700900002"], "checks": ["network.carrier_us"]}' ``` Rows from `GET /v1/jobs/{id}/results` (real test-mode output, trimmed): ```json {"input": "+12025****43", "country": "US", "status": "completed", "attributes": {"line_type": "mobile", "carrier": "Test Carrier"}, "billed": false} {"input": "+14165****23", "country": "CA", "status": "completed", "attributes": {"line_type": "mobile", "carrier": "Test Carrier"}, "billed": false} {"input": "+18095****00", "country": "DO", "status": "unsupported_country", "reason": "UNSUPPORTED_COUNTRY", "billed": false} {"input": "+44770*****02", "country": "GB", "status": "unknown", "reason": "NO_DATA", "billed": false} ``` The Dominican Republic number shares the `+1` country code but isn't covered, so it's `unsupported_country` and free. Identifiers in job results are masked. Run the free `POST /v1/jobs/estimate` first to see the maximum cost. See [bulk jobs](/docs/bulk-jobs). ## How fresh does carrier data need to be? Fresh enough for the decision you're making. Every conclusive answer has `checked_at`. Keep it next to the stored carrier. - **Before a campaign or a routing change**, re-check lists older than a few months. Numbers port continuously. - **Before a sensitive action**, such as a password reset or a payout to a new number, check again and compare with the stored carrier. A change of carrier or line type shortly before such an action deserves a step-up. - **Repeat checks** inside the freshness window can be served from your account's cache for free (`cached: true`, `billed: false`). Send `max_age: 0` to force a fresh, billed check. One limit is worth repeating. Carrier data doesn't tell you whether a number now belongs to a different person. In the US, the FCC's [Reassigned Numbers Database](http://web.archive.org/web/20260324041308/https://www.fcc.gov/reassigned-numbers-database) exists for that question, so callers can avoid calling a consumer who received a number previously held by someone else. ## What are the key takeaways? - Carrier and line type data describes the number: what kind of line it is and which network serves it now. It says nothing about the person or whether the phone is on. - The prefix shows the **allocated** network. After porting, the **current** carrier and even the line type can differ. - MobileValidate's carrier lookup returns `line_type`, `carrier`, `original_carrier` (only when it differs) and `country`, for every country, in real time or bulk. It's in beta because coverage varies. - No data means `unknown` with `NO_DATA`, and it's free. Don't treat it as a fake number. - Use carrier names as labels, not keys, and base line decisions on `line_type`. - For US and Canadian lists, use the bulk-only [US/CA carrier lookup](/services/us-carrier-lookup). Other `+1` countries come back `unsupported_country` at no charge. ## Sources 1. [ITU-T E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164) — International Telecommunication Union, 2026 2. [ITU-T E.212: The international identification plan for public networks and subscriptions](https://www.itu.int/rec/T-REC-E.212) — International Telecommunication Union, 2024 3. [Wireless Local Number Portability (WLNP)](http://web.archive.org/web/20251124041815/https://www.fcc.gov/general/wireless-local-number-portability-wlnp) — Federal Communications Commission (archived copy), 2025 4. [Reassigned Numbers Database](http://web.archive.org/web/20260324041308/https://www.fcc.gov/reassigned-numbers-database) — Federal Communications Commission (archived copy), 2026 5. [libphonenumber](https://github.com/google/libphonenumber) — Google, 2026 6. [NIST SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management](https://pages.nist.gov/800-63-4/sp800-63b.html) — NIST, 2025 ## Frequently asked questions ### What is the difference between a carrier lookup and an HLR lookup? A carrier lookup describes the number: its line type and the network that serves it according to numbering and porting data. An HLR lookup asks the home mobile network live whether the subscriber is reachable right now. MobileValidate's HLR lookup is coming soon. ### Why does the lookup return original_carrier for some numbers and not others? original_carrier is the network the number's range was allocated to. It is only returned when it differs from the current carrier, which usually means the number was ported. ### What happens when there is no carrier data for a number? The check returns status unknown with reason NO_DATA and registered null, and you are not charged. Treat it as missing data, not as a sign that the number is fake. ### Why is the carrier name different from the brand on the customer's bill? Many brands are virtual operators that run on another company's network, and operators merge and rebrand. The lookup returns the network name as held in our data, which can differ from the retail brand. ### Can I run the US/CA carrier lookup in real time? No. network.carrier_us runs in bulk jobs only. A real-time request is refused with 403 service_disabled. For real-time answers on any country, use the general carrier lookup. --- # How to check if a phone number uses iMessage > What iMessage registration means, blue vs green bubbles, iMessage vs RCS on iPhone, what businesses can and can't send, and a bulk API example. Canonical: https://mobilevalidate.com/blog/check-if-a-number-has-imessage · Last updated: 2026-09-25 ![Cover: How to check if a phone number uses iMessage](https://mobilevalidate.com/og/blog/check-if-a-number-has-imessage.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: iMessage, RCS, Channel selection, Bulk jobs, Consent To check whether a phone number uses iMessage, run an iMessage registration check in a bulk job. Each number comes back as a dated yes, no or unknown. Because iMessage exists only on Apple devices, a registered number almost always means an iPhone. A business can't use the answer to start iMessage conversations, though. It's a device-platform and channel-planning signal. This guide explains what the answer means, how iMessage relates to RCS and SMS on iPhone, and how to run the check with the API. ## What does iMessage registration actually mean? iMessage is Apple's messaging service, built into the Messages app on iPhone, iPad, Mac and other Apple devices. When someone activates an iPhone, iMessage normally registers the phone number so that other Apple users can reach it. According to [Apple's support page](https://support.apple.com/en-us/104972), iMessage sends messages "to another iPhone or another Apple device over Wi-Fi or cellular-data networks", with end-to-end encryption, read receipts and typing indicators. A registration check answers whether the number is registered for iMessage right now: - `registered: true`: the number is registered for iMessage on an Apple device. - `registered: false`: a conclusive answer that it isn't. Common for Android users. - `registered: null`: no conclusive answer, and not charged. There's no Android version of iMessage and nobody installs it separately. So `true` is strong evidence of an Apple device, which is why teams use it for device-platform planning. It says nothing about who the person is. We don't return Apple Account details, device models or anything else. ## What do blue and green bubbles tell you? The bubble colour shows which service carried a message. Apple's support page is explicit: messages sent with iMessage "appear in blue text bubbles", while RCS and SMS/MMS messages "appear in green text bubbles" ([Apple](https://support.apple.com/en-us/104972)). | Bubble on an iPhone | Service | Typical reason | |---|---|---| | Blue | iMessage | Both sides use Apple devices with iMessage on | | Green | RCS | Other side isn't on iMessage; both carriers support RCS (iOS 18 or later) | | Green | SMS/MMS | Other side isn't on iMessage and RCS isn't available | For a consumer, green is just a colour. For a business planning messages, it's a hint about which features will work: an iMessage-registered customer receiving a text from a business number still gets it as SMS, or as RCS if the sender uses RCS, never as iMessage. The registration check tells you about the handset, not about how your own messages will travel. ## How does iMessage relate to RCS on iPhone? Since iOS 18, released in September 2024, the Messages app "supports RCS for richer media and more reliable group messaging compared to SMS and MMS" when messaging contacts who don't have an Apple device ([Apple, 2024](https://www.apple.com/newsroom/2024/09/ios-18-is-available-today-making-iphone-more-personal-and-capable-than-ever/)). Apple's support page adds that RCS on iPhone needs iOS 18 and a carrier that supports RCS on iPhone. That creates a three-tier fallback inside the Messages app: iMessage first, then RCS, then SMS/MMS. For a business, the two checks answer different questions: | Question | Check | Mode | |---|---|---| | Is this customer on an Apple device? | `imessage` | bulk only | | Can this number receive RCS right now? | `rcs` (with `device_os` when reported) | bulk only | | Is it a mobile number at all? | `carrier` (`line_type`) | real time + bulk | If you plan to send branded RCS messages, the [RCS capability check](/blog/rcs-capability-check-explained) is the one that matters. The iMessage check tells you how many of your customers are on iPhones, which helps you preview message formats on the right platform. ## Can a business send iMessages? Not to a phone number. Apple's business channel is **Apple Messages for Business**, and it works differently from SMS or WhatsApp: - **The customer starts the conversation.** Apple's FAQ says customers initiate a conversation by tapping a Messages button on your website, app, e-mail, QR code, Apple Maps listing or registered business phone number ([Apple FAQ](https://register.apple.com/resources/messages/messaging-documentation/faq)). - **You don't see the phone number.** Apple generates an anonymous identifier for each conversation, and "your team never sees the customer's phone number unless the customer chooses to share it". - **Proactive messages have limits.** Businesses can send them only to customers who previously started a conversation, and only when it's in the customer's direct interest, such as order updates or appointment reminders. When a customer ends a conversation, you stop. - **You need an approved messaging service provider** to run it ([Apple](https://register.apple.com/resources/messages/messaging-documentation/)). So an iMessage registration answer never opens an outbound channel. It helps you decide where to put a "Message us" button and which formats to design for. Apple also asks businesses to call the service "Apple Messages" rather than "iMessage" in customer-facing communications. ## Why can the answer be wrong or out of date? Registration can outlast the device. Apple's own help page exists for this case: you "may need to turn off iMessage if you are now using a non-Apple phone and cannot get SMS or text messages someone sends you from an iPhone" ([Apple](https://selfsolve.apple.com/deregister-imessage/)). Until the person turns iMessage off or deregisters the number, it can still appear as registered. | Situation | What the check may say | How to read it | |---|---|---| | Customer moved from iPhone to Android recently | `true` | Stale registration; re-check later | | Customer uses iMessage only with an e-mail address | `false` | iMessage can register e-mail addresses too; the number isn't registered | | Number reassigned by the carrier | `true` or `false` for the new owner | Always read with `checked_at` | | Timeout or temporary problem | `null` (unknown) | Missing data; free | Treat the answer as a dated observation, not a fact about the person. Refresh monthly and don't use a single answer for anything with consequences for the customer. ## How do I run the iMessage check with the API? Create a bulk job. With a test key, test jobs finish at once, and the [test numbers](/docs/test-mode) return every state. A job can combine iMessage with RCS and WhatsApp: ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: imessage-guide-001" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003", "+447700900005"], "checks": ["imessage", "rcs", "whatsapp"]}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` The real test-mode job reported `"progress": {"total": 4, "checks_total": 12, "done": 12, "conclusive": 6, "non_billable": 12}`. The `imessage.registered` answers per row: ```json {"e164": "+447700900001", "status": "completed", "registered": true, "reason": null} {"e164": "+447700900002", "status": "completed", "registered": false, "reason": null} {"e164": "+447700900003", "status": "unknown", "registered": null, "reason": "UPSTREAM_TIMEOUT"} {"e164": "+447700900005", "status": "unsupported_country", "registered": null, "reason": "UNSUPPORTED_COUNTRY"} ``` In live mode iMessage accepts numbers from every country; `…005` is the test number that simulates `unsupported_country`. Only conclusive answers are billed, at the bulk price shown on the [pricing page](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Test keys never bill. ## How should I act on each answer? Use the answer for planning and formats, with SMS or your default channel always available: | Answer | Message design | Channel planning | Stored record | |---|---|---|---| | `imessage: true` | Preview links and media on iPhone; keep text readable as SMS | Consider an Apple Messages entry point on your site | `true` + `checked_at` | | `imessage: false`, `rcs: true` | Design for RCS on Android or iPhone | Candidate for RCS with SMS fallback | Both answers + dates | | `imessage: false`, `rcs: false` | Plain SMS | SMS or a consented app channel | Both answers + dates | | `null` (unknown) | Default format | Keep previous plan | Don't overwrite a stored answer | At aggregate level, the share of `true` answers in your consented base tells you how much of your audience is on iPhone in each country. That's useful for deciding whether to invest in Apple Messages for Business, or in RCS templates tested on both platforms. The [channel-by-country guide](/blog/choosing-a-messaging-channel-by-country) shows how to compute rates per country from a job download. ## What are the responsible-use rules? Device platform is personal information. This is general guidance, not legal advice, so consult counsel about your own situation. Keep to four rules: 1. **Check only numbers people gave you**, such as customers and sign-ups, for channel and format planning. Under the [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) you need a lawful basis (Article 6) and should keep only what you need (Article 5(1)(c)). 2. **Don't infer anything else.** Owning an iPhone isn't a proxy for income, age or any personal trait. Our [acceptable use policy](/legal/acceptable-use) forbids profiling and inferring sensitive characteristics. 3. **Don't treat it as permission.** The answer never replaces consent for any channel. 4. **No range scans.** Requests with 20 or more consecutive numbers are refused with `403 suspected_enumeration`. People can object through our [opt-out form](/opt-out). iMessage and Apple are named descriptively; MobileValidate is not affiliated with Apple. ## What are the key takeaways? - An iMessage check tells you whether a number is **registered for iMessage on an Apple device**: registered, not registered or unknown, with `checked_at`. - It runs in **bulk jobs only**. The real-time endpoint answers `403 service_disabled`. - Blue bubbles mean iMessage; green means RCS or SMS/MMS. iPhone has supported RCS since iOS 18 (2024), where carriers support it. - Businesses **can't send iMessages to phone numbers**. Apple Messages for Business is customer-initiated and hides the number. - Registration can outlast a move to Android, so read every answer with its date and refresh regularly. - Use it for format and channel planning over consented customers. See the [iMessage check](/services/imessage-number-check) for details and the [messaging-app checks guide](/blog/messaging-app-registration-checks-guide) for every other app. ## Sources 1. [What is the difference between iMessage, RCS, and SMS/MMS?](https://support.apple.com/en-us/104972) — Apple, 2026 2. [Deregister and Turn Off iMessage](https://selfsolve.apple.com/deregister-imessage/) — Apple, 2026 3. [iOS 18 is available today, making iPhone more personal and capable than ever](https://www.apple.com/newsroom/2024/09/ios-18-is-available-today-making-iphone-more-personal-and-capable-than-ever/) — Apple, 2024 4. [Apple Messages for Business](https://register.apple.com/resources/messages/messaging-documentation/) — Apple, 2026 5. [Apple Messages for Business Frequently Asked Questions](https://register.apple.com/resources/messages/messaging-documentation/faq) — Apple, 2026 6. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 ## Frequently asked questions ### Can I check iMessage registration in real time? No. The iMessage check runs in bulk jobs (POST /v1/jobs) only. POST /v1/lookup refuses it with 403 service_disabled. Run it ahead of time over your opted-in customers and store the answers. ### What do blue and green bubbles mean? On an iPhone, messages sent with iMessage appear in blue bubbles. RCS and SMS/MMS messages appear in green bubbles. Green therefore means the conversation isn't using iMessage, not that the other phone is broken. ### Can my business send iMessages to customers' phone numbers? No. Apple Messages for Business conversations are started by the customer through entry points such as a website button or Apple Maps, and the business doesn't see the phone number unless the customer shares it. ### Why is a number that moved to Android still registered? iMessage registration can outlast a switch to another phone until the number is turned off or deregistered. Apple provides a deregistration page for exactly this situation. ### Is the person notified when their number is checked? No. Nothing is sent to the number, and no name, Apple Account or device detail is returned. The answer is registered, not registered or unknown. --- # How to check if a phone number is on WhatsApp with an API > How a WhatsApp registration check works, what registered, not registered and unknown mean, and how to call it responsibly from cURL, Node and Python. Canonical: https://mobilevalidate.com/blog/check-if-a-number-is-on-whatsapp-api-guide · Last updated: 2026-09-25 ![Cover: How to check if a phone number is on WhatsApp with an API](https://mobilevalidate.com/og/blog/check-if-a-number-is-on-whatsapp-api-guide.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Developers · Tags: WhatsApp, API, Channel selection, Consent, Developers To check whether a phone number is on WhatsApp, send the number to a registration-check API and read back one of three answers: registered, not registered or unknown, each with a timestamp. With MobileValidate that is one `POST /v1/lookup` call with `checks: ["whatsapp"]`. This guide covers how such a check works, how to handle every answer in code, and how to use it responsibly. ## What does a WhatsApp registration check actually answer? It answers one narrow question: is a WhatsApp account associated with this phone number right now? Nothing more. It doesn't say who owns the number, whether they read messages, or whether the phone is switched on. WhatsApp ties each account to a phone number and verifies that number with a code at sign-up. So an account is a useful sign that the number was in use on a smartphone at some point. That makes the check valuable for two jobs: - **Channel selection.** Before you send a passcode, a delivery update or a reminder the person asked for, you know whether WhatsApp is an option at all. See [channel selection](/use-cases/channel-selection). - **A deliverability signal.** A number with an account is less likely to be a typo or a made-up entry. It is not proof of identity. Because WhatsApp is so widely used ([WhatsApp announced two billion users in 2020](https://blog.whatsapp.com/two-billion-users-connecting-the-world-privately)), a "no" in a market where almost everyone uses it is informative. A "no" in a market where other apps dominate says much less. The [channel-by-country guide](/blog/choosing-a-messaging-channel-by-country) covers that. ## How does a registration check work, conceptually? Messaging apps need a way for users to find friends. When you install one, it can compare the numbers in your address book with its account directory and show which contacts use the app. This is called **contact discovery**. A registration check asks the same kind of question for a single number: does the directory hold an account for it? The answer comes back as one of three states: | State | Meaning | Billed? | |---|---|---| | `registered: true` | An account exists for the number | Yes | | `registered: false` | A conclusive answer: no account | Yes | | `registered: null` | No conclusive answer (timeout, service unavailable, unsupported country) | No | Two design rules follow from this. First, **unknown is never a no**. A timeout says nothing about the number, so the API reports `null` and the `reason`. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Second, **every answer is dated**. `checked_at` says when the answer was obtained. Accounts come and go, and numbers get reassigned by carriers, so an answer from last quarter is weaker than one from this morning. We don't describe the upstream mechanics in more detail, and you shouldn't need them. Your integration depends only on the three states, the timestamp and the reason codes. ## How do I make my first request? Use a test key (`mv_test_…`). Test keys are free, never reach a real network and return fixed answers for the numbers in the reserved range `+44 7700 900000–900999`. `…001` is registered, `…002` is not, `…003` is unknown and `…004` stays pending for about five seconds. See [test mode](/docs/test-mode). ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003", "+447700900004"], "checks": ["whatsapp"], "wait": 2}' ``` With a short `wait` of 2 seconds, the request returns before `…004` is done. This is real test-mode output (the `summary`, `next` and the four check results): ```json { "id": "lkp_0VWFWKcBPOs4UJIn7CeW", "status": "pending", "next": {"poll_url": "/v1/lookups/lkp_0VWFWKcBPOs4UJIn7CeW", "poll_after_ms": 2000}, "summary": {"total": 4, "registered": 1, "not_registered": 1, "unknown": 1, "pending": 1, "invalid": 0, "suppressed": 0} } ``` ```json {"service": "whatsapp.registered", "status": "completed", "registered": true, "confidence": "high", "checked_at": "2026-09-25T16:15:04.516Z", "billed": false, "reason": null} {"service": "whatsapp.registered", "status": "completed", "registered": false, "confidence": "high", "checked_at": "2026-09-25T16:15:04.516Z", "billed": false, "reason": null} {"service": "whatsapp.registered", "status": "unknown", "registered": null, "confidence": null, "checked_at": null, "billed": false, "reason": "UPSTREAM_TIMEOUT"} {"service": "whatsapp.registered", "status": "pending", "registered": null, "confidence": null, "checked_at": null, "billed": false, "reason": null, "poll_after_ms": 2000} ``` (Excerpt: some fields are trimmed. `billed` is `false` here because test keys never bill.) ## How do I handle pending answers? Most answers arrive within the default 10-second `wait`. The rest come back as `pending`, and the whole lookup has `status: "pending"` plus a `next.poll_url`. You have two options: 1. **Poll.** `GET /v1/lookups/{id}?wait=10` long-polls (`wait` can be up to 30 seconds) and returns as soon as the lookup completes. Respect `poll_after_ms` between polls. 2. **Webhook.** Pass a verified `webhook_endpoint_id` and receive `lookup.completed`. See [webhooks](/docs/webhooks). Polling the lookup above five seconds later returned `"status": "completed"`, and the fourth result became: ```json {"service": "whatsapp.registered", "status": "completed", "registered": true, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T16:15:11.607Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null} ``` In a user-facing flow such as sign-up, don't block on a pending answer. Continue with your default channel and use the result for the next message. ## What does the code look like in Node and Python? Both examples send numbers in the POST body (never in the URL), poll while the lookup is pending, and map each answer to a channel decision. **Node.js / TypeScript (built-in `fetch`, Node 18+):** ```ts const API = "https://api.mobilevalidate.com"; const headers = { Authorization: `Bearer ${process.env.MOBILEVALIDATE_API_KEY}`, "Content-Type": "application/json", }; type Check = { status: string; registered: boolean | null; reason: string | null; checked_at: string | null }; export async function whatsappStatus(numbers: string[]) { let res = await fetch(`${API}/v1/lookup`, { method: "POST", headers, body: JSON.stringify({ numbers, checks: ["whatsapp"], wait: 10 }), }); let lookup = await res.json(); while (lookup.status === "pending") { await new Promise((r) => setTimeout(r, lookup.next?.poll_after_ms ?? 2000)); res = await fetch(`${API}/v1/lookups/${lookup.id}?wait=10`, { headers }); lookup = await res.json(); } return lookup.results.map((r: any) => { const c: Check | undefined = r.checks?.["whatsapp.registered"]; const channel = c?.registered === true ? "whatsapp" : "sms"; // null (unknown) falls back, never blocks return { e164: r.e164, number_status: r.number_status, registered: c?.registered ?? null, reason: c?.reason ?? null, checked_at: c?.checked_at ?? null, channel }; }); } ``` **Python (`requests`):** ```python import os, time, requests API = "https://api.mobilevalidate.com" H = {"Authorization": f"Bearer {os.environ['MOBILEVALIDATE_API_KEY']}"} def whatsapp_status(numbers, default_country=None): body = {"numbers": numbers, "checks": ["whatsapp"], "wait": 10} if default_country: body["default_country"] = default_country # needed for national formats like "07700 900001" lookup = requests.post(f"{API}/v1/lookup", json=body, headers=H, timeout=40).json() while lookup.get("status") == "pending": time.sleep((lookup.get("next") or {}).get("poll_after_ms", 2000) / 1000) lookup = requests.get(f"{API}/v1/lookups/{lookup['id']}", params={"wait": 10}, headers=H, timeout=40).json() out = [] for r in lookup["results"]: c = (r.get("checks") or {}).get("whatsapp.registered") or {} out.append({"e164": r.get("e164"), "number_status": r["number_status"], "registered": c.get("registered"), "reason": c.get("reason"), "checked_at": c.get("checked_at")}) return out ``` Production code should also handle HTTP errors: `429 rate_limited` (retry after `Retry-After`), `402 insufficient_balance`, and `403 suspected_enumeration`. The [errors reference](/docs/errors) lists them all. ## How should my application act on each answer? Store the answer with its timestamp and decide per message type. A reasonable starting table: | Answer | Sign-up / OTP | Order updates the customer opted into | CRM record | |---|---|---|---| | `registered: true` | Offer WhatsApp as a delivery option; keep SMS as fallback | Send on WhatsApp if the customer chose it | Store `true` + `checked_at` | | `registered: false` | Go straight to SMS or voice | Use SMS or e-mail | Store `false` + `checked_at` | | `registered: null` (`unknown`) | Use your default channel | Keep the previous routing | Don't overwrite a stored answer | | `number_status: invalid_number` | Ask the user to correct the number | Fix the record | Flag for cleanup | | `number_status: suppressed` | Use your default channel | Use your default channel | Don't check again | Two practical rules. **Never overwrite a conclusive stored answer with `null`.** A timeout today doesn't erase what you learned last week. And **refresh on a schedule**, for example monthly, or when a WhatsApp delivery fails. Repeat checks inside the freshness window are served from your account's cache for free (`cached: true, billed: false`). ## When should I use the WhatsApp Business check instead? Request `whatsapp.business` when you need to know whether the account is a business account, for example to tell a supplier's support line apart from a personal number in a B2B CRM. It answers both questions at once. If a request asks for both `whatsapp` and `whatsapp.business`, they collapse into one `whatsapp.business` result. Real test-mode output for `+447700900006` (registered, business) and `+447700900002` (no account): ```json {"whatsapp.business": {"service": "whatsapp.business", "status": "completed", "registered": true, "attributes": {"business": true}, "confidence": "high", "checked_at": "2026-09-25T16:15:11.650Z", "billed": false}} {"whatsapp.business": {"service": "whatsapp.business", "status": "completed", "registered": false, "attributes": {"business": false}, "confidence": "high", "checked_at": "2026-09-25T16:15:11.650Z", "billed": false}} ``` The older top-level `whatsapp` object mirrors this result with a `business` field, so integrations written for the first API version keep working. In real time the `business` flag may be `null` even when `registered` is conclusive. See the [WhatsApp Business check](/services/whatsapp-business-check). ## What are the limits, and why do they exist? | Limit | Value | |---|---| | Numbers (and e-mails) per real-time lookup | 100 | | Identifiers per bulk job | 50,000 | | Checks per request | 20 | | Identifiers × checks | 2,000 per lookup, 100,000 per job | | Request rate per key | 10 per second, burst 20 | | Consecutive numbers in one request | 19 at most; 20 or more → `403 suspected_enumeration` | The last rule deserves an explanation. Contact discovery can be abused to map who uses an app. In 2021, researchers showed they could query [10% of US mobile numbers against WhatsApp and 100% against Signal](https://eprint.iacr.org/2020/1119) with modest resources. In 2025, researchers from the University of Vienna and SBA Research reported that WhatsApp's contact discovery allowed the [enumeration of 3.5 billion accounts](https://www.univie.ac.at/en/news/press-room/press-releases/detail/researchers-discover-security-vulnerability-in-whatsapp), which Meta has since mitigated. A check API must not become a shortcut for that kind of mapping. So we refuse sequential ranges, cap daily volume per account, and return only yes/no/unknown, never names, photos or profiles. See [rate limits and abuse](/docs/rate-limits-and-abuse). ## How do I use the check responsibly? The number usually belongs to a person, so the check is likely to be processing of personal data. Three rules help you stay within data-protection law and platform policy (for your specific case, consult your own counsel): 1. **Check only numbers you have a lawful reason to process**: customers, sign-ups, and leads who gave you their number. Under the [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) you need a lawful basis (Article 6), and data minimisation (Article 5(1)(c)) suggests keeping only what you need: the answer and its date, not the whole response. 2. **Use the answer to choose a channel, not to start conversations.** The [WhatsApp Business Messaging Policy](https://business.whatsapp.com/policy) says businesses may only contact people who gave them their number and opted in to receive messages. A `true` answer is not consent. 3. **Respect objections.** People can object through our [opt-out form](/opt-out). Suppressed numbers are skipped and never charged, and you should honour objections in your own systems too. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging, building profiles of individuals and checking ranges of numbers to find out who uses WhatsApp. ## What are the key takeaways? - A WhatsApp check returns **registered, not registered or unknown**, always with `checked_at`. Only conclusive answers are billed. - **Unknown is missing data.** Fall back to your default channel and never overwrite a stored conclusive answer with it. - Use a short `wait` in user-facing flows and **poll `GET /v1/lookups/{id}`** or use a webhook for pending answers. - Request **`whatsapp.business`** when you also need the business flag. - The check is for **numbers you already hold** and for picking a channel for expected messages. Sequential ranges are refused by design. - Build and test every branch for free with the [test numbers](/docs/test-mode), then read the [WhatsApp check](/services/whatsapp-number-check) page for pricing and coverage. ## Sources 1. [WhatsApp Business Messaging Policy](https://business.whatsapp.com/policy) — WhatsApp, 2026 2. [Researchers discover security vulnerability in WhatsApp](https://www.univie.ac.at/en/news/press-room/press-releases/detail/researchers-discover-security-vulnerability-in-whatsapp) — University of Vienna, 2025 3. [All the Numbers are US: Large-scale Abuse of Contact Discovery in Mobile Messengers (NDSS 2021)](https://eprint.iacr.org/2020/1119) — IACR ePrint / NDSS, 2021 4. [Two Billion Users: Connecting the World Privately](https://blog.whatsapp.com/two-billion-users-connecting-the-world-privately) — WhatsApp, 2020 5. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 ## Frequently asked questions ### Does checking a number send a WhatsApp message or notify the person? No. The check answers whether an account is associated with the number. No message is sent, and nothing appears on the person's phone. No name, photo or profile is returned. ### What should my code do when the answer is unknown? Treat unknown (registered: null) as missing data, not as a no. Keep your default channel, such as SMS, and check again later. Unknown answers are not charged. ### Why was my request refused with suspected_enumeration? The request contained 20 or more consecutive numbers. Checking number ranges is how people try to build lists of platform users, so the API refuses it. Check only numbers you already hold, such as customers and sign-ups. ### Can I use the result to start WhatsApp conversations with new contacts? No. WhatsApp's business messaging policy requires that people gave you their number and opted in to receive messages from you. Use the check to pick a channel for messages people already expect. ### How do I find out whether the account is a WhatsApp Business account? Request whatsapp.business instead of whatsapp. It answers both questions: whether an account exists, and in the business attribute whether it is a business account. In real time the business flag may be null. --- # How to choose a messaging channel by country > WhatsApp, Telegram, Viber, LINE, Zalo, RCS or SMS? How to pick a channel per country for messages customers expect, and measure it on your own base. Canonical: https://mobilevalidate.com/blog/choosing-a-messaging-channel-by-country · Last updated: 2026-09-25 ![Cover: How to choose a messaging channel by country](https://mobilevalidate.com/og/blog/choosing-a-messaging-channel-by-country.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Deliverability · Tags: Channel selection, Messaging, Deliverability, RCS, SMS The right messaging channel depends on the country, but more on the customer. Start from the channels a customer opted into, check which of them their number is actually registered on, and fall back to SMS. Country patterns help you decide which channels to offer at all. Your own registration rates per country tell you which ones are worth building. ## Why does channel choice differ by country? Messaging habits formed differently in each market. Where one app became the default early, most people use it for everything, from family chats to delivery updates. Elsewhere several apps share the market, or SMS stayed the everyday channel. For a business sending messages people expect, such as passcodes, order updates or appointment reminders, this has direct consequences: - **Delivery.** A message on a channel the customer doesn't use fails or sits unread. - **Cost.** Where customers have opted into an app channel, sending there may cost less than an SMS to the same number, depending on your provider's rates. - **Features.** App channels and RCS support rich media, buttons and read receipts. SMS does not. - **Compliance.** Every channel has its own opt-in rules on top of data-protection law. This guide covers seven channels and gives verified numbers only where a platform published them. Everything else is described qualitatively, because usage estimates from third parties vary widely and change quickly. ## Which channels matter, and where? | Channel | Where it is strong (qualitative) | Published scale | Our check | Modes | |---|---|---|---|---| | WhatsApp | Default messenger in many countries | [Two billion users (WhatsApp, 2020)](https://blog.whatsapp.com/two-billion-users-connecting-the-world-privately) | `whatsapp`, `whatsapp.business` | real time + bulk | | Telegram | Widely used in a number of markets, often alongside another messenger | ["Over 1 billion active users" (Telegram FAQ, 2026)](https://telegram.org/faq) | `telegram` | real time + bulk | | Viber | Widely used in parts of Eastern Europe and Southeast Asia | — | `viber` | real time + bulk | | LINE | Widely used in Japan and Taiwan | — | `line` | bulk only | | Zalo | Widely used in Vietnam | — | `zalo` | real time + bulk | | RCS | Default messaging on many Android phones; iPhone since iOS 18 ([Apple, 2024](https://www.apple.com/newsroom/2024/09/ios-18-is-available-today-making-iphone-more-personal-and-capable-than-ever/)) | — | `rcs` (with `device_os`) | bulk only | | iMessage | iPhone users | — | `imessage` | bulk only | | SMS | Works on every mobile line | — | `carrier` (line type) | real time + bulk | The published figures are global and self-reported by each platform. They tell you a channel is large, not that your customers in a given country use it. The next sections show how to measure that. ## Is SMS still the baseline? Yes. SMS is the only channel that reaches every mobile number without an app, so it is the fallback in every routing plan. Its weaknesses are cost, lack of rich features and exposure to abuse such as [SMS pumping](/blog/sms-pumping-how-it-works-and-how-to-stop-it). Two checks make SMS routing safer: - **Line type.** The [carrier lookup](/services/carrier-lookup) returns `line_type`. Texts to `fixed_line`, `toll_free` or `premium_rate` numbers usually fail, but may still be charged. Route those to voice or e-mail. - **Current network.** Where your provider prices by destination network, the current `carrier` matters more than the prefix, because numbers move between networks. See [why carrier lookups can be wrong](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong). A practical rule: **never drop SMS from the plan**, even in markets where an app dominates. It covers new customers, customers who changed phones and every case where an app check is unknown. ## How do RCS and iMessage fit in? RCS (Rich Communication Services) upgrades the phone's built-in messaging app with rich media, typing indicators and branded business messages. Many Android phones use it by default, and [iPhone added RCS support with iOS 18 in 2024](https://www.apple.com/newsroom/2024/09/ios-18-is-available-today-making-iphone-more-personal-and-capable-than-ever/) for conversations with non-Apple devices. For a business this makes RCS attractive: no separate app is needed, and messages can fall back to SMS. Our [RCS capability check](/services/rcs-capability-check) runs in bulk jobs and can return `device_os`, which helps you preview a rich message on the right handset platform. The [iMessage check](/services/imessage-number-check) tells you whether a number is registered with Apple's messaging service, which indicates an Apple device. Both are bulk only, so run them over your opted-in base ahead of time rather than before each message. Sending business messages over RCS usually also requires a messaging provider that supports it and an approved sender. A capability answer tells you the number can receive RCS, not that your sender can reach it there. ## How do I measure channel reach on my own customer base? Platform totals and market reports can't tell you what share of *your* customers in Vietnam use Zalo or what share in Japan use LINE. A bulk job over your consented customer base can. The method: 1. Export the numbers of customers who opted in to messages, with their country if you have it. 2. Estimate the job for free with `POST /v1/jobs/estimate`, then create it with the channels you could actually send on, for example `["whatsapp", "telegram", "viber", "line", "zalo", "rcs"]`. 3. Download the CSV. It has a `country` column (derived from the number) and one `.registered` column per check. 4. Compute the registration rate per country and channel, counting only conclusive answers. A job with three test numbers and four channels returned this (real test-mode results, trimmed to the `registered` values): | Number | WhatsApp | LINE | RCS | iMessage | |---|---|---|---|---| | `+447700900001` | true | true | true | true | | `+447700900002` | false | false | false | false | | `+447700900010` | true | true | false | true | Test answers are invented. Your live base will show real differences between countries. ## What does the per-country analysis look like in code? This Python sketch reads the downloaded CSV and prints the registration rate per country and channel. It skips unknown answers, so they don't count as "no": ```python import csv from collections import defaultdict CHANNELS = ["whatsapp.registered", "telegram.registered", "viber.registered", "line.registered", "zalo.registered", "rcs.registered"] yes, total = defaultdict(int), defaultdict(int) with open("job-results.csv", newline="") as f: for row in csv.DictReader(f): if row["number_status"] != "valid": continue # invalid, duplicate, suppressed: never checked for ch in CHANNELS: if row.get(f"{ch}.status") != "completed": continue # unknown / unsupported: not a "no" key = (row["country"], ch.split(".")[0]) total[key] += 1 yes[key] += row[f"{ch}.registered"] == "true" for (country, ch), n in sorted(total.items()): if n >= 200: # ignore small samples print(f"{country} {ch:9} {yes[(country, ch)] / n:6.1%} (n={n})") ``` Treat the output as a planning input: it shows where a channel integration pays off and where SMS remains the main route. Only customers who opted in belong in the input file. The results describe your base, not the country's population. ## How should routing work once I have the data? Combine three things per customer: the channels they consented to, the channels their number is registered on, and your cost and feature needs per message type. A routing table might look like this: | Message type | First choice | Fallback | Condition | |---|---|---|---| | One-time passcode | Consented app channel with `registered: true` | SMS, then voice | Short timeout before fallback | | Order and delivery updates | Customer's preferred consented channel | SMS or e-mail | Registration checked within the last month | | Appointment reminders | Consented app channel or RCS | SMS | — | | Service outage notices | SMS (widest reach) | E-mail | Every affected customer | Store each answer with its `checked_at` and treat `registered: null` as "no new information": keep the previous routing. Real-time checks (WhatsApp, Telegram, Viber, Zalo) suit onboarding, when a customer first gives you a number. Bulk jobs suit monthly refreshes of the whole base and are the only way to check LINE, RCS, iMessage and Signal. See [channel selection](/use-cases/channel-selection) for the full workflow. ## What rules apply to consent? Channel data is only useful for messages people agreed to receive. Three rules: - **Opt-in per channel.** The [WhatsApp Business Messaging Policy](https://business.whatsapp.com/policy) allows businesses to contact people only if they gave their number and opted in to receive messages. Other platforms and national SMS rules have similar requirements. A `registered: true` answer is never a substitute for consent. - **Lawful basis and minimal data.** Checking a customer's number is likely to be processing of personal data. Under the [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) you need a lawful basis (Article 6), and data minimisation (Article 5(1)(c)) suggests storing only what routing needs: the answer and its date. Your counsel can confirm what applies to you. - **No list building.** Don't check numbers you don't already hold. Our API refuses sequential number ranges (`suspected_enumeration`) and our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging. People can object to checks through the [opt-out form](/opt-out). Suppressed numbers are skipped and not charged. ## What are the key takeaways? - **Country patterns tell you which channels to integrate; per-customer checks tell you where to send.** Use both. - Verified public scale figures are few: WhatsApp reported two billion users (2020), Telegram reports over one billion active users (2026), and iPhone supports RCS since iOS 18 (2024). Treat other usage claims with care. - **SMS stays in every plan** as the universal fallback. Check `line_type` before sending. - WhatsApp, Telegram, Viber and Zalo checks run **in real time**. LINE, RCS, iMessage and Signal run **in bulk jobs only**. - Measure registration rates **on your own consented base** with a bulk job, and skip unknown answers in the maths. - Consent comes first. Registration means a channel *can* reach someone, not that you *may* message them. For the API details, see the [WhatsApp check guide](/blog/check-if-a-number-is-on-whatsapp-api-guide). ## Sources 1. [Two Billion Users: Connecting the World Privately](https://blog.whatsapp.com/two-billion-users-connecting-the-world-privately) — WhatsApp, 2020 2. [Telegram FAQ](https://telegram.org/faq) — Telegram, 2026 3. [iOS 18 is available today, making iPhone more personal and capable than ever](https://www.apple.com/newsroom/2024/09/ios-18-is-available-today-making-iphone-more-personal-and-capable-than-ever/) — Apple, 2024 4. [WhatsApp Business Messaging Policy](https://business.whatsapp.com/policy) — WhatsApp, 2026 5. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 ## Frequently asked questions ### Which messaging channel should I use in each country? There is no single table that fits every business. Start from the channels your customers opted into, check which of them each customer's number is registered on, and measure registration rates per country on your own consented base. Keep SMS as the fallback. ### Can I check LINE, iMessage and RCS in real time? No. LINE, iMessage, RCS and Signal checks run in bulk jobs only. WhatsApp, Telegram, Viber and Zalo checks run in real time and in bulk jobs. ### Does registered on WhatsApp mean I can message the customer there? No. Registration only means the channel can reach the number. You still need the customer's opt-in for that channel. WhatsApp's business messaging policy requires it. ### How often should I refresh channel data? Channel registrations change slowly. Monthly refreshes, plus a re-check after a failed delivery, are enough for most customer bases. Repeat checks inside the freshness window come free from your account's cache. --- # E.164 phone number format: a practical guide for developers > How to normalize phone numbers to E.164 in JavaScript and Python, the pitfalls that break deduplication, and how the MobileValidate API normalizes input. Canonical: https://mobilevalidate.com/blog/e164-phone-number-format-guide-for-developers · Last updated: 2026-09-25 ![Cover: E.164 phone number format: a practical guide for developers](https://mobilevalidate.com/og/blog/e164-phone-number-format-guide-for-developers.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Developers · Tags: E.164, Phone number formatting, Libphonenumber, Data quality, API E.164 is the canonical way to write a phone number: `+`, the country code, then the national number, with no spaces and at most 15 digits. Converting every input to E.164 before you store, deduplicate or send is the single cheapest data-quality fix in a phone pipeline. This guide shows how to do it in JavaScript and Python, and which inputs trip people up. ## What exactly is an E.164 number? E.164 is the international numbering plan published by the ITU ([ITU-T E.164, 02/2026 edition](https://www.itu.int/rec/T-REC-E.164/en)). A number in this format has three parts: a `+` that stands for the international dialling prefix, a country code of one to three digits, and the national significant number without any trunk prefix. The whole thing is at most 15 digits. | Input as a person typed it | E.164 | |---|---| | `07911 123456` (typed in the UK) | `+447911123456` | | `+44 (0)20 7123 4567` | `+442071234567` | | `0049 151 23456789` | `+4915123456789` | | `(415) 555-2671` (typed in the US) | `+14155552671` | | `8 (912) 345-67-89` (typed in Russia) | `+79123456789` | All five come from real parsing runs with libphonenumber-js 1.13 (September 2026). The rows show three different trunk prefixes being removed: the UK `0`, the German `0` after `0049`, and the Russian `8`. The [E.164 glossary entry](/glossary/e164) covers the basics. The rest of this guide is about what goes wrong in production. ## Why not just strip non-digits and add a plus? Because the digits alone don't tell you the country, and some digits must be removed, not kept. A naive `replace(/\D/g, "")` gets these cases wrong: - **Trunk prefixes.** `07911 123456` becomes `07911123456`. Adding `+44` in front gives `+4407911123456`, which is not a number. The leading `0` has to go. - **International access codes.** `0049 151…` and `00 91 98765 43210` start with `00`, which means "international" in most of the world. In North America the access code is `011`. - **The "(0)" convention.** `+44 (0)20 7123 4567` is common on UK and German business cards. The `(0)` tells domestic callers to dial a 0. It is not part of the E.164 number. - **National format with no country.** `4155552671` could be a US number, or it could be read as `+41 55 552 671`, a Swiss-looking number. Only context decides. A numbering-plan library solves the first three and makes you state the fourth. Google's [libphonenumber](https://github.com/google/libphonenumber) is the reference implementation, and ports exist for most languages. It carries metadata about each country's ranges and number lengths, so it can reject numbers that can't exist, not just format them. ## How do you normalize a number in JavaScript? In Node.js or the browser, use `libphonenumber-js`. Import the `/max` metadata if you want real validation and line-type hints. The smaller default metadata only checks lengths. ```js import { parsePhoneNumberFromString } from "libphonenumber-js/max"; export function toE164(input, defaultCountry) { const raw = String(input ?? "").trim(); if (!raw || raw.length > 32) return { ok: false, reason: "unparseable" }; const p = parsePhoneNumberFromString(raw, defaultCountry); // defaultCountry: "GB", "US", … if (!p) return { ok: false, reason: "unparseable" }; if (!p.isValid()) return { ok: false, reason: "invalid" }; return { ok: true, e164: p.number, country: p.country ?? null, type: p.getType() ?? null }; } toE164("07911 123456", "GB"); // { ok: true, e164: "+447911123456", country: "GG", type: "MOBILE" } toE164("+44 (0)20 7123 4567"); // { ok: true, e164: "+442071234567", country: "GB", type: "FIXED_LINE" } toE164("+1 (415) 555-2671"); // { ok: true, e164: "+14155552671", country: "US", type: "FIXED_LINE_OR_MOBILE" } toE164("415-555-2671", "GB"); // { ok: false, reason: "invalid" } ``` Two details matter. `isValid()` checks the number against the country's ranges, while `isPossible()` only checks the length. Use `isValid()` before paying for anything downstream. Also, `p.number` is already the E.164 string. Don't rebuild it yourself from `countryCallingCode` and `nationalNumber`. ## How do you normalize a number in Python? The `phonenumbers` package is the Python port of libphonenumber. Its core API is `parse`, `is_valid_number`, `format_number` and `number_type`: ```python import phonenumbers from phonenumbers import NumberParseException, PhoneNumberFormat, PhoneNumberType def to_e164(raw: str, default_region: str | None = None): try: p = phonenumbers.parse(raw, default_region) # region like "GB"; None requires a leading + except NumberParseException: return None, "unparseable" if not phonenumbers.is_valid_number(p): return None, "invalid" e164 = phonenumbers.format_number(p, PhoneNumberFormat.E164) return e164, phonenumbers.number_type(p) # e.g. PhoneNumberType.MOBILE to_e164("07911 123456", "GB") # ('+447911123456', PhoneNumberType.MOBILE) to_e164("4155552671") # raises inside parse -> (None, 'unparseable'): no region, no + ``` `parse` raises `NumberParseException` when it can't interpret the input at all, for example national digits with no region. Catch it and treat it like an invalid number. `number_type` returns an enum such as `MOBILE`, `FIXED_LINE`, `FIXED_LINE_OR_MOBILE`, `VOIP` or `TOLL_FREE`. That value comes from the numbering plan, so it describes the range, not the current service. Pin the library version and upgrade it regularly: numbering plans change, and the metadata ships with the package. ## Which inputs cause the most surprises? These are from our own parsing runs, not from theory: | Input | What happens | Why it matters | |---|---|---| | `+44 7911 123456` | Country comes back as `GG` (Guernsey), not `GB` | `+44` is shared by the UK, Guernsey, Jersey and the Isle of Man. Don't map country code to country with a lookup table | | `+1 800 555 0199` | Valid, type `TOLL_FREE` | Valid is not the same as "can receive SMS" | | `+1 415 555 2671` | Type `FIXED_LINE_OR_MOBILE` | US and Canadian ranges don't separate mobile from fixed. You need a [carrier lookup](/services/carrier-lookup) to know | | `+33 06 12 34 56 78` | Parsed as `+33612345678` | The library drops a trunk `0` written after the country code | | `+44 7911 123456 ext 12` | E.164 `+447911123456`, extension `12` kept separately | E.164 has no extensions. Store the extension in its own field or you lose it | | `+447911123456` (full-width digits) | Parsed correctly | Copy-paste from some keyboards and PDFs produces non-ASCII digits. Don't reject them with a strict regex before parsing | | `447911123456` with no region | Not parsed by the library | Whether bare digits mean "international" is your decision, not the library's | The Guernsey case is the one that silently breaks analytics. If your dashboard groups by `country` and expects every `+44` number to be `GB`, a slice of your UK traffic disappears into a country you never selected. ## How does the MobileValidate API normalize numbers? The API converts every number to E.164 before deduplication, caching, pricing or any check, using libphonenumber metadata. You can send any format. The rules: 1. Input is trimmed. Empty strings and inputs over 32 characters are rejected. 2. If the input is 8 to 15 bare digits and the request has **no** `default_country`, a `+` is added, so the digits are read as an international number with country code. 3. Otherwise the input is parsed with `default_country` (ISO 3166-1 alpha-2, one value per request). 4. The number must be **valid** for its numbering plan, not just the right length. Otherwise `number_status` is `invalid_number`. 5. After conversion, repeats of the same E.164 number in one request are marked `duplicate`. Invalid and duplicate rows are never checked. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Here is a real test-mode request with mixed formats and `default_country: "GB"`: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+44 (0)20 7123 4567", "07911 123456", "447911123456", "0049 151 23456789", "415-555-2671", "+44 7911 123456 ext 12"], "checks": ["carrier"], "default_country": "GB", "wait": 5}' ``` The number fields of each result (excerpt): ```json [ {"input": "+44 (0)20 7123 4567", "e164": "+442071234567", "country": "GB", "number_status": "valid"}, {"input": "07911 123456", "e164": "+447911123456", "country": "GG", "number_status": "valid"}, {"input": "447911123456", "e164": "+447911123456", "country": "GG", "number_status": "duplicate"}, {"input": "0049 151 23456789", "e164": "+4915123456789", "country": "DE", "number_status": "valid"}, {"input": "415-555-2671", "e164": null, "country": null, "number_status": "invalid_number"}, {"input": "+44 7911 123456 ext 12", "e164": "+447911123456", "country": "GG", "number_status": "duplicate"} ] ``` Three different inputs collapse into one number, so only one of them is checked and billed. The US number in national format is invalid because the request's default country is GB. ## What goes wrong with default_country? `default_country` applies to the whole request, so a mixed-country list in national format will lose rows. Our test runs show two traps: - **Wrong default.** `415-555-2671` in a request with `default_country: "GB"` is `invalid_number`. The same number with `"US"`, or written as `+1 415 555 2671`, is valid. - **No default.** `4155552671` with no `default_country` gets a `+` and becomes `+4155552671`. That is read as country code 41, fails validation and comes back `invalid_number`. The fix is to keep the country with the number from the start. Capture it on the form (a country picker next to the phone field), store it with the record, and when you batch, group numbers by country and send one request per group. If your source data is already international, `+` plus country code, leave `default_country` out. For bulk lists, `POST /v1/jobs/estimate` shows the counts of valid, invalid and duplicate rows for free, so you can catch a wrong default before running a job. See [how to clean a phone number list in bulk](/blog/how-to-clean-a-phone-number-list-in-bulk). ## How should you store and compare numbers? A schema that avoids most future bugs: | Column | Example | Notes | |---|---|---| | `phone_e164` | `+447911123456` | The canonical value. Index it, deduplicate on it, join on it | | `phone_raw` | `07911 123456` | What the person typed, for support. Don't use it for logic | | `phone_country` | `GG` | From the parser, not from the country code | | `phone_ext` | `12` | Only if you accept extensions | | `phone_checked_at` | `2026-09-25T16:06:07Z` | When any lookup last ran, so you know how old the data is | Store E.164 as a string, never an integer. Compare with exact string equality after normalization. Mask numbers in logs (for example `+44791*****56`), because a full phone number is personal data. For tests and documentation, use ranges that are never assigned: Ofcom reserves `07700 900000` to `07700 900999` for drama ([Ofcom](https://www.ofcom.org.uk/phones-and-broadband/phone-numbers/numbers-for-drama)), and our [test mode](/docs/test-mode) uses numbers from that range. ## What doesn't E.164 tell you? A valid E.164 number is a number that *could* exist. It isn't proof that the number is assigned, switched on, a mobile, or used by the person who typed it. Once the format is right, the next questions are: - **What kind of line is it?** The library's type is range-based. A [carrier lookup](/services/carrier-lookup) returns the current `line_type` and carrier, which matters in the US and Canada and for [ported numbers](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong). - **Is it live?** That needs a network query. Our [HLR lookup](/services/hlr-lookup) is coming soon. See [how to check if a phone number is active](/blog/how-to-check-if-a-phone-number-is-active). - **Does it use a given channel?** Channel checks such as the [WhatsApp check](/services/whatsapp-number-check) answer that for opted-in contacts. [HLR vs MNP vs number validation](/blog/hlr-vs-mnp-vs-number-validation) compares these layers side by side. ## What are the key takeaways? - Normalize to E.164 with a numbering-plan library (libphonenumber or a port), not with regexes. - Use full validation (`isValid()` / `is_valid_number`) rather than length checks before you pay for any downstream check. - Keep the country with the number. `default_country` applies to a whole request, so group mixed lists by country. - Don't infer country from country code: `+44` can come back as `GG`, and `+1` covers many countries. - Deduplicate after normalization. The API does this for you, and duplicates are never charged. - Store E.164 as a string, keep the raw input and the extension separately, and mask numbers in logs. ## Sources 1. [ITU-T Recommendation E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en) — International Telecommunication Union, 2026 2. [libphonenumber](https://github.com/google/libphonenumber) — Google, 2026 3. [Telephone numbers for use in TV and radio drama programmes](https://www.ofcom.org.uk/phones-and-broadband/phone-numbers/numbers-for-drama) — Ofcom, 2026 ## Frequently asked questions ### What is the E.164 format? A plus sign, the country code and the national number, with no spaces or punctuation and at most 15 digits. +447911123456 is a UK-format mobile number written in E.164. The format comes from ITU-T Recommendation E.164. ### Should I store phone numbers as integers? No. Store the E.164 string. Integers lose the leading plus, can overflow in some languages, and invite arithmetic nobody should do on a phone number. Keep the raw input in a separate field if you need it for support. ### Why does a US number fail when my default country is GB? A number without a leading + or 00 is read in the default country's numbering plan. 415-555-2671 is a valid US number but not a valid UK number, so it is rejected. Send international format, or group numbers by country and send one default_country per request. ### Does a valid E.164 number mean the phone works? No. Validation checks the format and the numbering plan. It says nothing about whether the number is assigned, switched on or used on a messaging app. Use a carrier lookup or channel checks for that. --- # E.164 regex: why a pattern is not enough to validate phone numbers > The common E.164 regex accepts impossible numbers. Real counterexamples, the shortest valid international numbers, and what to use a regex for instead. Canonical: https://mobilevalidate.com/blog/e164-regex-is-not-enough · Last updated: 2026-09-25 ![Cover: E.164 regex: why a pattern is not enough to validate phone numbers](https://mobilevalidate.com/og/blog/e164-regex-is-not-enough.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Developers · Tags: E.164, Regex, Phone validation, Libphonenumber, Data quality The common E.164 regex, `^\+[1-9]\d{1,14}$`, only checks shape: a plus, a non-zero first digit, at most 15 digits. It accepts country codes that don't exist, area codes no country uses, and numbers of the wrong length. To validate a phone number, use a numbering-plan library such as libphonenumber. Keep the regex for one job: checking that stored values are already normalized. ## What does the standard E.164 regex actually check? ITU-T Recommendation [E.164](https://www.itu.int/rec/T-REC-E.164/en) defines the international number as a country code of one to three digits followed by the national significant number, at most 15 digits in all. Written with a leading `+`, that becomes the pattern most answers recommend. The Stack Overflow question ["Regular expression matching E.164 formatted phone numbers"](https://stackoverflow.com/questions/6478875) had about 112,000 views on 2026-09-25. The pattern encodes exactly three rules: 1. The string starts with `+`. 2. The first digit isn't `0` (no country code starts with 0). 3. There are 2 to 15 digits and nothing else. Everything else about a phone number lives in per-country rules: which country codes exist, how long national numbers are in each country, and which leading digits are allocated. None of that fits in a single pattern. ## Which impossible numbers does the regex accept? We ran eleven inputs through the regex and through libphonenumber-js 1.13.14 (`/max` metadata) on 2026-09-25. Every row is real output: | Input | Regex | Library | Why | |---|---|---|---| | `+12` | match | unparseable | Two digits can't be a phone number anywhere | | `+999123456789` | match | unparseable | `+999` isn't assigned to any country | | `+2101234567` | match | unparseable | `+210` isn't assigned either | | `+11234567890` | match | invalid | North American area codes never start with 1 | | `+15555555555` | match | invalid | `555` isn't a working area code | | `+4412345678` | match | invalid (length) | Too short for a UK number | | `+447700900123` | match | invalid | Ofcom reserves this range for drama; never assigned | | `+491511234567890` | match | invalid | German mobile with extra digits, still within 15 | | `+6834002` | match | valid, fixed line | A real 7-digit Niue number | | `+447911123456` | match | valid, mobile | UK-format mobile | | `+44 7911 123456` | **no match** | valid, mobile | The regex rejects spaces the user typed | Eight of the eleven strings pass the regex and fail the library. The last row shows the opposite problem: a regex applied to raw user input rejects valid numbers because of formatting. Either way, the regex is answering a different question from the one you asked. ## Do the "improved" regex variants help? Not much. Each tweak moves the error somewhere else. We tested three common variants on five strings in Node.js: | Input | `^\+?[1-9]\d{1,14}$` | `^\+?\d{10,15}$` | `^\+[1-9]\d{6,14}$` | |---|---|---|---| | `4155552671` (US national, no country) | match | match | no match | | `+6834002` (valid Niue number) | match | **no match** | match | | `+11234567890` (impossible US area code) | match | match | match | | `0079111234567` (UK number, `00` prefix) | no match | match | no match | | `+999123456789` (no such country code) | match | match | match | Making the `+` optional is the worst change: `4155552671` then passes as if it were international, although it's a US number written nationally and would be read as `+41` (Switzerland). A 10-digit minimum rejects real numbers in small countries. A 7-digit minimum is closer to reality but still accepts every impossible area code and unassigned country code. No length rule can fix a problem that's about which digits are allocated. ## What is the minimum length of a valid international phone number? That Stack Overflow question had about 199,000 views on 2026-09-25, and the honest answer is "it depends on the country". E.164 sets a maximum of 15 digits but no useful minimum. We scanned libphonenumber's metadata (Python `phonenumbers` 9.0.40) for the shortest valid numbers: - **7 digits including the country code** for ordinary subscriber numbers in small territories: Niue (`+683 4002`), Tokelau (`+690 3101`) and Tristan da Cunha (`+290 8999`). - **6 digits** for a handful of special-service numbers, for example 4-digit numbers in Iran (`+98 9601`). - Most countries need far more. A UK number usually has 12 digits with `+44`, and a German mobile has 12 or 13. So the popular "at least 10 digits" rule rejects real numbers, and the regex's minimum of 2 digits accepts nonsense. Length is per country, and only country-aware code can check it. ## Why not write a regex per country? Because you'd be rebuilding libphonenumber by hand, and chasing it forever. Google's [libphonenumber release notes](https://github.com/google/libphonenumber/blob/master/release_notes.txt) list 25 releases in 2025 and 19 from January to 23 September 2026. Every one of them updated the phone metadata for at least one region. The 2026-09-23 release alone changed 12 regions, from Bangladesh to Zimbabwe. A per-country pattern also fails in less obvious ways. `^\+447\d{9}$` looks like "UK mobile", but it matches `+447000000000`, which is a personal-numbering range, and the reserved drama range `+447700900xxx`. It also silently assumes `+44` means the United Kingdom, while `+44 7911` numbers belong to Guernsey. Libraries encode these details in metadata that gets updated. Hand-written patterns rot quietly. ## What should you use instead of a regex? A port of libphonenumber in your language, called in this order: parse the raw input with a default country, check validity, then format as E.164. ```js import { parsePhoneNumberFromString } from "libphonenumber-js/max"; const p = parsePhoneNumberFromString("+44 7911 123456"); p?.isValid(); // true p?.number; // "+447911123456" p?.country; // "GG" ``` ```python import phonenumbers p = phonenumbers.parse("+1 123 456 7890", None) phonenumbers.is_possible_number(p) # True: right length for the US phonenumbers.is_valid_number(p) # False: no area code starts with 1 ``` We have step-by-step tutorials for [JavaScript and Node](/blog/phone-number-validation-javascript), [Python](/blog/phone-number-validation-python) and [PHP and Laravel](/blog/phone-number-validation-php-laravel). All the ports share Google's metadata, so the same input gives the same answer, as long as the library versions match. ## Where does a regex still belong? In two places. **As a storage invariant.** Once a library has normalized a number, the stored string should always match the E.164 shape. A database constraint catches any code path that skips normalization. In PostgreSQL: ```sql CREATE TABLE contacts ( id bigint PRIMARY KEY, phone_e164 text NOT NULL CHECK (phone_e164 ~ '^\+[1-9][0-9]{6,14}$') ); INSERT INTO contacts VALUES (1, '+447911123456'); -- ok INSERT INTO contacts VALUES (2, '07911 123456'); -- ERROR: violates check constraint ``` We ran this on PostgreSQL 18: the second insert fails with `violates check constraint "contacts_phone_e164_check"`. Note that `+11234567890` still passes. The constraint guards the format, not the meaning. The `{6,14}` lower bound rejects the few 6-digit service numbers; use `{5,14}` if you need them. **As a cheap pre-filter.** Before you parse millions of rows, a loose pattern can throw out obvious junk such as empty cells, e-mail addresses or text. Keep it loose, for example "contains 6 to 20 digits", so it never rejects anything the library would accept. ## What can't even a perfect validator tell you? A number that passes libphonenumber is a number that could exist. Validation doesn't tell you whether it's assigned, in service, a mobile, ported to another network, or used on a messaging app. The next rungs of the ladder need data: | Question | Tool | |---|---| | Is the shape right? | Regex (storage invariant only) | | Does it fit the country's numbering plan? | libphonenumber or a port | | Is it a mobile, landline or VoIP line today? | [Carrier lookup](/services/carrier-lookup) | | Is it switched on and reachable? | Live network status (HLR); ours is [coming soon](/services/hlr-lookup) | | Does the person use a given channel? | Registration checks, such as [WhatsApp](/blog/check-if-a-number-is-on-whatsapp-api-guide) | Our API runs the first two rungs on every request for free: numbers are normalized with libphonenumber metadata, invalid ones come back as `invalid_number` and are never checked or billed. The [E.164 guide](/blog/e164-phone-number-format-guide-for-developers) covers the normalization rules in detail, and [HLR vs MNP vs number validation](/blog/hlr-vs-mnp-vs-number-validation) compares the paid rungs. ## How should you test your validation? Use inputs that exercise each rule, and pin the expected results to the library version you ship: - One valid number per country you serve, in national and international formats. - An impossible area code (`+1 123 456 7890`), an unassigned country code (`+999…`), and numbers one digit too short and too long. - A shared country code case (`+44 7911…` returns `GG`) so your analytics don't assume `+44` means GB. - Full-width digits and spaces, pasted from PDFs and phones. - Reserved fictional ranges for fixtures, never real customer numbers. Our [test mode](/docs/test-mode) uses Ofcom's drama range, `+44 7700 900000` to `900999`. libphonenumber rejects that range on purpose, so let it through only when you use a test key. When a library upgrade changes a result, that's usually the point of the upgrade. Read the release notes, then update the fixture. ## What are the key takeaways? - `^\+[1-9]\d{1,14}$` checks shape only. In our run, 8 of 11 test strings passed it and failed real validation. - Tweaking the pattern (optional `+`, a 10-digit minimum) trades one failure for another. - It accepts unassigned country codes, impossible area codes and wrong lengths, and it rejects valid numbers with spaces. - E.164 has no useful minimum length: valid numbers range from 6 or 7 digits (Niue, Tokelau, Iran service numbers) to 15. - Per-country regexes go stale. libphonenumber's metadata changed 25 times in 2025. - Validate with a libphonenumber port; keep the regex as a database constraint on normalized values. - For line type, reachability and channel presence, you need a lookup, not a better pattern. ## Sources 1. [ITU-T Recommendation E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en) — International Telecommunication Union, 2026 2. [List of ITU-T Recommendation E.164 assigned country codes](https://www.itu.int/pub/T-SP-E.164D) — International Telecommunication Union, 2026 3. [libphonenumber release notes](https://github.com/google/libphonenumber/blob/master/release_notes.txt) — Google, 2026 4. [Regular expression matching E.164 formatted phone numbers (question 6478875)](https://stackoverflow.com/questions/6478875) — Stack Overflow, 2026 5. [What is the minimum length of a valid international phone number? (question 14894899)](https://stackoverflow.com/questions/14894899) — Stack Overflow, 2026 ## Frequently asked questions ### What is the regex for an E.164 phone number? The usual pattern is ^\+[1-9]\d{1,14}$: a plus sign, a first digit from 1 to 9, and up to 15 digits in total. It checks the shape of an already-normalized string. It doesn't check whether the country code exists or whether the number is in an allocated range. ### What is the minimum length of a valid international phone number? E.164 sets a maximum of 15 digits but no practical minimum. In libphonenumber's metadata (version 9.0.40), ordinary subscriber numbers in Niue and Tokelau have 7 digits including the country code, and a few special-service numbers have 6. ### Can I write a separate regex for each country? You can, but you would be rebuilding libphonenumber's metadata by hand, and numbering plans change every few weeks. Google published 25 metadata releases in 2025. Use a maintained library and update it. ### Is there any good use for an E.164 regex? Yes: as a storage invariant. After a library has normalized and validated a number, a database CHECK constraint with the E.164 pattern catches code paths that write unnormalized values. --- # E-mail verification vs account existence checks: what's the difference? > Mailbox validation says an address can receive mail. An account check says it is registered on a service. When to use each, and the privacy rules. Canonical: https://mobilevalidate.com/blog/email-verification-vs-account-existence-checks · Last updated: 2026-09-25 ![Cover: E-mail verification vs account existence checks: what's the difference?](https://mobilevalidate.com/og/blog/email-verification-vs-account-existence-checks.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Email verification, Account security, Fraud prevention, Privacy, Deliverability E-mail verification and account existence checks answer different questions. Mailbox validation asks: *can this address receive mail?* An account existence check asks: *is this address registered on a particular service?* The first protects deliverability and catches typos. The second is a fraud and account-security signal, and it is more sensitive, so it needs a clearer purpose. ## What does e-mail verification (mailbox validation) answer? It answers whether the mailbox behind an address exists at the provider that hosts it. A syntax check only proves an address is well formed. `jane.doe@gmail.com` and `jane.dooe@gmail.com` both pass syntax, but only one of them may have a mailbox. Mailbox validation matters when you are about to send mail: a sign-up confirmation, a receipt, a password-reset link. Mail to a mailbox that doesn't exist bounces, and a high share of bounces damages your sending reputation with mailbox providers. MobileValidate's [mailbox check](/services/email-verification) (`email.valid`, alias `email`) returns: - `registered: true`: the provider has a mailbox at this address. - `registered: false`: a conclusive answer, and there is no such mailbox. - `registered: null`: no conclusive answer, and `reason` explains why. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). It never sends mail to the address and never reads the mailbox. ## What does an account existence check answer? It answers whether a specific online service has an account registered with the address. Examples in our catalog are [Apple ID](/services/apple-id-email-check) (`apple.email`), Amazon, Facebook, Instagram, Netflix, Spotify, LinkedIn and X. There are also per-provider checks, such as the [Gmail check](/services/gmail-email-check) (`gmail`), that answer whether an account exists at one mail provider. This is a different kind of fact. A mailbox tells you mail can arrive. An account tells you the address was used to sign up somewhere, which says something about how real and how established the address is: - A sign-up address with no accounts on the services you check can be one sign of a throwaway address. On its own it proves nothing, so combine it with other signals. - A recovery address added during a suspicious session that has no mailbox at all can't receive a reset link. Account checks return yes/no/unknown only. **They never return names, photos, profiles or any inference about the person.** ## How do the two compare side by side? | | Mailbox validation | Account existence check | |---|---|---| | Question | Can the address receive mail? | Is the address registered on service X? | | Main use | Deliverability, typo capture at sign-up | Fraud and account-security signals | | Codes | `email.valid` (`email`) | `apple.email`, `amazon.email`, `facebook.email`, `instagram.email`, `netflix.email`, `spotify.email`, `linkedin.email`, `x.email`; per-provider `gmail`, `outlook`, `yahoo`, `yandex`, `mailru` | | Modes | Real time + bulk | Service accounts: real time + bulk, except `linkedin.email` and `x.email` (bulk only). Per-provider checks: bulk only | | Coverage | Major webmail providers; other domains → `unknown`, `UNSUPPORTED_PROVIDER`, free | The named service | | Sensitivity | Lower: says the address works | Higher: says the person uses a service | | Typical retention | Result + date on the contact record | Only the decision (allow, step up, review) | The runtime list of e-mail services, their modes and prices comes from `GET /v1/services`. See [e-mail checks](/docs/emails). ## Why can't I just ask the mail server? The mail protocol was designed with a command for this. [RFC 5321](https://www.rfc-editor.org/rfc/rfc5321.html) defines `VRFY` to verify a user name (section 3.5). The same RFC also notes, in section 7.3, that these commands can reveal information about users, and that sites may disable or restrict them. In practice most large providers don't give useful answers to them. Other do-it-yourself methods have their own problems. Starting an SMTP delivery and stopping before the message body gives different answers depending on the provider. Some domains accept mail for any address ("catch-all") and bounce later. Repeated probing from your own servers can also get your IP addresses rate-limited or listed, which hurts the mail you actually want to deliver. That is why our mailbox check covers **major webmail providers only**, where most consumer sign-ups come from and where we can give conclusive answers. For company domains it answers `unknown` with `UNSUPPORTED_PROVIDER` rather than guessing, and you pay nothing for those rows. ## When should I use mailbox validation? Use it whenever an address is about to receive mail you care about, ideally at the moment it enters your system: | Moment | Why | Action on `false` | |---|---|---| | Sign-up form | Catch typos while the person is still on the page | "Please check your e-mail address" | | Checkout / receipt address | Make sure the receipt arrives | Ask for a correction before payment | | Lead form | Stop sales from chasing addresses that bounce | Ask for a correction or send to review | | List import | Remove dead addresses before a send | Suppress the address | Deliverability is also a reputation question. Google's [e-mail sender guidelines](https://support.google.com/a/answer/81126) ask senders to keep the spam rate reported in Postmaster Tools below 0.3%. That rate counts spam complaints, not bounces, but both come from the same habit: mailing addresses that never asked for your mail or no longer work. Cleaning addresses at entry and mailing only people who opted in addresses both. ## When is an account existence check appropriate? Use account checks when a decision about fraud or account security benefits from knowing whether the address is established, and when you have a lawful reason to process it. Good fits: - **Sign-up risk scoring.** An address with a mailbox and accounts on well-known services is less likely to be throwaway. Combine it with phone signals; see [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). - **Contact-detail changes.** When a user replaces the recovery address, a new address with no mailbox or no history is a reason to step up verification. See [account security](/use-cases/account-security). - **Lead verification** for B2C leads where a made-up address wastes sales time. See [lead verification](/use-cases/lead-verification). Poor fits: deciding whether someone gets credit, a job, housing or insurance; building marketing segments ("people who use service X"); checking addresses you didn't collect. The [acceptable use policy](/legal/acceptable-use) forbids these. ## What does a request look like? Put addresses in `emails` and at least one e-mail check in `checks`. This test-mode request checks the mailbox and an Apple account for three addresses on the reserved test domain: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["Registered@Test.MobileValidate.com", "unsupported@test.mobilevalidate.com", "not-an-address"], "checks": ["email", "apple.email"], "wait": 5}' ``` The first result row (real test-mode output, trimmed): ```json { "kind": "email", "input": "Registered@Test.MobileValidate.com", "email": "registered@test.mobilevalidate.com", "email_status": "valid", "e164": null, "country": null, "checks": { "email.valid": {"service": "email.valid", "status": "completed", "registered": true, "confidence": "high", "checked_at": "2026-09-25T16:15:27.672Z", "billed": false, "reason": null}, "apple.email": {"service": "apple.email", "status": "completed", "registered": true, "confidence": "high", "checked_at": "2026-09-25T16:15:27.672Z", "billed": false, "reason": null} }, "test": true } ``` The address was normalized by trimming and lowercasing (no dot or plus rewriting). The second address answers `"status": "unknown", "reason": "UNSUPPORTED_PROVIDER"`, which is how a company domain looks in live mode. The third row has `"email_status": "invalid_email"` and no checks at all, so nothing is billed. Per-provider checks such as `gmail` are bulk only: on `POST /v1/lookup` they return `403 service_disabled` with "available in bulk jobs only (POST /v1/jobs)". ## What limits protect people whose addresses are checked? E-mail checks have their own anti-enumeration rules, because generated address lists are how people try to discover who uses a service: - **Per request:** 20 or more distinct addresses on one domain whose local parts differ only by digits or separators (`jane1@`, `jane.2@`, `jane_3@`…) are refused with `403 suspected_enumeration`. `gmail.com` and `googlemail.com` count as one domain. We tested it: twenty `janeN@example.org` addresses were rejected with "The request looks like a generated list of e-mail addresses". - **Per day (live keys):** 50 or more distinct addresses of one such pattern from one account in a UTC day are refused, however they are split across requests. - **Size limits:** 100 identifiers per lookup, 50,000 per job, 20 checks per request, and at most 2,000 identifier × check combinations per lookup (100,000 per job). See [rate limits and abuse](/docs/rate-limits-and-abuse). ## What privacy rules apply? An e-mail address is usually personal data, and the answer to "does this person have an account on service X" can be more revealing than the address itself. Under the [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj), the following points typically apply (ask your counsel what applies to your case): - **Have a lawful basis** (Article 6) for each purpose. Fraud prevention and securing accounts are commonly argued under legitimate interests. Document your assessment. - **Minimise** (Article 5(1)(c)). Store the mailbox result and its date on the contact record. For account checks, store the decision (allow, step up, review) rather than a list of services. - **Honour objections** (Article 21). People can object to checks through our [opt-out form](/opt-out). Suppressed addresses come back as `email_status: "suppressed"`, are never checked and never charged. Tell users in your privacy notice that you verify contact details to prevent fraud and keep accounts secure. ## What are the key takeaways? - **Mailbox validation** answers "can this address receive mail?" Use it at sign-up, checkout and list import to protect deliverability. - **Account existence checks** answer "is this address registered on service X?" Use them as fraud and account-security signals, not for marketing or eligibility decisions. - The mailbox check covers **major webmail providers**. Other domains answer `unknown` (`UNSUPPORTED_PROVIDER`) and **aren't charged**. - DIY SMTP probing is unreliable: [RFC 5321](https://www.rfc-editor.org/rfc/rfc5321.html) lets servers disable `VRFY`, and catch-all domains accept everything. - Generated address lists are refused. Check only addresses people gave you, keep only the decision, and respect objections. - Start with the [mailbox check](/services/email-verification) and test every branch for free with the addresses in [test mode](/docs/test-mode). ## Sources 1. [RFC 5321: Simple Mail Transfer Protocol](https://www.rfc-editor.org/rfc/rfc5321.html) — IETF, 2008 2. [Email sender guidelines](https://support.google.com/a/answer/81126) — Google, 2026 3. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 ## Frequently asked questions ### What is the difference between e-mail verification and an account existence check? E-mail verification (mailbox validation) answers whether the address has a working mailbox at its provider, so mail will not bounce. An account existence check answers whether an account on a specific service, such as Apple or Amazon, is registered with that address. ### Why does the mailbox check return unknown for my company domain? The mailbox check covers major webmail providers only. Company and custom domains run their own mail servers with their own rules, so they answer unknown with reason UNSUPPORTED_PROVIDER. That result is not charged. ### Is an account existence check more sensitive than mailbox validation? Yes. Knowing that an address is registered on a particular service reveals more about a person than knowing their mailbox exists. Use account checks only with a clear purpose such as fraud prevention or account security, and store only the decision. ### Can I verify a list of addresses like name1@, name2@, name3@? No. Twenty or more addresses on one domain that differ only by digits or separators are refused as suspected enumeration, and live keys have a daily limit per pattern. Check only addresses people gave you. --- # HLR lookup vs MNP lookup vs number validation: what each one answers > Validation checks the digits, MNP finds the current network, HLR asks the network if the number is live. What each answers, costs and misses. Canonical: https://mobilevalidate.com/blog/hlr-vs-mnp-vs-number-validation · Last updated: 2026-09-25 ![Cover: HLR lookup vs MNP lookup vs number validation: what each one answers](https://mobilevalidate.com/og/blog/hlr-vs-mnp-vs-number-validation.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: HLR lookup, MNP, Number validation, Carrier lookup, Deliverability Number validation, MNP lookups and HLR lookups answer three different questions. Validation asks whether the digits could be a real number. An MNP (mobile number portability) lookup asks which network the number belongs to today. An HLR lookup asks the home network whether the subscriber is live right now. Most teams need the first on every number and the others only where a wrong answer costs money. ## What does each check actually answer? The three names get used as if they were grades of the same product. They aren't. Each one reads a different source of truth, so each one can be right while the others are silent. | Check | Question it answers | Source of truth | Works for | |---|---|---|---| | Number validation | Is this a possible, correctly formatted number for its country? | The national numbering plan, as published and encoded in libraries | Every number type | | MNP lookup | Which network serves this number today, and was it ported? | Porting databases or reference data built from them | Mobile, and landline where fixed portability exists | | HLR lookup | Is the number assigned, is the subscriber reachable now, are they roaming? | The home network's subscriber register, queried live | Mobile numbers only | A useful way to remember it: validation is about the **number**, MNP is about the **network**, and HLR is about the **subscriber**. The further down the list, the more you learn, and the more things can go wrong on the way to an answer. ## How does number validation work? Validation is an offline calculation. Every country publishes a numbering plan that says which ranges exist, how long numbers are and which ranges are mobile, fixed, toll-free or premium-rate. The international frame for these plans is [ITU-T Recommendation E.164](https://www.itu.int/rec/T-REC-E.164/en) ([ITU, 2010](https://www.itu.int/rec/T-REC-E.164/en)), which also caps a full international number at 15 digits. Open-source libraries such as [Google's libphonenumber](https://github.com/google/libphonenumber) ([Google, 2026](https://github.com/google/libphonenumber)) package those plans as metadata. Given `07911 123456` and the country `GB`, they return `+447911123456`, say the number is valid, and give a range-based type such as `MOBILE`. It takes microseconds and costs nothing. What validation cannot do is tell you whether anyone holds the number. A valid number may never have been issued. It may have been disconnected last year. The type is a property of the range, not of the current service, so a number ported from a landline to a VoIP provider still looks like a landline. See [E.164](/glossary/e164) and the [E.164 guide for developers](/blog/e164-phone-number-format-guide-for-developers) for the pitfalls. ## How does an MNP lookup work? When a subscriber keeps their number and moves to another operator, the number no longer lives on the network its range was allocated to. Regulators made this a right: in the US, wireless portability has existed since November 2003 in the largest metropolitan areas and since May 2004 elsewhere ([FCC](https://www.fcc.gov/general/wireless-local-number-portability-wlnp)), and the EU's electronic communications code requires porting in [Article 106](https://eur-lex.europa.eu/eli/dir/2018/1972/oj) ([EU, 2018](https://eur-lex.europa.eu/eli/dir/2018/1972/oj)). To route calls and texts, networks keep track of ported numbers. Many countries run a central porting database; others make each operator keep its own copy. An MNP lookup reads that data, directly or through a regularly refreshed replica, and returns the current network and usually a "ported" flag. It answers "who serves this number?" well. It does not say whether the phone is on, whether the line is still in service, or whether the subscriber is abroad. Because the data is copied, a very recent port can take a while to show up. The [portability post](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong) explains why prefix-based carrier guesses fail. ## How does an HLR lookup work? An HLR lookup asks the mobile network itself. Every operator keeps a register of its subscribers: the Home Location Register in 2G and 3G, with successors in 4G and 5G. When an SMS is delivered between networks, the sending side first asks the recipient's home register where to send it, using the MAP protocol specified in [3GPP TS 29.002](https://www.3gpp.org/DynaReport/29002.htm) ([3GPP](https://www.3gpp.org/DynaReport/29002.htm)). An HLR lookup makes the same kind of routing query without sending a message. The reply shows whether the number is assigned, whether the subscriber is currently attached to a network, which network holds the subscription (so porting) and whether it is a foreign network (so roaming). Nothing appears on the handset. Its limits are practical. It works for mobile numbers only. Some operators block or mask external queries, and some return generic answers to protect subscribers, so a good service reports those cases as unknown rather than guessing. Raw replies can include identifiers such as the IMSI or the serving switch, which should never leave a telecom environment. See the [HLR lookup glossary entry](/glossary/hlr-lookup). ## How do cost and speed compare? We won't quote market prices or response times here, because they vary by country, route and volume, and any single number would be misleading. The relative order is stable, though, and it follows from how each check works. | | Validation | MNP lookup | HLR lookup | |---|---|---|---| | Where the answer comes from | Local metadata | A database or replica | The home network, live | | Relative cost | Free | Low | Higher: each query crosses networks | | Relative speed | Instant | Fast | Depends on the destination network | | Freshness | As fresh as the library release | As fresh as the replica | Real time | | Typical failure | Outdated metadata | Recent port not yet visible | Network blocks or masks the query | The practical consequence: run validation on everything, because it is free and removes junk before you pay for anything. Spend on network data only where the answer changes what you do next. ## Which check should you use when? Pick the cheapest check that answers the question your workflow actually asks. This decision table covers the common cases. | Situation | Minimum check | Add when it pays off | Why | |---|---|---|---| | Form field accepts a phone number | Validation | Line type | Catches typos while the person is still on the page | | Sending an OTP by SMS | Validation + line type | Live reachability | Don't send codes to landlines or premium-rate ranges; offer another channel to unreachable phones | | Routing or pricing SMS by network | Current network (MNP) | — | Prefix-based guesses are wrong for every ported number | | Cleaning an old contact list before a consented campaign | Validation + line type | Live reachability | Removes disconnected and never-assigned numbers before you pay per message | | Fraud review of a new sign-up | Line type + current network | Channel checks, spam reputation | Signals about how the number is used, not only whether it exists | | Checking landlines or VoIP numbers | Validation + line type | — | HLR lookups don't work for these line types | | Deciding whether to call now | Live reachability | — | The only check that reflects the phone's state at this moment | If you are unsure, start with validation and line type, measure how many messages still fail, and add a live check where the failure rate justifies it. ## What does MobileValidate offer for each? Each layer maps to something you can call today, except the live one: - **Validation and normalization** happen on every request, free. Each result carries `e164`, `country` and `number_status` (`valid`, `invalid_number`, `duplicate`, `suppressed`). Invalid and duplicate rows are never checked or charged. - **Network data** comes from the [carrier lookup](/services/carrier-lookup): `line_type`, the current `carrier`, `country` and, when it differs, `original_carrier`, which is the porting signal. For US and Canadian numbers, where porting between landline, mobile and VoIP is common, the [US/CA carrier lookup](/services/us-carrier-lookup) runs in bulk jobs. - **Live network status**, the [HLR lookup](/services/hlr-lookup), is **coming soon**. It will return `status`, `ported`, `roaming` (true or false only, never a location), `network`, `mcc_mnc` and `country`, and never the IMSI or serving switch. One request can combine several checks. Here is a real test-mode call: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["07700 900001", "12345"], "checks": ["carrier"], "default_country": "GB"}' ``` Response (excerpt, test mode: the two items of `results`): ```json [ {"input": "07700 900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": {"network.carrier": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "billed": false, "reason": null}}}, {"input": "12345", "e164": null, "country": null, "number_status": "invalid_number"} ] ``` The invalid number never reaches a check. With a live key it would not be billed either. ## How should you read the answers together? The layers disagree in predictable ways, and the disagreements are informative: - **Valid, but carrier data is `unknown`.** The number fits the plan, but no data exists for it. That can mean a new range, a small operator or a number never issued. The result is free; treat it as "no evidence", not as "fake". - **`original_carrier` differs from `carrier`.** The number was ported. Normal on its own; worth a look only together with other signals, such as a password reset minutes later. - **Range says mobile, lookup says VoIP.** The number moved to an internet-telephony service. The lookup wins, because it describes the number today. See [VoIP detection for sign-ups](/blog/voip-number-detection-for-signups). - **Valid mobile, unreachable in an HLR reply.** The phone is off or out of coverage. One unreachable answer means "try later"; repeated ones over weeks suggest an abandoned number. Store `checked_at` with every answer. Network facts change, and a decision is only as good as the age of the data behind it. ## What are the key takeaways? - **Validation** is free and offline. It checks the digits against the numbering plan, not whether anyone holds the number. - **MNP** tells you the current network. It fixes routing and pricing mistakes caused by ported numbers. - **HLR** asks the home network. It is the only one of the three that says whether a mobile subscriber is reachable now, and it doesn't work for landlines or most VoIP. - Run validation on everything; add network checks where a wrong answer costs more than the check. - In MobileValidate: normalization on every request, carrier lookup for line type and porting hints, HLR lookup coming soon. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). See [pricing](/pricing). For a step-by-step view of what "active" means, read [how to check if a phone number is active](/blog/how-to-check-if-a-phone-number-is-active). ## Sources 1. [Recommendation ITU-T E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en) — ITU, 2010 2. [3GPP TS 29.002: Mobile Application Part (MAP) specification](https://www.3gpp.org/DynaReport/29002.htm) — 3GPP, 2024 3. [Wireless Local Number Portability (WLNP)](https://www.fcc.gov/general/wireless-local-number-portability-wlnp) — FCC, 2004 4. [Directive (EU) 2018/1972 establishing the European Electronic Communications Code](https://eur-lex.europa.eu/eli/dir/2018/1972/oj) — European Union, 2018 5. [libphonenumber](https://github.com/google/libphonenumber) — Google, 2026 ## Frequently asked questions ### Is an HLR lookup always better than an MNP lookup? No. An HLR lookup answers more questions (is the subscriber reachable right now, are they roaming), but it only works for mobile numbers, depends on the home network answering, and usually costs more. If you only need to know which network a number is on, porting data is enough. ### Does number validation tell me whether a number exists? No. Validation checks that the digits fit the country's numbering plan. A valid number may never have been assigned, or may have been disconnected years ago. ### Which check should run before every OTP? Validation plus line type is the usual minimum: it removes impossible numbers and lines that cannot take SMS. Add a live reachability check where failed or wasted messages are expensive. ### Does MobileValidate offer HLR and MNP lookups? Validation and E.164 normalization are free on every request. The carrier lookup returns line type, current carrier and the original carrier when a number was ported. The HLR lookup is coming soon. --- # How to check if a phone number is active (and what "active" really means) > Valid, active, reachable and registered mean different things. The methods for checking each one, what they cost and where every method falls short. Canonical: https://mobilevalidate.com/blog/how-to-check-if-a-phone-number-is-active · Last updated: 2026-09-25 ![Cover: How to check if a phone number is active (and what "active" really means)](https://mobilevalidate.com/og/blog/how-to-check-if-a-phone-number-is-active.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Number reachability, HLR lookup, Carrier lookup, Number validation, Reassigned numbers To check if a phone number is active, first decide which kind of "active" you need. A number can be **valid** (the digits are possible), **in service** (assigned to a line), **reachable** (the phone is on the network now) or **registered** (it has an account on an app). Each needs a different check, and only a reply from the person proves they hold the phone. ## What are the four meanings of "active"? People say "active number" and mean different things. Separating them avoids paying for the wrong check. | Meaning | Question | Changes how often | Example of a number that fails | |---|---|---|---| | Valid | Do the digits fit the country's numbering plan? | Rarely (plan changes) | `+44 7911 12345`: one digit short | | In service | Is the number assigned to a line with some operator? | Weeks to years | A mobile number disconnected when a contract ended | | Reachable | Is the phone attached to a network right now? | Minutes | A phone switched off overnight | | Registered | Does the number have an account on a given app or service? | Months | A basic phone with no messaging apps | The layers are independent. A number can be valid and never assigned. It can be in service and unreachable because the phone is off. It can be reachable and registered nowhere. The [number reachability glossary entry](/glossary/number-reachability) describes the same ladder from the network's point of view. ## How do you check that a number is valid? Validation compares the number with the numbering plan of its country. The international format, E.164, allows at most 15 digits including the country code ([ITU, 2010](https://www.itu.int/rec/T-REC-E.164/en)), and each country's plan sets the lengths and ranges inside that. Open-source libraries encode those plans, so the check runs offline and costs nothing. MobileValidate validates and normalizes every number on every request. You see the outcome in `number_status`: `valid`, `invalid_number` or `duplicate` (the same number appeared earlier in the request after conversion to [E.164](/glossary/e164)). Invalid and duplicate rows are never checked and never charged. A real test-mode request shows how three inputs collapse into one number plus one error: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["07700 900001", "0044 7700-900001", "12345"], "checks": ["whatsapp"], "default_country": "GB"}' ``` The results come back as `valid`, `duplicate` and `invalid_number`, in input order. Validation is the right first filter. It removes typos while the person is still on your form. It says nothing about whether the line exists. ## How do you check that a number is in service? A number is in service when an operator has assigned it to a line. Two kinds of data help. **Carrier and line-type data.** A [carrier lookup](/services/carrier-lookup) returns the number's `line_type`, the current `carrier` and its `country` from reference data. If we hold no data for a valid number, the answer is `unknown` with reason `NO_DATA` and it is free. Data for a number is a sign it belongs to an operator's active ranges, but reference data isn't refreshed every minute, and it doesn't prove a subscriber holds that exact number. **A live network query.** For mobile numbers, the home network's subscriber register knows whether a number is assigned. An [HLR lookup](/glossary/hlr-lookup) asks it directly and gets back reachable, unreachable or invalid (not assigned). This is the most direct in-service test for mobile numbers. MobileValidate's [HLR lookup](/services/hlr-lookup) is **coming soon**; today a request for it returns `403 service_disabled`, even with a test key. Neither method works the same way for landlines, and most VoIP numbers have no subscriber register an outsider can query. ## How do you check that a phone is reachable right now? Reachability is the most short-lived property on the list. The home network knows whether the SIM is currently attached; that changes whenever the phone is switched off, loses coverage or goes into a tunnel. A live network query reports it without contacting the handset. The planned `number.hlr` service defines these states: | `status` | Meaning | What to do | |---|---|---| | `reachable` | Assigned and attached | Send or call | | `unreachable` | Assigned, but not attached now | Retry later, or use another channel | | `invalid` | Not assigned | Remove from the list | | `unknown` | No conclusive answer (network error, timeout) | Keep your default behaviour; not charged | Two cautions. First, a single `unreachable` is not a verdict. A phone off at 3 a.m. is fine at 9 a.m.; repeated answers over several weeks are what suggest an abandoned number. Second, some operators block or mask external queries, so a good service returns `unknown` rather than a guess. ## How do you check that a number is registered on an app? Messaging apps keep their own account databases, and each one verified the number at sign-up. A channel check asks whether an account exists for the number, without sending a message and without returning any name or profile. In MobileValidate, [WhatsApp](/services/whatsapp-number-check), [Telegram](/services/telegram-number-check) and [Viber](/services/viber-number-check) checks run in real time; others, such as iMessage, RCS and Signal, run in bulk jobs. Each answers `registered: true`, `false` or `null` (unknown), with `checked_at`. What a registration tells you: somebody completed the app's verification with this number at some point and the account still exists. What it doesn't tell you: whether that person still holds the number, or whether the phone is on today. Apps remove inactive accounts only after a period they set themselves, so a recycled number can keep the previous owner's account for a while. Use channel checks to choose a channel for messages people agreed to receive, and as one sign of real use. See [check if a number is on WhatsApp](/blog/check-if-a-number-is-on-whatsapp-api-guide). ## What is the only definitive test? Contacting the person, and getting an answer back. A one-time passcode that is entered on your site proves that whoever is in front of the screen controls the phone right now. That is why authentication standards treat the code, not a lookup, as the verification step. They also warn that the phone channel has weaknesses: NIST's 2025 guidelines classify out-of-band codes sent over the phone network as a *restricted* authenticator and say verifiers should consider risk indicators such as a SIM change or a number port before relying on it ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). So lookups and codes play different roles: - **Lookups** run before you contact anyone. They are cheap filters that decide whether a message is worth sending, and on which channel. - **A confirmed code or a call answered** proves possession. It costs a message and needs the person's cooperation. Don't send test messages to numbers that never agreed to hear from you. A lookup exists precisely so you don't have to. ## How do you know if a number now belongs to someone else? This is the question none of the network or app checks answer. Operators recycle numbers after disconnection. A reassigned number is valid, in service, reachable and possibly registered on apps, all for its new owner. In the US there is a dedicated source. The FCC's Reassigned Numbers Database became operational on 1 November 2021, and by 17 February 2023 it held over 305.9 million geographic and toll-free numbers; providers must report permanent disconnections monthly ([FCC, 2023](https://www.fcc.gov/reassigned-numbers-database)). Under US rules, callers who query it may qualify for a safe harbor from liability for calls to reassigned numbers; check the conditions with your counsel. A reachability or porting result is not a substitute for that database. In other countries, the practical approach is to reconfirm consent when you haven't been in touch for a long time, and to treat a change in carrier or line type since your last check as a prompt to reconfirm. ## Which checks should you combine? Pick by the cost of being wrong: | Situation | Checks | Notes | |---|---|---| | Sign-up form | Validation + line type | Instant feedback; add a channel check if you deliver codes by app | | Before sending an OTP | Validation + line type; reachability once available | Don't block on `unknown`; fall back to your normal flow | | Cleaning an old list before a consented campaign | Validation + line type in a bulk job; reachability once available | See [cleaning a list in bulk](/blog/how-to-clean-a-phone-number-list-in-bulk) | | Choosing a channel for an opted-in customer | Channel checks | Refresh monthly or after a failed delivery | | US calling list | The FCC database for reassignment, plus line type | Network checks don't answer "same person?" | In MobileValidate, you pay per check and only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). See [pricing](/pricing). ## How do you test this without real numbers? Use numbers that can never belong to anyone. The UK regulator reserves `07700 900000` to `07700 900999` for drama, and they are never assigned ([Ofcom](https://www.ofcom.org.uk/phones-and-broadband/phone-numbers/numbers-for-drama)). MobileValidate's [test mode](/docs/test-mode) uses this range: `+447700900001` answers registered or with carrier data, `…002` answers "no" or no data, `…003` simulates a timeout, `…004` stays pending for about five seconds, and `…005` returns unsupported country. Test keys never reach a real network and are never billed, so you can build every branch of your "is it active?" logic first. ## What are the key takeaways? - "Active" means one of four things: valid, in service, reachable or registered. Decide which you need first. - Validation is free and catches typos, but not dead numbers. - Carrier data shows line type and network; a live network query (HLR, coming soon in MobileValidate) shows whether a mobile phone is attached right now. - App registration shows real use at some point, not today's state. - Only a reply from the person proves possession. In the US, the FCC's database is the dedicated source for whether a number was disconnected and may have changed hands. - Treat `unknown` as missing data, never as a negative. It is free. For the difference between the network checks, read [HLR vs MNP vs number validation](/blog/hlr-vs-mnp-vs-number-validation). ## Sources 1. [Recommendation ITU-T E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en) — ITU, 2010 2. [Reassigned Numbers Database](https://www.fcc.gov/reassigned-numbers-database) — FCC, 2023 3. [NIST SP 800-63B-4: Digital Identity Guidelines, Authentication and Authenticator Management](https://csrc.nist.gov/pubs/sp/800/63/b/4/final) — NIST, 2025 4. [Telephone numbers for use in TV and radio drama programmes](https://www.ofcom.org.uk/phones-and-broadband/phone-numbers/numbers-for-drama) — Ofcom, 2025 ## Frequently asked questions ### Can I check whether a number is active without sending a message? Partly. Validation, a carrier lookup, a live network query (HLR) and channel checks all work without contacting the person. Only a message or call that the person answers proves they hold the phone right now. ### Does a WhatsApp account prove a number is active? It shows the number passed WhatsApp's sign-up verification at some point and still has an account. It doesn't prove the phone is on today, and a recycled number can still show the previous owner's account for a while. ### What does unreachable mean in a live network check? The number is assigned, but the phone is switched off, out of coverage or not attached to the network at that moment. Treat a single unreachable answer as 'try later', not as 'dead'. ### How do I know if a US number was reassigned to someone else? The FCC's Reassigned Numbers Database is built for that question: it tells callers whether a US number has been permanently disconnected since a given date, based on providers' reports. Network and channel checks can't tell you whether the person behind a number changed. --- # How to clean a phone number list in bulk, step by step > A step-by-step way to clean a phone list with the MobileValidate jobs API: free estimate, dedupe, invalid rows, capped cost, CSV download and data purge. Canonical: https://mobilevalidate.com/blog/how-to-clean-a-phone-number-list-in-bulk · Last updated: 2026-09-25 ![Cover: How to clean a phone number list in bulk, step by step](https://mobilevalidate.com/og/blog/how-to-clean-a-phone-number-list-in-bulk.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Deliverability · Tags: List cleaning, Bulk jobs, Data quality, Deliverability, CSV Cleaning a phone list means removing numbers you can't or shouldn't contact before you pay to message them: malformed entries, duplicates, lines that can't take SMS, and numbers with no sign of use. With the MobileValidate jobs API the process is: estimate for free, run a job with a cost cap, download a CSV, act on the columns, and purge the data. This guide walks through each step with real test-mode output. ## Why do phone lists go bad? Every list decays. People change numbers and operators recycle the old ones. In the US, the FCC runs a Reassigned Numbers Database so callers can check whether a number was permanently disconnected after they got consent. It held over 305.9 million numbers by February 2023 ([FCC, 2023](https://www.fcc.gov/reassigned-numbers-database)). Lists also pick up typos from web forms, numbers typed in five different formats, and the same person imported twice from two systems. Each bad row costs you something: an SMS fee for a message that can't arrive, a dialler minute, a bounce that drags down delivery statistics, or a message reaching someone who never agreed to it. Cleaning doesn't make a list consented. That comes from how you collected it. It does make sure you only spend money and attention on rows that can work. ## What does a cleaning pass check? A practical pass has four layers, from free to paid: | Layer | What it finds | How | Cost | |---|---|---|---| | 1. Format | Impossible numbers, typos, wrong country | Conversion to [E.164](https://www.itu.int/rec/T-REC-E.164/en) against the numbering plan | Free (`invalid_number`) | | 2. Duplicates | The same number in different formats | Deduplicate after conversion | Free (`duplicate`) | | 3. Line type | Landlines, toll-free, premium-rate, VoIP | [Carrier lookup](/services/carrier-lookup) (`carrier`) | Per conclusive answer | | 4. Channel | Whether an opted-in contact uses WhatsApp, Telegram and so on | Channel checks, e.g. [WhatsApp](/services/whatsapp-number-check) | Per conclusive answer | For US and Canadian lists, the bulk-only [US/CA carrier lookup](/services/us-carrier-lookup) (`network.carrier_us`) adds current-carrier data where porting between landline, mobile and VoIP is common. Live reachability through the [HLR lookup](/services/hlr-lookup) is coming soon. Layers 1 and 2 run automatically on every job. You choose layers 3 and 4 with `checks`. ## Step 1: How do you prepare the file? Put one number per row in a column named `phone` or `number`. Keep your own ID column so you can join the results back. The download keeps your original row order, so the row number works as a join key too. ```text crm_id,phone C-1001,+44 7700 900001 C-1002,07700 900002 C-1003,+447700900002 C-1004,+447700900003 C-1005,12345 ``` Two things to settle before you upload: - **Country.** National-format numbers such as `07700 900002` need `default_country`. It applies to the whole job, so split a mixed-country list by country, or convert to international format first. The [E.164 guide](/blog/e164-phone-number-format-guide-for-developers) covers the traps. - **Purpose.** Only check numbers you have a lawful reason to process, such as customers and people who asked to be contacted. Under the GDPR you also need to keep what you process to what the purpose requires ([GDPR Art. 5(1)(c), 2016](https://eur-lex.europa.eu/eli/reg/2016/679/oj)). Don't add checks you won't act on. ## Step 2: What does the free estimate tell you? `POST /v1/jobs/estimate` takes the same body as a job and checks nothing. It validates, deduplicates and counts, and it returns the most the job could cost: ```bash curl https://api.mobilevalidate.com/v1/jobs/estimate \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "07700 900002", "+44 7700 900002", "+447700900003", "+447700900005", "12345", "+447700900010", "+447700900011"], "checks": ["carrier", "whatsapp"], "default_country": "GB"}' ``` ```json {"object": "estimate", "total": 8, "valid": 6, "invalid": 1, "duplicate": 1, "cached": 0, "unsupported": 0, "suppressed": 0, "checks": ["network.carrier", "whatsapp.registered"], "checks_total": 16, "billable_max": 0, "max_cost": {"amount": "0", "currency": "USD"}} ``` `total`, `valid`, `invalid`, `duplicate`, `unsupported` and `suppressed` count rows. `checks_total`, `cached` and `billable_max` count checks (rows × services). Test keys always estimate zero. With a live key, `max_cost` is the ceiling. Read the estimate before going further. A high `invalid` share usually means a wrong `default_country` or a broken export, not a bad list. Fix it and estimate again. It costs nothing. ## Step 3: How do you create the job safely? Create the job with the same body plus two safety settings: `max_cost` set to the estimate's amount, and an `Idempotency-Key` header, so a network retry can't create a second job. ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: list-clean-2026-09-25-001" \ -d '{"numbers": ["+447700900001", "07700 900002", "+44 7700 900002", "+447700900003", "+447700900005", "12345", "+447700900010", "+447700900011"], "checks": ["carrier", "whatsapp"], "default_country": "GB", "max_cost": {"amount": "0", "currency": "USD"}}' ``` With a CSV file, send `multipart/form-data` instead: `-F file=@leads.csv -F checks=carrier,whatsapp -F default_country=GB -F max_cost=0.00`. The API answers `201` with the job object and a `Location` header. Limits to plan around: up to 50,000 numbers and e-mails per job, 20 checks per request, and at most 100,000 identifier × check combinations per job. Five checks on 50,000 numbers is 250,000 and will be refused, so split the list or drop checks. Requests that look like sequential number ranges are refused as `suspected_enumeration`. That rule stops people from checking made-up ranges. A real customer list rarely contains 20 or more numerically consecutive numbers. ## Step 4: How do you know when it's done? Poll with a long-poll so you aren't hammering the API. `GET /v1/jobs/{id}?wait=30` returns as soon as the status changes, or after 30 seconds: ```json {"object": "job", "id": "job_0VWFUGFpEfab2Ucnj33Z", "status": "completed", "livemode": false, "checks": ["network.carrier", "whatsapp.registered"], "progress": {"total": 8, "checks_total": 16, "done": 16, "conclusive": 7, "non_billable": 16}, "cost": {"estimated_max": {"amount": "0", "currency": "USD"}, "reserved": {"amount": "0", "currency": "USD"}, "charged": {"amount": "0", "currency": "USD"}, "released": {"amount": "0", "currency": "USD"}}, "retention_days": 30} ``` `progress.total` counts rows. The other progress fields count checks. `cost` shows what was reserved at the start, what was charged for conclusive answers, and what was released for everything else. For large jobs, register a [webhook](/docs/webhooks) for `job.completed` and `job.failed` instead of polling. Job webhooks carry a summary and a results URL, never the numbers. ## Step 5: How do you read the results? Download the whole file with `GET /v1/jobs/{id}/download?format=csv`. It has one line per input row, in your original order, with one group of columns per check. Real output from the test job above: ```text row_no,input_masked,e164,country,number_status,…,network.carrier.status,network.carrier.line_type,…,whatsapp.registered.status,whatsapp.registered.registered,whatsapp.registered.billed 1,"'+44770*****01",+447700900001,GB,valid,…,completed,mobile,…,completed,true,false 2,"'+44770*****02",+447700900002,GB,valid,…,unknown,,…,completed,false,false 3,"'+44770*****02",+447700900002,GB,duplicate,…,,,…,,, 4,"'+44770*****03",+447700900003,GB,valid,…,unknown,,…,unknown,,false 5,"'+44770*****05",+447700900005,GB,valid,…,unsupported_country,,…,unsupported_country,,false 6,***,,,invalid_number,…,,,…,,, ``` The input is masked, and a value a spreadsheet might read as a formula gets a leading apostrophe. To work with one slice through the API instead, page through `GET /v1/jobs/{id}/results` with filters, for example `?service=whatsapp®istered=true` for rows with a WhatsApp account, or `?service=carrier&status=unknown` for rows to retry later. ## What should you do with each row? | Result | Action | |---|---| | `number_status: invalid_number` | Remove, or ask the contact to correct it next time they log in | | `number_status: duplicate` | Merge the records. Keep the one with the best consent history | | `line_type: mobile` | Keep for SMS | | `line_type: fixed_line` | Move to voice or e-mail. Don't send SMS | | `line_type: toll_free`, `premium_rate`, `shared_cost` | Remove from SMS campaigns. These are rarely a person's own phone | | `line_type: voip` | Keep, but watch delivery. Some VoIP numbers can't receive SMS | | Channel `registered: true` and the contact opted in to it | Eligible for that channel | | Any check `unknown` or `unsupported_country` | Keep the row. Retry later. It wasn't charged | The last row matters most. `unknown` is missing data, not a bad number. Deleting unknown rows throws away good contacts, and you paid nothing for them. ## What does a cleaning pass cost? You pay per check, at the bulk price, and only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate), and suppressed rows are never checked. See [pricing](/pricing) for current rates. To size a run: `billable_max` from the estimate × the bulk price of each check is your worst case, and `max_cost` makes it a hard ceiling. The real bill is usually lower because of the free rows. Checks of the same number and service within the freshness window come from your account's cache for free, so re-running an updated list doesn't bill unchanged numbers twice. Compare the total with what you'd waste otherwise: SMS to invalid, duplicate and fixed-line rows, and SMS you can move to a cheaper channel the contact opted into. The [SMS cost reduction](/use-cases/sms-cost-reduction) page shows the arithmetic. ## Step 6: What happens to the data afterwards? Results stay available for your account's retention period (30 days by default) and are then deleted automatically. Once you've imported the columns you need into your CRM, you can purge earlier: ```bash curl -X DELETE https://api.mobilevalidate.com/v1/jobs/job_0VWFUGFpEfab2Ucnj33Z \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" ``` The job object remains with `purged_at` set, so your records show the job ran. Its per-row results and downloads are gone. In your own system, keep the decision and `checked_at` rather than the whole response. People can object to checks through our [opt-out form](/opt-out), and suppressed numbers come back as `suppressed` in later jobs. ## How often should you re-clean? It depends on how fast your list changes and what failure costs you: - **Before each large send** to a list that hasn't been checked for a while, run layers 1 to 3. - **Monthly or quarterly** for an active customer base, add channel checks if you route by channel. - **At the point of capture** for new sign-ups, use a real-time `POST /v1/lookup` instead of waiting for the next batch. See [OTP fraud prevention](/blog/otp-fraud-prevention-checks-before-sending-a-code). Store `checked_at` per number and re-check the oldest rows first. ## What are the key takeaways? - Estimate first. It's free and catches wrong-country and broken-export problems before you spend anything. - Always pass `max_cost` and an `Idempotency-Key` when you create a job. - Invalid, duplicate, suppressed and inconclusive rows are never charged. Don't delete `unknown` rows. - Act on `line_type` for SMS and on channel results only for contacts who opted in. - Purge job data when you're done, and keep only the decision and `checked_at`. ## Sources 1. [Reassigned Numbers Database](https://www.fcc.gov/reassigned-numbers-database) — Federal Communications Commission, 2023 2. [ITU-T Recommendation E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en) — International Telecommunication Union, 2010 3. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 ## Frequently asked questions ### How many numbers can one bulk job hold? Up to 50,000 numbers and e-mail addresses in total, with up to 20 checks per request and at most 100,000 identifier × check combinations per job. Split bigger lists into several jobs. ### Am I charged for invalid or duplicate rows? No. Invalid, duplicate and suppressed rows are never checked or charged, and neither are inconclusive answers such as unknown, unsupported country or timeouts. The free estimate shows these counts before you start. ### Should I delete numbers that come back unknown? No. Unknown means we couldn't get a conclusive answer, not that the number is bad. Keep the row, don't pay for it, and check it again later. ### How long are job results kept? For your account's retention period, 30 days by default. You can purge a finished job's results earlier with DELETE /v1/jobs/{id}. --- # How to reduce fake sign-ups with layered phone and e-mail checks > A layered sign-up defence: format, line type, messaging presence, spam reputation and e-mail checks, mapped to friction tiers you can measure. Canonical: https://mobilevalidate.com/blog/how-to-reduce-fake-signups-with-phone-and-email-checks · Last updated: 2026-09-25 ![Cover: How to reduce fake sign-ups with layered phone and e-mail checks](https://mobilevalidate.com/og/blog/how-to-reduce-fake-signups-with-phone-and-email-checks.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Fraud prevention · Tags: Sign up fraud, Fake accounts, Email verification, Line type, Fraud prevention To reduce fake sign-ups, check the phone number and the e-mail address a person enters in layers: format first, then line type, messaging presence, spam reputation and mailbox validity. Then turn the combined signals into friction tiers, from "allow" through "confirm" and "step up" to "review", instead of a single block rule. Measure how many real users each tier catches, and adjust. This guide shows how to build and tune that defence. ## Why do fake sign-ups need more than one check? Because fake accounts come from different sources, and each source leaves a different trace. A bot filling forms with random digits fails a format check. A promotion farmer using app-based numbers shows up in line type. A made-up address fails a mailbox check. A number already used for robocalls shows up in reputation data. No single signal sees all of them. A single strong rule also hurts real users. Many genuine people use VoIP numbers, company e-mail domains or messaging apps you don't check. If one weak signal can block a sign-up, you pay for it in lost customers. Layering lets you require **agreement** between signals before you add real friction. The attacker's economics matter too. Sign-ups often trigger a paid action: an SMS code, a trial, a referral bonus. [OWASP](https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/) describes how an SMS-sending endpoint without limits can be turned into a cost attack, with the attacker triggering tens of thousands of paid messages. Our guides on [OTP checks before sending a code](/blog/otp-fraud-prevention-checks-before-sending-a-code) and [SMS pumping](/blog/sms-pumping-how-it-works-and-how-to-stop-it) cover that pre-send step. This post covers the account itself. ## Which layers can you check at sign-up? From cheapest to most sensitive: | Layer | Question | Cost | Catches | Misses | |---|---|---|---|---| | Format and normalization | Is it a dialable number or a valid address? | Free | Random digits, typos, junk | Well-formed fakes | | Your own history | Seen this number, address or device before? | Free | Repeat sign-ups, ban evasion | First-time attackers | | Line type and carrier | Mobile, fixed, VoIP, premium rate? | Per check | Throwaway and non-mobile numbers | Real SIMs used for fraud | | Messaging presence | Does the number have a WhatsApp or Telegram account? | Per check | Numbers nobody uses day to day | New numbers, privacy settings | | Spam reputation | Has the number been reported? | Per check; US, CA, DE | Numbers with a report history | New or rarely used numbers | | Mailbox validation | Does the mailbox exist? | Per check | Made-up and mistyped addresses | Real throwaway mailboxes | | Account existence | Is the address registered on well-known services? | Per check | Freshly created addresses | Established addresses bought or stolen | A few notes on reading each layer: - **Line type.** The [carrier lookup](/services/carrier-lookup) returns `line_type` and the current `carrier`. Premium-rate and shared-cost numbers are poor sign-up numbers in almost any product. VoIP is a signal, not a verdict. Our [VoIP detection guide](/blog/voip-number-detection-for-signups) explains why. NIST's current guidance even removed the earlier prohibition on VoIP numbers for out-of-band authentication, while still asking verifiers to consider risk indicators such as SIM change and number porting ([NIST, 2025](https://pages.nist.gov/800-63-4/sp800-63b.html)). - **Messaging presence.** A [WhatsApp](/services/whatsapp-number-check) or [Telegram](/services/telegram-number-check) account means the number passed that platform's own sign-up at some point. No account is weak evidence on its own. Adoption differs by country, and some people keep their number private. - **Spam reputation.** The [spam reputation](/services/spam-reputation) check (limited access; US, CA and DE numbers) reports `risk_level` with explainable reasons. `no_reports` means no negative signals in our data. It doesn't mean safe. - **E-mail.** The [mailbox check](/services/email-verification) covers major webmail providers and answers `unknown` (`UNSUPPORTED_PROVIDER`) for company domains. Account existence checks are more revealing. Our comparison of [e-mail verification and account existence checks](/blog/email-verification-vs-account-existence-checks) explains when each fits. ## How do you run phone and e-mail checks in one request? Send the number and the address together, with the checks you want for each. Phone checks run on `numbers` and e-mail checks on `emails`: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "emails": ["registered@test.mobilevalidate.com"], "checks": ["carrier", "whatsapp", "telegram", "spam", "email"], "wait": 5}' ``` Real test-mode output, trimmed to the answers: ```json "network.carrier": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}}, "whatsapp.registered": {"status": "completed", "registered": true}, "telegram.registered": {"status": "completed", "registered": true}, "number.spam": {"status": "completed", "registered": true, "attributes": {"risk_level": "high", "risk_score": 95, "reason_regulator": true, "reason_community": true, "top_category": "robocall", "sources": 2}}, "email.valid": {"status": "completed", "registered": true} ``` This test number is deliberately contradictory: a mobile line with messaging accounts and a high spam level. That's what real data looks like too, and it's why you need rules that weigh signals instead of reacting to the first one. Every answer also carries `checked_at` and `billed`. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Test keys return fixed data and never bill; see [test mode](/docs/test-mode). ## How do you turn signals into friction tiers? Map combinations of signals to a small set of responses. Five tiers cover most products: | Tier | Response | User impact | |---|---|---| | 0: Allow | Create the account normally | None | | 1: Confirm | Require e-mail confirmation or a CAPTCHA before first use | Low | | 2: Step up | Ask for a second verification method, or verify by voice or a messaging app instead of SMS | Medium | | 3: Hold value | Create the account, but hold bonuses, credits or payouts until it has history | Low at sign-up, visible later | | 4: Review | Queue for manual review, or refuse | High | A simple points model is easy to explain and to tune. A starting point: | Signal | Points | |---|---| | `line_type` is `premium_rate` or `shared_cost` | +5 | | `line_type` is `voip` | +2 | | No WhatsApp and no Telegram account, in a market where both are common | +1 | | Spam `risk_level` is `high` | +3 | | Mailbox doesn't exist (`email.valid` is `false`) | +3 | | Number or device seen on a banned account | +5 | | Many sign-ups from one carrier or IP range in the last hour | +2 | | Any check `unknown` or `pending` | 0 | Then set thresholds: 0–1 allow, 2 confirm, 3–4 step up or hold value, 5 or more review. The exact numbers matter less than two principles. **Unknown adds nothing**, because missing data isn't evidence. And **no single weak signal reaches the review tier on its own**. Tier 3 is often the most effective and least visible. Fake accounts are usually created to extract something. If the bonus only arrives after a week of normal use, most farms move on, and real users barely notice. ## Where should each check sit in the flow? Not every check has to happen while the person waits. 1. **In the browser:** format validation and obvious typos. Free and instant. 2. **On submit, synchronously:** carrier and mailbox checks with a short `wait`, for example 3–5 seconds. These decide whether to send a code at all and whether to ask for a correction ("please check your e-mail address"). 3. **After account creation, asynchronously:** messaging presence, reputation and account existence. Use them to set the account's tier before it can earn or withdraw anything. Poll `GET /v1/lookups/{id}` or receive a `lookup.completed` [webhook](/docs/webhooks). 4. **At the moment of value:** re-check just before a payout, a referral credit or a change of contact details. See [account security](/use-cases/account-security). If an answer is still `pending` when your wait expires, continue with the default path and apply the result later. A slow check should never become a failed sign-up. ## How do you measure false positives? Measure before you enforce, and keep measuring after. - **Shadow mode.** Run a new rule for two to four weeks without acting on it. Log the tier it would have assigned, then compare those accounts' behaviour with the rest: chargebacks, abuse reports, promotion redemptions, retention. - **Step-up completion rate.** Of the users sent to tier 2, how many complete the extra verification and go on to behave normally? A high completion rate with normal behaviour means the rule is catching real people. - **Appeals and support contacts.** Tag tickets that mention verification problems. A rise after a rule change is a direct false-positive signal. - **Precision by signal.** For each rule, track what share of flagged accounts turned out to be fake. Drop or down-weight rules with low precision. - **Segment by country.** Messaging adoption, VoIP use and mailbox providers differ by market. A rule that works in one country can fail in another. Log decision codes (for example `tier=2 reasons=voip,cluster`) and `checked_at`, not full API responses. That's enough to analyse rules, and it keeps personal data to a minimum. ## What do attackers do when you add friction? They adapt, so expect your precision to drift. Common moves: - **Switching number sources**, for example from app-based VoIP numbers to cheap prepaid SIMs. Line type then shows `mobile`, and cluster signals (many sign-ups on one carrier in a short time) matter more. - **Ageing accounts** before using them, to get past holds. Re-check at the moment of value. - **Using real but stolen contact details.** Checks confirm that details are real, not that they belong to the person typing. That's where your own device and behaviour signals come in. Review rule performance monthly and after any spike in abuse. ## What privacy rules apply? Checking a sign-up's number and address is processing of personal data, so you need a lawful basis and a clear purpose. The [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) says in Recital 47 that processing strictly necessary for preventing fraud also constitutes a legitimate interest of the controller. The European Data Protection Board adds that this isn't automatic: the interest must be legitimate, and the processing must pass both a necessity and a balancing test ([EDPB, 2024](http://web.archive.org/web/20260425071638/https://www.edpb.europa.eu/our-work-tools/documents/public-consultations/2024/guidelines-12024-processing-personal-data-based_en)). In practice: - Run only the checks your tiers actually use (data minimisation, Article 5(1)(c)). - Mention fraud-prevention checks in your privacy notice. - Keep decisions, not raw results, and delete them when they are no longer needed. This isn't legal advice. Consult your counsel for your case. Our [privacy checklist for phone and e-mail checks](/blog/privacy-checklist-for-phone-and-email-checks) goes through each point. Our [acceptable use policy](/legal/acceptable-use) also forbids using results for eligibility decisions such as credit or employment. ## What are the key takeaways? - Fake sign-ups come from different sources. Layer cheap checks (format, history) with paid ones (line type, messaging presence, reputation, mailbox) so each catches what the others miss. - Run phone and e-mail checks in one request, synchronously for what decides the next step and asynchronously for the rest. - Map signals to friction tiers. Require agreement between signals before adding heavy friction, and let unknown answers add nothing. - Holding rewards until an account has history stops a lot of abuse with little impact on real users. - Measure false positives with shadow mode, step-up completion and appeals, per country, and review rules regularly. - Keep only decision codes and timestamps, and document your lawful basis. Start with the [OTP and sign-up fraud use case](/use-cases/otp-and-signup-fraud). ## Sources 1. [NIST SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management](https://pages.nist.gov/800-63-4/sp800-63b.html) — NIST, 2025 2. [API4:2023 Unrestricted Resource Consumption](https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/) — OWASP API Security Project, 2023 3. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 4. [Guidelines 1/2024 on processing of personal data based on Article 6(1)(f) GDPR (version 1.0)](http://web.archive.org/web/20260425071638/https://www.edpb.europa.eu/our-work-tools/documents/public-consultations/2024/guidelines-12024-processing-personal-data-based_en) — European Data Protection Board (archived copy), 2024 ## Frequently asked questions ### Which single check stops the most fake sign-ups? There isn't one. Each check catches a different kind of fake: malformed input, throwaway numbers, numbers nobody uses, reported numbers, mailboxes that don't exist. Combining cheap layers, and adding friction only when several signals agree, works better than relying on one strong rule. ### Should a sign-up be blocked when a check returns unknown? No. Unknown means the check could not reach a conclusion, for example because of a timeout. Treat it as missing data, continue with your default path and, if needed, re-check later. Unknown answers are not charged. ### How do I know whether my rules are turning away real customers? Run new rules in shadow mode first, log the decision they would have made, and follow those accounts. Once live, track how many stepped-up users complete the extra verification and how many blocked users appeal successfully. ### Can I use account existence checks on every sign-up? You can, but they reveal more about a person than a mailbox check, so use them only where the fraud risk justifies it, such as sign-ups that unlock money or rewards. Store the resulting decision, not the list of services. --- # IRSF: international revenue share fraud, explained > How international revenue share fraud turns your calls and texts into someone else's income, how it differs from SMS pumping, and how to cap your exposure. Canonical: https://mobilevalidate.com/blog/irsf-international-revenue-share-fraud · Last updated: 2026-09-25 ![Cover: IRSF: international revenue share fraud, explained](https://mobilevalidate.com/og/blog/irsf-international-revenue-share-fraud.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Fraud prevention · Tags: Irsf, Toll fraud, SMS pumping, OTP, Fraud prevention International revenue share fraud (IRSF) is telecom fraud in which someone generates calls or texts to numbers they profit from, usually international or special-rate ranges, and collects part of the fee you pay. Your app becomes the traffic source. The defence is to limit where traffic can go, how fast it can grow and which line types it may reach. ## What is IRSF? IRSF exploits how money moves between operators. When a call or message crosses networks, the originating side pays for it, and part of that payment flows to whoever terminates the traffic. If a fraudster controls a block of numbers, or has a deal with a party that does, every call or text to those numbers earns them a share. The short version is in our [IRSF glossary entry](/glossary/irsf). The traffic can come from anywhere that places calls or sends texts to a number someone else chooses: - **A compromised phone system** that dials expensive destinations overnight. - **A voice or SMS verification form** that a bot fills in with the fraudster's numbers. - **A callback or "call me" feature** on a website. - **Stolen or trial accounts** on a communications platform. For app developers, the second and third routes matter most. Nobody breaks into your systems. The attacker uses a feature exactly as it was built, just with numbers of their choosing and at a volume you didn't plan for. ## How is IRSF different from SMS pumping? They are close relatives. OWASP groups both under **cost-inflation fraud** (OAT-003), the "mass use of functionality to illegitimately profit from chargeable supporting services", and lists "SMS pumping" and "toll fraud" among its other names ([OWASP](https://github.com/OWASP/www-project-automated-threats-to-web-applications/blob/master/assets/oats/EN/OAT-003_Cost-Inflation_Fraud.md)). | | IRSF (classic) | SMS pumping | |---|---|---| | Channel | Mostly voice calls; texts too | Text messages | | Typical trigger | Compromised phone systems, callback features, voice OTP | Sign-up, login and "send code" forms | | Destinations | International, premium-rate and global-service ranges | Often ordinary mobile ranges in high-fee countries | | Money per event | Per minute, so long calls pay more | Per message | | First sign | A spike in call minutes to unusual countries | Codes sent rise, codes verified stay flat | If you already defend against pumping, you have half the controls. Voice needs extra care because the cost grows with call length. See [SMS pumping: how it works and how to stop it](/blog/sms-pumping-how-it-works-and-how-to-stop-it) for the text-message side. ## Why do international premium and special ranges matter? Most numbers belong to a country code. A few codes belong to no country. The ITU publishes the list of E.164 country codes, which includes codes assigned to global services rather than to countries ([ITU](https://www.itu.int/pub/T-SP-E.164D)). Examples are satellite systems (+881 and others) and international networks (+882, +883). The ITU also defined a numbering scheme for universal international premium rate numbers ([ITU-T E.169.2](https://www.itu.int/rec/T-REC-E.169.2/en)), carried under +979. These ranges share three properties that suit IRSF: 1. **Termination can be expensive**, which makes the revenue share worth having. 2. **Real customers rarely use them** for sign-ups or OTP, so blocking them costs you little. 3. **They are easy to miss** in a country allow-list that only thinks in terms of countries. Within ordinary countries, the same logic applies to premium-rate, shared-cost and universal access (UAN) ranges. A number's [line type](/glossary/line-type) tells you which kind of range it belongs to. ## Which destinations are high-risk? We won't publish a country list. Fraud moves, and any list gets out of date the week after it is written. The telecom industry tracks the trend: the Communications Fraud Control Association publishes a periodic fraud loss survey of its members ([CFCA](https://cfca.org/fraud-loss-survey/)). What stays stable is the pattern. Risk concentrates where: - terminating a call or text is expensive, - number ranges can be hijacked or sub-allocated without much oversight, and - you have no customers, so any traffic there is suspect by default. That last point is the one you control. One of the most effective IRSF controls is still a short list of the countries you serve. ## How do you cap your exposure? Stack independent controls, so one gap doesn't cost you a month of revenue. This decision table covers the main layers: | Control | Rule | Stops | Cost to real users | |---|---|---|---| | Country allow-list (geo permissions) | Calls and texts only to countries you serve; exceptions reviewed | Most IRSF destinations | None in your markets | | Global-service block | Block +881, +882, +883, +979 and similar codes by default | Satellite, network and premium global ranges | Near zero | | Line-type check | Refuse `premium_rate`, `shared_cost`, `uan`; don't call `toll_free` for OTP | Special-rate ranges inside allowed countries | Near zero | | Per-prefix velocity | Cap sends or calls per number prefix per hour | Bots walking through a block | Low | | Per-country ceiling | Alert and pause when a country exceeds its normal hourly volume | Sudden spikes | Low, with a manual override | | Call-duration cap | Hang up verification calls after the message plays | Long-duration payouts | None | | Spend cap | Daily limit on your telephony account, with an alert well below it | Runaway bills | None | OWASP's API Security Top 10 frames this as protecting a **sensitive business flow**: the risk is not a bug, but a legitimate flow used at a scale that hurts the business ([OWASP, 2023](https://owasp.org/API-Security/editions/2023/en/0xa6-unrestricted-access-to-sensitive-business-flows/)). The fix is the same: limit who can trigger the flow, how often and to where. ## How does a line-type check help? Allow-lists work at country level. Inside an allowed country, a number check tells you what kind of line you are about to pay for. With MobileValidate, the [carrier lookup](/services/carrier-lookup) returns `line_type`, `carrier` and `country` for a number, and a normalization step rejects impossible numbers for free. A test-mode request with a valid test number, a repeated number and one that can't be parsed: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900001", "12345"], "checks": ["carrier"], "wait": 5}' ``` Response (excerpt of `results`, test mode): ```json [ {"input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": {"network.carrier": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "billed": false}}}, {"input": "+447700900001", "e164": "+447700900001", "country": "GB", "number_status": "duplicate"}, {"input": "12345", "e164": null, "country": null, "number_status": "invalid_number"} ] ``` The duplicate and the invalid input are never checked or charged. Your code then applies the rules: ```js const BLOCKED_CODES = ["881", "882", "883", "979"]; // global services; extend as needed const REFUSE = ["premium_rate", "shared_cost", "uan"]; function irsfGate(r, allowedCountries) { if (r.number_status !== "valid") return "refuse"; if (BLOCKED_CODES.some((cc) => r.e164.startsWith("+" + cc))) return "refuse"; if (!allowedCountries.includes(r.country)) return "review"; const line = r.checks?.["network.carrier"]?.attributes?.line_type; // undefined when unknown if (REFUSE.includes(line)) return "refuse"; if (line === "toll_free" || line === "fixed_line") return "no_sms"; // voice only, under stricter limits return "allow"; // includes unknown: fall back to your limits } ``` An `unknown` answer means we have no data for that number. It isn't charged, and it shouldn't block anyone on its own. The prefix limits and spend caps still apply. ## What should you monitor? IRSF shows up in cost data before it shows up anywhere else. Watch these, per hour rather than per day: - **Minutes and messages per destination country**, against the same hour last week. - **Verification rate per country and per prefix**: codes entered divided by codes sent. Real users verify. Fraud traffic doesn't. - **Average call duration** on verification calls. A verification call plays a short message. Long calls deserve a look. - **New destinations**: the first call or text ever to a country or global-service code. - **Spend against your cap**, with an alert at a fraction of it. Give on-call staff a per-country kill switch. An attack that starts on a Friday night is cheap to stop at 2 a.m. and expensive to discover on Monday. ## What should you do when an attack is under way? Speed matters more than precision. Every hour of an active attack is billed, so act first and fine-tune later: 1. **Pause the destination.** Switch off calls and texts to the affected country or global-service code with your kill switch or your provider's geographic permissions. 2. **Tighten the flow.** Lower per-prefix and per-IP limits on the feature being abused, and add a bot challenge if it had none. 3. **Tell your provider.** Report the traffic with timestamps and destination ranges. Providers see fraud across many customers and can often block ranges faster than you can. 4. **Preserve the evidence.** Keep request logs, IP addresses and the destination numbers (masked where you share them internally) for the dispute and for tuning your rules. 5. **Review after the fact.** Which control would have stopped it earliest? Add that control to the default setup for every new market. Reopen the destination only when the controls that failed have been fixed and real customers there need it. ## What are the key takeaways? - IRSF makes you pay for calls or texts to numbers that earn the fraudster a share of the fee. Your own verification and callback features are the usual route. - It is a close relative of SMS pumping. OWASP groups both under cost-inflation fraud. - Global-service codes and premium or shared-cost ranges are prime targets, and real customers rarely need them. - Cap exposure in layers: country allow-list, global-service block, line-type check, per-prefix velocity, call-duration and spend caps. - A line-type check removes special-rate ranges before you pay. Unknown answers are free and should fall back to your default limits, not block users. The [SMS cost reduction use case](/use-cases/sms-cost-reduction) shows the same checks from a budget angle. For the pre-send pipeline behind every code, read [OTP fraud prevention: checks to run before sending a code](/blog/otp-fraud-prevention-checks-before-sending-a-code). ## Sources 1. [OAT-003 Cost-Inflation Fraud (OWASP Automated Threats to Web Applications)](https://github.com/OWASP/www-project-automated-threats-to-web-applications/blob/master/assets/oats/EN/OAT-003_Cost-Inflation_Fraud.md) — OWASP, 2026 2. [List of ITU-T Recommendation E.164 assigned country codes](https://www.itu.int/pub/T-SP-E.164D) — ITU 3. [Recommendation ITU-T E.169.2: universal international premium rate numbers](https://www.itu.int/rec/T-REC-E.169.2/en) — ITU, 2000 4. [Fraud Loss Survey](https://cfca.org/fraud-loss-survey/) — Communications Fraud Control Association (CFCA) 5. [OWASP API Security Top 10 (2023): API6 Unrestricted Access to Sensitive Business Flows](https://owasp.org/API-Security/editions/2023/en/0xa6-unrestricted-access-to-sensitive-business-flows/) — OWASP, 2023 ## Frequently asked questions ### What is the difference between IRSF and SMS pumping? They share the same money trail: someone earns part of the fee for traffic to numbers they control. IRSF is the older, broader term and covers calls as well as texts, often to international or special ranges. SMS pumping is the variant that abuses app forms that send one-time passcodes. ### Which countries are high-risk for IRSF? It changes over time, so a fixed list goes stale. Risk follows expensive termination and ranges that are easy to abuse. The safest rule is to allow calls and texts only to the countries where you have customers, and to review exceptions. ### Can a line-type check stop IRSF? It removes a whole class of destinations, such as premium-rate, shared-cost and global-service ranges, before you pay. It doesn't catch fraud on ordinary mobile ranges, which is why you also need a country allow-list, per-prefix limits and spend caps. ### Who pays when IRSF happens? Usually the business whose account generated the traffic. Your provider charges you for the calls or messages, whether or not a real person received them. --- # Is a phone number personal data under the GDPR? > When a phone number is personal data under the GDPR, what changes for business numbers, and how lookups and enrichment fit lawful basis, notice and rights. Canonical: https://mobilevalidate.com/blog/is-a-phone-number-personal-data-gdpr · Last updated: 2026-09-25 ![Cover: Is a phone number personal data under the GDPR?](https://mobilevalidate.com/og/blog/is-a-phone-number-personal-data-gdpr.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: GDPR, Privacy, Personal data, Compliance, Phone lookups In most cases, yes. A phone number that belongs to a person, or can be linked to one with reasonable effort, is personal data under Article 4(1) GDPR. That includes most mobile numbers, sole traders' numbers and employees' direct lines. A company switchboard usually isn't. The result of a lookup on a personal number is personal data too. **This is not legal advice.** It explains how the GDPR text and the case law of the EU Court of Justice are commonly read, applied to phone-number lookups and enrichment. The UK GDPR and national laws differ in detail. Consult your counsel for your own case. For a step-by-step compliance list, see our [privacy checklist for phone and e-mail checks](/blog/privacy-checklist-for-phone-and-email-checks). This article goes one level deeper on the questions that come before it. ## What does the GDPR say counts as personal data? The [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) defines personal data in Article 4(1) as "any information relating to an identified or identifiable natural person". A person is identifiable if they "can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier" or other factors. Recital 26 explains how to test identifiability. You take account of "all the means reasonably likely to be used, such as singling out, either by the controller or by another person". Relevant factors include "the costs of and the amount of time required for identification", given the technology available at the time. Anonymous information, where the person is not or no longer identifiable, falls outside the regulation. A phone number is a textbook identifier. It is usually unique, it is often tied to one person for years, and it can be linked to a name through contracts, contact lists, messaging apps or a simple call. For a mobile number, the link to one individual is usually direct. That's why regulators and courts treat most phone numbers as personal data without much debate. ## Does it matter who can identify the person? Yes, and two judgments of the Court of Justice shape the answer. In **Breyer** (C-582/14, 19 October 2016), decided under the GDPR's predecessor, Directive 95/46, the Court held that a dynamic IP address stored by a website operator was personal data for that operator where it "has the legal means which enable it to identify the data subject with additional data" held by the internet provider ([CJEU, 2016](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:62014CJ0582)). It added that a means of identification is not "likely reasonably to be used" if identification is prohibited by law or practically impossible because it requires "a disproportionate effort in terms of time, cost and man-power, so that the risk of identification appears in reality to be insignificant" (paragraphs 45 and 46). The additional information doesn't have to sit with one party. In **EDPS v SRB** (C-413/23 P, 4 September 2025), the Court looked at pseudonymised data passed to a third party. It held that the existence of additional information does not mean that such data must be regarded "as constituting, in all cases and for every person, personal data", because pseudonymisation may prevent persons other than the controller from identifying anyone. It also held that the controller's own duty to inform people is assessed from the controller's point of view, at the time of collection ([CJEU, 2025](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:62023CJ0413)). The case concerned the EU institutions' data protection regulation (2018/1725), whose definitions mirror the GDPR's. For phone numbers the practical lesson is simple. The business that holds the number almost always knows whose it is. For that business, the number and anything learned about it are personal data. ## Are business phone numbers personal data? It depends on whether the number relates to a legal person or to a natural person. Recital 14 says the GDPR "does not cover the processing of personal data which concerns legal persons", including "the contact details of the legal person". | Number | Usually personal data? | Why | |---|---|---| | Company switchboard or general support line | Usually not | Contact detail of a legal person (Recital 14) | | Shared department line (e.g. sales desk) | Often not, but check | Relates to the company, unless one person answers it | | Employee's direct dial or work mobile | Yes | Relates to an identifiable employee | | Sole trader's or freelancer's number | Yes | The business and the person are the same | | Personal mobile used for a business account | Yes | Relates to the individual | | Number in a B2B lead list with a contact name | Yes | Linked to a named person | Two cautions apply. First, a list rarely says which kind each number is. A B2B list is usually a mix, and many small businesses run on the owner's mobile. Second, the line type doesn't settle the question. A mobile number can be a company's shared phone, and a fixed line can be one person's home. When you can't tell, treat the number as personal data. The cost of that assumption is low, and the cost of the opposite mistake is higher. ## Is a hashed or masked phone number still personal data? Usually, yes, for the business that did the hashing. Article 4(5) defines pseudonymisation as processing data so it can no longer be attributed to a person "without the use of additional information", kept separately. Recital 26 says pseudonymised data that "could be attributed to a natural person by the use of additional information should be considered to be information on an identifiable natural person". Phone numbers are also a weak case for hashing. A country has only a limited set of possible numbers, so an unsalted hash can often be reversed by hashing every candidate and comparing. A keyed hash (with a secret key) is much better, and it's how we store identifiers ourselves. It is still pseudonymisation, not anonymisation. Masking works the same way. A number shown as `+44770*****01` in a log is safer to handle. But if the full number sits elsewhere in the same system, the masked value is still personal data. Masking reduces risk; it doesn't take you outside the regulation. ## Is a lookup or enrichment "processing"? Yes. Article 4(2) defines processing as any operation on personal data, including "consultation, use, disclosure by transmission" and "alignment or combination". A number lookup touches several of these at once: 1. **Transmission.** You send the number to a lookup provider. 2. **Creation of new data.** The provider returns facts about the number: line type, carrier, porting, whether an account exists on a messaging app, a spam-report level. 3. **Combination.** You attach those facts to your customer record. The answer is personal data too. "This number is a mobile line on carrier X" or "this number has a messaging account" relates to the person who holds the number. Some answers reveal more than others: an account on a specific service says more about a person's life than a line type. That difference should shape which checks you run. Our comparison of [e-mail verification and account existence checks](/blog/email-verification-vs-account-existence-checks) walks through it. Roles matter as well. When you send numbers to a provider to answer your question, you are normally the **controller** and the provider acts as your **processor** under an Article 28 contract. A provider can also be a controller for data it compiles itself, such as spam-report data. Our [data-subject notice](/legal/data-subject-notice) explains which role we take for which check, and our [DPA](/legal/dpa) covers the processor side. ## Which lawful basis fits a phone lookup? Every purpose needs one of the six bases in Article 6(1). For lookups and enrichment, three are common: | Purpose | Likely basis | Key test | |---|---|---| | Fraud screening at sign-up or before sending a passcode | Legitimate interests, Art. 6(1)(f) | Balancing test; Recital 47 names fraud prevention | | Choosing the channel for messages the customer asked for | Contract, Art. 6(1)(b), or legitimate interests | Must be necessary, not merely useful | | Cleaning a list before a campaign | Legitimate interests; consent for the campaign itself under e-privacy rules | Reasonable expectations of the people on the list | | Enriching records "in case it's useful" | Hard to justify | No specific purpose, so purpose limitation fails | Recital 47 says processing "strictly necessary for the purposes of preventing fraud also constitutes a legitimate interest of the data controller concerned". It also says the interests of the person can override where data is processed "in circumstances where data subjects do not reasonably expect further processing". The European Data Protection Board sets out three cumulative conditions for Article 6(1)(f): a legitimate interest, necessity, and a balancing test that the person's rights don't override ([EDPB, 2024](https://www.edpb.europa.eu/our-work-tools/documents/public-consultations/2024/guidelines-12024-processing-personal-data-based_en)). Enrichment deserves a separate warning. If you collected a number to send order updates and later run lookups to build marketing segments, that is a new purpose. Article 6(4) asks you to assess whether it is compatible with the original one, looking at the link between purposes, the context and the person's reasonable expectations. Silent enrichment often fails that test. ## What must people be told when you check their number? It depends on where the data came from. Article 13 applies when you collect data from the person, for example when they type their number into your sign-up form. Your privacy notice should then mention that you verify numbers, why, and on which lawful basis. Article 14 applies where personal data "have not been obtained from the data subject". Lookup results fit that description: they come from a provider, not from the person. Article 14(2)(f) asks you to say "from which source the personal data originate". Under Article 14(3), you must inform people within a reasonable period, "at the latest within one month", or at the first communication if you use the data to contact them. Article 14(5)(b) lets a controller skip individual notices where providing them "proves impossible or would involve a disproportionate effort". In that case it must take "appropriate measures to protect the data subject's rights", "including making the information publicly available". That's the reason lookup providers publish notices for people who never dealt with them. Ours is the [notice to people whose number or e-mail is checked](/legal/data-subject-notice). It covers what we hold, where it comes from, how long we keep it and how to object. ## How long can you keep lookup results? Only as long as the purpose needs. Article 5(1)(e) (storage limitation) applies to results as much as to the number. For most flows, the decision and its date are enough, for example `sms_ok=true, checked 2026-09-25`. The raw response can go within days. Results also age. Numbers are ported, recycled and abandoned, so an old result can be wrong about the person who holds the number today. Keeping stale results is a retention problem and an accuracy problem (Article 5(1)(d)). On our side, real-time lookups are kept for 7 days and bulk jobs for 30 days by default. Customers can set a shorter or longer bulk retention period, up to 24 months. The [trust page](/trust) lists the details. ## What rights do people have over lookup data? The usual rights apply to the number and to the results: access (Article 15), rectification (Article 16), erasure (Article 17) and objection (Article 21). Objection matters most for lookups, because most rely on legitimate interests. Under Article 21(1), once someone objects, "the controller shall no longer process the personal data unless the controller demonstrates compelling legitimate grounds" that override the person's interests, or needs the data for legal claims. For direct marketing, the right to object in Article 21(2) and (3) has no such exception. In practice: - Keep a suppression list keyed on the E.164 number and check it before every lookup. - Tell your provider about objections that affect it. - People can object to checks made through MobileValidate with our [opt-out form](/opt-out). Suppressed numbers come back as `suppressed`, aren't checked and aren't charged. The confirmation doesn't reveal whether we hold data about a number. ## Can a lookup result decide on its own to reject someone? Be careful. Article 22(1) gives people the right "not to be subject to a decision based solely on automated processing" that "produces legal effects" or "similarly significantly affects" them. Exceptions exist, for example where the decision is necessary for a contract, but they come with safeguards such as human review. Blocking a passcode to a premium-rate number is unlikely to reach that threshold. Refusing someone a financial product because a lookup said "VoIP" might. Use lookups as signals, offer another route (a different channel, a manual review), and never treat `unknown` as a negative. We return `registered / not registered / unknown` with a `checked_at` time so that your rules can tell "no" apart from "no data". ## What are the key takeaways? - Most phone numbers are personal data under Article 4(1) GDPR: mobiles, sole traders' numbers and employees' direct lines. A company switchboard usually isn't (Recital 14). - Identifiability is judged by the means reasonably likely to be used (Recital 26; *Breyer*, 2016). The business that holds the number can almost always identify the person. - Hashing and masking are pseudonymisation, not anonymisation. Hashed numbers usually remain personal data for you. - A lookup is processing, and its result is new personal data. It needs a purpose, a lawful basis, a retention period and transparency (Articles 5, 6, 13 and 14). - Honour objections with a suppression list, keep decisions rather than raw results, and don't let a lookup alone make significant decisions about people. - This isn't legal advice. Consult your counsel, and see our [privacy checklist](/blog/privacy-checklist-for-phone-and-email-checks) for the operational steps. ## Sources 1. [Regulation (EU) 2016/679 (General Data Protection Regulation), Articles 4, 5, 6, 13, 14, 21, 22; Recitals 14, 26, 47](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union (EUR-Lex), 2016 2. [Judgment of 19 October 2016, Breyer, C-582/14, EU:C:2016:779](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:62014CJ0582) — Court of Justice of the European Union (EUR-Lex), 2016 3. [Judgment of 4 September 2025, EDPS v SRB, C-413/23 P](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:62023CJ0413) — Court of Justice of the European Union (EUR-Lex), 2025 4. [Guidelines 1/2024 on processing of personal data based on Article 6(1)(f) GDPR (version 1.0)](https://www.edpb.europa.eu/our-work-tools/documents/public-consultations/2024/guidelines-12024-processing-personal-data-based_en) — European Data Protection Board, 2024 ## Frequently asked questions ### Is a mobile phone number personal data under the GDPR? In most cases, yes. Article 4(1) GDPR covers any information relating to an identified or identifiable natural person, and a mobile number usually belongs to one person and can be linked to them with reasonable effort. Treat mobile numbers as personal data unless you have a documented reason not to. ### Are business phone numbers personal data? It depends on whose number it is. Recital 14 says the GDPR does not cover the contact details of legal persons, so a company switchboard is usually outside it. A sole trader's number, or an employee's direct line or work mobile, relates to a natural person and is usually personal data. ### Is a hashed phone number still personal data? Usually, yes. Hashing is a form of pseudonymisation, and Recital 26 says pseudonymised data that can be attributed to a person with additional information should be treated as personal data. Because the set of possible numbers is small, many hashes can be reversed by trying every number. ### Is the result of a phone number lookup personal data too? Usually, yes. A result such as a line type, a carrier or whether the number has an account on a messaging app is information relating to the person who holds the number. It is new data about them, so it needs a purpose, a lawful basis, a retention period and transparency like the number itself. ### Is this article legal advice? No. It explains how the GDPR text and case law are commonly read for phone-number lookups. Your obligations depend on your purpose, your role and your country, so consult your own counsel before relying on it. --- # Messaging-app registration checks: a guide for every app > What a registration check tells you for WhatsApp, Telegram, Viber, Signal, iMessage, RCS, LINE, Zalo and more: modes, unknowns, billing and consent. Canonical: https://mobilevalidate.com/blog/messaging-app-registration-checks-guide · Last updated: 2026-09-25 ![Cover: Messaging-app registration checks: a guide for every app](https://mobilevalidate.com/og/blog/messaging-app-registration-checks-guide.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Channel selection, Messaging, WhatsApp, iMessage, RCS, Consent A messaging-app registration check tells you whether a phone number you hold can be associated with an account on an app such as WhatsApp, Telegram, Viber, Signal, LINE or Zalo, or whether it can receive iMessage or RCS. Each answer is registered, not registered or unknown, with the time of the check. Nothing is sent to the number. This guide covers all twelve messaging checks we offer: which ones run in real time and which in bulk only, what a "no" means on each app, why answers come back unknown, how billing works, and how to use them with consent. ## What does a registration check answer, and what doesn't it? It answers one narrow question per app: can an account on this platform be found through this phone number right now? That supports three jobs: - **Channel selection** for messages people already expect, such as passcodes, delivery updates and reminders. See [channel selection](/use-cases/channel-selection). - **Deliverability.** A number with a messenger account passed that app's own phone verification at some point, so it's less likely to be a typo or an invented entry. - **Risk signals** at sign-up, as one input among several. It doesn't answer who owns the number, whether they read messages, whether the phone is switched on, or whether you may contact them. We never return names, photos, usernames, profile links or last-seen times. Every conclusive answer carries `checked_at`, because accounts come and go and carriers reassign numbers. An answer from this morning is stronger than one from last quarter. ## Which apps can be checked, and in which mode? This table covers every messaging check in our [services reference](/docs/services). Prices change, so see current per-check prices for real-time and bulk on the [pricing page](/pricing). | App | Check (alias) | Mode | What `true` means | |---|---|---|---| | WhatsApp | `whatsapp` | real time + bulk | An account exists for the number | | WhatsApp Business | `whatsapp.business` | real time + bulk | Account exists; `business` flag says if it's a business account | | Telegram | `telegram` | real time + bulk | An account can be found by the number | | Viber | `viber` | real time + bulk | An account exists for the number | | Zalo | `zalo` | real time + bulk | An account exists for the number | | Signal | `signal` | bulk only | A discoverable account exists | | iMessage | `imessage` | bulk only | Registered for iMessage on an Apple device | | RCS | `rcs` | bulk only | The number could receive RCS at check time; may add `device_os` | | LINE | `line` | bulk only | An account is associated with the number | | Botim | `botim` | bulk only | An account exists for the number | | MAX | `max` | bulk only | An account exists for the number | | Messenger | `messenger` | bulk only | A Messenger account is linked to the number | You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). All twelve checks accept numbers from every country. ## Why are some checks real time and others bulk only? Real-time checks answer inside the request, usually within seconds, which suits sign-up forms and the moment before you send a passcode. Bulk-only checks gather answers in batches, so they run in [bulk jobs](/docs/bulk-jobs): you submit up to 50,000 numbers and e-mails, follow progress, then download the results. If you send a bulk-only check to the real-time endpoint, it's refused before anything is checked. This is the real test-mode response for `POST /v1/lookup` with `checks: ["imessage"]`: ```json {"error": {"code": "service_disabled", "message": "The check 'imessage.registered' is available in bulk jobs only (POST /v1/jobs).", "status": 403, "retryable": false, "param": "checks[0]"}} ``` The practical rule: use real-time checks at the moment of contact, and run bulk-only checks ahead of time over your opted-in base, for example once a month. Store the answers with their dates and route from your own records. `GET /v1/services` returns a `modes` array for each service, so your code never has to hard-code which is which. ## What does a "not registered" answer mean on each app? A conclusive `false` is not equally strong everywhere. On apps where the phone number *is* the account, it's informative. On apps where people can hide from number search, it often means "not findable through this number". | App | How strong is `false`? | Why | |---|---|---| | WhatsApp, Viber, Zalo | Strong | The number is the account. Zalo users can limit who finds them by number, so a small share may not be confirmable | | Telegram | Weak | By default a number is visible only to the person's contacts, and people control whether they can be found by number ([Telegram FAQ](https://telegram.org/faq)) | | Signal | Weak | Since 2024 people can set "Who can find me by my number" to Nobody; then others can't "even know that you have a Signal account" ([Signal, 2024](https://signal.org/blog/phone-number-privacy-usernames/)) | | LINE | Medium | People also connect by LINE ID and QR code, and not every account is findable by number | | Messenger | Weak | A phone number is optional on the account | | iMessage | Medium | `false` is normal for Android users; see our [iMessage guide](/blog/check-if-a-number-has-imessage) | | RCS | Medium, and short-lived | Depends on carrier, handset and settings; see the [RCS guide](/blog/rcs-capability-check-explained) | Never treat `false` alone as proof that a number is fake. Combine it with line type and your own signals. ## Why do checks come back unknown? Unknown (`registered: null`) means no conclusive answer was obtained. The `status` and `reason` fields say why: | `status` / `reason` | Typical cause | What to do | |---|---|---| | `unknown` / `UPSTREAM_TIMEOUT` | The answer didn't arrive in time | Keep your default channel; try again later | | `unsupported_country` | The service doesn't cover the number's country | Use another channel for that country | | `pending` | Still being checked when `wait` ran out | Poll `GET /v1/lookups/{id}` or use a [webhook](/docs/webhooks) | | `number_status: invalid_number` or `duplicate` | The number was never checked | Fix the record or drop the duplicate | Two rules keep your data clean. **Unknown is never a no**: don't route, score or delete on it. And **never overwrite a stored conclusive answer with unknown**. A timeout today doesn't erase what you learned last month. ## What does a real request look like? A real-time lookup can check several apps at once. This is real test-mode output for three [test numbers](/docs/test-mode) with `checks: ["whatsapp", "telegram", "viber", "zalo"]`, trimmed to the answers: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["whatsapp", "telegram", "viber", "zalo"], "wait": 5}' ``` ```json "+447700900001": {"whatsapp.registered": true, "telegram.registered": true, "viber.registered": true, "zalo.registered": true} "+447700900002": {"whatsapp.registered": false, "telegram.registered": false, "viber.registered": false, "zalo.registered": false} "+447700900003": all four "status": "unknown", "registered": null, "reason": "UPSTREAM_TIMEOUT" ``` For bulk-only apps, the same numbers go into `POST /v1/jobs`. A real test job for the same three numbers with `["line", "zalo", "botim", "max"]` completed at once with `"progress": {"total": 3, "checks_total": 12, "done": 12, "conclusive": 8}`. Test keys are free and never bill, and test answers are invented. ## How do I route checks to real-time lookups and bulk jobs automatically? Read the catalog once, then send each check where it can run. This Python sketch uses `modes` from `GET /v1/services`: ```python import os, requests API = "https://api.mobilevalidate.com" H = {"Authorization": f"Bearer {os.environ['MOBILEVALIDATE_API_KEY']}"} services = requests.get(f"{API}/v1/services", headers=H, timeout=20).json()["data"] realtime = {s["code"] for s in services if "realtime" in s["modes"]} ALIASES = {"whatsapp": "whatsapp.registered", "telegram": "telegram.registered", "imessage": "imessage.registered", "rcs": "rcs.registered", "line": "line.registered"} def split_checks(checks): rt = [c for c in checks if ALIASES.get(c, c) in realtime] bulk = [c for c in checks if ALIASES.get(c, c) not in realtime] return rt, bulk rt, bulk = split_checks(["whatsapp", "telegram", "imessage", "rcs", "line"]) # rt → POST /v1/lookup at sign-up (up to 100 numbers per request) # bulk → POST /v1/jobs nightly or monthly over consented customers (up to 50,000 per job) print(rt, bulk) # ['whatsapp', 'telegram'] ['imessage', 'rcs', 'line'] ``` Limits to plan around: 20 checks per request, 2,000 number × check pairs per lookup and 100,000 per job. Call the free `POST /v1/jobs/estimate` first to see the maximum cost of a job. ## How often do registration results change? It depends on the app. Established messengers where the number is the account change slowly: people keep the same WhatsApp or Viber account for years, until they change number. Some things change faster: - **RCS capability** depends on the carrier, the handset and a setting. Google's RCS documentation notes that a device must have connected to the RCS service within the last 31 days to be reported as capable ([Google, 2026](https://developers.google.com/business-communications/rcs-business-messaging/guides/build/capabilities)). - **New apps** such as MAX gain users quickly, so answers for the same number can flip. - **Recycled numbers.** When a carrier reassigns a number, the old account may linger or disappear. A practical cadence: refresh messenger checks monthly and after any failed delivery. Use a shorter `max_age` for RCS. Repeat checks inside the freshness window are served from your account's cache for free (`cached: true`, `billed: false`), and `max_age: 0` forces a fresh, billed check. ## How do I pick which apps to check in each country? Check the channels you could actually send on, and weight them by where your customers live. Published scale figures are few, and they're self-reported: WhatsApp reported [two billion users in 2020](https://blog.whatsapp.com/two-billion-users-connecting-the-world-privately), Telegram's [FAQ](https://telegram.org/faq) says it has over one billion active users, and LY Corporation reports that LINE had about [100 million monthly active users in Japan](https://www.lycbiz.com/jp/service/line-official-account/) at the end of March 2026. | Customer base | Checks worth running | |---|---| | Global consumer app | `whatsapp`, `telegram`, plus `rcs` and `imessage` in bulk for format planning | | Japan, Taiwan, Thailand | `line` (bulk), `whatsapp` | | Vietnam | `zalo`, `whatsapp` | | Eastern Europe, Balkans | `viber`, `whatsapp`, `telegram` | | UAE | `whatsapp`, `botim` (bulk) | | Russia | `telegram`, `whatsapp`, `max` (bulk) | Then measure registration rates on your own consented base, as our [channel-by-country guide](/blog/choosing-a-messaging-channel-by-country) shows. Your data beats any country table. ## Is it legal, and how do I use the checks responsibly? The number usually belongs to a person, so a check is likely to be processing of personal data. This is general guidance, not legal advice (consult counsel about your own situation), but three rules cover most situations: 1. **Check only numbers you hold for a legitimate reason**: customers, sign-ups and leads who gave you their number. Under the [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) you need a lawful basis (Article 6), and data minimisation (Article 5(1)(c)) suggests storing only the answer and its date. 2. **Registration is not consent.** The [WhatsApp Business Messaging Policy](https://business.whatsapp.com/policy) allows businesses to contact only people who gave their number and opted in. Other platforms have similar rules. 3. **No list building.** Researchers showed in 2021 that contact discovery can be abused to map who uses an app at scale ([NDSS 2021](https://eprint.iacr.org/2020/1119)). So the API refuses 20 or more consecutive numbers (`403 suspected_enumeration`), applies a daily cap per account, and our [acceptable use policy](/legal/acceptable-use) forbids finding recipients for unsolicited messages. People can object through our [opt-out form](/opt-out); suppressed numbers are skipped and never charged. Platform names are used descriptively only. MobileValidate is not affiliated with any of the platforms named here. ## What are the key takeaways? - A registration check returns **registered, not registered or unknown** with `checked_at`, and nothing else: no names, profiles or activity. - **WhatsApp, WhatsApp Business, Telegram, Viber and Zalo** run in real time. **Signal, iMessage, RCS, LINE, Botim, MAX and Messenger** run in bulk jobs only. - A `false` is strong where the number is the account (WhatsApp, Viber) and weak where people can hide from number search (Telegram, Signal, Messenger). - **Unknown is missing data.** It's free, and it should never overwrite a stored answer. - Refresh monthly, faster for RCS and new apps, and use the cache to keep repeat checks free. - Check only numbers you hold, for messages people expect. Start with the [test numbers](/docs/test-mode) and the app guides for [WhatsApp](/blog/check-if-a-number-is-on-whatsapp-api-guide), [Telegram](/blog/telegram-number-check-guide), [iMessage](/blog/check-if-a-number-has-imessage) and [RCS](/blog/rcs-capability-check-explained). ## Sources 1. [Two Billion Users: Connecting the World Privately](https://blog.whatsapp.com/two-billion-users-connecting-the-world-privately) — WhatsApp, 2020 2. [Telegram FAQ](https://telegram.org/faq) — Telegram, 2026 3. [Keep your phone number private with Signal usernames](https://signal.org/blog/phone-number-privacy-usernames/) — Signal, 2024 4. [What is the difference between iMessage, RCS, and SMS/MMS?](https://support.apple.com/en-us/104972) — Apple, 2026 5. [Capability checks (RCS for Business)](https://developers.google.com/business-communications/rcs-business-messaging/guides/build/capabilities) — Google for Developers, 2026 6. [LINE Official Account (LINE monthly active users in Japan)](https://www.lycbiz.com/jp/service/line-official-account/) — LY Corporation, 2026 7. [All the Numbers are US: Large-scale Abuse of Contact Discovery in Mobile Messengers (NDSS 2021)](https://eprint.iacr.org/2020/1119) — IACR ePrint / NDSS, 2021 8. [WhatsApp Business Messaging Policy](https://business.whatsapp.com/policy) — WhatsApp, 2026 9. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 ## Frequently asked questions ### Which messaging-app checks run in real time? WhatsApp, WhatsApp Business, Telegram, Viber and Zalo run in real time on POST /v1/lookup and in bulk jobs. Signal, iMessage, RCS, LINE, Botim, MAX and Messenger run in bulk jobs only. ### What does unknown mean in a registration check? It means no conclusive answer was obtained, for example because of a timeout, an unsupported country or a temporary service problem. It is not a no. registered is null, a reason is given, and the check is not charged. ### Does a registered answer mean I can message the person on that app? No. It means an account can be associated with the number. You still need the person's consent for that channel, and each platform has its own business-messaging rules. ### Is anything sent to the number when it is checked? No. Nothing is sent to the number and no name, photo, username or profile is returned. The answer is registered, not registered or unknown, with the time of the check. ### How often should I re-check registration? Monthly is enough for most messenger checks, plus a re-check after a failed delivery. RCS capability and newer apps change faster, so use a shorter max_age for them. --- # Mobile number portability: why carrier lookups can be wrong > A number's prefix shows the network its range was given to, not the one serving it today. How porting works, where lookups fail and how to handle it. Canonical: https://mobilevalidate.com/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong · Last updated: 2026-09-25 ![Cover: Mobile number portability: why carrier lookups can be wrong](https://mobilevalidate.com/og/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Mobile number portability, Carrier lookup, SMS routing, Line type, Fraud prevention A carrier lookup can be wrong because a phone number's digits describe the network its range was **allocated** to, not the network that **serves** it today. When a subscriber keeps their number and switches operator, which regulators in most competitive markets guarantee, every prefix-based guess about that number becomes outdated. Reliable answers need current data about the specific number. ## Why doesn't the prefix tell you the network any more? Numbering plans hand out numbers in blocks. A regulator gives an operator a range, and for years the first digits of a mobile number really did identify the network. Many systems still work that way: a table maps prefixes to operators, and the table is right as long as nobody moves. Mobile number portability (MNP) broke that link on purpose. It lets a subscriber take their number to a competitor, so that changing operator no longer means changing number. After the move, the number stays in the old operator's block, but calls and texts must go to the new one. The prefix table doesn't know. It still says "Operator A" for a number now on Operator B. Even open-source numbering libraries document this. [Google's libphonenumber](https://github.com/google/libphonenumber) ships a carrier mapper that returns the original range holder, not the current network ([Google, 2026](https://github.com/google/libphonenumber)). That is the right answer to a different question. ## How common is porting, and where does it apply? Portability is a regulatory obligation in the markets most businesses message: | Market | What the rules say | Source | |---|---|---| | United States | Wireless number portability since November 2003 in the top 100 metropolitan areas, May 2004 elsewhere. Numbers can also move between landline and wireless service | [FCC](https://www.fcc.gov/general/wireless-local-number-portability-wlnp) | | Canada | Wireless number portability required from 14 March 2007 in the main markets, with a phased rollout elsewhere | [CRTC, 2005](https://crtc.gc.ca/eng/archive/2005/dt2005-72.htm) | | European Union | Article 106 of the European Electronic Communications Code gives end-users the right to port; Article 106(5) requires the number to be activated with the new provider within one working day of the date agreed with the end-user | [EU, 2018](https://eur-lex.europa.eu/eli/dir/2018/1972/oj) | Outside these, most countries with competitive mobile markets offer MNP in some form, but the mechanics differ. Some run one central database that every operator queries. Others leave the number with its original operator, which forwards traffic onward. The practical point is the same everywhere: after two decades of porting, you should assume any given number may have moved, and in the US it may also have changed line type. ## How do networks find a ported number? Networks need to route calls and texts correctly, so they keep track of ports. The common designs: - **Central database.** A national database lists every ported number and its current network. Operators query it or keep a synchronized copy. - **Distributed copies.** Each operator keeps its own list, updated by messages from the others. - **Onward routing.** Traffic goes to the original range holder first, which knows where the number went and passes it on. Commercial lookups read the result of these processes in one of two ways. A **database lookup** reads porting data, directly where licences allow or from a regularly refreshed replica. A **live network query** (an [HLR lookup](/glossary/hlr-lookup)) asks the number's home register, whose reply shows the network that holds the subscription as an [MCC/MNC code](/glossary/mcc-mnc), the network identity defined in ITU-T E.212 ([ITU](https://www.itu.int/rec/T-REC-E.212/en)). The comparison is in [HLR vs MNP vs number validation](/blog/hlr-vs-mnp-vs-number-validation). ## In what ways can a carrier lookup be wrong? Even with current data, several things produce a wrong or confusing carrier: | Failure | What happens | How to spot it | |---|---|---| | Prefix guessing | The original range holder is returned for a ported number | Results never show a port, even for numbers you know moved | | Stale replica | A port completed recently isn't in the copy yet | The customer says they switched this week; the lookup disagrees | | Ported back | A number returned to its original operator is shown as still ported | Rare, but explains "ported to itself" oddities | | Virtual operators (MVNOs) | The brand the customer sees runs on another operator's network | The carrier name doesn't match the customer's bill | | Mergers and rebrands | Operator names change faster than reference data | Two names for what the customer calls one company | | Change of line type | In the US a landline number can move to wireless or VoIP | Numbering plan says "fixed", lookup says `mobile` or `voip` | | Masked network replies | Some operators hide the real network in live queries | Live answers show the same network for every number | None of these mean the data is useless. They mean the carrier field is a **fact with a timestamp**, not a permanent property of the number. ## Why does a wrong carrier matter? Three kinds of cost follow from a carrier error: - **Routing and pricing.** Some messaging routes and wholesale rates are set per destination network. Routing a ported number by its prefix can send traffic down a more expensive or less reliable path. - **Wrong line-type assumptions.** Where numbers move between services, as in the US, a range-based "fixed line" might now be a mobile, and a range-based "mobile" might now be VoIP. Filtering SMS by range type drops good numbers and keeps bad ones. See [line type](/glossary/line-type). - **Missed fraud signals.** In a *port-out scam*, a criminal moves a victim's number to a SIM they control and then receives the victim's one-time passcodes. NIST's 2025 authentication guidelines list number porting, next to SIM change and device swap, among the risk indicators a verifier should consider before sending a code by phone ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). The last point is why you shouldn't treat porting as noise to filter out. A port is ordinary, but a port right before an account-recovery attempt is exactly the pattern to catch. ## How do you detect a ported number with MobileValidate? The [carrier lookup](/services/carrier-lookup) returns `carrier`, the network serving the number as far as our data shows, and `original_carrier`, the network its range was allocated to. `original_carrier` appears **only when it differs**, so its presence is the porting signal. It covers all countries in real time or bulk and is labelled beta because coverage varies by country; a number we hold no data for returns `unknown` with reason `NO_DATA` and is free. For US and Canadian numbers, the [US/CA carrier lookup](/services/us-carrier-lookup) returns `line_type` and the current `carrier` in bulk jobs. This matters there because numbers move between landline, wireless and VoIP. A real test-mode job with `network.carrier_us` (excerpt of one result row): ```json {"e164": "+12025550143", "country": "US", "checks": {"network.carrier_us": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier"}, "checked_at": "2026-09-25T16:14:46.219Z", "billed": false, "reason": null}}} ``` Test data never includes a port. Live answers add `original_carrier` to `network.carrier` results when the number moved. The [HLR lookup](/services/hlr-lookup), **coming soon**, will add a live `ported` flag and the current network's `mcc_mnc`. ## How should your code handle porting? Treat the carrier as time-stamped data and compare it with what you saw before. A minimal pattern in TypeScript: ```ts type Carrier = { carrier?: string; original_carrier?: string; line_type?: string }; function portingSignals(now: Carrier, checkedAt: string, previous?: Carrier & { checkedAt: string }) { return { ported: Boolean(now.original_carrier), // present only when it differs from carrier carrierChanged: previous ? previous.carrier !== now.carrier : false, lineTypeChanged: previous ? previous.line_type !== now.line_type : false, checkedAt, }; } ``` Then act on the signals in context: | Signal | Suggested action | |---|---| | `original_carrier` present | Route and price by `carrier`, not by prefix. No other action | | `carrier` changed since your last check, no sensitive action | Update your record | | `carrier` changed shortly before a 2FA or password reset | Step up: confirm through another channel you already trust | | `line_type` changed from `mobile` to `voip` or `fixed_line` | Re-evaluate the channel; don't keep sending SMS by habit | | `unknown` (`NO_DATA`, timeout) | Keep your previous routing; the check is free | Store `checked_at` with every stored carrier. A carrier seen a year ago is a hint; one seen today is evidence. ## How often should you refresh carrier data? It depends on what you use it for: - **Routing live traffic:** look up at send time, or cache for a short window. Our account cache serves repeat checks of the same number inside the freshness window for free; `max_age: 0` forces a fresh, billed check. - **Account security:** check when contact details change and before sensitive actions, then compare with the stored value. - **Lists and CRM hygiene:** refresh in a bulk job before each campaign to consented contacts, or on a schedule such as quarterly. See [cleaning a phone list in bulk](/blog/how-to-clean-a-phone-number-list-in-bulk). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). See [pricing](/pricing). ## What are the key takeaways? - A prefix identifies the network a range was allocated to. After a port, it names the wrong network. - Portability is a legal right in the US (since 2003–2004), Canada (since 2007) and the EU (activation within one working day), and exists in most other competitive markets. - Carrier data can still lag, hide MVNOs or show old brand names. Treat it as a timestamped fact. - Route and price by current network; use a change of carrier as context, especially right before account recovery. - In MobileValidate, `original_carrier` in the carrier lookup marks a port; the US/CA lookup covers North America in bulk; a live `ported` flag arrives with the HLR lookup. ## Sources 1. [Wireless Local Number Portability (WLNP)](https://www.fcc.gov/general/wireless-local-number-portability-wlnp) — FCC, 2004 2. [Telecom Decision CRTC 2005-72: Implementation of wireless number portability](https://crtc.gc.ca/eng/archive/2005/dt2005-72.htm) — CRTC, 2005 3. [Directive (EU) 2018/1972 establishing the European Electronic Communications Code](https://eur-lex.europa.eu/eli/dir/2018/1972/oj) — European Union, 2018 4. [NIST SP 800-63B-4: Digital Identity Guidelines, Authentication and Authenticator Management](https://csrc.nist.gov/pubs/sp/800/63/b/4/final) — NIST, 2025 5. [Recommendation ITU-T E.212: The international identification plan for public networks and subscriptions](https://www.itu.int/rec/T-REC-E.212/en) — ITU, 2016 6. [libphonenumber](https://github.com/google/libphonenumber) — Google, 2026 ## Frequently asked questions ### Can I find a number's carrier from its prefix? Only the carrier its range was originally allocated to. Once a number has been ported, the prefix points to the wrong network. You need data about the specific number, such as a carrier lookup or a live network query. ### Is a ported number a fraud signal? Not on its own. Porting is a normal consumer right and very common. A port becomes relevant when it is recent and followed by a sensitive action, such as a password or 2FA reset. ### Why can a carrier lookup be out of date? Most lookups read porting data from a database or a replica that is refreshed on a schedule. A port completed in the last hours may not be visible yet. A live network query reflects the current state but only works for mobile numbers. ### What does original_carrier mean in the MobileValidate carrier lookup? It is the network the number range was first allocated to. It appears only when it differs from carrier, the network serving the number today, which usually means the number was ported. --- # How often do phone numbering plans change? 2019–2026 data > We counted 182 phone-metadata releases from 2019 to 2026: a median of 14 days apart, and 230 of 245 regions changed. Why validation goes stale. Canonical: https://mobilevalidate.com/blog/numbering-plans-change-research-2026 · Last updated: 2026-09-25 ![Cover: How often do phone numbering plans change? 2019–2026 data](https://mobilevalidate.com/og/blog/numbering-plans-change-research-2026.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Research · Tags: Numbering plans, Phone validation, E.164, Original research, Developers, Data quality Phone numbering rules change about every two weeks. We counted 182 metadata releases of the open-source libphonenumber library between January 2019 and 23 September 2026. The median gap between releases was 14 days, and every release changed the rules of at least one region. 230 of the library's 245 regions changed at least once. ## What did we measure? We measured how often the rules that decide whether a phone number is valid are updated. Those rules are the numbering plan of each country: which prefixes exist, how long numbers are, which ranges are mobile, fixed, toll-free or premium-rate. Nobody publishes a single global log of numbering-plan changes. National regulators publish their own plans, and countries notify the International Telecommunication Union (ITU), which keeps a page per country ([ITU, 2026](https://www.itu.int/oth/T0202.aspx?parent=T0202)). Those notices come in many formats and languages. So we used the best public proxy we know: the release notes of **libphonenumber**, the open-source library that many applications and ports use to parse and validate numbers. Every release lists the regions whose phone metadata changed, plus changes to carrier, geocoding and short-number data ([libphonenumber, 2026](https://github.com/google/libphonenumber/blob/master/release_notes.txt)). The notes are public under the Apache License 2.0, and they are consistent enough to count. The full dataset is free to download: [numbering-plan changes 2019–2026](/datasets/numbering-plan-changes-2019-2026) (3,784 rows, CSV and JSON, CC BY 4.0). ## How often are numbering rules updated? About twice a month, and very regularly. From 8 January 2019 (v8.10.3) to 23 September 2026 (v9.0.40) there were 182 releases. The median gap between two releases was 14 days, the mean 15.6 days. 80% of gaps were 16 days or shorter, and the longest was 35 days. | Year | Releases | Distinct regions with phone-rule changes | Region updates (sum) | Carrier-data updates | Median days between releases | |---|---:|---:|---:|---:|---:| | 2019 | 23 | 178 | 351 | 128 | 14 | | 2020 | 20 | 156 | 307 | 198 | 16 | | 2021 | 25 | 108 | 234 | 123 | 14 | | 2022 | 21 | 127 | 266 | 157 | 16 | | 2023 | 24 | 123 | 271 | 141 | 14 | | 2024 | 25 | 108 | 222 | 138 | 14 | | 2025 | 25 | 98 | 206 | 162 | 14 | | 2026 (to 23 Sep) | 19 | 114 | 198 | 149 | 14 | "Region updates" counts each region once per release in which its phone metadata changed. A typical release changed the phone rules of 9 regions (median), and every one of the 182 releases changed at least one region. Some releases were far larger. v8.10.5 (6 February 2019) touched 107 regions, which looks like a broad clean-up rather than 107 national reforms. The yearly pace is stable: 20 to 25 releases a year, an average of 23.3 for the full years 2019–2025. The number of distinct regions touched per year has fallen from 178 in 2019 to about 100–125 since 2021. That is consistent with the metadata catching up on older changes and then tracking new ones. ## Which regions change most often? A handful of regions change much more often than the rest. Singapore's phone rules changed in 64 of the 182 releases, the United States' in 53 and Hong Kong's in 42. | Region | Releases with phone-rule changes, 2019–2026 | Since 2024 | |---|---:|---:| | Singapore | 64 | 25 | | United States | 53 | 23 | | Hong Kong | 42 | 20 | | Morocco | 38 | 9 | | Israel | 35 | 17 | | Georgia | 31 | 13 | | Uganda | 30 | 19 | | Western Sahara | 30 | 8 | | United Kingdom | 28 | 4 | | Australia | 25 | 7 | | Tajikistan | 24 | 7 | | Guyana | 23 | 19 | | Cocos (Keeling) Islands | 23 | 6 | | Christmas Island | 23 | 6 | | Réunion | 22 | 5 | Twenty-one regions had changes in every single year from 2019 to 2026, among them the United States, Germany, Japan, South Korea, Poland, Malaysia, Singapore and Hong Kong. Two patterns explain much of the list: - **Growing markets open new ranges.** Singapore and Hong Kong both use eight-digit national numbers and regularly open new blocks as demand grows. The United States adds area codes and overlays; each new code is a rule change for validation. - **Shared country codes move together.** Morocco and Western Sahara share +212, and Australia, Cocos (Keeling) Islands and Christmas Island share +61. Réunion shares +262 with Mayotte. When one member of such a group changes, the others are often updated in the same release, so they appear together in the ranking. ## How much of the world changed? Almost all of it. 230 of the 245 regions in the library's metadata ([libphonenumber, 2026](https://github.com/google/libphonenumber/blob/master/resources/PhoneNumberMetadata.xml)) had at least one phone-rule change between 2019 and September 2026. That is 94%. Since the start of 2023 alone, 186 regions (76%) changed at least once. Among the 230 regions that changed, the median region changed in 7 releases over the period. 78 regions changed in at least six of the eight calendar years. Only 15 regions had no phone-rule change in the notes since 2019. They include small territories such as the Falkland Islands and Tokelau, but also countries such as Iraq, Cambodia and Serbia. The ITU's own records show similar activity from the regulator side. On 25 September 2026 we read the posting date of the latest national numbering-plan document on each of the ITU's 239 country pages ([ITU, 2026](https://www.itu.int/oth/T0202.aspx?parent=T0202)). 83 of them (35%) were last posted in 2024, 2025 or 2026, including 40 in 2026 alone. At the other end, 69 pages still show a document from before 2016, so the ITU record is only as fresh as each country's notifications. ## What else changes besides validity rules? Carrier and location data change even more often than validity rules. Between 2019 and September 2026 the release notes record 1,196 updates to carrier data across 213 country calling codes, and 302 updates to geocoding data across 167 codes. | Change type (2019 to Sep 2026) | Updates | Distinct codes | |---|---:|---:| | Phone metadata (validity, number types, formats) | 2,055 region updates | 230 regions | | Carrier data (original operator of a range) | 1,196 | 213 calling codes | | Geocoding data (area names) | 302 | 167 calling codes | | Short-number data (emergency, service codes) | 162 | 102 regions | | Alternate formatting | 69 | 40 calling codes | Carrier data in a library describes which operator a number range was **originally** assigned to. In countries with number portability, the operator that serves a number today can differ. That's the gap our post on [why carrier lookups can be wrong](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong) explains, and why a live [carrier lookup](/services/carrier-lookup) answers a different question than a prefix table. ## Why does this matter for phone validation? Because a validation library is a snapshot of rules that keep moving. If your app bundles metadata and ships an update twice a year, it misses about a dozen releases in between. During that time: - **New numbers fail validation.** A customer with a number from a newly opened range sees "invalid phone number" and may not come back. - **Line types are wrong.** A new mobile range can be classified as unknown or fixed-line, which breaks rules like "send OTP only to mobile". - **Carrier and region hints go stale.** Routing, fraud rules and analytics that use the original-carrier or area data drift over time. Practical steps that follow from the data: 1. **Update validation metadata on a schedule.** A monthly dependency update keeps you within about two releases of the latest rules. Automate it the way you update security patches. 2. **Validate on the server, not only in the client.** Mobile apps and cached front-end bundles can lag for months. A server-side check can be updated in one deployment. 3. **Don't hard-reject on validity alone.** If a number fails a local check but looks plausible, accept it with a flag or ask for confirmation, and re-check it later. 4. **Separate format from reality.** A valid format doesn't mean the number is assigned, active or mobile. Our [E.164 guide](/blog/e164-phone-number-format-guide-for-developers) covers formatting; a live [check whether a number is active](/blog/how-to-check-if-a-phone-number-is-active) covers the rest. 5. **Re-check stored lists.** Numbers you validated years ago were checked against old rules. Our [bulk list-cleaning guide](/blog/how-to-clean-a-phone-number-list-in-bulk) shows a cadence. MobileValidate normalizes every number to [E.164](/glossary/e164) on the server before any check, and marks numbers that fail the numbering-plan rules as `invalid_number`. Those aren't charged, and neither are other inconclusive results. ## How did we collect and count the data? ### Methodology 1. **Download.** We fetched `release_notes.txt` from the libphonenumber repository on 25 September 2026. The latest entry was v9.0.40, dated 23 September 2026. 2. **Parse.** We split the file into releases by their header line (date and version) and kept releases dated 1 January 2019 to 25 September 2026: 182 releases, from v8.10.3 to v9.0.40. 3. **Classify.** Each "Metadata changes" bullet was mapped to a change type: phone metadata, short-number metadata, carrier data, geocoding data or alternate formatting, each as "updated" or "new". Region lists (two-letter codes) and country-calling-code lists (for carrier, geocoding and formatting data) were split into one row per code, de-duplicated within a release. 4. **Count.** Releases per year, the gaps in days between consecutive release dates, distinct codes per year and per period, and releases per region. 5. **Denominator.** The 245 two-letter regions defined in `PhoneNumberMetadata.xml` on the same date (non-geographic calling codes excluded). 6. **Regulator cross-check.** For the ITU figure, we read the latest "Posted" date on each of the 239 country pages linked from the ITU national numbering plans index, with one request per page and a pause between requests. The resulting rows are the [dataset](/datasets/numbering-plan-changes-2019-2026). Anyone can reproduce the counts from the public release notes with a short script. ### Limitations - **One library is a proxy.** The release notes record what the library's maintainers changed, which usually follows a national decision with some delay. Some changes may be batched, split or never recorded. - **A row isn't a size.** "Updated phone metadata for SG" can mean one new block of numbers or a large reform. We count occurrences, not the number of ranges affected. - **Clean-ups inflate some releases.** Large releases such as v8.10.5 (107 regions) likely include corrections and consistency fixes, not only national changes. - **Shared codes double-count.** Regions that share a calling code often change together, so their counts are not independent. - **ITU dates are posting dates.** A page's latest date can reflect a re-posted document. Several pages share the same 2026 date, which suggests batch publication. - **2026 is partial.** It covers 1 January to 23 September. ## What are the key takeaways? - Phone numbering rules are updated about every two weeks: 182 releases from 2019 to September 2026, with a median gap of 14 days. - 94% of regions (230 of 245) had at least one rule change in that period, and 76% changed since 2023. - Singapore, the United States and Hong Kong change most often. New ranges and new area codes drive much of the churn. - Carrier data changes often too (1,196 updates across 213 calling codes), and prefix-based carrier data can't reflect ported numbers. - Update validation metadata monthly, validate on the server, and don't hard-reject numbers on a stale local check. - The dataset behind this post is free to reuse: [numbering-plan changes 2019–2026](/datasets/numbering-plan-changes-2019-2026). We plan to refresh it periodically and will state the new date range each time. ## Sources 1. [libphonenumber release_notes.txt (v8.10.3 to v9.0.40)](https://github.com/google/libphonenumber/blob/master/release_notes.txt) — libphonenumber project, 2026 2. [libphonenumber PhoneNumberMetadata.xml](https://github.com/google/libphonenumber/blob/master/resources/PhoneNumberMetadata.xml) — libphonenumber project, 2026 3. [National Numbering Plans](https://www.itu.int/oth/T0202.aspx?parent=T0202) — International Telecommunication Union (ITU-T), 2026 4. [Recommendation ITU-T E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164) — International Telecommunication Union, 2010 ## Frequently asked questions ### How often do phone number formats change? Often. The widely used open-source libphonenumber library published 182 metadata releases between January 2019 and 23 September 2026, a median of 14 days apart. Every one of them changed the phone-number rules of at least one region, and a typical release touched 9 regions. ### Which countries change their numbering plans most? In release notes from 2019 to September 2026, Singapore's rules were updated in 64 releases, the United States' in 53 and Hong Kong's in 42, followed by Morocco (38), Israel (35) and Georgia (31). Frequent updates usually mean new number ranges are being opened, not that the whole plan is redesigned. ### Why does a phone validation library go stale? Because its metadata is a snapshot. When a regulator opens a new mobile range or area code, numbers in that range fail validation until the application ships a newer version of the metadata. With a release every two weeks, a library that is a year old has missed about 24 updates. ### Can I download the data behind this analysis? Yes. The dataset lists every region and country calling code named in the release notes, with the date, version and change type. It's free to reuse under CC BY 4.0 at /datasets/numbering-plan-changes-2019-2026. ### Does this measure every numbering change in the world? No. It measures what one widely used library recorded. Some national changes are published by regulators before or without a library update, and one release note line can cover a small range or a large reform. Treat the counts as a lower-bound signal of churn. --- # OTP fraud prevention: the checks to run before you send a code > A practical pre-send pipeline for one-time passcodes: normalize, check line type, messenger presence and reputation in one request, then decide. Canonical: https://mobilevalidate.com/blog/otp-fraud-prevention-checks-before-sending-a-code · Last updated: 2026-09-25 ![Cover: OTP fraud prevention: the checks to run before you send a code](https://mobilevalidate.com/og/blog/otp-fraud-prevention-checks-before-sending-a-code.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Fraud prevention · Tags: OTP, Sign up fraud, Phone verification, Fraud prevention, API Before you send a one-time passcode, check the number: normalize it, look up its line type, see whether it has a messenger account and, where available, whether it has spam reports. One API request can run all of these. Your code then sends, steps up or refuses, before you pay for a message. ## Why check the number before sending a one-time passcode? Because the send is the expensive, irreversible step. Once an SMS has left, you've paid for it, and if the number belongs to an attacker you've also started an account they control. Three kinds of abuse hit OTP endpoints: - **SMS pumping.** Bots trigger codes to number ranges that earn the attacker a share of the fee. See [SMS pumping: how it works and how to stop it](/blog/sms-pumping-how-it-works-and-how-to-stop-it). - **Fake account creation.** Cheap, disposable numbers pass verification and then farm sign-up bonuses, referral credits or free trials. - **Account takeover.** An attacker adds or swaps the phone number on someone else's account so that future codes go to them. Standards bodies share the concern. NIST's authentication guidance classes one-time codes over the phone network as a **restricted** authenticator, and says verifiers should consider risk indicators such as "device swap, SIM change, number porting, other abnormal behavior" before using the phone network to deliver a code ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). In 2023 Twitter restricted SMS two-factor authentication after seeing it "used - and abused - by bad actors" ([Twitter, 2023](https://blog.x.com/en_us/topics/product/2023/an-update-on-two-factor-authentication-using-sms-on-twitter)). A pre-send check is how you gather some of those risk indicators cheaply. ## What does the pre-send pipeline look like? It is five steps between "user entered a number" and "code sent": | Step | What happens | Cost | Blocks on failure? | |---|---|---|---| | 1. Throttle | Rate limits per number, prefix, IP, device and country; bot challenge | Free | Yes | | 2. Normalize | Convert to [E.164](/glossary/e164); reject numbers that can't exist | Free | Yes (ask for a correction) | | 3. Check | One lookup: line type, messenger presence, reputation | Per conclusive check | Only on clear negatives | | 4. Decide | Apply a rule table, combine with your session signals | Free | Send / step up / refuse | | 5. Record | Store the decision, `checked_at` and verification outcome | Free | No | Steps 1 and 2 are cheap and can remove much of the bot traffic. Step 3 is where the paid checks run. Put it after the throttle, so a bot can't make you pay for checks either. The whole pipeline should add no more than a few seconds to the user's wait, and it must degrade gracefully: if the check is slow or unavailable, the user still gets a code under your default rules. ## Which checks should you combine, and what does each one tell you? Each check answers a different question. None proves identity. Together they give a risk engine something concrete. | Check (alias) | Question it answers | Useful for | Coverage | |---|---|---|---| | Carrier lookup (`carrier`) | What [line type](/glossary/line-type) is this, which carrier, which country? | Stopping SMS to premium-rate or fixed lines; flagging VoIP | All countries (beta; `NO_DATA` is free) | | Messenger checks (`whatsapp`, `telegram`, `viber`) | Does the number have an account on this app? | Evidence the number was used on a phone; alternative delivery channel | All countries | | Spam reputation (`spam`) | Does the number appear in spam and nuisance reports? | Flagging numbers with regulator actions or fraud reports | US, CA, DE; limited access | | Live network status (`hlr`) | Is the number reachable on its network right now? | Skipping switched-off or unassigned numbers | Coming soon | A messenger account matters because each app verified the number at sign-up at some point. That makes a number with an account more likely to be in real use. It doesn't say the person in front of you owns it. Porting and SIM changes, two of NIST's examples, need live network data. The [carrier lookup](/services/carrier-lookup) shows a porting hint today (`original_carrier` differs from `carrier`). The [HLR lookup](/services/hlr-lookup), with a `ported` flag, is coming soon. ## How do you run several checks in one request? Send the checks as a list. You get one result per number and check, in request order. Here is a real test-mode request for three test numbers with three checks: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: otp-7f3a1c" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["carrier", "whatsapp", "spam"], "wait": 5}' ``` Response (excerpt of the first result, test mode): ```json { "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "network.carrier": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "checked_at": "2026-09-25T16:06:07.732Z", "cached": false, "billed": false, "reason": null}, "whatsapp.registered": {"status": "completed", "registered": true, "checked_at": "2026-09-25T16:06:07.732Z", "cached": false, "billed": false, "reason": null}, "number.spam": {"status": "completed", "registered": true, "attributes": {"risk_level": "high", "risk_score": 95, "reason_regulator": true, "reason_community": true, "reason_unassigned": false, "voip_range": false, "top_category": "robocall", "sources": 2}, "billed": false} } } ``` The other two numbers show the paths your code must handle. `+447700900002` returns carrier `unknown` with `reason: "NO_DATA"`, WhatsApp `registered: false` and spam `risk_level: "no_reports"` (we hold no reports for it, which doesn't mean the number is safe). `+447700900003` returns `unknown` with `reason: "UPSTREAM_TIMEOUT"` for every check. Test keys are free, and `billed` is always `false` in test mode. Live keys bill each conclusive check. Limits worth knowing: up to 100 numbers per lookup, up to 20 checks per request, and numbers × checks capped at 2,000 per lookup. An OTP request is one number, so these never bind. ## What happens when a check doesn't finish in time? Some answers take longer than your sign-up flow can wait. The API returns what it has when `wait` runs out and marks the rest as `pending`. A real test-mode example with `wait: 1`: ```text status: pending next: {"poll_url": "/v1/lookups/lkp_0VWFWI29sBicsxfDEYuZ", "poll_after_ms": 2000} +447700900002 network.carrier: unknown (NO_DATA) whatsapp.registered: completed, registered false +447700900004 network.carrier: pending whatsapp.registered: pending ``` Don't hold the user for the pending answer. Send the code under your default rules and fetch the lookup later with `GET /v1/lookups/{id}` (or receive a `lookup.completed` webhook). Use the late answer for after-the-fact review: flag the new account, limit what it can do until it's verified, or queue it for a fraud analyst. ## How do you turn results into a decision? Write the rules as a small, explicit function. Explainable rules are easier to tune and to defend when a customer complains. ```js // Decide what to do with one number. r = one item from results[]. function otpDecision(r) { if (r.number_status === "invalid_number") return { action: "ask_correction" }; const c = r.checks ?? {}; const line = c["network.carrier"]?.attributes?.line_type; // undefined when unknown const wa = c["whatsapp.registered"]?.registered; // true | false | null const risk = c["number.spam"]?.attributes?.risk_level; // undefined outside US/CA/DE if (["premium_rate", "shared_cost", "uan"].includes(line)) return { action: "refuse" }; if (risk === "high") return { action: "step_up", why: "spam_high" }; if (line === "fixed_line" || line === "toll_free") return { action: "voice_or_other" }; if (line === "voip" && wa !== true) return { action: "step_up", why: "voip_no_messenger" }; return { action: "send" }; // includes every unknown/pending case } ``` Note the last line. Missing data always falls through to `send` under your normal rate limits. A check that timed out must never lock out a real user. The rule table in the [OTP and sign-up fraud use case](/use-cases/otp-and-signup-fraud) lists the same rules in prose. "Step up" can mean several things: a CAPTCHA, an e-mail confirmation, a voice call instead of SMS, a lower send limit for that number, or a hold on sign-up rewards until the account has some history. ## Which of your own signals should you add? The number check covers the number. Your own data covers the session, and NIST's list of risk indicators ("device swap, SIM change, number porting, other abnormal behavior") mixes both ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). | Your signal | Why it matters | Combine with | |---|---|---| | New device for an existing account | Classic account-takeover pattern | A new number on the account → step up | | Phone number changed in the last days | Attackers swap numbers before resets | Porting hint from the carrier lookup | | Country of the number ≠ IP country ≠ account country | Weak on its own, strong in combination | `country` from the lookup | | Many sign-ups sharing a number prefix | Pumping or bulk fake accounts | Line type, messenger presence | | Code requested but never entered | Pumping or a bot | Verification rate per country | Account changes deserve stricter rules than first sign-ups. When someone changes the number on an existing account, confirm through the old number or e-mail first. The [account security use case](/use-cases/account-security) walks through that flow. ## What does it cost, and how do you keep it low? You pay per check, and only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Spam reputation is an exception worth knowing: every level, including `no_reports`, is a conclusive answer and is billed. Current rates are on the [pricing page](/pricing). Ways to keep the bill proportional: - **Throttle first.** Checks after the rate limiter can't be run up by a bot. - **Check once per number.** A repeat check within the freshness window is served from your account's cache for free. That covers the "resend code" button. - **Use `max_cost`.** It caps what one request can cost. If the maximum possible cost is higher, the request is refused before anything is checked. - **Pick checks per flow.** First sign-up: carrier plus one messenger. Number change on a high-value account: add spam reputation where available. - **Send an `Idempotency-Key`.** A retried request after a network error returns the first answer instead of running and billing the checks again. ## How do you know the pipeline works? Measure it like any fraud control, with a baseline and a comparison. 1. Log each decision (`send`, `step_up`, `refuse`, `voice_or_other`) with the reason and `checked_at`. Don't store the full response longer than you need it. 2. Track the verification rate (codes entered ÷ codes sent) per decision. `send` should verify at your normal rate. `refuse` and `step_up` should have been low before the rules existed. 3. Watch false positives: users who were stepped up and then completed verification. If that share is high, the rule is too strict. 4. Track SMS spend per country before and after. 5. Revisit the rules every quarter, and whenever an attack changes shape. ## What are the key takeaways? - Put the number check between the rate limiter and the send. It is the last cheap moment before an irreversible cost. - One request can run several checks: line type, messenger presence and, where enabled, spam reputation. - Refuse only on clear negatives such as premium-rate lines. Step up on combinations. Send on missing data. - Handle `pending` and `unknown` as "no information": send under default rules and review later. - Combine number facts with your own session signals, following NIST's risk indicators, and measure the verification rate per decision. Start in test mode with the numbers on the [test mode page](/docs/test-mode). They cover every path above, including timeouts and pending answers, at no cost. ## Sources 1. [NIST SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management](https://csrc.nist.gov/pubs/sp/800/63/b/4/final) — NIST, 2025 2. [An update on two-factor authentication using SMS on Twitter](https://blog.x.com/en_us/topics/product/2023/an-update-on-two-factor-authentication-using-sms-on-twitter) — Twitter (now X), 2023 ## Frequently asked questions ### How long should a sign-up flow wait for the checks? Set wait to a few seconds, for example 3–5. Anything not back in time comes back as pending. Your flow should then continue with its default rules rather than keep the user waiting. ### Should an unknown result block the code? No. Unknown means no conclusive answer, for example a timeout or no data for that number. It has registered set to null, it isn't charged, and the flow should behave as if the check had not run. ### Which checks are worth running on every OTP request? Format validation (free) and line type from the carrier lookup catch a lot of common abuse. Add a messenger check if you deliver codes on a messenger, and spam reputation where it is enabled for your account and the number is from the US, Canada or Germany. ### Does this replace rate limiting and bot protection? No. The checks add facts about the number. Rate limits, bot protection and a country allow-list still do most of the work against automated attacks. --- # Phone intelligence for AI agents with MCP > How to give an AI agent phone and e-mail checks through the MobileValidate MCP server: tools, example prompts, real tool calls and spend safeguards. Canonical: https://mobilevalidate.com/blog/phone-intelligence-for-ai-agents-with-mcp · Last updated: 2026-09-25 ![Cover: Phone intelligence for AI agents with MCP](https://mobilevalidate.com/og/blog/phone-intelligence-for-ai-agents-with-mcp.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Developers · Tags: MCP, AI agents, Phone validation, Developer tools, Security The MobileValidate MCP server lets an AI agent check phone numbers and e-mail addresses the way a developer would call the API: normalize, estimate, check, read results. It exposes nine tools, returns structured results, and puts a spend confirmation, a cost ceiling and anti-enumeration rules between the model and your balance. This post shows how to connect it, what to ask, and what the tool calls look like. ## Why give an agent phone checks at all? Agents increasingly do the operational work around customer data: tidy a CRM export, triage sign-ups flagged by a fraud rule, prepare a list before an operations team sends appointment reminders. Each of those tasks has a step where someone asks "is this number real, what kind of line is it, which channel will reach this customer?" Without a tool, a model guesses from the digits. That fails in predictable ways. It can't know that a US number was ported to a VoIP provider, that `+44 7911…` belongs to Guernsey, or whether an opted-in customer has a WhatsApp account. With the [Model Context Protocol](https://modelcontextprotocol.io/specification/2025-06-18/server/tools), the agent calls a tool that answers from data and says `unknown` when it has none. You get answers you can audit, with `checked_at` timestamps, instead of plausible-sounding guesses. The same controls that apply to the REST API apply here. The agent gets no more access than a developer would. ## Which tools does the server expose? | Tool | What it does | Spends credit | |---|---|---| | `normalize_numbers` | Formats numbers as E.164 locally; flags ambiguous inputs and duplicates | no | | `estimate_cost` | Free pre-flight: valid, invalid, duplicate and cached counts plus maximum cost | no | | `lookup_numbers` | Checks up to 100 numbers (optionally with e-mails) against real-time services | yes | | `lookup_emails` | Checks up to 100 e-mail addresses in real time | yes | | `check_spam_reputation` | Spam reputation for up to 100 US, CA or DE numbers | yes | | `create_lookup_job` | Bulk job with up to 50,000 numbers and/or e-mails, including bulk-only services | yes | | `get_lookup_job` | Job status plus a filtered, paginated page of results | no | | `list_services` | Services the key can use, with modes, attributes, countries and prices | no | | `get_account` | Balance, reserved credit, today's usage, limits | no | Each tool declares a title, annotations such as read-only and idempotent, and input and output schemas. `checks` takes the same codes and aliases as the API: `whatsapp`, `telegram`, `carrier`, `spam`, `email` and so on. Spam reputation is in limited access (internal customers only for now), and live network status (`hlr`) is coming soon and not offered by the tools yet. The [MCP docs](/docs/mcp) have the full reference. ## How do you connect a client? The hosted server speaks Streamable HTTP. Pass an agent key or a test key as a bearer token. In Claude Code: ```bash claude mcp add --transport http mobilevalidate https://mcp.mobilevalidate.com/mcp \ --header "Authorization: Bearer $MOBILEVALIDATE_API_KEY" ``` The equivalent `.mcp.json` entry, which Cursor and other clients read in the same shape: ```json { "mcpServers": { "mobilevalidate": { "type": "http", "url": "https://mcp.mobilevalidate.com/mcp", "headers": { "Authorization": "Bearer ${MOBILEVALIDATE_API_KEY}" } } } } ``` A local stdio package, [`@mobilevalidate/mcp`](https://www.npmjs.com/package/@mobilevalidate/mcp), runs the same tools with `npx -y @mobilevalidate/mcp`. The key is forwarded to the API for each request only and is never stored by the server. Start with a test key: the documented test numbers return fixed answers and nothing is billed. ## What can you ask the agent? Prompts work best when they name the goal and the checks, and leave the mechanics to the tools. Some that map cleanly onto the tools: - *"Normalize these 40 numbers from the event sign-up sheet, assume UK where there's no country code, and tell me which ones are invalid or duplicated."* This uses `normalize_numbers`, which is free and local. - *"What would it cost to check this list for carrier and WhatsApp?"* This uses `estimate_cost`, which is free. - *"For these three new sign-ups, check line type and WhatsApp and flag anything that isn't a mobile."* This uses `lookup_numbers` with `checks: ["carrier", "whatsapp"]`. - *"Run a bulk job on the opted-in customer file for WhatsApp, Telegram and RCS, then list the customers with none of them."* This uses `create_lookup_job`, then `get_lookup_job` with filters. RCS is bulk only. - *"Check the spam reputation of these inbound caller IDs from last night."* This uses `check_spam_reputation` (US, CA and DE only, where it is enabled for your account). Prompts that ask the agent to find out who owns a number, or to check a generated range of numbers, fail by design. The tools return no identity data, and ranges are refused. ## What does a real tool call return? Here is `lookup_numbers` with a test key, checking three test numbers for WhatsApp and carrier. The request the client sends: ```json {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "lookup_numbers", "arguments": {"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["whatsapp", "carrier"]}}} ``` The one-line text summary the model reads first (real output): ```text 3 numbers (0 invalid): whatsapp.registered 1 registered / 1 not / 1 unknown / 0 pending; network.carrier 1 registered / 0 not / 2 unknown / 0 pending. Cost $0 (balance $9.97907). ``` And one item from `structuredContent` (excerpt): ```json {"kind": "phone", "e164": "+447700900002", "country": "GB", "number_status": "valid", "checks": { "whatsapp.registered": {"status": "completed", "registered": false, "attributes": null, "confidence": "high", "checked_at": "2026-09-25T16:07:07.013Z", "cached": false, "billed": false, "reason": null}, "network.carrier": {"status": "unknown", "registered": null, "attributes": null, "confidence": null, "checked_at": null, "cached": false, "billed": false, "reason": "NO_DATA"}}} ``` The summary gives the model the answer in one sentence. The structured part gives your code something to check. Unknown answers carry a `reason`. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). `registered: null` means "we don't know", and a well-prompted agent should say so rather than read it as "no". ## How does spam reputation look to an agent? `check_spam_reputation` flattens the result so a model can reason about it without digging through nested attributes. Real test-mode output for three numbers: ```text 3 numbers: 1 high, 0 medium, 0 low, 1 no reports, 1 not conclusive (unknown / pending / unsupported country), 0 invalid. Cost $0. "no reports" does not mean the number is safe. ``` ```json {"input": "+447700900001", "status": "completed", "risk_level": "high", "risk_score": 95, "reasons": ["regulator", "community"], "voip_range": false, "top_category": "robocall", "first_seen": "2025-11", "last_seen": "2026-08", "sources": 2, "billed": false, "test": true} ``` The caveat is part of the tool output on purpose. Models tend to turn "no reports" into "safe". Putting the correction in the text the model reads makes that mistake less likely. The [spam reputation](/services/spam-reputation) page explains levels, reasons and coverage. ## How is spending kept under control? OWASP lists *excessive agency*, giving an LLM more functionality, permissions or autonomy than the task needs, as a top risk for LLM applications ([OWASP, 2025](https://genai.owasp.org/llmrisk/llm062025-excessive-agency/)). The MCP server limits what an agent can do in layers: 1. **Keys.** Only agent keys (`mv_agent_…`, scoped, with a daily spend cap) and test keys are accepted. Live keys are rejected, so an agent never holds an uncapped key. 2. **Free first.** Before `lookup_numbers` or `create_lookup_job` spends anything, the server runs the free estimate. 3. **Confirmation.** If the maximum cost is above the threshold (default USD 1.00) or the list has more than 100 numbers, the call is refused with `confirmation_required`. 4. **Hard ceiling.** The confirmed amount is sent to the API as `max_cost`, so the request can never cost more than what was shown. A real refusal, from a 101-number job with a test key: ```json {"error": {"code": "confirmation_required", "message": "Confirmation required: the list has 101 numbers/e-mails (more than 100). This will check 101 numbers/e-mails for at most $0 (non-conclusive results are not billed). Show this amount to the USER and ask them to approve it explicitly. Only if they approve, call create_lookup_job again with the same arguments plus confirm_max_cost: \"0\". Do not confirm on the user's behalf.", "max_cost": {"amount": "0", "currency": "USD"}, "numbers": 101, "confirm_above": "1.00"}} ``` ## Can the server prove a human approved the spend? No, and we say so. The confirmation passes through the agent, so a misbehaving agent could repeat the call with `confirm_max_cost` without asking anyone. That is why the hard limits sit outside the model: the agent key's daily spend cap, its scopes, and `max_cost` enforced by the API. The MCP specification puts the rest on the client. It says there SHOULD always be a human in the loop with the ability to deny tool invocations, and that clients should show confirmation prompts for operations ([MCP spec, 2025](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)). In practice: - Keep tool approval on for the four spending tools (`lookup_numbers`, `lookup_emails`, `check_spam_reputation`, `create_lookup_job`). The free tools (`normalize_numbers`, `estimate_cost`, `list_services`, `get_account`, `get_lookup_job`) are safe to auto-approve. - Give each agent its own key with a small daily cap, and raise it only when a workflow needs it. - Don't reuse an agent key across projects. A key per agent makes usage and audit logs readable. The spec also says clients must treat tool annotations as untrusted unless they come from a trusted server. Our annotations describe the tools honestly, but your client's approval settings are what actually protect you. ## What else protects the people behind the numbers? Phone numbers belong to people, and an agent can repeat an action far faster than a person. The server applies the API's rules unchanged: - **Anti-enumeration.** 20 or more consecutive numbers in one request are refused (`suspected_enumeration`), and so are generated lists of e-mail addresses. Contact-discovery systems have been abused at scale. Researchers showed in 2025 that WhatsApp's contact discovery allowed 3.5 billion accounts to be enumerated before Meta mitigated it ([University of Vienna, 2025](https://www.univie.ac.at/en/news/press-room/press-releases/detail/researchers-discover-security-vulnerability-in-whatsapp)). We don't want an agent to become another way to do that. - **No identity data.** Answers are yes/no/unknown, network attributes or reputation attributes. There are no names, photos or profiles. - **No side channels.** There is no webhook URL parameter, request metadata is never echoed into tool output, and server logs contain counts and ids, with anything that looks like a number or address masked. - **Opt-outs.** Numbers on the suppression list come back as `suppressed` and aren't checked. Use the tools for identifiers you already hold for a legitimate purpose, such as your customers, sign-ups and leads. ## How should you design an agent workflow around these tools? A pattern that works well for list tasks: 1. `normalize_numbers` to catch bad formats locally and cheaply. 2. `estimate_cost` so the model can tell the user the worst-case cost in plain words. 3. `lookup_numbers` for up to 100 numbers, or `create_lookup_job` for anything bigger or bulk-only. 4. `get_lookup_job` with filters, for example `registered: true` for one service, rather than pulling every row into the context window. 5. Report `unknown` rows separately and suggest a retry later, rather than treating them as failures. Keep the model's output to decisions and counts. Your code should read the structured results for anything that changes data. See [how to clean a phone number list in bulk](/blog/how-to-clean-a-phone-number-list-in-bulk) for what to do with each result, and [OTP fraud prevention](/blog/otp-fraud-prevention-checks-before-sending-a-code) for real-time decision tables. ## What are the key takeaways? - The MCP server gives agents nine tools. Five are free; four spend credit. - Start with a test key: fixed answers, no cost, same request shapes. - Spending is bounded by the agent key's cap, a confirmation step and `max_cost`. Keep client-side approval on for spending tools. - `unknown` means no answer and is free. `no_reports` in spam reputation is not "safe". - Anti-enumeration and no-identity rules apply to agents exactly as they do to the API. ## Sources 1. [Model Context Protocol specification (2025-06-18): Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) — Model Context Protocol, 2025 2. [LLM06:2025 Excessive Agency](https://genai.owasp.org/llmrisk/llm062025-excessive-agency/) — OWASP Gen AI Security Project, 2025 3. [Researchers discover security vulnerability in WhatsApp](https://www.univie.ac.at/en/news/press-room/press-releases/detail/researchers-discover-security-vulnerability-in-whatsapp) — University of Vienna, 2025 ## Frequently asked questions ### Which MCP clients work with the MobileValidate server? Any client that supports the Model Context Protocol over Streamable HTTP, such as Claude Code, Claude Desktop through its connector settings, or Cursor. A local stdio package also runs with npx -y @mobilevalidate/mcp. ### Can an agent spend money without me noticing? Only within limits you set. The server accepts agent keys with a daily spend cap, refuses calls above a confirmation threshold (default USD 1.00) or with more than 100 numbers until they are confirmed, and sends the confirmed amount to the API as max_cost. ### Can an agent use the MCP server to look up who owns a number? No. Tools return registered / not registered / unknown, carrier and line type, or spam-reputation attributes. They never return names, photos or profiles, and sequential number ranges are refused. ### Can I try it without paying? Yes. Test keys (mv_test_…) work with the MCP server and return fixed, free answers for the documented test numbers. --- # How to evaluate a phone number validation API: a buyer's checklist > The criteria that matter when you choose a phone-intelligence API: coverage, conclusive rates, unknown billing, latency, freshness, privacy and test mode. Canonical: https://mobilevalidate.com/blog/phone-number-validation-api-buyers-guide · Last updated: 2026-09-25 ![Cover: How to evaluate a phone number validation API: a buyer's checklist](https://mobilevalidate.com/og/blog/phone-number-validation-api-buyers-guide.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Phone validation, API, Buyers guide, Data quality, Privacy To evaluate a phone number validation API, test it on your own numbers and measure how many checks come back conclusive, what unknown answers cost, how fast and how fresh the answers are, and how the provider protects the people behind the numbers. Marketing pages rarely answer these questions. A two-week trial with a structured checklist usually does. This guide gives you that checklist. ## What should a phone-intelligence API actually answer? Start with your own questions, not the provider's feature list. "Phone validation" covers several different checks, and each answers a different question: | Question | Check | Typical use | |---|---|---| | Is the number well formed and dialable? | Format validation and normalization | Form input, deduplication | | What kind of line is it, and which network serves it? | Carrier and line type lookup | SMS cost control, sign-up risk | | Is the phone reachable on its network right now? | Live network query (HLR) | Pre-send checks, list hygiene | | Can the number receive messages on app X? | Messaging registration check | Channel selection | | Has the number been reported for spam or fraud? | Reputation check | Call screening, fraud review | Our comparison of [HLR, MNP and number validation](/blog/hlr-vs-mnp-vs-number-validation) explains the network checks in detail. Write down which questions you need answered, for which countries, and whether you need the answer in real time or in bulk. That list is your evaluation scope. A provider that answers ten questions you don't have isn't better than one that answers your three well. ## How do you judge coverage claims? Ask for coverage **per service and per country**, in a form you can check. A single "global coverage" claim hides the fact that data depth varies a lot between markets and between check types. Good signs: - The catalog states which countries each service supports, and numbers outside them get an explicit status such as `unsupported_country` instead of a guess. - Services with uneven data are labelled, for example as beta. - The provider distinguishes "we checked and found nothing" from "we could not check". MobileValidate publishes this in [`GET /v1/services`](/docs/services). Each entry lists `countries` (empty means any country), `modes` (real time, bulk or both) and a `beta` flag. The carrier lookup, for example, answers for every country but is marked `"beta": true` because coverage varies. The US/CA carrier lookup lists `"countries": ["US", "CA"]` and runs in bulk only. ## Why does the conclusive rate matter more than "accuracy"? Because an answer you can't act on has no value, however accurate the other answers are. Every check has three possible outcomes: a definite yes, a definite no, and unknown. The **conclusive rate** is the share of checks that end in yes or no. Ask the provider for conclusive rates by country and service, then verify them on your own sample: 1. Take a few hundred numbers you already hold with a lawful basis, such as recent customers, spread across the countries you care about. 2. Run every check you plan to buy. 3. Count yes, no and unknown per country. 4. Where you have ground truth, for example numbers that recently received and answered a message, compare it with the answers. Be careful with providers that never return unknown. Timeouts and gaps exist everywhere. An API that always says yes or no is filling the gaps with guesses. Look for an explicit third state (for example `registered: null`) with a machine-readable `reason`, and a timestamp such as `checked_at` on every conclusive answer. ## How should unknown answers be handled and billed? Unknown answers should be free, clearly marked and never silently converted into "no". Ask for the billing rule in writing, and check it on a usage report during your trial. At MobileValidate the rule is simple. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Billing works as reserve, settle and release. A request reserves its maximum possible cost, each check settles when its answer arrives, and inconclusive checks are released. Three more cost controls are worth asking about: - **A free estimate before bulk work.** Our `POST /v1/jobs/estimate` counts valid, invalid, duplicate, cached, unsupported and suppressed rows and returns the maximum cost without checking anything. - **A hard cost ceiling per request.** Our `max_cost` field refuses a request whose maximum possible cost is higher, with `402 cost_limit_exceeded`. - **Deduplication before billing.** Two spellings of the same number should count once. Cost per check is only part of the picture. What matters is **cost per conclusive answer**: total spend divided by the number of yes and no answers you actually received. ## What latency and freshness should you expect? Ask how long answers take, what happens when they take longer, and how old a returned answer can be. **Latency.** Some checks answer in milliseconds from reference data. Others need a live query and take seconds. A good API tells you which is which and doesn't make you hold a connection open indefinitely. Ours waits up to `wait` seconds (default 10, maximum 30) and then returns `202` with `status: "pending"` and a `poll_url`. You can long-poll `GET /v1/lookups/{id}` or receive a signed `lookup.completed` webhook. In a sign-up form, you want a short wait and a default path for anything still pending. **Freshness.** Every conclusive answer should carry the time it was obtained. Caching is fine, and it saves money, as long as it is visible. Ask: - Is the cache per customer, or are answers shared across customers? Ours is per account. - Does a cached answer say so? Ours returns `cached: true` and `age_seconds`, and cache hits aren't billed. - Can you force a fresh check? Ours accepts `max_age: 0`, which is billed and rate limited. ## What should you ask about privacy and retention? Phone numbers and e-mail addresses are usually personal data, so the provider processes personal data on your behalf. Under the [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj), that requires a contract meeting Article 28: the processor acts only on your documented instructions, keeps the data secure, tells you about sub-processors and lets you object to changes, and deletes or returns the data at the end (Article 28(2) and 28(3)). Consult your own counsel on what applies to you. Questions to put in your provider questionnaire: | Topic | What to ask | |---|---| | Contract | Is a data processing agreement available? How are sub-processors disclosed? | | Retention | How long are inputs and results kept by default? Can you shorten it or delete on demand? | | Minimization | Does the API return only what you asked for? Are profile fields such as names or photos ever returned? | | Exposure | Are numbers kept out of URLs and logs? Are identifiers masked in dashboards and downloads? | | Objections | Can the people whose numbers are checked object, and is that honoured? | Our answers are on the [trust page](/trust): real-time lookups are kept for 7 days and bulk jobs for 30 days by default, identifiers are masked wherever people see them, and numbers are only accepted in request bodies. Checks never return names, photos or profiles. Our [privacy checklist for phone and e-mail checks](/blog/privacy-checklist-for-phone-and-email-checks) covers your side of the work. ## What anti-abuse controls should a responsible provider have? A phone-intelligence API can be misused to find out who uses a messaging app, or to build contact lists from number ranges. A provider that doesn't guard against that is a risk to you as a customer, because your data sits on the same platform and your brand may be associated with it. Look for: - **Anti-enumeration rules.** We refuse requests with 20 or more consecutive numbers (`403 suspected_enumeration`) and generated e-mail lists. - **Daily caps and spend caps** per account and per key, visible through an endpoint such as `GET /v1/limits`. - **A suppression list** for people who object, applied before any check. - **An acceptable use policy** that forbids unsolicited messaging and profiling, and is enforced. The same thinking applies to your own integration. [OWASP's API Security Top 10](https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/) lists unrestricted resource consumption as a risk, including missing spending limits on paid third-party services such as SMS. Put a check behind your own rate limits, and set a spending ceiling on it. ## How good is the developer experience? You will live with the integration for years, so evaluate it like any other dependency. - **Test mode.** Documented test numbers that return every state (registered, not registered, unknown, pending, unsupported and errors) let you build every branch without real personal data. Our [test mode](/docs/test-mode) uses the UK's reserved drama range `+44 7700 900000–900999`. - **A machine-readable contract.** An [OpenAPI](https://spec.openapis.org/oas/v3.1.0.html) description lets you generate clients and give coding agents the exact contract. Ours is at [/docs/openapi](/docs/openapi). - **Predictable errors.** Stable error codes, a `retryable` flag and a request ID. Ask to see the [errors reference](/docs/errors). - **Safe retries.** Idempotency keys on every POST, so a retry never double-charges. - **Standard headers and webhooks.** Rate-limit state in headers such as those in the IETF [RateLimit header fields draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/), and webhooks signed to a published scheme such as [Standard Webhooks](https://www.standardwebhooks.com/). - **SDK, CLI and agent access.** A typed SDK saves boilerplate. If AI agents will call the checks, an MCP server built to the [Model Context Protocol](https://modelcontextprotocol.io/specification/2025-06-18) specification should come with spend caps and scoped keys. See our [MCP docs](/docs/mcp). ## What does a real evaluation request look like? Send one request that exercises the edge cases, not just the happy path. This test-mode call mixes a registered number, a national-format number, a malformed one and two e-mail addresses: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "07700 900002", "12345"], "emails": ["registered@test.mobilevalidate.com", "not-registered@test.mobilevalidate.com"], "default_country": "GB", "checks": ["carrier", "whatsapp", "email"], "wait": 5}' ``` The totals from the real test-mode response: ```json "summary": {"total": 5, "registered": 2, "not_registered": 1, "unknown": 1, "pending": 0, "invalid": 1, "suppressed": 0, "by_service": { "network.carrier": {"completed": 1, "registered": 1, "not_registered": 0, "unknown": 1, "pending": 0}, "whatsapp.registered": {"completed": 2, "registered": 1, "not_registered": 1, "unknown": 0, "pending": 0}, "email.valid": {"completed": 2, "registered": 1, "not_registered": 1, "unknown": 0, "pending": 0}}}, "billing": {"billed_units": 0, "cost": {"amount": "0", "currency": "USD"}} ``` What to look for in any provider's version of this: `07700 900002` was normalized to `+447700900002` using `default_country`. `12345` came back as `invalid_number` with no checks run. The carrier lookup returned `unknown` with `reason: "NO_DATA"` for the second number instead of a guess. And the summary counts unknowns separately. (`billed_units` is 0 because test keys never bill.) ## What does the checklist look like? Copy this table into your evaluation document and fill in one column per provider. | Criterion | What good looks like | How to verify | |---|---|---| | Scope | Answers the questions you listed, in the modes you need | Service catalog, `modes` per service | | Coverage | Per-country support stated; `unsupported_country` instead of guesses | Catalog plus your own sample | | Conclusive rate | Published per service and country; matches your sample | Run a few hundred numbers you hold | | Unknown handling | Explicit third state with a `reason`; never coerced to "no" | Test-mode unknown and timeout cases | | Billing | Inconclusive, invalid and duplicate results free; estimate and cost cap | Usage report after the trial | | Latency | Clear wait, poll and webhook model; no endless open connections | Time real-time calls from your region | | Freshness | `checked_at` on every answer; visible cache age; forced refresh option | Repeat a check and compare | | Privacy | Article 28 DPA; sub-processor disclosure; short default retention; masking | Contract and trust documentation | | Anti-abuse | Enumeration refusal, caps, suppression, enforced AUP | Send a consecutive range in test mode | | Test mode | Deterministic test numbers for every state and error | Build every branch before going live | | Docs and tooling | OpenAPI, stable error codes, idempotency, signed webhooks, SDK, MCP | Integrate one flow end to end | ## What are the key takeaways? - Define your questions and countries first. Evaluate providers against that scope, not against feature counts. - Measure the **conclusive rate** on your own numbers, and compare **cost per conclusive answer**, not price per check. - Insist on an explicit unknown state with a reason and a timestamp on every answer. Unknowns should be free. - Check freshness and caching: `checked_at`, visible cache age, a per-customer cache and a way to force a fresh check. - Treat privacy and anti-abuse controls as selection criteria: an Article 28 DPA, short retention, masking, enumeration limits and suppression. - Use test mode to build every branch before any real number is checked. Start with our [test numbers](/docs/test-mode) and the [services reference](/docs/services). ## Sources 1. [API4:2023 Unrestricted Resource Consumption](https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/) — OWASP API Security Project, 2023 2. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 3. [OpenAPI Specification v3.1.0](https://spec.openapis.org/oas/v3.1.0.html) — OpenAPI Initiative, 2021 4. [Standard Webhooks](https://www.standardwebhooks.com/) — Standard Webhooks, 2026 5. [RateLimit header fields for HTTP (draft-ietf-httpapi-ratelimit-headers)](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/) — IETF, 2026 6. [Model Context Protocol specification (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18) — Model Context Protocol, 2025 ## Frequently asked questions ### What is the single most important number to ask a phone-intelligence provider for? The conclusive rate per service and per country: the share of checks that return a definite yes or no instead of unknown. Headline accuracy figures mean little if a large share of your numbers never get a conclusive answer. ### Should I pay for unknown answers? Ideally not. An unknown answer gives you nothing to act on. Ask how the provider bills timeouts, unsupported countries, invalid input and duplicates, and check it on a real invoice or usage report during your trial. ### How can I test an API without using real phone numbers? Look for a test mode with documented test numbers that return every state: registered, not registered, unknown, pending, unsupported and the main errors. That lets you build and test every branch without touching real personal data. ### Is a lower price per check always cheaper? Not necessarily. What you pay per useful answer depends on the conclusive rate, whether unknowns and duplicates are billed, and whether repeat checks are served from a free cache. Compare cost per conclusive answer on your own sample. --- # Phone number validation in JavaScript and Node.js > Validate phone numbers in JavaScript with libphonenumber-js: min vs max metadata, E.164, form input, and a live line-type check with runnable Node code. Canonical: https://mobilevalidate.com/blog/phone-number-validation-javascript · Last updated: 2026-09-25 ![Cover: Phone number validation in JavaScript and Node.js](https://mobilevalidate.com/og/blog/phone-number-validation-javascript.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Developers · Tags: Phone validation, Javascript, Nodejs, Libphonenumber, E.164, API To validate a phone number in JavaScript, parse it with `libphonenumber-js/max`, call `isValid()`, and store `number` (the E.164 string). That checks the format and the country's numbering plan. It can't tell you whether the line is a mobile or whether it's in service. For that, send the valid E.164 number to a lookup API from your server. This guide has runnable Node code for both steps. ## Why do so many JavaScript answers get phone validation wrong? Most answers start with a regex. The Stack Overflow question ["Validate phone number with JavaScript"](https://stackoverflow.com/questions/4338267) was asked in 2010 and had over 806,000 views on 2026-09-25, and most of its answers are patterns. A pattern can check that a string looks like a number. It can't know that `+1 123 456 7890` is impossible (no US area code starts with 1) or that a German number has one digit too many. Phone numbers follow national numbering plans that change all the time. Google's [libphonenumber](https://github.com/google/libphonenumber) published 25 metadata releases in 2025, and every one of them updated the phone metadata of at least one country. A regex you wrote last year can't keep up with that. A library that ships the metadata can, provided you update it. We compare the patterns and their failures in [E.164 regex: why a pattern is not enough](/blog/e164-regex-is-not-enough). The short version: use a regex, if at all, as a cheap pre-filter, and let a numbering-plan library decide. ## Which library should you use: libphonenumber-js or google-libphonenumber? For most projects, [libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js). It's a JavaScript rewrite of Google's library, released under the MIT licence, with metadata you can pick by size. Version 1.13.14 was current on 2026-09-25. Google's own JavaScript build is tied to the Closure toolchain, and the library's README estimates it at about 550 kB when bundled. libphonenumber-js ships three metadata sets. The file sizes below come from version 1.13.14: | Import | Metadata file | What `isValid()` checks | `getType()` | |---|---|---|---| | `libphonenumber-js` (default, "min") | 84 kB | Mostly length and leading digits | Usually `undefined` | | `libphonenumber-js/mobile` | 99 kB | Exact ranges, mobile numbers only | Mobile only | | `libphonenumber-js/max` | 157 kB | Exact ranges for every number type | `MOBILE`, `FIXED_LINE`, `TOLL_FREE`… | The difference is real. On 2026-09-25, `+49 1112 3456789` returned `true` from `isValidPhoneNumber()` with the min metadata and `false` with max. On the server, always import from `libphonenumber-js/max`. In the browser, min is fine for live formatting, as long as the server has the final say. ```bash npm install libphonenumber-js ``` ## How do you parse and validate a number in Node? Here's a small function that returns either an E.164 number or a reason. We use the same logic in our own API. ```js // phone.mjs import { parsePhoneNumberFromString, validatePhoneNumberLength } from "libphonenumber-js/max"; // Our reserved test range (+44 7700 9xxxxx). libphonenumber marks it invalid on purpose. const TEST_RANGE = /^\+4477009\d{5}$/; export function normalizePhone(input, defaultCountry, { allowTestRange = false } = {}) { const raw = String(input ?? "").trim(); if (!raw || raw.length > 32) return { ok: false, reason: "unparseable" }; const p = parsePhoneNumberFromString(raw, defaultCountry); if (!p) return { ok: false, reason: "unparseable" }; if (allowTestRange && TEST_RANGE.test(p.number)) return { ok: true, e164: p.number, country: "GB", type: "TEST" }; if (!p.isValid()) { const len = validatePhoneNumberLength(raw, defaultCountry); // "TOO_SHORT", "TOO_LONG", … or undefined return { ok: false, reason: len ? len.toLowerCase() : "invalid" }; } return { ok: true, e164: p.number, country: p.country ?? null, type: p.getType() ?? null }; } ``` Real output from Node 24 with libphonenumber-js 1.13.14: | Call | Result | |---|---| | `normalizePhone("(202) 555-0143", "US")` | `{ ok: true, e164: "+12025550143", country: "US", type: "FIXED_LINE_OR_MOBILE" }` | | `normalizePhone("07911 123456", "GB")` | `{ ok: true, e164: "+447911123456", country: "GG", type: "MOBILE" }` | | `normalizePhone("+49 1512 3456789")` | `{ ok: true, e164: "+4915123456789", country: "DE", type: "MOBILE" }` | | `normalizePhone("+1 800 555 0199")` | `{ ok: true, e164: "+18005550199", country: "US", type: "TOLL_FREE" }` | | `normalizePhone("+44 1234")` | `{ ok: false, reason: "too_short" }` | | `normalizePhone("+1 123 456 7890")` | `{ ok: false, reason: "invalid" }` | | `normalizePhone("202-555-0143")` | `{ ok: false, reason: "unparseable" }` | Three rows deserve a comment. The UK mobile comes back as `GG` (Guernsey), because `+44 7911` ranges are shared, so don't map country codes to countries yourself. US numbers are `FIXED_LINE_OR_MOBILE`, because the North American plan doesn't separate the two. And a national number with no default country can't be parsed at all. Ask for the country on the form. ## What's the difference between isPossible() and isValid()? `isPossible()` checks only the length for the country. `isValid()` also checks that the digits fall inside a range the country has allocated. `+1 123 456 7890` has the right length for the US, so it's possible, but it isn't valid, because US area codes never start with 1. Use `isPossible()` while the user is still typing, if you want to show "keep going" feedback. Use `isValid()` before you save the number or pay for anything downstream. The same distinction exists in every port of libphonenumber: `is_possible_number` in Python, `isPossibleNumber` in PHP and Java. ## How do you handle phone input in a form? Format as the user types, validate on the server. `AsYouType` gives live formatting and a parsed number when the input is complete: ```js import { AsYouType } from "libphonenumber-js/min"; const typer = new AsYouType("US"); typer.input("2025550143"); // "(202) 555-0143" typer.getNumber()?.number; // "+12025550143" ``` In React, Vue or Svelte, call this in the input's change handler and keep two values in state: what the user sees and the E.164 value you submit. Put a country picker next to the field and pass its value as the default country. Without it, a UK user typing `07911 123456` gives you digits you can't place. Then run `normalizePhone()` again on the server. Client-side checks are for the user's convenience. Anyone can bypass them. ## How do you check whether the number is a mobile and in service? Validation stops at the numbering plan. It can't see that a number was ported to a VoIP provider, or that a US number is a landline. That's what a [carrier lookup](/services/carrier-lookup) is for. It returns the current `line_type` and carrier. Here's a plain `fetch` client for the MobileValidate API (Node 18 or later). It sends numbers in the POST body, waits for slow answers, and retries only errors the API marks as retryable: ```js // lookup.mjs const API = process.env.MOBILEVALIDATE_BASE_URL ?? "https://api.mobilevalidate.com"; const headers = { Authorization: `Bearer ${process.env.MOBILEVALIDATE_API_KEY}`, "Content-Type": "application/json", }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function call(path, init = {}, attempt = 0) { const res = await fetch(`${API}${path}`, { ...init, headers, signal: AbortSignal.timeout(45_000) }); const body = await res.json().catch(() => ({})); // e.g. an HTML error page from a proxy if (res.ok) return body; const err = body.error ?? { code: "http_" + res.status, retryable: res.status >= 500 }; if (err.retryable && attempt < 3) { const retryAfter = Number(res.headers.get("retry-after")) || 2 ** attempt; await sleep(retryAfter * 1000); return call(path, init, attempt + 1); } throw Object.assign(new Error(err.message ?? err.code), { code: err.code, status: res.status }); } // Checks up to 100 E.164 numbers. Numbers go in the POST body, never in the URL. export async function lookup(numbers, checks = ["carrier"]) { let lookup = await call("/v1/lookup", { method: "POST", body: JSON.stringify({ numbers, checks, wait: 10 }), }); while (lookup.status === "pending") { await sleep(lookup.next?.poll_after_ms ?? 2000); lookup = await call(`/v1/lookups/${lookup.id}?wait=10`); } return lookup.results.map((r) => { const c = r.checks?.["network.carrier"]; return { e164: r.e164, number_status: r.number_status, // valid | invalid_number | duplicate | suppressed status: c?.status ?? null, // completed | unknown | pending | unsupported_country line_type: c?.attributes?.line_type ?? null, // null = unknown, never "not mobile" carrier: c?.attributes?.carrier ?? null, reason: c?.reason ?? null, checked_at: c?.checked_at ?? null, }; }); } ``` ## What does the whole flow look like with a test key? Test keys (`mv_test_…`) are free and return fixed answers for the [test numbers](/docs/test-mode). This script validates locally, skips what's invalid and checks the rest: ```js // demo.mjs import { normalizePhone } from "./phone.mjs"; import { lookup } from "./lookup.mjs"; const testKey = process.env.MOBILEVALIDATE_API_KEY?.startsWith("mv_test_"); const input = ["+44 7700 900001", "07700 900002", "+447700900003", "+447700900004", "+44 1234"]; const ok = [], rejected = []; for (const n of input) { const r = normalizePhone(n, "GB", { allowTestRange: testKey }); (r.ok ? ok : rejected).push(r.ok ? r.e164 : { input: n, reason: r.reason }); } console.log("rejected locally:", JSON.stringify(rejected)); console.table(await lookup(ok)); ``` Real output (`MOBILEVALIDATE_API_KEY=mv_test_… node demo.mjs`, trimmed): ```text rejected locally: [{"input":"+44 1234","reason":"too_short"}] e164 number_status status line_type carrier reason +447700900001 valid completed mobile Test Carrier null +447700900002 valid unknown null null NO_DATA +447700900003 valid unknown null null UPSTREAM_TIMEOUT +447700900004 valid completed mobile Test Carrier null ``` `…004` was pending for about five seconds, so the loop polled once. The `allowTestRange` flag exists because libphonenumber treats `+44 7700 900xxx` as invalid: the UK regulator Ofcom reserves that range for drama and it's never assigned. Our API uses it as the test range, so let those numbers through only when you're using a test key. With a test key, `+447700900429` makes the API answer `429 rate_limited` with `Retry-After: 1`. In our run the client retried three times, about one second apart, and then threw `rate_limited`, which is the behaviour you want from a batch script. ## How should your code act on each answer? | Answer | Sign-up form | Stored record | |---|---|---| | `ok: false` locally | Ask the user to fix the number; don't call the API | Don't store it as a phone number | | `line_type: "mobile"` | Continue; SMS is a sensible channel | Store `line_type` and `checked_at` | | `line_type` is `fixed_line`, `toll_free` or `voip` | Offer voice or e-mail, or add a step, depending on your risk rules | Store it; don't text it | | `status: "unknown"` | Continue with your default; never block on it | Keep the previous value; retry later | | `number_status: "duplicate"` | n/a | Deduplicate on the E.164 value | Unknown answers aren't billed, and they're missing data, not a "no". Don't turn `null` into `false`. The same applies to channel checks such as [WhatsApp registration](/blog/check-if-a-number-is-on-whatsapp-api-guide). ## Can you use the SDK instead of fetch? Yes. The [`mobilevalidate`](https://www.npmjs.com/package/mobilevalidate) TypeScript SDK (`npm install mobilevalidate`) adds automatic waiting, typed errors and safe retries with idempotency keys. The same lookup looks like this: ```ts import { MobileValidate } from "mobilevalidate"; const mv = new MobileValidate(); // reads MOBILEVALIDATE_API_KEY const { data, error } = await mv.lookup(["+447700900001", "+447700900003"], { checks: ["carrier"] }); if (error) console.error(error.code, error.message); else for (const r of data.results) { const c = r.checks?.["network.carrier"]; console.log(r.e164, c?.status, c?.attributes?.line_type ?? null, c?.reason); } // +447700900001 completed mobile null // +447700900003 unknown null UPSTREAM_TIMEOUT ``` The [SDK docs](/docs/sdk) cover jobs, e-mail checks and webhooks. Until the package is published, the `fetch` client above does the same job. ## What should you test? Keep two kinds of tests. Unit tests for `normalizePhone()` need no network: one valid number per country you serve, one impossible number (`+1 123 456 7890`), one too short, one without a country. Integration tests run against the API with a test key and the test numbers: `…001` gives data, `…002` and `…003` give `unknown`, `…004` exercises polling, and `…429` exercises your retry path. Nothing is billed and no real network is queried. Don't use real customer numbers in fixtures. Besides being personal data, they change owners. ## What are the key takeaways? - Import from `libphonenumber-js/max` on the server. The default min metadata checks little more than lengths. - Use `isValid()` before storing or paying for anything; `isPossible()` only for live typing feedback. - Store `p.number` (E.164) and keep the country from the parser, not from the country code. - Validation can't tell you the line type or whether the number is in service. A carrier lookup can, from your server, with numbers in the POST body. - Treat `unknown` as missing data, retry only when the API says `retryable`, and build every branch with free test numbers. - Update libphonenumber-js regularly. Numbering plans change every few weeks. ## Sources 1. [libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js) — GitHub (catamphetamine), 2026 2. [libphonenumber](https://github.com/google/libphonenumber) — Google, 2026 3. [Validate phone number with JavaScript (question 4338267)](https://stackoverflow.com/questions/4338267) — Stack Overflow, 2026 4. [ITU-T Recommendation E.164](https://www.itu.int/rec/T-REC-E.164/en) — International Telecommunication Union, 2026 ## Frequently asked questions ### Which library should I use to validate phone numbers in JavaScript? libphonenumber-js is the most widely used option. It is a JavaScript rewrite of Google's libphonenumber with smaller metadata files. Import it from libphonenumber-js/max on the server if you need full validation and number types. ### Why does isValid() accept numbers that don't exist? With the default min metadata, isValid() checks mostly lengths and leading digits, not the exact number ranges. Import from libphonenumber-js/max to check ranges. Even then, a valid number can be unassigned or switched off. Only a network lookup tells you that. ### Can I validate phone numbers with a regular expression instead? Only as a rough pre-filter. A pattern such as ^\+[1-9]\d{1,14}$ accepts numbers with impossible area codes and unassigned country codes. A numbering-plan library knows each country's ranges and lengths. ### Should I call a phone lookup API from the browser? No. Keep API keys on your server. Validate the format in the browser for quick feedback, then call the lookup API from your backend. --- # Phone number validation in PHP and Laravel > Validate phone numbers in PHP with libphonenumber-for-php, write a Laravel rule, and add a live line-type check with Guzzle that never breaks the form. Canonical: https://mobilevalidate.com/blog/phone-number-validation-php-laravel · Last updated: 2026-09-25 ![Cover: Phone number validation in PHP and Laravel](https://mobilevalidate.com/og/blog/phone-number-validation-php-laravel.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Developers · Tags: Phone validation, Php, Laravel, Libphonenumber, E.164, API To validate a phone number in PHP, install `giggsey/libphonenumber-for-php`, parse the input with a default region, check `isValidNumber()`, and store the E.164 format. In Laravel, wrap that in a validation rule. Then, if you need to know whether the number is a mobile or still in service, call a lookup API after validation, and don't let a slow answer fail the form. All the code below was run on PHP 8.3. ## Why isn't a regex or a digit count enough? Stack Overflow's ["How to validate phone number using PHP?"](https://stackoverflow.com/questions/3090862) had over 324,000 views on 2026-09-25, and ["How to validate phone number in laravel 5.2?"](https://stackoverflow.com/questions/36777840) over 274,000. Many answers suggest `preg_match('/^[0-9]{10}$/', …)` or Laravel's `digits:10`. Those rules accept `1234567890`, which isn't a US number (area codes never start with 1), and reject `+44 7911 123456`, which is a perfectly good one. Every country has its own lengths and ranges, and they change. Google's libphonenumber shipped 25 metadata releases in 2025. A PHP port that tracks those releases is the practical way to keep up. More failure cases are in [E.164 regex: why a pattern is not enough](/blog/e164-regex-is-not-enough). ## Which PHP library should you use? [`giggsey/libphonenumber-for-php`](https://github.com/giggsey/libphonenumber-for-php) is the PHP port of Google's library (Apache 2.0). Its version follows Google's metadata: 9.0.40 was published on Packagist on 2026-09-24, one day after Google's release. If you don't need geocoding, carrier names or time zones, `giggsey/libphonenumber-for-php-lite` has the same core API and a smaller footprint. ```bash composer require giggsey/libphonenumber-for-php guzzlehttp/guzzle ``` Laravel apps already have Guzzle, so the second package is only needed outside Laravel. ## How do you parse and validate a number in PHP? This helper returns the E.164 number with its region and type, or a reason. Load Composer's autoloader as usual (Laravel does it for you). ```php 32) { return ['ok' => false, 'reason' => 'unparseable']; } $util = PhoneNumberUtil::getInstance(); try { $number = $util->parse($raw, $region); // $region like "GB"; null requires a leading + } catch (NumberParseException) { return ['ok' => false, 'reason' => 'unparseable']; } $e164 = $util->format($number, PhoneNumberFormat::E164); if ($allowTestRange && preg_match(self::TEST_RANGE, $e164)) { return ['ok' => true, 'e164' => $e164, 'region' => 'GB', 'type' => 'TEST']; } if (! $util->isValidNumber($number)) { return ['ok' => false, 'reason' => 'invalid']; } return [ 'ok' => true, 'e164' => $e164, 'region' => $util->getRegionCodeForNumber($number), 'type' => $util->getNumberType($number)->name, // e.g. MOBILE, FIXED_LINE_OR_MOBILE ]; } } ``` In version 9 of the library, `getNumberType()` returns a PHP enum, hence `->name`. Real output: ```text ["(202) 555-0143","US"] => {"ok":true,"e164":"+12025550143","region":"US","type":"FIXED_LINE_OR_MOBILE"} ["+44 7911 123456",null] => {"ok":true,"e164":"+447911123456","region":"GG","type":"MOBILE"} ["07911 123456","GB"] => {"ok":true,"e164":"+447911123456","region":"GG","type":"MOBILE"} ["+1 123 456 7890",null] => {"ok":false,"reason":"invalid"} ["202-555-0143",null] => {"ok":false,"reason":"unparseable"} ["07700 900001","GB"] => {"ok":false,"reason":"invalid"} ``` Two surprises worth knowing. `+44 7911` numbers belong to Guernsey (`GG`), so don't derive the country from the dialling code. And a national number without a region can't be parsed, so collect the country on the form. ## How do you validate a 10-digit US phone number? Parse it with the region `US` and let the library decide. Counting digits accepts impossible numbers and rejects valid ones typed with a leading `1`. Real results from the helper above: | Input (region `US`) | Result | |---|---| | `2025550143` | `+12025550143`, `FIXED_LINE_OR_MOBILE` | | `12025550143` | `+12025550143` (the leading `1` is the country code) | | `1234567890` | `invalid`: ten digits, but no area code starts with 1 | | `202-555-014` | `invalid`: one digit short | | `(202) 555-01433` | `invalid`: one digit too many | The type is `FIXED_LINE_OR_MOBILE` because US and Canadian ranges don't separate mobiles from landlines. If you need to know which one it is, for example before sending an SMS, that takes a lookup (below). Also remember that `+1` isn't only the US: Canada and many Caribbean countries share it, and `getRegionCodeForNumber()` tells them apart. ## How do you write a Laravel validation rule for phone numbers? Laravel's `ValidationRule` interface needs one method. The rule reuses the helper: ```php defaultRegion); if (! $result['ok']) { $fail('The :attribute is not a valid phone number.'); } } } ``` Use it like any other rule: `'phone' => ['required', 'string', new PhoneNumber('GB')]`. We ran it through `illuminate/validation` 13.33: `07911 123456` passes, while `12345` and `+1 123 456 7890` fail with "The phone is not a valid phone number." If users pick a country, pass it in: `new PhoneNumber($request->input('country'))`. Validation rules don't change the input, so convert to E.164 after validation with `Phone::normalize()` and save that. If you'd rather not maintain a rule, [`propaganistas/laravel-phone`](https://github.com/Propaganistas/Laravel-Phone) (6.0.3) packages the same library as rules and Eloquent casts. ## How do you check whether the number is a mobile and in service? A valid number can be a landline, a VoIP line or a number nobody uses anymore. A [carrier lookup](/services/carrier-lookup) returns the current `line_type` and carrier. Here's a small Guzzle client for the MobileValidate API. It posts numbers in the body, waits for pending answers and retries only errors the API marks as retryable: ```php http = new Client([ 'base_uri' => $baseUrl, 'headers' => ['Authorization' => "Bearer {$apiKey}", 'Accept' => 'application/json'], 'timeout' => 45, // the server may long-poll for up to `wait` seconds ]); } /** Checks up to 100 E.164 numbers. Numbers go in the POST body, never in the URL. */ public function lookup(array $numbers, array $checks = ['carrier']): array { $res = $this->call('POST', '/v1/lookup', ['json' => ['numbers' => $numbers, 'checks' => $checks, 'wait' => 10]]); while ($res['status'] === 'pending') { usleep(($res['next']['poll_after_ms'] ?? 2000) * 1000); $res = $this->call('GET', "/v1/lookups/{$res['id']}", ['query' => ['wait' => 10]]); } return $res['results']; } private function call(string $method, string $path, array $options, int $attempt = 0): array { try { $response = $this->http->request($method, $path, $options); return json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR); } catch (RequestException $e) { $response = $e->getResponse(); $error = $response ? (json_decode((string) $response->getBody(), true)['error'] ?? []) : []; if (($error['retryable'] ?? false) && $attempt < 3) { sleep((int) ($response?->getHeaderLine('Retry-After') ?: 2 ** $attempt)); return $this->call($method, $path, $options, $attempt + 1); } throw new \RuntimeException($error['code'] ?? 'http_error', $response?->getStatusCode() ?? 0, $e); } } } ``` Register it in a service provider with the key from your environment, for example `new MobileValidate(config('services.mobilevalidate.key'))`, where the config value reads `env('MOBILEVALIDATE_API_KEY')`. Keep the key on the server; never put it in a Blade view or front-end bundle. With a free test key, `lookup(['+447700900001', '+447700900003', '+447700900004'])` printed this (real output, one line per row: E.164, status, line type, reason): ```text +447700900001 completed mobile +447700900003 unknown null UPSTREAM_TIMEOUT +447700900004 completed mobile ``` `…004` is pending for about five seconds in test mode, so the loop polled once. `…402` threw `RuntimeException('insufficient_balance', 402)` immediately, and `…429` retried three times, about a second apart as `Retry-After: 1` asked, before giving up with `rate_limited`. ## How do you handle unknown answers without failing the form? This is where many integrations go wrong. The user typed a valid number, and your lookup timed out. Rejecting the sign-up punishes the user for your dependency. Split the decision in two: 1. **Format** is checked by the rule, synchronously. It's deterministic and free, so failing the form is fine. 2. **Line type and status** are extra facts. Use them when they're conclusive, and ignore them otherwise. ```php $e164 = Phone::normalize($validated['phone'], $validated['country'])['e164']; $lineType = null; try { $row = app(MobileValidate::class)->lookup([$e164])[0]; $check = $row['checks']['network.carrier'] ?? []; if (($check['status'] ?? null) === 'completed') { $lineType = $check['attributes']['line_type'] ?? null; } } catch (\Throwable $e) { report($e); // log the error code, never the number } $user->forceFill(['phone' => $e164, 'phone_line_type' => $lineType])->save(); ``` `$lineType` stays `null` when the answer is unknown. Store `null`, not `false` or `'mobile'`, and re-check later with a queued job. Unknown answers aren't billed. When you do have an answer, act on it: skip SMS for `fixed_line` or `toll_free`, and add a step for `voip` if your fraud rules call for it. The [errors reference](/docs/errors) says which codes are worth retrying. ## How do you validate numbers in bulk? For imports, don't call the lookup row by row. One request takes up to 100 numbers, and a [bulk job](/docs/bulk-jobs) takes up to 50,000. Normalize each row with `Phone::normalize()` first, so obviously invalid rows never leave your server, then send the E.164 values. The API deduplicates after normalization, and invalid or duplicate rows are never charged. The request rate is 10 per second per key, so batching matters more than parallelism. [How to clean a phone number list in bulk](/blog/how-to-clean-a-phone-number-list-in-bulk) covers the full workflow. ## Why do the test numbers need special handling? Our [test mode](/docs/test-mode) uses `+44 7700 900000` to `900999`, a range the UK regulator Ofcom reserves for drama. libphonenumber knows it's never assigned and reports it invalid, so your rule would reject test numbers. Pass `allowTestRange: true` to `Phone::normalize()` only when the configured key starts with `mv_test_`. In PHPUnit or Pest, a test key gives fixed answers (`…001` data, `…002` and `…003` unknown, `…004` pending) and costs nothing. ## What are the key takeaways? - Use `giggsey/libphonenumber-for-php`, keep it updated, and validate with `isValidNumber()`, not digit counts. - Wrap it in a Laravel `ValidationRule`, then save the E.164 form after validation. - Take the region from the parser (`getRegionCodeForNumber()`), not from the dialling code. - Call the lookup API from the server, with numbers in the POST body. Retry only retryable errors. - Never fail a form because a lookup was slow or unknown. Store `null` and re-check later. - Batch imports: up to 100 numbers per lookup, 50,000 per job. ## Sources 1. [libphonenumber-for-php](https://github.com/giggsey/libphonenumber-for-php) — GitHub (Joshua Gigg), 2026 2. [Validation (Laravel 13.x)](https://laravel.com/docs/13.x/validation) — Laravel, 2026 3. [How to validate phone number using PHP? (question 3090862)](https://stackoverflow.com/questions/3090862) — Stack Overflow, 2026 4. [libphonenumber](https://github.com/google/libphonenumber) — Google, 2026 ## Frequently asked questions ### How do I validate a phone number in PHP? Install giggsey/libphonenumber-for-php, parse the input with PhoneNumberUtil::parse() and a default region, then call isValidNumber(). Store the result of format($number, PhoneNumberFormat::E164). ### How do I validate a 10-digit US phone number? Parse it with the region "US" and call isValidNumber(). Checking for ten digits isn't enough: 123 456 7890 has ten digits but no US area code starts with 1, so the library rejects it. ### Should a Laravel form fail when the phone lookup API is down? No. Validate the format synchronously with a rule, then treat the lookup as extra information. If the API times out or returns unknown, accept the number and re-check it later. ### Is there a ready-made Laravel package? Yes. propaganistas/laravel-phone wraps the same library in validation rules and casts. The custom rule in this guide is a few lines and has no extra dependency. --- # Phone number validation in Python: phonenumbers, Pydantic and a live check > Validate phone numbers in Python with the phonenumbers library: is_valid vs is_possible, E.164, a Pydantic validator, pytest and a live check with httpx. Canonical: https://mobilevalidate.com/blog/phone-number-validation-python · Last updated: 2026-09-25 ![Cover: Phone number validation in Python: phonenumbers, Pydantic and a live check](https://mobilevalidate.com/og/blog/phone-number-validation-python.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Developers · Tags: Phone validation, Python, Libphonenumber, Pydantic, E.164, API To validate a phone number in Python, parse it with `phonenumbers.parse()`, check `phonenumbers.is_valid_number()`, and store the result of `format_number(..., PhoneNumberFormat.E164)`. That tells you the number fits its country's numbering plan. It doesn't tell you whether the line is a mobile or in service. For that, send the E.164 number to a lookup API. This guide has tested code for both, plus a Pydantic validator and pytest tests. ## Which Python library should you use? Use [`phonenumbers`](https://github.com/daviddrysdale/python-phonenumbers), the Python port of Google's libphonenumber (Apache 2.0 licence). Its version numbers follow Google's: `phonenumbers` 9.0.40 on PyPI matches libphonenumber 9.0.40, released on 2026-09-23. That matters, because numbering plans change often. Google shipped 25 metadata releases in 2025 and 19 more by late September 2026, each one updating the phone metadata of at least one country. ```bash pip install phonenumbers==9.0.40 httpx pydantic ``` The full package installs about 46 MB, most of it offline geocoding data (38 MB in our install). If you only parse, validate and format, `phonenumberslite` offers the same API without the geocoder, carrier and time-zone data. Avoid regex-only validation. A pattern like `^\+[1-9]\d{1,14}$` accepts numbers no country has allocated. [E.164 regex: why a pattern is not enough](/blog/e164-regex-is-not-enough) shows real counterexamples. ## How do you parse and validate a number? This function returns either an E.164 number with its region and type, or a reason. It's the same flow our API uses on every request. ```python # phone.py import re import phonenumbers from phonenumbers import NumberParseException, PhoneNumberFormat, PhoneNumberType, ValidationResult # Our reserved test range (+44 7700 9xxxxx). libphonenumber marks it invalid on purpose. TEST_RANGE = re.compile(r"^\+4477009\d{5}$") LENGTH_REASONS = {ValidationResult.TOO_SHORT: "too_short", ValidationResult.TOO_LONG: "too_long"} def normalize_phone(raw: str, region: str | None = None, allow_test_range: bool = False) -> dict: raw = (raw or "").strip() if not raw or len(raw) > 32: return {"ok": False, "reason": "unparseable"} try: p = phonenumbers.parse(raw, region) # region like "GB"; None requires a leading + except NumberParseException: return {"ok": False, "reason": "unparseable"} e164 = phonenumbers.format_number(p, PhoneNumberFormat.E164) if allow_test_range and TEST_RANGE.match(e164): return {"ok": True, "e164": e164, "region": "GB", "type": "TEST"} if not phonenumbers.is_valid_number(p): why = phonenumbers.is_possible_number_with_reason(p) return {"ok": False, "reason": LENGTH_REASONS.get(why, "invalid")} return { "ok": True, "e164": e164, "region": phonenumbers.region_code_for_number(p), "type": PhoneNumberType.to_string(phonenumbers.number_type(p)), } ``` Real results from Python 3.12 with `phonenumbers` 9.0.40: | Call | Result | |---|---| | `normalize_phone("(202) 555-0143", "US")` | `ok`, `+12025550143`, `US`, `FIXED_LINE_OR_MOBILE` | | `normalize_phone("07911 123456", "GB")` | `ok`, `+447911123456`, `GG`, `MOBILE` | | `normalize_phone("+1 800 555 0199")` | `ok`, `+18005550199`, `US`, `TOLL_FREE` | | `normalize_phone("+44 79111 234567")` | `too_long` | | `normalize_phone("+1 123 456 7890")` | `invalid` | | `normalize_phone("202-555-0143")` | `unparseable` (no region, no `+`) | | `normalize_phone("07700 900001", "GB")` | `invalid` (reserved drama range) | Note the `GG`: `+44 7911` numbers belong to Guernsey. Take the region from `region_code_for_number()`, never from a country-code lookup table of your own. ## When should you use is_possible_number instead of is_valid_number? `is_possible_number()` checks only the length for the country. `is_valid_number()` also checks the leading digits against allocated ranges. The difference shows up with numbers like `+1 123 456 7890`: ten digits is the right length for the US, so it's possible, but no US area code starts with 1, so it isn't valid. Use `is_possible_number()` for "keep typing" hints in a UI, and `is_valid_number()` before you store a number or spend money on it. `is_possible_number_with_reason()` returns a `ValidationResult` (`TOO_SHORT`, `TOO_LONG`, `INVALID_COUNTRY_CODE`…), which makes friendlier error messages than a plain "invalid". One more trap: `number_type()` and the `phonenumbers.carrier` module describe the **range** a number was allocated from. After a number is ported, both can be wrong. See [why carrier lookups can be wrong](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong). ## How do you validate phone numbers in Pydantic? Pydantic v2 validators can transform the value, so the model stores E.164 no matter what the user typed: ```python # models.py from typing import Annotated from pydantic import AfterValidator, BaseModel from phone import normalize_phone def _e164(value: str) -> str: r = normalize_phone(value, "GB") # your default region, e.g. from the form's country picker if not r["ok"]: raise ValueError(f"not a valid phone number ({r['reason']})") return r["e164"] E164Phone = Annotated[str, AfterValidator(_e164)] class SignUp(BaseModel): name: str phone: E164Phone ``` `SignUp(name="Ada", phone="07911 123456").phone` is `"+447911123456"`, and `phone="12345"` raises a `ValidationError`. The type works in FastAPI request models unchanged. In Django, [`django-phonenumber-field`](https://pypi.org/project/django-phonenumber-field/) (8.5.0 on 2026-09-25) wraps the same library in a model and form field. If your region varies per request, validate in a `model_validator` that reads the country field first. ## How do you check the line type and whether the number is in service? A valid number can still be a landline, a VoIP number or out of service. A [carrier lookup](/services/carrier-lookup) returns the current `line_type` and carrier. Here's a small `httpx` client for the MobileValidate API. It posts numbers in the body, polls while answers are pending and retries only errors the API marks as retryable, respecting `Retry-After`: ```python # mvclient.py import os import time import httpx API = os.environ.get("MOBILEVALIDATE_BASE_URL", "https://api.mobilevalidate.com") class ApiError(Exception): def __init__(self, code: str, status: int, message: str = ""): super().__init__(f"{code} ({status}): {message}") self.code, self.status = code, status def _client() -> httpx.Client: return httpx.Client( base_url=API, headers={"Authorization": f"Bearer {os.environ['MOBILEVALIDATE_API_KEY']}"}, timeout=httpx.Timeout(45.0), # the server may long-poll for up to `wait` seconds ) def _call(client: httpx.Client, method: str, path: str, **kw) -> dict: for attempt in range(4): r = client.request(method, path, **kw) if r.is_success: return r.json() try: err = r.json().get("error", {}) except ValueError: # e.g. an HTML error page from a proxy err = {"retryable": r.status_code >= 500} if err.get("retryable") and attempt < 3: time.sleep(float(r.headers.get("retry-after", 2**attempt))) continue raise ApiError(err.get("code", f"http_{r.status_code}"), r.status_code, err.get("message", "")) def lookup(numbers: list[str], checks: list[str] | None = None) -> list[dict]: """Check up to 100 E.164 numbers. Numbers travel in the POST body, never in the URL.""" with _client() as client: res = _call(client, "POST", "/v1/lookup", json={"numbers": numbers, "checks": checks or ["carrier"], "wait": 10}) while res["status"] == "pending": time.sleep((res.get("next") or {}).get("poll_after_ms", 2000) / 1000) res = _call(client, "GET", f"/v1/lookups/{res['id']}", params={"wait": 10}) out = [] for r in res["results"]: c = (r.get("checks") or {}).get("network.carrier") or {} attrs = c.get("attributes") or {} out.append({ "e164": r.get("e164"), "number_status": r["number_status"], # valid | invalid_number | duplicate | suppressed "status": c.get("status"), # completed | unknown | pending | unsupported_country "line_type": attrs.get("line_type"), # None = unknown, never "not mobile" "carrier": attrs.get("carrier"), "reason": c.get("reason"), "checked_at": c.get("checked_at"), }) return out ``` With a free test key (`MOBILEVALIDATE_API_KEY=mv_test_…`), `lookup(["+447700900001", "+447700900004"])` returned this real output. `…004` stays pending for about five seconds, so the client polled once: ```python {'e164': '+447700900001', 'number_status': 'valid', 'status': 'completed', 'line_type': 'mobile', 'carrier': 'Test Carrier', 'reason': None, 'checked_at': '2026-09-25T18:59:29.902Z'} {'e164': '+447700900004', 'number_status': 'valid', 'status': 'completed', 'line_type': 'mobile', 'carrier': 'Test Carrier', 'reason': None, 'checked_at': '2026-09-25T18:59:34.921Z'} ``` `lookup(["+447700900402"])` raised `ApiError` with code `insufficient_balance` and status 402, without retrying, because that error isn't retryable. The [errors reference](/docs/errors) lists every code with its retry flag. ## How should your code treat each answer? - **`line_type` is `mobile`:** SMS is a sensible channel. Store `line_type` with `checked_at`. - **`fixed_line`, `toll_free` or `voip`:** don't send SMS. Offer voice or e-mail, or add a verification step if your fraud rules call for it. - **`status` is `unknown`:** keep your default behaviour and retry later. Unknown answers aren't billed. `None` is missing data, so never store it as `False`. - **`number_status` is `duplicate` or `invalid_number`:** the API normalizes and deduplicates before checking, and neither is billed. Don't block a sign-up because a lookup timed out. Fail open on `unknown`, fail closed only on answers you're sure about. ## How do you test it with pytest? Unit tests need no network. The integration test runs only when a test key is set, and uses the [test numbers](/docs/test-mode), which never reach a real network and are never billed: ```python # test_phone.py import os import pytest from pydantic import ValidationError from models import SignUp from phone import normalize_phone @pytest.mark.parametrize("raw,region,e164", [ ("(202) 555-0143", "US", "+12025550143"), ("07911 123456", "GB", "+447911123456"), ("+49 1512 3456789", None, "+4915123456789"), ]) def test_normalizes_to_e164(raw, region, e164): assert normalize_phone(raw, region)["e164"] == e164 @pytest.mark.parametrize("raw,reason", [ ("+1 123 456 7890", "invalid"), # right length, impossible area code ("+44 79111 234567", "too_long"), ("202-555-0143", "unparseable"), # no region, no + ]) def test_rejects(raw, reason): assert normalize_phone(raw)["reason"] == reason def test_pydantic_model(): assert SignUp(name="Ada", phone="07911 123456").phone == "+447911123456" with pytest.raises(ValidationError): SignUp(name="Ada", phone="12345") @pytest.mark.skipif(not os.environ.get("MOBILEVALIDATE_API_KEY", "").startswith("mv_test_"), reason="needs a test key") def test_lookup_in_test_mode(): from mvclient import lookup rows = {r["e164"]: r for r in lookup(["+447700900001", "+447700900002", "+447700900003"])} assert rows["+447700900001"]["line_type"] == "mobile" assert rows["+447700900002"]["status"] == "unknown" # no data: not billed assert rows["+447700900003"]["reason"] == "UPSTREAM_TIMEOUT" ``` Our run: `8 passed in 0.24s`. The skip guard means CI without a key still runs the offline tests, and a live key can never reach the integration test by accident. ## Why does the test range need special handling? libphonenumber treats `+44 7700 900000` to `900999` as invalid. The UK regulator Ofcom reserves the range for TV and radio drama, so it's never assigned to a subscriber. That makes it a safe test range, and it's the one our test mode uses. Your validator would reject those numbers before they reach the API. The `allow_test_range` flag in `normalize_phone()` lets them through, and you should set it only when the key starts with `mv_test_`. ## What are the key takeaways? - Use `phonenumbers` (or `phonenumberslite`) and keep its version current. Its version tracks Google's metadata releases. - Call `is_valid_number()` before storing or paying; `is_possible_number()` is for typing hints. - Store the E.164 string and the region from `region_code_for_number()`. - A Pydantic `AfterValidator` gives you E.164 in every model and FastAPI request. - For line type and service status, call a lookup API from your backend with numbers in the POST body. Retry only when the API says `retryable`. - Treat `None` and `unknown` as missing data, and test every branch for free with test numbers. ## Sources 1. [python-phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) — GitHub (David Drysdale), 2026 2. [phonenumbers on PyPI](https://pypi.org/project/phonenumbers/) — Python Package Index, 2026 3. [Validators](https://docs.pydantic.dev/latest/concepts/validators/) — Pydantic, 2026 4. [libphonenumber release notes](https://github.com/google/libphonenumber/blob/master/release_notes.txt) — Google, 2026 ## Frequently asked questions ### What is the best Python library for phone number validation? phonenumbers, the Python port of Google's libphonenumber. It parses any common format, validates against each country's numbering plan and formats to E.164. If you don't need geocoding or carrier names, phonenumberslite has the same API with a smaller install. ### What is the difference between is_possible_number and is_valid_number? is_possible_number checks only the length for the country. is_valid_number also checks that the digits fall inside an allocated range. +1 123 456 7890 has the right length for the US but is not valid, because no US area code starts with 1. ### Why does phonenumbers.parse raise NumberParseException? Usually because the input has no leading + and you passed no region, so the library can't tell which country the digits belong to. Pass a region such as "GB" or "US", ideally from a country picker on your form. ### Does a valid number mean the phone is active? No. Validation only checks the numbering plan. To know the current line type or whether the number is in service, use a carrier or network lookup from your backend. --- # A privacy checklist for phone and e-mail checks > A practical GDPR-oriented checklist for phone and e-mail checks: purpose, lawful basis, minimisation, retention, objections, rights, DPAs and logging. Canonical: https://mobilevalidate.com/blog/privacy-checklist-for-phone-and-email-checks · Last updated: 2026-09-25 ![Cover: A privacy checklist for phone and e-mail checks](https://mobilevalidate.com/og/blog/privacy-checklist-for-phone-and-email-checks.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Privacy, GDPR, Data minimisation, Compliance, Email verification Before you check phone numbers or e-mail addresses, decide why you're checking, which lawful basis covers it, which checks you actually need, how long you'll keep the results, and how you'll handle objections and requests from the people concerned. Then make sure your provider contract, logging and masking match those decisions. The checklist below walks through each point with the GDPR article it comes from. **This is not legal advice.** It describes common practice under the EU GDPR. The UK GDPR and other laws have similar ideas but differ in detail, and your sector may add its own rules. Consult your counsel for your specific case. ## Is checking a number or address processing personal data? Usually, yes. The [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) defines personal data as any information relating to an identified or identifiable natural person (Article 4(1)). A mobile number or a personal e-mail address normally qualifies. "Processing" covers almost anything you do with it, including consultation, use and disclosure by transmission (Article 4(2)). Sending a number to a check API and storing the answer is processing. The answer can also be personal data, and some answers reveal more than others. "This number is a mobile line" says little about a person. "This address has an account on service X" says more. That difference should shape which checks you run, as our comparison of [e-mail verification and account existence checks](/blog/email-verification-vs-account-existence-checks) explains. ## 1. Have you written down the purpose? Write one sentence per purpose, before the first check. Article 5(1)(b) requires personal data to be collected for "specified, explicit and legitimate purposes" and not further processed in an incompatible way (purpose limitation). Good purpose statements are specific: - "Screen new sign-ups for fake or high-risk phone numbers before sending a passcode." - "Remove unreachable numbers from our customer list before a service announcement customers opted into." - "Choose the messaging channel for order updates a customer asked to receive." Vague purposes such as "enrich our data" or "understand our customers better" make every later question harder. Checks run for fraud prevention shouldn't quietly feed marketing segments later. That would be a new purpose, and it needs its own assessment. ## 2. Which lawful basis applies? Pick one per purpose and document why. Article 6(1) lists six bases. For contact-data checks, three come up most often: | Basis | Article | Typical fit | Watch out for | |---|---|---|---| | Contract | 6(1)(b) | Checking a number to deliver a service the customer requested, e.g. sending their order updates on the channel they chose | Must be necessary for the contract, not merely useful | | Legitimate interests | 6(1)(f) | Fraud prevention at sign-up; list hygiene for transactional messages | Needs a documented balancing test | | Consent | 6(1)(a) | Rarely the best fit for fraud checks | Must be freely given (Art. 4(11)) and can be withdrawn at any time (Art. 7(3)), which fits badly with security controls | On legitimate interests, Recital 47 says processing strictly necessary for preventing fraud "also constitutes a legitimate interest of the data controller concerned". The European Data Protection Board adds that this isn't automatic. In its Guidelines 1/2024, it sets out three cumulative conditions: a legitimate interest, the need to process personal data for that interest, and a balance in which the person's interests and rights don't take precedence. Controllers should assess and document these before processing ([EDPB, 2024](http://web.archive.org/web/20260425071638/https://www.edpb.europa.eu/our-work-tools/documents/public-consultations/2024/guidelines-12024-processing-personal-data-based_en)). A short legitimate interests assessment per purpose is the usual way to show that. ## 3. Are you requesting only the checks you need? Run the smallest set of checks that serves the purpose. Article 5(1)(c) requires data to be "adequate, relevant and limited to what is necessary" (data minimisation), and Article 25 asks for data protection by design and by default. In practice: - **Choose checks per purpose, not per record.** A pre-send SMS check may need only line type. A sign-up risk check may add messaging presence. Few purposes need every check. - **Prefer less revealing checks.** A mailbox check says an address works. An account existence check says the person uses a service. Use the second only where the risk justifies it. - **Skip what you already know.** Don't re-check a number you checked last week for the same purpose. With MobileValidate, repeat checks inside the freshness window come from your account's cache and aren't billed, but the better habit is not to ask again. - **Don't send what the check doesn't need.** Our API takes the identifier and the check code. It doesn't need names, and it never returns names, photos or profiles. - **Clean input first.** Malformed and duplicate entries are flagged (`invalid_number`, `duplicate`) and never checked, so they go no further than the validation step. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate) either. ## 4. How long will you keep inputs and results? Decide a retention period per data type, and make sure your systems actually delete. Article 5(1)(e) requires data to be kept no longer than necessary (storage limitation), and Article 30(1)(f) asks controllers to record, where possible, the envisaged time limits for erasure. A common pattern: | Data | Suggested retention | |---|---| | Raw API responses | Days: only as long as you need them to reach a decision | | Decision and date on the record (e.g. `sms_ok=true, checked 2026-09-25`) | As long as the customer relationship, or until re-checked | | Bulk job inputs and result files | Until the list is updated; then delete | | Logs | Masked identifiers only | On the provider side, check the defaults and your options. MobileValidate keeps real-time lookups for 7 days and bulk jobs for 30 days by default. Bulk retention can be set from 1 day to 24 months, and `DELETE /v1/jobs/{id}` purges a finished job's results at once. See the [trust page](/trust). ## 5. How will you handle objections and suppression? Give people a way to object, and make sure an objection stops future checks everywhere. Article 21(1) gives people the right to object to processing based on legitimate interests. You must then stop unless you demonstrate compelling legitimate grounds that override their interests, rights and freedoms, or the processing is needed for legal claims. For direct marketing, Article 21(3) is absolute: once someone objects, the data can no longer be processed for that purpose. What this means in your systems: - **Keep a suppression list** keyed on the normalized identifier (E.164 for numbers, lowercased for e-mails), and check it before calling any API. - **Honour provider-side objections too.** People can object to MobileValidate checks through our [opt-out form](/opt-out). Suppressed identifiers come back as `suppressed`, aren't checked and aren't charged. - **Don't let suppression leak information.** Our opt-out form replies the same way in every case, so it never confirms whether we hold data about someone. ## 6. Can you answer data-subject requests on time? Know where check results live, so you can answer access and erasure requests within the deadline. Article 12(3) requires a response without undue delay and in any event within one month, extendable by two further months for complex or numerous requests. - **Access (Article 15).** Be able to say whether you checked someone's number, for what purpose, which categories of recipient received it (for example, a verification provider) and how long you keep it. - **Erasure (Article 17).** Applies, among other grounds, when data is no longer necessary for its purpose or after a successful objection. Delete the stored decision and ask your provider to delete anything it holds for you. - **Assistance from your provider.** Under Article 28(3)(e), processors must help controllers respond to these requests. Our [data-subject notice](/legal/data-subject-notice) explains how individuals can contact us directly. ## 7. What does your privacy notice say? Tell people you check their contact details and why, in plain language. Article 13 requires information at the time you collect data from the person, including the purposes and the legal basis. Article 14 covers data you obtained elsewhere. A sentence such as "We verify phone numbers and e-mail addresses with specialist providers to prevent fraud, keep accounts secure and make sure our messages reach you" covers most sign-up and messaging uses. If you rely on legitimate interests, say so, and say how people can object. ## 8. Does your provider contract meet Article 28? Sign a data processing agreement with every check provider. Article 28(3) requires a contract that, among other things, has the processor act only on your documented instructions, keep staff under confidentiality, secure the data under Article 32, assist you with data-subject requests, and delete or return the data at the end. Article 28(2) requires your authorisation before the processor engages other processors, with notice of changes so you can object. Ask each provider for: - the DPA itself, and how sub-processors are disclosed and changed; - where data is processed, and the transfer mechanism if it leaves the EEA; - default retention and deletion options; - how they handle objections from individuals. MobileValidate's [DPA](/legal/dpa) is available on request. We publish sub-processor categories, and customers who sign the DPA receive the named list under confidentiality, with notice before changes. Our [buyer's guide](/blog/phone-number-validation-api-buyers-guide) lists more provider questions. ## 9. Are identifiers masked in logs and tools? Keep full numbers and addresses out of places that don't need them. Article 32 asks for security appropriate to the risk, naming pseudonymisation and encryption as examples. - **Never put identifiers in URLs or headers.** They end up in proxy and access logs. Our API accepts them only in request bodies. - **Mask in logs, dashboards and exports.** For example `+44770*****01` and `re•••@example.com`, which is how our job results and downloads show them. - **Log decisions, not responses.** A decision code and `checked_at` are enough for debugging and audits. - **Limit who can see raw data**, and use scoped API keys with IP allowlists where possible. ## 10. Do you need a DPIA? Possibly, if you check at scale or combine results in new ways. Article 35 requires a data protection impact assessment where processing, in particular using new technologies, is likely to result in a high risk to people's rights and freedoms. Screening large volumes of sign-ups, combining many checks into automated decisions, or checking people who never dealt with you are all reasons to ask the question. Your counsel or data protection officer can tell you whether it applies. ## What does the whole checklist look like? | # | Item | GDPR reference | Done when | |---|---|---|---| | 1 | Purpose written down | Art. 5(1)(b) | One sentence per purpose, reviewed | | 2 | Lawful basis chosen | Art. 6(1); Recital 47 | Basis documented; balancing test for legitimate interests | | 3 | Checks minimised | Art. 5(1)(c), 25 | Each check mapped to a purpose | | 4 | Retention set | Art. 5(1)(e), 30(1)(f) | Periods configured and deletion tested | | 5 | Objections honoured | Art. 21 | Suppression list checked before every call | | 6 | Requests answerable | Art. 12(3), 15, 17 | You can find and delete a person's results within a month | | 7 | Notice updated | Art. 13, 14 | Privacy notice mentions checks, purpose and basis | | 8 | Provider DPA signed | Art. 28 | DPA, sub-processor disclosure, transfer terms | | 9 | Logs masked | Art. 32 | No identifiers in URLs; masked logs and exports | | 10 | DPIA considered | Art. 35 | Decision recorded, with reasons | ## What are the key takeaways? - Checking a phone number or e-mail address is usually processing of personal data, and some answers reveal more than others. - Write the purpose down, choose and document a lawful basis, and run only the checks that purpose needs. - Keep decisions and dates rather than raw responses, set retention periods, and test that deletion works. - Honour objections with a suppression list checked before every call, and be ready to answer access and erasure requests within a month. - Sign an Article 28 DPA with every provider, and keep identifiers out of URLs and logs. - This checklist isn't legal advice. Consult your counsel, and see our [trust page](/trust) for how MobileValidate handles its side. ## Sources 1. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 2. [Guidelines 1/2024 on processing of personal data based on Article 6(1)(f) GDPR (version 1.0)](http://web.archive.org/web/20260425071638/https://www.edpb.europa.eu/our-work-tools/documents/public-consultations/2024/guidelines-12024-processing-personal-data-based_en) — European Data Protection Board (archived copy), 2024 ## Frequently asked questions ### Is a phone number personal data? Usually, yes. The GDPR defines personal data as information relating to an identified or identifiable person, and a phone number or e-mail address normally relates to someone who can be identified. Business switchboard numbers and generic addresses may be exceptions, but most checks involve personal data. ### Do I need consent to check a phone number? Not necessarily. Consent is one of six lawful bases in Article 6 GDPR. Many businesses rely on legitimate interests for fraud prevention, or on contract performance for delivering a service the customer asked for. Which basis fits depends on your purpose, so document your reasoning and ask your counsel. ### How long should I keep check results? Only as long as your purpose needs them. For many uses, the decision and its date on the customer record are enough, and the raw result can go within days. MobileValidate keeps real-time lookups for 7 days and bulk jobs for 30 days by default, and bulk retention can be set from 1 day to 24 months. ### What should I do when someone objects to being checked? Stop the processing unless you have compelling legitimate grounds that override their interests, add the identifier to your own suppression list, and point them to the provider's opt-out process if the provider offers one. People can object to MobileValidate checks through our opt-out form. ### Is this legal advice? No. This checklist describes common practice under the GDPR. Your obligations depend on your purpose, your country and your sector, so consult your own counsel. --- # RCS capability check: what it tells senders before they send > What an RCS capability check tells you before sending, iPhone support, RCS vs SMS fallback, what RBM is, and a decision table with bulk API examples. Canonical: https://mobilevalidate.com/blog/rcs-capability-check-explained · Last updated: 2026-09-25 ![Cover: RCS capability check: what it tells senders before they send](https://mobilevalidate.com/og/blog/rcs-capability-check-explained.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Deliverability · Tags: RCS, SMS, Channel selection, Deliverability, Bulk jobs An RCS capability check tells you whether a phone number can receive RCS messages right now: the rich, app-free successor to SMS built into the phone's messaging app. It's a planning signal. It helps you size an RCS rollout, choose message formats and budget for SMS fallback. It isn't a delivery guarantee, and it doesn't mean your business sender can reach that number yet. This guide explains what capability discovery is, how iPhone fits in, how RCS falls back to SMS, and how to act on each answer. ## What is RCS, and what is capability discovery? RCS (Rich Communication Services) upgrades the default messaging app with typing indicators, read receipts, high-resolution media and branded business messages. The GSMA's **Universal Profile** is the industry-agreed feature set for it, and it lists **capability discovery** first among its core features, alongside chat, group chat and file transfer ([GSMA](http://web.archive.org/web/20260916095937/https://www.gsma.com/solutions-and-impact/technologies/networks/rcs/universal-profile/)). Capability discovery is how a phone or a sender learns, before sending, whether the other side can take part in RCS. Your messaging app does this in the background when it decides whether to show a chat as RCS or SMS. Business platforms expose it too. Google's RCS for Business documentation describes a capability check that returns the features a device supports, and advises that "if a user's device isn't capable of receiving RCS messages at all, you can communicate with the user through other services, such as SMS/MMS" ([Google, 2026](https://developers.google.com/business-communications/rcs-business-messaging/guides/build/capabilities)). Our [RCS capability check](/services/rcs-capability-check) gives you the same yes/no answer for lists of numbers, without needing your own RCS agent first. ## Does iPhone support RCS? Yes. Since iOS 18, released in September 2024, Apple's Messages app "supports RCS for richer media and more reliable group messaging compared to SMS and MMS" when messaging people who don't use an Apple device ([Apple, 2024](https://www.apple.com/newsroom/2024/09/ios-18-is-available-today-making-iphone-more-personal-and-capable-than-ever/)). Apple's support page adds two conditions: iOS 18 or later, and a text-messaging plan from a carrier that supports RCS on iPhone ([Apple](https://support.apple.com/en-us/104972)). That's why our RCS answer can include `device_os` with `ios` or `android` when it's reported. It matters for design: rich cards and suggested replies can render differently across platforms, so preview on the handset mix you actually have. One nuance for iPhone: between two Apple devices, Messages uses iMessage first. RCS is what an iPhone uses with non-Apple phones, and what it can receive from businesses. If you also want to know how many customers are on Apple devices, run the [iMessage check](/blog/check-if-a-number-has-imessage) in the same job. ## What is RBM, and why isn't capability enough? **RBM** (RCS Business Messaging) is how brands send RCS: a verified sender, called an agent on Google's platform, sends branded, interactive messages through carriers. To reach a user, three things must be true: 1. The number is RCS-capable (what our check answers). 2. Your agent is verified and launched on the recipient's carrier. 3. The person agreed to receive your messages. Google's documentation lists the two causes of a "not found" answer from its own capability endpoint: the user isn't reachable by RBM, for example because the device doesn't support RCS, **or** "the user has RCS, but your agent isn't launched on their mobile network" ([Google, 2026](https://developers.google.com/business-communications/rcs-business-messaging/guides/build/capabilities)). So a capable number is a *candidate* for RCS, not a guaranteed recipient. Use our check to size the opportunity before you invest in an agent and carrier launches, and use your messaging provider's send-time capability check once you're live. ## How does RCS fall back to SMS? RCS is designed to degrade gracefully. If a message can't go as RCS, a business platform or the handset falls back to SMS or MMS. The fallback decision needs rules, because the two channels differ in cost, features and length limits: | Situation | First attempt | Fallback | Why | |---|---|---|---| | Capable, agent launched on carrier, customer opted in | RCS rich message | SMS with a plain-text version and link | Richest experience; SMS covers delivery failures | | Capable, agent not launched on that carrier | SMS | — | Capability alone won't reach this number | | Not capable (`false`) | SMS (or a consented app channel) | Voice for passcodes | Avoid a failed RCS attempt and its delay | | Unknown (`null`) | Your default channel | — | Missing data is not a no | | `line_type` is `fixed_line` | Voice or e-mail | — | Texts to landlines usually fail | Write every RCS message with an SMS version from the start. Fallback shouldn't mean a truncated rich card. And an SMS fallback can fail for its own reasons, from invalid numbers to carrier filtering; our guide to [why SMS messages aren't delivered](/blog/why-sms-is-not-delivered) walks through them. ## How fresh must a capability answer be? Fresher than a messenger registration. Capability depends on the carrier, the handset, the messaging app and a user setting, and any of them can change. Google's documentation explains the mechanics on its side: a capability answer is returned only if the number "has connected to the RCS service within the last 31 days", online RCS devices check in "every 1–4 hours on average", and moving a SIM to another device replaces the old device association ([Google, 2026](https://developers.google.com/business-communications/rcs-business-messaging/guides/build/capabilities)). It also notes that its bulk capability checks read from a cache updated by devices as people use RCS, so "results may not be current". The same caution applies to any capability data, including ours. Practical rules: - Refresh RCS answers **before each major send**, not monthly. Use a short `max_age`, such as one week. - Keep the answer with `checked_at` and treat anything old as a hint only. - Expect `true` to flip to `false` for customers who changed phones, and the other way round. ## How do I run an RCS check with the API? The RCS check runs in bulk jobs only. `POST /v1/lookup` refuses it with `403 service_disabled`. Create a job over your opted-in customers, with a short `max_age`: ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: rcs-campaign-2026-10" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003", "+447700900010"], "checks": ["rcs"], "max_age": 604800}' # then: GET /v1/jobs/{id}?wait=30 and GET /v1/jobs/{id}/results ``` The real test-mode job reported `"progress": {"total": 4, "checks_total": 4, "done": 4, "conclusive": 3}`. The answers per row: ```json {"e164": "+447700900001", "rcs.registered": {"status": "completed", "registered": true, "reason": null}} {"e164": "+447700900002", "rcs.registered": {"status": "completed", "registered": false, "reason": null}} {"e164": "+447700900003", "rcs.registered": {"status": "unknown", "registered": null, "reason": "UPSTREAM_TIMEOUT"}} {"e164": "+447700900010", "rcs.registered": {"status": "completed", "registered": false, "reason": null}} ``` Test answers don't include `device_os`; live answers include it when reported, and downloads add an `rcs.registered.device_os` column. For large lists, estimate first with the free `POST /v1/jobs/estimate`, and pass its `max_cost` to the job so it can never cost more. Only conclusive answers are billed, at the bulk price on the [pricing page](/pricing). You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## How do I estimate the business case for RCS? Capability rates on your own base turn a vague "RCS is growing" into numbers you can plan with. From a job download, compute per country: - **Capable share** = `true` ÷ (`true` + `false`). Leave unknowns out of both. - **Platform split** among capable numbers, from `device_os` where reported. - **Expected SMS fallback volume** = sends × (1 − capable share × share of carriers your agent is launched on). A small worked example, with invented numbers for illustration: 100,000 opted-in customers in one country, 60% capable, and an agent launched on carriers covering 80% of them. About 48,000 customers could receive RCS; the other 52,000 messages go as SMS. If your RCS and SMS prices differ, this split is what drives your cost model. Our [SMS cost reduction](/use-cases/sms-cost-reduction) use case covers the wider picture. Re-run the job before launch and a few weeks after. Growth in the capable share tells you whether to widen the rollout. ## What are the responsible-use rules? RCS business messages are for people who agreed to receive them. Carrier and platform rules require verified senders and consent, and capability is never permission. This is general guidance, not legal advice; consult counsel about your own situation. Keep to these rules: - **Check only numbers you hold** for a legitimate purpose, such as opted-in customers. - **Use `device_os` only for design and delivery.** Don't infer anything about a person from their handset platform. Our [acceptable use policy](/legal/acceptable-use) forbids profiling and inferring sensitive characteristics. - **No range scans.** 20 or more consecutive numbers in one request are refused with `403 suspected_enumeration`. - **Honour objections.** People can object through our [opt-out form](/opt-out); suppressed numbers are skipped and never charged. RCS, iPhone and Google's platform are named descriptively. MobileValidate is not affiliated with the GSMA, Apple or Google. ## What are the key takeaways? - An RCS capability check answers **whether a number can receive RCS now**: capable, not capable or unknown, sometimes with `device_os`. - **iPhone supports RCS since iOS 18** (2024), where the carrier supports it. - Capability isn't reachability for your brand: your **RBM agent must be launched on the recipient's carrier**, and the customer must have opted in. - Always write an **SMS fallback**. Unknown means "use the default", never "not capable". - Capability changes quickly. Keep `max_age` short and refresh before big sends. - Our RCS check runs in **bulk jobs only**. See the [RCS check](/services/rcs-capability-check), the [iMessage guide](/blog/check-if-a-number-has-imessage) and the [messaging-app checks guide](/blog/messaging-app-registration-checks-guide). ## Sources 1. [Universal Profile](http://web.archive.org/web/20260916095937/https://www.gsma.com/solutions-and-impact/technologies/networks/rcs/universal-profile/) — GSMA, 2026 2. [Capability checks (RCS for Business)](https://developers.google.com/business-communications/rcs-business-messaging/guides/build/capabilities) — Google for Developers, 2026 3. [RCS for Business](https://developers.google.com/business-communications/rcs-business-messaging) — Google for Developers, 2026 4. [iOS 18 is available today, making iPhone more personal and capable than ever](https://www.apple.com/newsroom/2024/09/ios-18-is-available-today-making-iphone-more-personal-and-capable-than-ever/) — Apple, 2024 5. [What is the difference between iMessage, RCS, and SMS/MMS?](https://support.apple.com/en-us/104972) — Apple, 2026 ## Frequently asked questions ### What is an RCS capability check? It asks whether a phone number can currently receive RCS messages, the richer successor to SMS built into phone messaging apps. The answer is capable, not capable or unknown, sometimes with the handset platform. ### Does iPhone support RCS? Yes, since iOS 18 in 2024, where the user's carrier supports RCS on iPhone. That is why an RCS capability answer can report device_os ios as well as android. ### Is RCS capability the same as being able to send RCS business messages? No. Business messaging needs a verified sender (an RBM agent) that is launched on the recipient's carrier. A capable number may still be unreachable for your agent if it isn't launched on that network. ### Can I check RCS capability in real time? Not with MobileValidate. The RCS check runs in bulk jobs only. Run it ahead of a campaign or on a schedule, and let your messaging provider's own send-time fallback handle the rest. ### How long is an RCS capability answer valid? Not long. Capability changes when people switch phones, change settings or move carriers. Keep max_age short and refresh before each major send. --- # The Reassigned Numbers Database (RND), explained > What the FCC Reassigned Numbers Database does, how its yes/no/no-data answers and TCPA safe harbor work, what it costs, and where other checks fit. Canonical: https://mobilevalidate.com/blog/reassigned-numbers-database-explained · Last updated: 2026-09-25 ![Cover: The Reassigned Numbers Database (RND), explained](https://mobilevalidate.com/og/blog/reassigned-numbers-database-explained.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Reassigned numbers, Tcpa, FCC, Compliance, List hygiene, US The Reassigned Numbers Database (RND) is the FCC-established database of US phone numbers that were permanently disconnected, with the date of the most recent disconnection. Before calling or texting someone who gave consent, you can ask it whether the number has been disconnected since that date. A correct "no" answer may give you a limited TCPA safe harbor. **This is not legal advice.** It summarises public FCC rules and the RND administrator's documentation as they stood on 25 September 2026. TCPA exposure depends on your facts, so consult your counsel before you rely on any of it. ## Why does a reassigned number create risk? A number that someone gave you with consent can later belong to a stranger. Carriers recycle disconnected numbers, and the new subscriber never agreed to hear from you. The scale is large. When the FCC created the database in its Second Report and Order of 13 December 2018 (FCC 18-177), it estimated that "approximately 35 million numbers are disconnected and made available for reassignment" each year ([FCC, 2018](https://docs.fcc.gov/public/attachments/FCC-18-177A1.pdf)). Every one of them can sit in a CRM or a marketing list long after its owner changed. Calling or texting the new subscriber with an autodialer or a prerecorded voice can breach the Telephone Consumer Protection Act. The consent you hold belongs to the previous subscriber. The TCPA lets people sue for $500 per violation, and a court may increase that up to three times for wilful or knowing violations under 47 U.S.C. §227(b)(3) ([LII, 2026](https://www.law.cornell.edu/uscode/text/47/227)). Across a campaign, a small share of reassigned numbers can therefore turn into real exposure, besides annoying people who never asked for your messages. ## What exactly does the RND contain? It holds one fact per number: the most recent date on which it was **permanently disconnected**. It holds no names, addresses or current owners. Under 47 CFR 64.1200(l), every provider that gets US numbers from the numbering administrator must keep records of the most recent permanent disconnection of each number and report them to the database administrator on the 15th of every month ([eCFR, 2026](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-B/part-64/subpart-L/section-64.1200)). For toll-free numbers, the FCC put the reporting duty on the Toll Free Numbering Administrator (FCC 18-177, paragraph 23). The rule defines "permanently disconnected" narrowly. A number counts when the subscriber has permanently given it up, or the provider has permanently reversed the assignment. A **ported number is not permanently disconnected**, because it still belongs to the same person on a different network. A temporary suspension for an unpaid bill doesn't count either, according to the administrator's FAQ. According to the RND administrator's FAQ, large and medium providers began reporting on 15 April 2021, small providers on 15 October 2021, and the database held "over 361 million geographic and toll-free numbers" when we checked it on 25 September 2026 ([RND FAQ, 2026](https://www.reassigned.us/resources/faq)). ## How does a query work, and what do the answers mean? You send a number and a date, usually the date you last knew the number belonged to your customer (typically when consent was given). The database answers **yes**, **no** or **no data**. | Answer | What it means | Safe harbor? | |---|---|---| | **Yes** | The number was permanently disconnected on or after your date. It may now belong to someone else | Doesn't apply. Treat the consent as stale | | **No** | No permanent disconnection since your date | May apply if the answer turns out to be wrong | | **No data** | No disconnection record, and your date is before 27 January 2021, the date by which all providers had to keep disconnection records | Doesn't apply | The table follows the definitions on the administrator's About page ([RND, 2026](https://www.reassigned.us/about)). The 27 January 2021 cut-off matters for old lists. If your consent dates from before that date, the database can't vouch for the whole period, so you get "no data". One practical answer is to re-confirm consent with those customers through a channel that is not an autodialed call or text. Queries can be run in the web interface (up to 50 numbers), by file upload or SFTP (up to 1,000,000 numbers per file) or by API (up to 1,000 numbers per request), according to the administrator's [querying page](https://www.reassigned.us/querying-data). ## What is the RND safe harbor? It is a narrow defence, written into 47 CFR 64.1200(m). You aren't liable for calling a reassigned number if you prove two things. First, that you queried the most recent version of the database and got a "no" for the number and the date consent was obtained. Second, that your call happened **because the database wrongly returned "no"** ([eCFR, 2026](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-B/part-64/subpart-L/section-64.1200)). Three details are easy to miss: - **The burden is yours.** The rule says the caller bears "the burden of proof and persuasion". Keep the query, the date you sent, the answer and the time of the call. - **It only covers database errors.** If the database said "yes" and you called anyway, there is no safe harbor. - **It only covers the FCC database.** In FCC 18-177 (paragraph 57), the Commission declined to extend the safe harbor to other commercial databases. A private reassigned-number product may still be useful, but it doesn't give you this defence. Timing matters as well. Providers must age a disconnected number before reassigning it. Under 47 CFR 52.15(f)(1)(ii), residential numbers are aged for at least 45 and at most 90 days, and business numbers for at least 45 and at most 365 days ([eCFR, 2026](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-B/part-52/subpart-B/section-52.15)). The FCC chose 45 days so that monthly reports reach the database before a number can be handed to someone new. A query run shortly before each campaign therefore catches most reassignments. A query from last year does not. ## Who has to use the RND, and what does it cost? Nobody has to. The administrator's FAQ states that "use of the RND is not mandatory" ([RND FAQ, 2026](https://www.reassigned.us/resources/faq)). The incentive is the safe harbor and fewer calls to the wrong person. Access is a **prepaid subscription** for registered callers or their agents, available since 1 November 2021. The pricing table on reassigned.us (a graphic dated 2 July 2025, retrieved 25 September 2026) lists twelve tiers (Tier 1 to Tier 10, plus 3A and 4A), from $0.008 per query at the smallest to $0.00056 at the largest ([RND pricing, 2025](https://www.reassigned.us/pricing)): | One-month tier | Maximum queries | Price | Price per query | |---|---:|---:|---:| | Tier 1 | 1,000 | $8 | $0.0080 | | Tier 2 | 10,000 | $60 | $0.0060 | | Tier 3 | 50,000 | $280 | $0.0056 | | Tier 5 | 500,000 | $2,000 | $0.0040 | | Tier 6 | 2,000,000 | $3,200 | $0.0016 | | Tier 10 | 50,000,000 | $28,000 | $0.00056 | Three-, six- and twelve-month terms multiply the query allowance. Twelve-month plans carry a listed discount of 10% (tiers 1–6) or 15% (tiers 7–10). Prices change, so check the site before budgeting. ## Where does the RND fit next to validation and carrier checks? The RND answers one question: **has this number changed hands since my consent date?** Other checks answer different questions, and a clean contact process usually needs more than one. | Question | Check | What it can't tell you | |---|---|---| | Is the number well-formed and possible? | Format validation (E.164, numbering plan) | Whether anyone uses it | | Mobile, landline, VoIP or toll-free? Which carrier serves it now? | Carrier and line-type lookup | Whether the subscriber is the same person | | Was it permanently disconnected since my consent date? | FCC Reassigned Numbers Database | Line type, carrier, or whether it works today | | Has the person revoked consent or opted out? | Your own opt-out and do-not-call records | Anything about the network | | Is the number linked to spam or abuse reports? | Spam reputation | Whether your customer still owns it | MobileValidate **does not sell RND queries** and our checks don't provide the RND safe harbor. What we offer sits next to it. The [US and Canada carrier lookup](/services/us-carrier-lookup) (bulk jobs) and the global [carrier lookup](/services/carrier-lookup) return the current carrier and line type. That helps you route SMS only to mobile numbers, spot landlines that can't receive texts and find numbers whose type changed, which is itself a hint that something happened to the line. Invalid and duplicate inputs are flagged for free, and inconclusive results aren't charged. ## What does a practical pre-campaign workflow look like? A simple order keeps costs low and puts each check where it helps most: 1. **Normalize and dedupe.** Convert every number to E.164 and remove duplicates. Our [bulk cleaning guide](/blog/how-to-clean-a-phone-number-list-in-bulk) shows how. 2. **Apply your own suppression lists first.** Opt-outs, revocations and internal do-not-call records. They are free and they are legally the most important. 3. **Query the RND** with each number and its consent date. Drop or re-confirm every "yes". Flag "no data" rows with old consent for re-confirmation. 4. **Run a carrier and line-type check** on what remains, to choose SMS or voice and to catch non-mobile numbers before you pay for messages. 5. **Store the evidence.** Keep the RND answer, the query date and your consent record next to each contact, so you can meet the safe-harbor burden if you ever need it. 6. **Repeat before each campaign.** Numbers are aged for as little as 45 days, so a result has a short shelf life. For one-off checks, such as a support agent calling back an old lead, the same logic applies in miniature: check your opt-out list, then the RND, then the line. Our guide to [checking whether a number is active](/blog/how-to-check-if-a-phone-number-is-active) covers the network side. ## What are the common mistakes? - **Treating a working number as the same subscriber.** A number that rings or receives SMS may belong to someone new. Only the RND answers the reassignment question. - **Querying with today's date.** The query date should be the date you last knew the number belonged to your customer, usually the consent date. Today's date asks a different and less useful question. - **Forgetting ported numbers.** Porting isn't disconnection. A ported number keeps its owner, so it won't show as reassigned. That's correct, but it means carrier changes alone say nothing about ownership. - **Relying on a private database for the safe harbor.** Only the FCC database qualifies under 47 CFR 64.1200(m). - **Not keeping records.** Without the query result and timestamp, the safe harbor is hard to prove. The broader consent picture, including revocation rules whose main parts took effect on 11 April 2025, is covered in our upcoming TCPA compliance checklist for SMS senders. ## What are the key takeaways? - The FCC's Reassigned Numbers Database records the latest permanent-disconnection date of US geographic and toll-free numbers. It answers yes, no or no data for a number and a date. - A "no" that turns out to be wrong can give a narrow TCPA safe harbor under 47 CFR 64.1200(m). You carry the burden of proof, and only the FCC database qualifies. - Consent dates before 27 January 2021 return "no data". Re-confirm consent for those contacts. - Access is a prepaid subscription. Published prices range from $0.008 to $0.00056 per query (table dated July 2025). - Carrier and line-type lookups complement the RND but don't replace it. MobileValidate doesn't sell RND queries. - This is guidance, not legal advice. Consult counsel for your own program. ## Sources 1. [Advanced Methods to Target and Eliminate Unlawful Robocalls, Second Report and Order (FCC 18-177)](https://docs.fcc.gov/public/attachments/FCC-18-177A1.pdf) — Federal Communications Commission, 2018 2. [47 CFR 64.1200 Delivery restrictions (paragraphs (l) and (m))](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-B/part-64/subpart-L/section-64.1200) — eCFR, US Government Publishing Office, 2026 3. [47 CFR 52.15 Central office code administration (aging numbers)](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-B/part-52/subpart-B/section-52.15) — eCFR, US Government Publishing Office, 2026 4. [About the Reassigned Numbers Database](https://www.reassigned.us/about) — Reassigned Numbers Database Administrator, 2026 5. [RND Frequently Asked Questions](https://www.reassigned.us/resources/faq) — Reassigned Numbers Database Administrator, 2026 6. [RND subscription pricing](https://www.reassigned.us/pricing) — Reassigned Numbers Database Administrator, 2025 7. [47 U.S.C. 227 Restrictions on use of telephone equipment](https://www.law.cornell.edu/uscode/text/47/227) — Legal Information Institute, Cornell Law School, 2026 ## Frequently asked questions ### What is the Reassigned Numbers Database? It is a database set up by the FCC that records the most recent date each US geographic and toll-free number was permanently disconnected. Callers enter a number and the date they last had consent, and the database answers yes, no or no data. ### Is using the RND mandatory? No. The RND administrator's FAQ says use is not mandatory. Checking it can, however, give callers a limited safe harbor from TCPA liability under 47 CFR 64.1200(m) if the database wrongly answered no. ### How much does an RND query cost? The RND is a prepaid subscription. The pricing table on reassigned.us (retrieved 25 September 2026) starts at $8 for 1,000 queries per month and falls to $0.00056 per query at the 50-million-query tier. Check the site for current prices. ### Does a carrier or line-type lookup replace the RND? No. A carrier lookup tells you who serves a number today and what kind of line it is. It can't tell you whether the number changed hands since the person gave consent, and it doesn't qualify for the RND safe harbor. Use both for different jobs. ### Is this legal advice? No. This article explains public FCC rules and RND documentation as of 25 September 2026. TCPA exposure depends on your facts, so consult your own counsel. --- # Screening inbound calls with spam reputation > A call-center workflow for routing inbound calls by spam reputation: levels, reasons, STIR/SHAKEN, limits of the data, and why no_reports isn't safe. Canonical: https://mobilevalidate.com/blog/screening-inbound-calls-with-spam-reputation · Last updated: 2026-09-25 ![Cover: Screening inbound calls with spam reputation](https://mobilevalidate.com/og/blog/screening-inbound-calls-with-spam-reputation.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Fraud prevention · Tags: Call center, Spam reputation, Robocalls, Call screening, Fraud prevention Screening inbound calls with spam reputation means looking up the caller ID when a call arrives and routing it by what others have reported about that number: to an agent, an IVR, or a verification step. It saves agent time on robocalls and slows down impersonation scams. It works best as one layer next to network caller-ID authentication and your own verification, because a clean-looking number proves nothing. ## What problem does inbound screening solve? Contact centers pay for every minute an agent spends on a call, and unwanted calls take real minutes. US consumers filed over 2.6 million Do Not Call complaints with the FTC in fiscal year 2025 ([FTC, 2025](https://www.ftc.gov/reports/national-do-not-call-registry-data-book-fiscal-year-2025)). Businesses receive the same traffic: robocalls into published service lines, callers impersonating customers to reset accounts, and diallers probing IVRs. Screening gives you a decision point before the call reaches a person. The goal isn't to block as much as possible. It is to send each call to the cheapest path that still serves a genuine caller: - **Likely genuine:** straight to the queue. - **Doubtful:** an IVR prompt, a callback, or extra identity checks before account changes. - **High risk with strong evidence:** an automated path. A person reaches an agent only after passing verification. Your [call-center screening](/use-cases/call-center-screening) setup decides which path each result takes. ## What does a spam reputation answer contain? The [spam reputation](/services/spam-reputation) check (`number.spam`) answers from reports about the number, described by class only: telecom regulator actions, government nuisance-call complaint data, community reports, and a signal for numbers recently offered for sale as unassigned. It covers US, Canadian and German numbers and is in **limited access** (internal customers only) for now. | Field | Meaning | |---|---| | `risk_level` | `high`, `medium`, `low` or `no_reports` | | `risk_score` | 0–100; reports last seen more than 12 months ago count half | | `reason_regulator` / `reason_government` / `reason_community` / `reason_unassigned` | Which signal classes are behind the answer | | `voip_range` | Hint only: the range belongs to a VoIP carrier. Adds no points | | `top_category` | Most frequent report category, e.g. `robocall`, `impersonation`, `debt_relief` | | `first_seen` / `last_seen` | Months the number first and last appeared in our data | | `sources` | Number of independent signal classes | `high` needs a score of at least 80 **and** either a regulator action or two or more independent signal classes. One noisy source can't produce `high` on its own. ## Why isn't number reputation enough on its own? Because most bad calls come from numbers nobody has reported yet. We analysed 362,116 complaints from 30 daily FTC Do Not Call files (August to September 2026). There were 297,872 distinct caller numbers, and **92.5% of them appeared in exactly one complaint**. The 100 most-reported numbers accounted for only 2.9% of complaints. FCC unwanted-call complaints for January to August 2026 show the same shape: 96.6% of caller IDs appear once. The method and tables are in [our analysis of US nuisance-call complaints](/blog/us-nuisance-call-complaints-what-the-data-shows), built from the [FTC](https://www.ftc.gov/policy-notices/open-government/data-sets/do-not-call-data) and [FCC](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e) public data. That pattern fits caller-ID rotation and spoofing: bad actors change numbers faster than reports accumulate. A reputation check catches the persistent offenders and the numbers regulators have acted on, which is valuable. It will miss fresh numbers. So `no_reports` is the absence of evidence, never a clean bill of health, and your other layers have to carry the rest. ## How does STIR/SHAKEN fit in? STIR/SHAKEN is caller-ID authentication inside the phone network. The originating provider signs the call to say how well it knows the caller is entitled to the number shown. The FCC adopted rules in 2020 requiring voice providers to implement it in the IP portions of their networks by June 30, 2021 ([FCC](https://www.fcc.gov/call-authentication)). Many carriers and SIP trunks pass the result to you as an attestation level or a verification flag. The two signals answer different questions: | Signal | Question it answers | Blind spot | |---|---|---| | STIR/SHAKEN attestation | Was this caller ID asserted by a provider that knows the caller? | Doesn't say whether the caller is welcome. A fully attested number can still run a robocall campaign. Non-IP legs and some international calls arrive unsigned | | Spam reputation | Has this number collected complaints or regulator action? | New and rotated numbers have no history. Spoofed numbers can collect reports that belong to someone else | Use both. Weak attestation plus reports is a much stronger signal than either one alone. Full attestation plus `no_reports` is a reasonable default for the normal queue. ## What does the workflow look like? The check sits between "the call arrives" and "the call is routed": 1. Your telephony platform receives the call and the caller ID, plus any attestation your carrier passes on. 2. Convert the caller ID to E.164. Most platforms already provide it in that form. 3. Call `POST /v1/lookup` with `checks: ["spam", "carrier"]` and a short `wait`, for example 2 seconds. 4. Combine `risk_level`, the reasons, `line_type` and attestation in your routing rules (table below). 5. If the answer is `pending`, `unknown` or `unsupported_country`, route the call normally. 6. Log the level, the reasons and `checked_at`, not the raw response or the full number. ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002"], "checks": ["spam", "carrier"], "wait": 2}' ``` The test numbers above work for spam reputation in test mode, although live checks cover only US, CA and DE. The first returns `high`, the second `no_reports`. ## What do the results look like? The `number.spam` part of each result, from real test-mode output: ```json {"risk_level": "high", "risk_score": 95, "reason_regulator": true, "reason_government": false, "reason_community": true, "reason_unassigned": false, "voip_range": false, "top_category": "robocall", "first_seen": "2025-11", "last_seen": "2026-08", "sources": 2} ``` ```json {"risk_level": "no_reports", "risk_score": 0, "reason_regulator": false, "reason_government": false, "reason_community": false, "reason_unassigned": false, "voip_range": false, "sources": 0} ``` The reasons matter as much as the level. A `high` with `reason_regulator: true` and `top_category: robocall` is a strong case for the automated path. A `medium` driven only by community reports and last seen a year ago deserves a lighter touch. Report texts, reporter details and names are never returned. ## How should you route each result? A starting table for an inbound service line. Tune it with your own outcome data. | Result | Suggested routing | |---|---| | `high` with `reason_regulator` | IVR or automated path. Agent only after verification | | `high` without regulator action | IVR with a simple human check (press a key, state a reference) | | `medium` | Normal queue, but require full verification before account changes | | `low` | Normal queue. Watch for patterns | | `no_reports` | Normal queue. **Not** a guarantee of anything | | `reason_unassigned: true` | Treat the caller ID as possibly spoofed. Verify before discussing an account | | `top_category: impersonation` | Flag to the agent. Don't reset credentials on this call | | `voip_range: true` alone | No action. Many businesses and people call from VoIP | | `unknown`, `pending`, `unsupported_country` | Normal routing. Not charged | Two rules keep this fair. Never refuse service outright based on a level alone. Give a path to a human. And never treat `no_reports` as a reason to skip your normal verification for sensitive actions. ## How do you avoid hurting genuine callers? Reputation data describes numbers, and numbers get spoofed and reassigned. A scam campaign can spoof your customer's real number, so their number collects reports they had nothing to do with. A number reported years ago may now belong to someone new. That's why reports last seen more than 12 months ago count half, and why every level carries its reasons and `last_seen`. Practical safeguards: - **Route, don't block.** An IVR step costs a genuine caller seconds. A block costs you the customer. - **Show the agent the reason, not a verdict.** "Reported for robocalls, last seen August 2026" helps the agent. "SPAM" invites rudeness to a real customer. - **Re-check repeat callers.** Scores change as reports arrive and age out, and repeat checks within 24 hours are served free from your account's cache. - **Don't use it for eligibility.** Spam levels are not for decisions about credit, jobs, housing or insurance. Our [acceptable use policy](/legal/acceptable-use) forbids that. People who find their number in our data can ask for a review through the [opt-out form](/opt-out). ## What does screening cost? Each conclusive spam answer is billed, **including `no_reports`**, because the check was carried out and answered. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). So calls from outside the US, Canada and Germany cost nothing to screen. The carrier lookup is billed only when it returns carrier data. See [pricing](/pricing) for current rates. Call centers see many repeat callers. Answers for the same number within 24 hours come from your account's cache for free, which can keep the average cost per call below the per-check price. Screen only the lines that need it, such as published service numbers and fraud-sensitive queues, and skip internal extensions. ## How does this help outbound teams too? The same data protects your own caller reputation. Run a bulk job over outbound call lists of people who asked to be contacted, with `checks: ["carrier", "spam"]` (plus `network.carrier_us` for US and Canadian lists) and remove numbers recently offered as unassigned or reported for fraud before they reach the dialler. You can also check your own outbound numbers periodically. If they start collecting complaints, you'll see it before your answer rates fall. For list mechanics, see [how to clean a phone number list in bulk](/blog/how-to-clean-a-phone-number-list-in-bulk). ## What are the key takeaways? - Spam reputation routes inbound calls by evidence: levels with reasons, for US, CA and DE numbers (limited access today). - In public FTC data, 92.5% of reported caller numbers appear only once, so reputation catches persistent offenders but misses fresh numbers. - Combine it with STIR/SHAKEN attestation and your own verification. Neither signal is enough alone. - `no_reports` is not safe. `voip_range` alone is not risk. `unknown` is free and should route normally. - Route, don't block, and show agents reasons rather than verdicts. ## Sources 1. [Combating Spoofed Robocalls with Caller ID Authentication](https://www.fcc.gov/call-authentication) — Federal Communications Commission, 2021 2. [National Do Not Call Registry Data Book for Fiscal Year 2025](https://www.ftc.gov/reports/national-do-not-call-registry-data-book-fiscal-year-2025) — Federal Trade Commission, 2025 3. [Do Not Call (DNC) Reported Calls Data](https://www.ftc.gov/policy-notices/open-government/data-sets/do-not-call-data) — Federal Trade Commission, 2026 4. [Consumer Complaints Data - Unwanted Calls](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e) — Federal Communications Commission, 2026 ## Frequently asked questions ### Can I block every call with a high spam level? You can, but routing is usually better than blocking. Send high-risk calls to an IVR or a verification step. Caller IDs can be spoofed, so a real customer may occasionally arrive from a number with reports. ### Does no_reports mean the caller is safe? No. It means we hold no reports for the number. New, rarely used and spoofed numbers often have no history. Handle the call normally and rely on your usual verification. ### Which numbers does the spam reputation check cover? Numbers from the United States, Canada and Germany. Other numbers return unsupported_country and are not charged. The service is in limited access (internal customers only) for now. ### Is the check fast enough to run while the phone is ringing? Spam answers come from our own daily-refreshed reference data, not a live call to another service, so real-time answers are fast. Set a short wait and route the call normally if the answer isn't back in time. --- # SIM swap fraud: the signals to check before you trust an SMS code > What a SIM swap is, which SIM-swap data exists and who can get it, and which signals to combine before you trust an SMS code when that data is missing. Canonical: https://mobilevalidate.com/blog/sim-swap-fraud-signals · Last updated: 2026-09-25 ![Cover: SIM swap fraud: the signals to check before you trust an SMS code](https://mobilevalidate.com/og/blog/sim-swap-fraud-signals.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Fraud prevention · Tags: Sim swap, Account takeover, OTP, Fraud prevention, Porting A SIM swap moves a phone number to a new SIM, so every SMS code for that number goes to whoever holds the new SIM. Direct SIM-change data comes only from operators and covers some countries. Where you can't get it, combine device, account and number signals, and step up to a factor that doesn't depend on the phone network. ## What is a SIM swap, and why does it break SMS codes? A SIM swap is the transfer of a phone number from one SIM card or eSIM profile to another. Operators do it every day for customers who lose or replace a phone. In SIM swap fraud, a criminal gets the operator to move the victim's number to a SIM the criminal controls. Our [SIM swap glossary entry](/glossary/sim-swap) has the short definition. The FBI describes three ways criminals do it: social engineering (impersonating the victim to the carrier), paying off an insider, and phishing carrier employees to plant malware. "Once the SIM is swapped, the victim's calls, texts, and other data are diverted to the criminal's device," the FBI wrote, and the criminal can then request password resets that arrive by SMS ([FBI IC3, 2022](https://www.ic3.gov/PSA/2022/PSA220208)). That is why a swap defeats SMS one-time passcodes. The code is delivered correctly. It just arrives on the wrong phone. Nothing in the message, the delivery receipt or the code itself tells your app that the recipient changed. ## How is a SIM swap different from a port-out? Both attacks move the number away from the victim. They differ in where it goes, and that decides which data can spot them. | | SIM swap | Port-out | |---|---|---| | What changes | The SIM or eSIM behind the number | The operator that serves the number | | Carrier reported by a lookup | Unchanged | Changes to the new operator | | Who can see it | The operator (SIM-change records) | Anyone with current porting data | | Typical attacker route | Carrier store or support desk | A porting request to another operator | | Signal you can buy widely | SIM-change date, only where operators expose it | Current carrier and porting status | In the US, the FCC adopted rules in November 2023 that require wireless providers to use secure methods to authenticate a customer before moving their number to a new device or provider, and to notify customers of SIM-change and port-out requests ([FCC, 2023](https://www.federalregister.gov/documents/2023/12/08/2023-26338/protecting-consumers-from-sim-swap-and-port-out-fraud)). The rules took effect in January 2024, with some parts delayed. Check the FCC's current status before relying on a specific requirement. Carrier-side protections reduce the risk. They don't remove it, so your own flow still needs defences. ## How big is the problem? Public figures are few, and the best-known ones are dated. The FBI's Internet Crime Complaint Center received 320 SIM-swapping complaints with adjusted losses of about $12 million from January 2018 to December 2020. In 2021 alone it received 1,611 complaints with adjusted losses of more than $68 million ([FBI IC3, 2022](https://www.ic3.gov/PSA/2022/PSA220208)). Those are reported US cases only. In Europe, ENISA published a study in December 2021 on how SIM-swapping attacks work and which measures operators can take against them ([ENISA, 2021](https://www.enisa.europa.eu/publications/countering-sim-swapping)). Standards bodies treat the risk as structural. NIST's current authentication guidance classes one-time codes over the phone network as a **restricted** authenticator. It says verifiers "SHOULD consider risk indicators (e.g., device swap, SIM change, number porting, other abnormal behavior) before using the PSTN to deliver an out-of-band authentication secret" ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). The rest of this guide is about gathering those indicators. ## Which SIM-swap data exists, and who can get it? SIM-change data lives with the operator. Getting it means going through an API that the operator exposes, directly or through a partner. The main open specification is **CAMARA SimSwap**, maintained under the Linux Foundation and used by GSMA Open Gateway operators. Its latest public release, r3.3 (December 2025), defines two operations: `check`, which says whether the SIM was swapped within a period you give (`maxAge`, between 1 and 2,400 hours in the spec), and `retrieve-date`, which returns the latest SIM change. The spec also lets operators return `null` when local rules stop them keeping the date that long, optionally with the period they do monitor ([CAMARA, 2025](https://github.com/camaraproject/SimSwap)). | Source | What it tells you | Coverage | Practical limits | |---|---|---|---| | Operator APIs following CAMARA SimSwap | Swapped within N hours, or the latest change date | Operators and countries that have launched it | Contracts per operator or aggregator; monitoring windows vary with local rules | | Communications platforms that resell operator data | The same facts through one API | Usually a short list of countries, sometimes in beta | Check each country and the consent model | | Your own records | A new device, a new number on the account, a reset request | Every user | Doesn't see what happens at the operator | | Number intelligence (carrier, porting, line type) | Carrier and porting changes, line-type changes | Wide | Can't see a SIM change within the same operator | The honest summary: if your users sit in countries where operators expose SIM-change data, buy it for your high-risk flows. Everywhere else, you are combining indirect signals. ## Does MobileValidate sell SIM-swap data? No. We don't offer SIM-change dates, and we don't claim that any of our checks detects a SIM swap. Here is what we do provide and what each one can and can't tell you: | Check (alias) | What it tells you | Helps against | Doesn't tell you | |---|---|---|---| | Carrier lookup (`carrier`) | `line_type`, current `carrier`, `country`, and `original_carrier` when it differs | Port-outs (compare with your last snapshot); VoIP or unexpected line types | A SIM change at the same operator | | Messenger checks (`whatsapp`, `telegram`, `viber`) | Whether the number has an account | Choosing a non-SMS code channel the user already has | Whether the SIM changed; accounts usually survive a swap | | Spam reputation (`spam`) | Report-based risk level, US, CA and DE, limited access | Numbers with regulator actions or fraud reports | Anything about the SIM | | Live network status (`hlr`) | Reachable, `ported`, roaming (true or false) | Port detection from the network itself | Coming soon. It will never return the IMSI or other SIM identifiers | That last line matters. SIM-change detection at the network level relies on subscriber identifiers such as the IMSI. We never expose them, by design. See the [account security use case](/use-cases/account-security) for how these checks fit a login and recovery flow. ## Which signals should you combine when SIM-swap data is missing? Score the moment, not the number alone. Most attacks show several of these at once: | Signal | Where it comes from | Why it matters | |---|---|---| | New or unknown device | Your session data | A swap is usually followed by a login from the attacker's device | | Password or recovery reset requested by SMS | Your auth logs | The typical first move after a swap | | Phone number changed on the account recently | Your account history | Attackers swap the number, then the recovery settings | | Carrier differs from your last stored snapshot | Carrier lookup vs your records | Points to a port-out since the last check | | `original_carrier` differs from `carrier` | Carrier lookup | The number was ported at some point. Normal on its own | | Line type changed to `voip` | Carrier lookup | The number moved to an internet service | | Country of the IP differs from the number and the account | Your session data plus `country` | Weak alone, strong combined | | Large transfer, payout or contact change right after login | Your transaction data | The attacker's goal | The strongest pattern is **timing**: an SMS-based reset, a new device and a high-value action within a short window. Each alone is common among real users. ## How do you compare carrier snapshots in code? Store the carrier facts when the user first verifies the number, then compare before a sensitive action. A test-mode request: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["carrier", "whatsapp"], "max_age": 3600, "wait": 5}' ``` Response (excerpt of `results[0].checks`, test mode): ```json { "network.carrier": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "checked_at": "2026-09-25T19:03:29.021Z", "billed": false, "reason": null}, "whatsapp.registered": {"status": "completed", "registered": true, "billed": false, "reason": null} } ``` `max_age: 3600` accepts a cached answer up to an hour old, which is free. Then a small function turns the comparison into a signal: ```js // stored = what you saved at enrolment; r = results[0] from the lookup. function numberChangeSignals(stored, r) { const c = r.checks?.["network.carrier"]; if (c?.status !== "completed") return []; // unknown or pending: no evidence either way const a = c.attributes; const out = []; if (stored.carrier && a.carrier && a.carrier !== stored.carrier) out.push("carrier_changed"); if (a.original_carrier && a.original_carrier !== a.carrier) out.push("ported_at_some_point"); if (stored.line_type === "mobile" && a.line_type === "voip") out.push("now_voip"); return out; } ``` A carrier change is evidence of a port since your last snapshot, not proof of fraud. People switch operators all the time. Feed the result into the same score as your device and session signals. ## What should a step-up look like? Decide in advance what each risk level triggers, so support staff can explain it to a real customer: | Situation | Suggested action | |---|---| | No risk signals | Send the SMS code as usual | | One weak signal (new device only) | Send the code; notify the user by e-mail or push | | SMS reset + new device | Require a second factor that isn't SMS: passkey, authenticator app or e-mail link | | SMS reset + carrier changed since the last snapshot | Step up, and hold payouts or contact changes for a cooling-off period | | Any of the above + high-value action | Manual review or a verified call-back to a number you had before the change | | Carrier data `unknown` | Treat as no evidence. Use your other signals | NIST also requires services that accept a restricted authenticator to offer at least one alternative that isn't restricted ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). Offering passkeys or an authenticator app to every user is the most durable fix, because it takes the phone network out of the login path. ## What else should you tell users? Part of the defence sits with the customer and their operator. The FBI advises people not to post their phone number or financial assets online, to "be aware of any changes in SMS-based connectivity", and to use strong factors such as physical security tokens or standalone authentication apps. It asks carriers to set strict protocols for verifying customers before moving a number to a new device ([FBI IC3, 2022](https://www.ic3.gov/PSA/2022/PSA220208)). Useful prompts in your product: - Suggest a passkey or authenticator app at the second login. - Explain that many operators offer account PINs or port-out protection, and link to your help page on it. - Tell users that sudden loss of mobile signal, with no explanation, is a reason to contact their operator straight away. Keep the lookups proportional. Check the numbers of your own users, for the security purpose you told them about, and store only the facts you compare against. The [acceptable use policy](/legal/acceptable-use) applies, and people can object through the [opt-out form](/opt-out). ## What are the key takeaways? - A SIM swap moves a number to a new SIM, so SMS codes reach the attacker without any visible error. - SIM-change data comes from operators (for example through CAMARA SimSwap) and covers only some countries. Buy it where it exists for your high-risk flows. - MobileValidate doesn't sell SIM-swap data. Our carrier lookup shows porting hints and line type, and the HLR lookup is coming soon. Neither sees a SIM change at the same operator. - Where SIM-swap data is missing, combine device, account-history and number signals, and weight their timing. - Step up to passkeys, authenticator apps or e-mail when signals line up, and offer non-SMS factors to everyone. For the number facts behind porting, read [why carrier lookups can be wrong after porting](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong). The pre-send checks for every code are in [OTP fraud prevention: checks to run before sending a code](/blog/otp-fraud-prevention-checks-before-sending-a-code). ## Sources 1. [Criminals Increasing SIM Swap Schemes to Steal Millions of Dollars from US Public (I-020822-PSA)](https://www.ic3.gov/PSA/2022/PSA220208) — FBI Internet Crime Complaint Center, 2022 2. [Protecting Consumers From SIM-Swap and Port-Out Fraud (Report and Order)](https://www.federalregister.gov/documents/2023/12/08/2023-26338/protecting-consumers-from-sim-swap-and-port-out-fraud) — Federal Communications Commission, Federal Register, 2023 3. [Countering SIM-Swapping](https://www.enisa.europa.eu/publications/countering-sim-swapping) — ENISA, 2021 4. [CAMARA SimSwap API (release r3.3)](https://github.com/camaraproject/SimSwap) — CAMARA Project (Linux Foundation), 2025 5. [NIST SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management](https://csrc.nist.gov/pubs/sp/800/63/b/4/final) — NIST, 2025 ## Frequently asked questions ### Does MobileValidate offer a SIM swap check? No. We don't sell SIM-change dates. We provide related signals: line type, current and original carrier (a porting hint), messenger presence and, in limited access for US, Canadian and German numbers, spam reputation. A live network status check (HLR) is coming soon. ### What is the difference between a SIM swap and a port-out? A SIM swap moves the number to a new SIM or eSIM at the same operator. A port-out moves the number to a different operator. Both hand the number to whoever controls the new SIM, but only a port-out changes the carrier a lookup reports. ### Can a messenger check detect a SIM swap? No. A messenger account usually stays registered to the number after a swap, so presence neither confirms nor rules out a swap. It is useful for other things, such as offering a code channel the user already has. ### What should we do when no SIM-swap API covers a country? Combine what you do know: device and session changes, a recent number change on the account, a carrier or porting change compared with your last snapshot, and the value of the action. Step up to a factor that doesn't depend on the phone network when several signals line up. --- # SMS delivery receipts vs HLR lookup: before or after you send > A delivery receipt reports what happened after you sent an SMS. An HLR lookup asks the network before you send. What each can see, miss and cost. Canonical: https://mobilevalidate.com/blog/sms-delivery-receipt-vs-hlr-lookup · Last updated: 2026-09-25 ![Cover: SMS delivery receipts vs HLR lookup: before or after you send](https://mobilevalidate.com/og/blog/sms-delivery-receipt-vs-hlr-lookup.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Deliverability · Tags: Delivery receipts, HLR lookup, SMS, Deliverability, Number reachability A delivery receipt (DLR) tells you what happened to a message after you sent it. An HLR lookup asks the recipient's home network, before you send, whether the number is assigned and reachable. The receipt is free with most sends but arrives late. The lookup costs a query but saves wasted sends. Many teams use both. ## What does each one answer? Both come from the same mobile network machinery, at different moments. The comparison in one table: | | Delivery receipt (DLR) | HLR lookup | |---|---|---| | When | After the send, seconds to days later | Before the send | | Question | What happened to this message? | Is this number assigned and reachable now? | | Source | The message centre, based on the delivery attempt | The number's home network register | | Cost | Usually included with the message | A paid query per number | | Works for | Every message you send | Mobile numbers only | | Can't see | Anything before you paid; reads | The handset's inbox, filters, content blocks | | Typical use | Reporting, retries, support tickets | OTP pre-checks, list cleaning, routing | The short version: a DLR is an **outcome**, an HLR lookup is a **forecast**. Our glossary has short entries on the [delivery receipt](/glossary/delivery-receipt-dlr) and the [HLR lookup](/glossary/hlr-lookup). ## How does a delivery receipt work? When you submit a message, your provider (or you, if you connect directly) can request a receipt. In SMPP, the protocol most message centres speak, you set a flag on the submission, and the message centre later sends back a receipt as a separate message. The SMPP 3.4 specification is blunt about it: "To determine the eventual outcome of the SMS delivery, the ESME must request an SMSC Delivery Receipt" ([SMPP Developers Forum](https://smpp.org/SMPP_v3_4_Issue1_2.pdf)). The specification lists the final message states a receipt can carry: | State | Receipt text | Meaning | |---|---|---| | DELIVERED | `DELIVRD` | Delivered to the destination | | EXPIRED | `EXPIRED` | The validity period ran out before delivery | | DELETED | `DELETED` | The message was deleted | | UNDELIVERABLE | `UNDELIV` | The message can't be delivered | | ACCEPTED | `ACCEPTD` | Accepted on the subscriber's behalf | | UNKNOWN | `UNKNOWN` | Invalid state | | REJECTED | `REJECTD` | Rejected | Two details matter in practice. First, the spec gives only a "typical example" of the receipt text and leaves the exact format to each message-centre implementation. Second, the `err` field "may hold a Network specific error code or an SMSC error code", and those codes are not standardized in SMPP ([SMPP Developers Forum](https://smpp.org/SMPP_v3_4_Issue1_2.pdf)). Your provider translates them, or passes them through. Inside the mobile network, the handset's acknowledgement travels back as an SMS status report, defined in [3GPP TS 23.040](https://www.3gpp.org/DynaReport/23040.htm) ([3GPP](https://www.3gpp.org/DynaReport/23040.htm)). That is where a real `DELIVRD` comes from. ## Is a DLR proof of delivery? It is strong evidence, with caveats: - **Some routes answer early.** An intermediary can report delivery when it hands the message on, not when the phone confirms. The result looks like a delivered message that nobody received. - **Receipts can be slow.** Amazon notes that "depending on the destination phone number's carrier, it can take up to 72 hours for delivery logs to appear" ([AWS](https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html)). - **Delivered isn't read.** The handset may file the message as spam or hide it. - **No receipt isn't failure.** Missing receipts are common on some routes. For OTP, the verification rate (codes entered ÷ codes sent) is a more honest delivery metric than receipts. For marketing and notices with consent, receipts are what you have. Watch them per route and per country, not only in total. ## How does an HLR lookup work? Before an SMS can reach a mobile phone on another network, the sending side asks the recipient's home network where to deliver it. That routing query, part of the MAP protocol in [3GPP TS 29.002](https://www.3gpp.org/DynaReport/29002.htm) ([3GPP](https://www.3gpp.org/DynaReport/29002.htm)), is what an HLR lookup performs, without sending a message. The reply shows whether the number is assigned, whether the subscriber can be reached, and which network currently holds the subscription, so it also reveals porting. It has limits. It works only for mobile numbers. Some operators block or mask external queries. And the raw reply contains identifiers, such as the IMSI and the serving switch address, that should never leave the telecom side. The [HLR vs MNP vs number validation](/blog/hlr-vs-mnp-vs-number-validation) guide covers the wider comparison. ## What do the network errors mean? Whether you meet them in a receipt's error field or in an HLR reply, the same MAP errors keep coming up. The names come from 3GPP TS 29.002. The codes are the MAP local error values in its error module ([3GPP](https://www.3gpp.org/DynaReport/29002.htm)). | MAP error (code) | Plain meaning | What to do | |---|---|---| | `unknownSubscriber` (1) | The number isn't assigned on the home network | Remove it; re-confirm the contact | | `absentSubscriberSM` (6) | The phone couldn't take a text right now | Retry later, or offer another channel for OTP | | `teleserviceNotProvisioned` (11) | The subscription doesn't include SMS | Use another channel | | `callBarred` (13) | Barred by the operator or subscriber | Stop; don't retry in a loop | | `absentSubscriber` (27) | The subscriber couldn't be reached | Same as absent for SMS | | `subscriberBusyForMT-SMS` (31) | The phone is busy with another delivery | Retry shortly | | `sm-DeliveryFailure` (32) | Delivery to the handset failed (for example, memory full) | Retry later | | `systemFailure` (34) | A network problem, not the number | Retry; don't blame the number | Your provider may show these under its own labels. The point is the category: **not assigned** (stop), **temporarily unreachable** (retry or switch channel), **not allowed** (stop), **network trouble** (retry). ## Should you run the lookup before or after sending? It depends on what a failed send costs you. A decision table: | Situation | Use | Why | |---|---|---| | OTP by SMS | Validation + line type before; live status before where available | A code that can't arrive costs a user. Switch channel at once | | Transactional notices to active customers | Delivery receipts after | Numbers recently worked; retries handle short outages | | First campaign to an old consented list | Live status before | Removes unassigned numbers before you pay for each message | | Routing by current network | Carrier or live status before | Prefix-based routing is wrong for ported numbers | | Support ticket "I never got it" | Receipt first, then a lookup | The receipt shows the outcome; the lookup shows the number's state now | | Landlines or VoIP | Line type before | HLR queries don't apply | A rough rule: if you'd act differently depending on the answer, and you'd have to act before a receipt arrives, check before sending. ## What does MobileValidate offer today? Today, validation and normalization run free on every request, and the [carrier lookup](/services/carrier-lookup) returns line type, current carrier, country and, when a number was ported, the original carrier. The [HLR lookup](/services/hlr-lookup) is **coming soon**. It is switched off for everyone, including test keys: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["hlr"]}' ``` ```json {"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"}} ``` When it launches, `number.hlr` will return `status` (`reachable`, `unreachable`, `invalid` or `unknown`), `ported`, `roaming` as true or false only, `network`, `mcc_mnc` and `country`. It will never return the IMSI, the serving switch, cell or location. The test numbers are already defined on the [test mode](/docs/test-mode) page, so you can write the handling code now. On billing: `reachable`, `unreachable` and `invalid` will be conclusive and billed. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Delivery receipts come from your messaging provider, not from us. ## How do you use both together? The two signals complement each other, and the combination catches things neither sees alone: 1. **Before sending:** normalize, check line type, and (once available) check live status for high-stakes sends. 2. **At send time:** request delivery receipts and store the message ID with the number's `checked_at`. 3. **After sending:** classify receipt errors into stop, retry or switch channel. 4. **Feed back:** numbers with `unknownSubscriber` receipts go to a re-check or removal list. Numbers absent for weeks get re-confirmed. 5. **Measure:** track delivered-receipt rate and, for OTP, verification rate per country and route. ## How should you store and act on receipts? Receipts are only useful if you can join them back to a number and a decision. A few habits make that easy: - **Store the message ID** your provider returns, with the masked number, the country, the route or sender ID and the time of sending. - **Keep the raw status and error** next to your own category (delivered, retry, stop, switch channel). Provider labels change. Your categories shouldn't. - **Set a deadline.** If no final receipt arrives within your window (minutes for OTP, a day or more for notices), record "no receipt" as its own state instead of assuming success. - **Count per number over time.** One failure is noise. Three `unknownSubscriber` answers in a row are a pattern worth acting on. - **Don't keep more than you need.** Delivery logs contain phone numbers, so apply the same retention rules as for the rest of your customer data. ## What are the key takeaways? - A delivery receipt reports the outcome after you paid. An HLR lookup forecasts reachability before you send. - SMPP defines receipt states such as `DELIVRD`, `EXPIRED` and `UNDELIV`, but leaves the text format and error codes to each implementation. - Receipts can be early, late or missing, so use verification rates for OTP. - MAP errors fall into four groups: not assigned, temporarily unreachable, not allowed and network trouble. Act on the group. - MobileValidate's carrier lookup is available now. The HLR lookup is coming soon and will never expose SIM or location identifiers. For the full list of reasons a text doesn't arrive, read [why SMS messages aren't delivered](/blog/why-sms-is-not-delivered). For checking whether a number is in use at all, see [how to check if a phone number is active](/blog/how-to-check-if-a-phone-number-is-active). ## Sources 1. [Short Message Peer to Peer Protocol Specification v3.4, Issue 1.2](https://smpp.org/SMPP_v3_4_Issue1_2.pdf) — SMPP Developers Forum, 1999 2. [3GPP TS 29.002: Mobile Application Part (MAP) specification](https://www.3gpp.org/DynaReport/29002.htm) — 3GPP 3. [3GPP TS 23.040: Technical realization of the Short Message Service (SMS)](https://www.3gpp.org/DynaReport/23040.htm) — 3GPP 4. [Amazon SNS SMS delivery monitoring with Amazon CloudWatch metrics and logs](https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html) — Amazon Web Services ## Frequently asked questions ### Is a delivery receipt proof that the message was delivered? It is the best evidence you get, but not proof. A final DELIVRD status normally means the network reports delivery to the handset. Receipt formats vary between message centres, some routes send receipts before the handset confirms, and delivered doesn't mean read. ### Should I run an HLR lookup before or after sending? Before, when a failed send is expensive or time-critical, such as OTP or large campaigns to old lists. After, a delivery receipt is enough for everyday transactional messages to numbers that recently worked. ### What does absent subscriber code 27 mean? In the MAP protocol, error 27 is absentSubscriber: the subscriber couldn't be reached. For text messages the network usually reports absentSubscriberSM (error 6). Both mean the phone was off or out of reach at that moment, not that the number is invalid. ### Does MobileValidate offer HLR lookups? The HLR lookup is coming soon. Requests are refused with 403 service_disabled today. The carrier lookup is available now and returns line type, current carrier and a porting hint. --- # SMS pumping: how it works and how to stop it > How SMS pumping (artificially inflated traffic) drains OTP budgets, the signals that give it away, and a layered checklist to stop it before you pay. Canonical: https://mobilevalidate.com/blog/sms-pumping-how-it-works-and-how-to-stop-it · Last updated: 2026-09-25 ![Cover: SMS pumping: how it works and how to stop it](https://mobilevalidate.com/og/blog/sms-pumping-how-it-works-and-how-to-stop-it.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Fraud prevention · Tags: SMS pumping, Artificially inflated traffic, OTP, Fraud prevention, Rate limiting SMS pumping is fraud in which bots make your app send large numbers of text messages, usually one-time passcodes, to numbers that earn the attacker a share of every delivery fee. You pay for messages nobody reads. The fix is layered: limit who can trigger a text, where it can go, and check the number before you pay. ## What is SMS pumping, exactly? SMS pumping is a scheme to generate paid message traffic that has no real recipient. The telecom industry calls it **artificially inflated traffic (AIT)**. Other names are SMS toll fraud and OTP abuse. It works because every SMS creates money along its route. You pay your messaging provider. Your provider pays carriers and intermediaries, and the network that delivers the message to the handset collects a termination fee. If an attacker controls a block of numbers, or has a revenue-share deal with someone who does, every message sent to those numbers pays them. The attacker doesn't need to break into anything. They only need a public form that sends an SMS to any number typed into it: sign-up, login with a code, "send me a download link", password reset. Bots fill that form thousands of times with numbers from the attacker's ranges. The codes are never entered, because nobody is waiting for them. Our [glossary entry on SMS pumping](/glossary/sms-pumping) has a shorter definition. ## Why are OTP forms the favourite target? A passcode form is built to send an SMS to a stranger, fast, with as little friction as possible. That's exactly what an attacker needs. - **No account required.** The SMS is sent before the user has proven anything. - **Any destination accepted.** Many forms take any international number, because blocking a real customer abroad feels worse than paying for a few extra texts. - **Resend buttons.** "Didn't get the code?" invites repeated sends to the same number. - **Automatic retries.** Some verification setups fall back from SMS to a voice call, which can add a second payout on expensive destinations. The attack also hides well. On a dashboard it looks like growth: more sign-ups started, more codes sent. The problem shows up later, as an invoice. Large platforms have acted on this publicly. In February 2023 Twitter said it had "seen phone-number based 2FA be used - and abused - by bad actors", and stopped offering SMS two-factor authentication to non-subscribers after 20 March 2023 ([Twitter, 2023](https://blog.x.com/en_us/topics/product/2023/an-update-on-two-factor-authentication-using-sms-on-twitter)). Most companies can't simply switch SMS off, so they need controls instead. ## What does a pumping attack look like in your data? Pumping leaves a fingerprint. You'll usually see several of these signals at once: | Signal | What it looks like | Why it points to pumping | |---|---|---| | Conversion collapse | Codes sent rise, codes verified stay flat | Nobody is receiving the codes | | Country shift | Traffic to a destination you rarely serve | Attackers pick destinations with high fees | | Range clustering | Many numbers share the first 7–9 digits | The attacker owns a block of numbers | | Sequential numbers | …101, …102, …103 in minutes | Bots iterate through the block | | Odd line types | Premium-rate, shared-cost or unallocated ranges | Real users rarely sign up with them | | Burst timing | Hundreds of requests in a few minutes, often at night in your main market | Automated traffic | | Rotating clients | Many IP addresses or user agents, few completed sessions | The bot is spreading out to dodge limits | The single most useful metric is the **verification rate per country**: codes verified divided by codes sent. Real users verify most of their codes. A pumped destination verifies almost none. Alert on it per country and per hour, not as a global daily average, where a small attack disappears. ## How do you stop SMS pumping with a layered checklist? No single control is enough, because attackers adapt. Stack these layers so that each one catches what the previous one missed. **1. Limit who can trigger a send** - Put bot protection (a challenge or proof-of-work) in front of every endpoint that sends an SMS. - Require a session that did something human first, such as loading the page and filling other fields. - Delay the resend button (for example 30–60 seconds) and cap resends per session. **2. Rate-limit on several keys at once** - Per destination number: a few codes per hour, a small daily maximum. - Per number prefix (the first digits of the national number): catches attacks that rotate through a block. - Per IP address, device fingerprint and session. - Per country: a ceiling on codes per hour, set from your normal traffic. - A global daily spend cap on your SMS account, with an alert well below it. **3. Restrict destinations (geo permissions)** - Allow OTP only to countries where you have customers. Everything else needs a manual exception. - Use your messaging provider's geographic permissions as a second, independent control. - Treat a new country as a release decision, not a default. **4. Check the number before you pay** - Normalize to [E.164](/glossary/e164) and reject numbers that can't exist. This is free. - Look up the [line type](/glossary/line-type): don't send SMS codes to premium-rate, shared-cost, toll-free or fixed lines. - Check whether the number has a messenger account the user can receive codes on, and whether the number is new to you. **5. Watch and respond** - Alert on verification rate per country and per prefix. - Keep a kill switch per country that support staff can flip at night. - Review the SMS invoice by destination every week, not every month. ## How does a pre-send number check help? Rate limits slow attackers down. A number check removes whole categories of pumped destinations before a single message is paid for. The check runs between "user entered a number" and "we send a code". With MobileValidate, one `POST /v1/lookup` request can return the number's normalized form, its line type and carrier from the [carrier lookup](/services/carrier-lookup), and whether it has an account on a messenger such as [WhatsApp](/services/whatsapp-number-check). Here is a real test-mode example: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002"], "checks": ["carrier", "whatsapp"], "wait": 5}' ``` Response (excerpt: the `checks` of both numbers): ```json [ {"e164": "+447700900001", "number_status": "valid", "checks": { "network.carrier": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "billed": false}, "whatsapp.registered": {"status": "completed", "registered": true, "billed": false}}}, {"e164": "+447700900002", "number_status": "valid", "checks": { "network.carrier": {"status": "unknown", "registered": null, "reason": "NO_DATA", "billed": false}, "whatsapp.registered": {"status": "completed", "registered": false, "billed": false}}} ] ``` Your code then applies a rule per result. A `premium_rate` line type is a hard stop. A `mobile` number with a messenger account is a normal send. An `unknown` answer means "no data", so the flow continues with its default rules. It is never a reason to block, and it isn't charged. ## Which rules should the pre-send check apply? Keep the rules simple and explainable, so that support can tell a user why their code didn't arrive. | Result | Suggested action | |---|---| | `number_status: invalid_number` | Ask the user to correct the number; send nothing | | `line_type` is `premium_rate`, `shared_cost` or `uan` | Block the SMS | | `line_type` is `fixed_line` or `toll_free` | Offer a voice call or another method instead of SMS | | `line_type` is `voip` | Send, but with a lower per-number limit; see [VoIP detection](/blog/voip-number-detection-for-signups) | | Country not on your allow-list | Block, or send to manual review | | `mobile` with a messenger account the user chose | Send the code there or by SMS as usual | | Any check `unknown` or `pending` | Fall back to your default rules; don't block on missing data | Two API behaviours help directly against pumping. First, requests with 20 or more numerically consecutive numbers are refused with `403 suspected_enumeration`, which is the pattern a bot walking through a number block produces in batch tools. Second, repeat checks of the same number inside the freshness window come from your account's cache for free, so a bot hammering "resend" on one number doesn't multiply your check costs. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## Should you move OTP away from SMS altogether? Sometimes, partly. Every channel has trade-offs, and removing SMS entirely locks out users without a smartphone app. NIST's current authentication guidance treats one-time codes sent over the phone network as a **restricted** authenticator. It says verifiers should consider risk indicators such as device swap, SIM change and number porting before sending a code that way ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). That guidance is about account security rather than cost, but it points the same way: use SMS where it's needed and with checks, not as the only option. Practical options: - **Offer app-based codes** (authenticator apps, passkeys) to returning users and make them the default after the first login. - **Deliver codes on a messenger the user already has**, if they chose it. A messenger delivery doesn't create an SMS termination fee. Check that the number has an account first; see our [WhatsApp check API guide](/blog/check-if-a-number-is-on-whatsapp-api-guide). - **Keep SMS as a fallback**, protected by every layer in the checklist above. ## How do you size the problem before and after? Measure before you change anything, so you can show that the controls worked. 1. **Baseline.** For the last 90 days, export codes sent and codes verified per country and per week. Note the SMS cost per country from your invoice. 2. **Find the gap.** Countries with a verification rate far below your main market's are candidates for pumping. Look for sudden jumps in volume in the same weeks. 3. **Estimate the waste.** Unverified codes × cost per SMS in that country gives an upper bound of what pumping cost you. Some unverified codes are real users who gave up, so treat it as a ceiling. 4. **Roll out the layers**, starting with the country allow-list and rate limits. They are cheap and fast to deploy. 5. **Compare.** After two to four weeks, the verification rate in previously abused countries should rise and total SMS volume should fall, while sign-up completions in your main markets stay the same. If completions in your main markets drop, a rule is too strict. Loosen it instead of piling on more friction. ## What are the key takeaways? - SMS pumping (artificially inflated traffic) makes you pay termination fees on messages sent to numbers the attacker profits from. - The clearest signal is a collapsing verification rate in one country or number range. Monitor it per country and per hour. - Layer the defences: bot protection, rate limits on number, prefix, IP and country, a country allow-list, a spend cap, and a pre-send number check. - A number check removes impossible numbers and non-mobile line types before any SMS is paid for. Unknown results aren't charged and should never block a user. - Offer app-based or messenger delivery where users choose it, and keep SMS as a protected fallback. For a full implementation of the pre-send check, including multi-check requests and timeouts, read [OTP fraud prevention: checks to run before sending a code](/blog/otp-fraud-prevention-checks-before-sending-a-code). The [OTP and sign-up fraud use case](/use-cases/otp-and-signup-fraud) and [SMS cost reduction](/use-cases/sms-cost-reduction) pages show the same checks from a product angle. ## Sources 1. [An update on two-factor authentication using SMS on Twitter](https://blog.x.com/en_us/topics/product/2023/an-update-on-two-factor-authentication-using-sms-on-twitter) — Twitter (now X), 2023 2. [NIST SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management](https://csrc.nist.gov/pubs/sp/800/63/b/4/final) — NIST, 2025 ## Frequently asked questions ### Is SMS pumping the same as artificially inflated traffic (AIT)? Yes, in practice. Artificially inflated traffic is the industry term for messages generated only to earn termination fees. SMS pumping and SMS toll fraud describe the same scheme from the victim's side: bots trigger texts that nobody reads. ### Which countries does SMS pumping target? It follows the money, so it moves. Attackers favour destinations where terminating an SMS is expensive and where they have a share of the fee. The safest assumption is that any country you don't actively serve can be abused, so allow OTP only to the countries where you have customers. ### Will a CAPTCHA alone stop SMS pumping? It raises the cost of each attempt, but determined attackers solve challenges with humans or services. Combine bot protection with per-number, per-IP and per-country limits, a country allow-list and a check of the number before you send. ### Does checking a number cost more than the SMS it prevents? It depends on your SMS rates and prices. Invalid and duplicate numbers are found for free, and inconclusive checks are not charged. Run the check only on paths where an SMS would be sent, and compare its price with your cost per SMS in the countries you serve. --- # Telegram number check: a practical guide to what it can and can't tell you > How Telegram's phone and username model differs, what a registration check can and can't tell you, how privacy settings affect it, with API examples. Canonical: https://mobilevalidate.com/blog/telegram-number-check-guide · Last updated: 2026-09-25 ![Cover: Telegram number check: a practical guide to what it can and can't tell you](https://mobilevalidate.com/og/blog/telegram-number-check-guide.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: Telegram, Channel selection, Privacy, Consent, API A Telegram number check tells you whether a Telegram account can be found for a phone number you hold: registered, not registered or unknown, with the time of the check. Because Telegram is built around usernames and gives people control over who can find them by number, a "not registered" answer is weaker here than on apps where every account is reachable by number. Read it as "not reachable through this number", which is usually what a channel decision needs. ## How does Telegram's phone and username model differ? Telegram starts from a phone number but doesn't depend on it afterwards. Three design choices matter for a number check. **A number at sign-up, usernames after.** Each phone number is a separate Telegram account, according to Telegram's [FAQ](https://telegram.org/faq). But people can chat without showing that number. Since October 2014, users have been able to set a public username so that "anyone will be able to find you by your username and contact you – without having to know your phone number" ([Telegram, 2014](https://telegram.org/blog/usernames-and-secret-chats-v2)). **Numbers hidden by default.** Telegram's FAQ says that by default a user's number is visible only to people they have saved as contacts, and that this can be changed in the privacy settings. A dedicated "Who Can See My Number" control was added in May 2019 ([Telegram, 2019](https://telegram.org/blog/privacy-discussions-web-bots)). Separately, users control whether others are allowed to find them by their phone number at all ([Telegram, 2022](https://telegram.org/blog/ultimate-privacy-topics-2-0)). Both appear as distinct keys in Telegram's own [privacy API documentation](https://core.telegram.org/api/privacy): one for the phone number's visibility and one for being added by phone. **Accounts without a SIM.** Since December 2022, people can create a Telegram account without a SIM card, using anonymous numbers bought on the Fragment platform ([Telegram, 2022](https://telegram.org/blog/ultimate-privacy-topics-2-0)). Such an account isn't tied to a number from a mobile network at all. Compare that with apps where an account is normally discoverable through the number it was registered with. On Telegram, the link between "a person" and "a phone number you hold" is looser, and it's under the person's control. ## What can a Telegram registration check tell you? It answers one narrow question: can a Telegram account be found for this number right now? That supports a few practical decisions: - **Channel selection.** If a customer asked for updates on Telegram, you know whether their number leads to an account before you try. See [channel selection](/use-cases/channel-selection). - **Verification code routing.** Before sending a passcode through Telegram, you know whether the number is likely to receive it there, or whether SMS is the better first attempt. - **A presence signal at sign-up.** A number with an account passed Telegram's own sign-up verification at some point. That makes it a little more likely to be a number in real use. It's one signal among several, as our guide to [reducing fake sign-ups](/blog/how-to-reduce-fake-signups-with-phone-and-email-checks) explains. Every conclusive answer carries `checked_at`. Accounts come and go, and numbers get reassigned by carriers, so a recent answer is stronger than an old one. ## What can't it tell you? More than on most platforms, because of the design choices above: - **Whether the person uses Telegram at all.** They may be on Telegram with a different number, with an anonymous number, or with number discovery switched off. - **Their username or identity.** The check never returns usernames, names, photos, bios or last-seen times, and it can't be used to look up who is behind a number. - **Whether they read messages there.** Registration isn't activity. Many people keep accounts they rarely open. - **Whether you may message them.** An account is not consent. More on this below. The practical consequence: a `true` is fairly informative, a `false` much less so. Never use `false` alone to conclude that a number is fake. ## How do privacy settings affect the answer? They can turn "has an account" into "can't be found by this number". When someone restricts who can find them by their phone number, a check through that number may return `registered: false` or `unknown` even though the account exists. Our [Telegram check](/services/telegram-number-check) page notes the same limit. That's a feature of Telegram, not a fault in the check, and it's the right outcome for the person. They chose not to be found by number. Respect it in your design: - Don't retry repeatedly to "get a better answer". A privacy setting won't change because you ask again. - Don't try to work around it through other data sources. - For people who matter to you, such as a customer who opted in to Telegram updates, ask them directly for the channel and handle they prefer. ## How do you check Telegram with the API? Use `checks: ["telegram"]` (the alias for `telegram.registered`) on `POST /v1/lookup`. It works in real time for up to 100 numbers per request, and in bulk jobs for larger lists. The test numbers return every state: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003", "+447700900005"], "checks": ["telegram"], "wait": 5}' ``` The four `telegram.registered` results from the real test-mode response, trimmed: ```json {"status": "completed", "registered": true, "confidence": "high", "checked_at": "2026-09-25T17:17:34.544Z", "billed": false, "reason": null} {"status": "completed", "registered": false, "confidence": "high", "checked_at": "2026-09-25T17:17:34.544Z", "billed": false, "reason": null} {"status": "unknown", "registered": null, "confidence": null, "checked_at": null, "billed": false, "reason": "UPSTREAM_TIMEOUT"} {"status": "unsupported_country", "registered": null, "confidence": null, "checked_at": null, "billed": false, "reason": "UNSUPPORTED_COUNTRY"} ``` Telegram has no country restrictions in live mode; `+447700900005` is the test number that simulates `unsupported_country`. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Test keys never bill. Answers that take longer than `wait` come back as `pending`. With test number `+447700900004` and `"wait": 1`, the real response was: ```json "status": "pending", "next": {"poll_url": "/v1/lookups/lkp_0VWFm9xa9KWE4k5QYBeh", "poll_after_ms": 2000} ``` Poll `GET /v1/lookups/{id}?wait=10`, or register a [webhook](/docs/webhooks) for `lookup.completed`. You can also combine Telegram with other checks in one request, for example `["telegram", "whatsapp", "viber"]`, to see which apps a customer can be reached on. Answers differ per service for the same number. ## How should you act on each answer? Decide per message type, and remember that Telegram's "no" is soft: | Answer | Passcode delivery | Updates the customer opted into | Stored record | |---|---|---|---| | `registered: true` | Offer Telegram if you support it; keep SMS as fallback | Send on Telegram if the customer chose it | Store `true` with `checked_at` | | `registered: false` | Use SMS or another channel | Use the customer's other chosen channel | Store `false`, but treat it as "not found by number" | | `registered: null` (unknown) | Use your default channel | Keep previous routing | Don't overwrite a stored conclusive answer | | `pending` | Don't wait in a user-facing flow; use the default | Apply the answer to the next message | Update when it arrives | | `unsupported_country`, `invalid_number` | Use your default; fix the number if invalid | Same | Flag for cleanup if invalid | Repeat checks within the freshness window are served from your account's cache for free (`cached: true`, `billed: false`). A monthly refresh, or a re-check after a failed Telegram delivery, is usually enough. ## Where does Telegram fit for verification codes? It can be a cheaper route for passcodes in markets where Telegram is widely used. Telegram runs a Gateway API that lets businesses send verification codes to users inside Telegram. At the time of writing (September 2026), its own page lists a price of $0.01 per delivered code and says you only pay for codes delivered within the time you specify ([Telegram Gateway](https://core.telegram.org/gateway)). A registration check fits in front of that: send the code through Telegram only when the number leads to an account, and send the rest by SMS or voice. That avoids a failed attempt and a delay for users who aren't on Telegram. The general pre-send logic, including line type and fraud checks, is in [OTP fraud prevention: checks to run before sending a code](/blog/otp-fraud-prevention-checks-before-sending-a-code). ## What does consent look like for Telegram? The same as for any channel: people must have given you their number and agreed to hear from you there. A registration check helps you deliver messages people expect. It doesn't create permission to send new ones. Telegram's public groups and channels make it attractive for spam, which is why the check is built with limits: - **Check only numbers you hold** for a legitimate purpose: customers, sign-ups and leads who gave you their number. - **Ranges are refused.** Requests with 20 or more consecutive numbers return `403 suspected_enumeration`, and each account has a daily cap. See [rate limits and abuse](/docs/rate-limits-and-abuse). - **Yes, no or unknown only.** No usernames, profiles or activity. Our [acceptable use policy](/legal/acceptable-use) forbids profiling and finding people to message unsolicited. - **Objections are honoured.** People can object through our [opt-out form](/opt-out). Suppressed numbers are skipped and never charged. Respect the fact that many Telegram users hide their number on purpose. Telegram is named here descriptively. MobileValidate is not affiliated with Telegram. ## What are the key takeaways? - Telegram links accounts to phone numbers at sign-up but is built around usernames. Numbers are hidden by default, and people control whether they can be found by number. - A registration check answers whether an account can be found for a number you hold. `true` is informative; `false` often means "not reachable through this number", not "doesn't use Telegram". - Privacy settings, other numbers and SIM-free anonymous numbers all limit what the check can see. Don't work around them. - Use `checks: ["telegram"]` in real time or bulk. Handle `pending` without blocking users, and never overwrite a stored answer with unknown. - Use the answer to route passcodes and expected messages, for example in front of Telegram's own verification gateway. - It's not consent and it's not a lookup tool. Test every branch with the [test numbers](/docs/test-mode) and see the [Telegram check](/services/telegram-number-check) page for pricing. ## Sources 1. [Telegram FAQ](https://telegram.org/faq) — Telegram, 2026 2. [Usernames and Secret Chats 2.0](https://telegram.org/blog/usernames-and-secret-chats-v2) — Telegram, 2014 3. [Focused Privacy, Discussion Groups, Seamless Web Bots and More](https://telegram.org/blog/privacy-discussions-web-bots) — Telegram, 2019 4. [No-SIM Signup, Auto-Delete All Chats, Topics 2.0 and More](https://telegram.org/blog/ultimate-privacy-topics-2-0) — Telegram, 2022 5. [Privacy settings (API documentation)](https://core.telegram.org/api/privacy) — Telegram, 2026 6. [Telegram Gateway](https://core.telegram.org/gateway) — Telegram, 2026 ## Frequently asked questions ### Why can a Telegram check say not registered when the person uses Telegram? Telegram lets people control whether others can find them by their phone number, and an account can also be linked to a different number than the one you hold. When an account can't be found through the number you checked, the answer can be not registered or unknown even though the person uses Telegram. ### Does the check return the Telegram username or profile? No. It answers only whether an account is associated with the number. Usernames, names, photos, bios and last-seen times are never returned. ### Is the person notified when their number is checked? No. Nothing is sent to the number, and the person is not notified by us. ### Can I check Telegram in real time? Yes. Use the telegram alias (telegram.registered) on POST /v1/lookup for up to 100 numbers per request, or in bulk jobs for up to 50,000 numbers and e-mails. ### Can I use the result to message people on Telegram? Only people who gave you their number and agreed to hear from you on Telegram. A registered answer is not consent, and our acceptable use policy forbids finding recipients for unsolicited messages. --- # US nuisance-call complaints in 2026: what the public data shows > Our analysis of 362,116 FTC Do Not Call complaints and FCC unwanted-call data: top subjects, robocall share, weekday pattern and trend. Canonical: https://mobilevalidate.com/blog/us-nuisance-call-complaints-what-the-data-shows · Last updated: 2026-09-25 ![Cover: US nuisance-call complaints in 2026: what the public data shows](https://mobilevalidate.com/og/blog/us-nuisance-call-complaints-what-the-data-shows.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Research · Tags: Robocalls, Spam calls, FTC, FCC, Original research, Call screening We analysed 362,116 nuisance-call complaints that consumers filed with the US Federal Trade Commission between 13 August and 23 September 2026, plus the FCC's unwanted-call complaint data for January 2025 to August 2026. Most complaints are about robocalls (69.4%), debt-relief pitches lead the named subjects, and weekdays carry 93.4% of reported calls. Most reported numbers appear only once. ## What data did we use, and how? We used two public US government datasets and published only aggregates. **FTC Do Not Call reported calls.** The FTC publishes a daily file of complaints about Do Not Call and robocall violations, with the reported originating number, the call date, the consumer's city, state and area code, a subject and a robocall flag ([FTC, 2026](https://www.ftc.gov/policy-notices/open-government/data-sets/do-not-call-data)). We downloaded the 30 most recent files available on 25 September 2026, from `DNC_Complaint_Numbers_2026-08-14.csv` to `…2026-09-24.csv` ([FTC daily files, 2026](https://www.ftc.gov/sites/default/files/DNC_Complaint_Numbers_2026-09-24.csv)). Files are published on weekdays only, so those 30 files cover complaints created from 13 August to 23 September 2026. **FCC unwanted-call complaints.** The FCC's open dataset holds informal consumer complaints about unwanted calls since 31 October 2014 ([FCC, 2026](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e)). We queried it with server-side aggregate queries (counts by month, call type and service type) and did not download individual rows beyond one sample. Caller numbers were used only in memory, to measure how often numbers repeat. They were never written to our output files. Area codes below belong to the **consumers who complained**, not to the callers. ## What are people complaining about? Debt-relief calls lead the named subjects, and impersonation is second. The FTC's subject list is chosen by the consumer. More than half of the complaints fall into "Other", "No subject provided" or "Dropped call or no message", so read the named shares as a lower bound. | Subject (as chosen by the consumer) | Complaints | Share | Of which flagged robocall | |---|---:|---:|---:| | Other | 138,978 | 38.4% | 66% | | Reducing your debt (credit cards, mortgage, student loans) | 87,168 | 24.1% | 90% | | Calls pretending to be government, businesses, or family and friends | 41,429 | 11.4% | 67% | | Dropped call or no message | 27,853 | 7.7% | 56% | | No subject provided | 25,147 | 6.9% | 49% | | Medical & prescriptions | 22,285 | 6.2% | 74% | | Warranties & protection plans | 4,889 | 1.4% | 57% | | Home improvement & cleaning | 4,512 | 1.2% | 37% | | Vacation & timeshares | 2,165 | 0.6% | 54% | | Lotteries, prizes & sweepstakes | 2,054 | 0.6% | 30% | | Energy, solar & utilities | 1,793 | 0.5% | 36% | | Computer & technical support | 1,559 | 0.4% | 58% | | Other named subjects (4) | 2,284 | 0.6% | — | | **Total** | **362,116** | **100%** | **69.4%** | Debt relief stands out twice. It is the largest named subject, and 90% of those complaints are flagged as robocalls, the highest share of any large subject. Impersonation calls ("pretending to be government, businesses, or family and friends") are the second-largest group. That is the category that matters most for fraud teams, because these calls try to extract money or credentials rather than sell something. ## How many complaints are about robocalls? About seven in ten. 251,123 of the 362,116 complaints (69.4%) have the robocall or recorded-message flag set to "Y". Another 87,639 say "N" and 23,354 leave it blank. Among complaints that answered the question, the robocall share is 74.1%. | Robocall / recorded message flag | Complaints | Share | |---|---:|---:| | Yes | 251,123 | 69.4% | | No (live caller) | 87,639 | 24.2% | | Blank | 23,354 | 6.4% | The FCC data tells a similar story with its own categories. For calls dated in 2025, consumers labelled 35.2% as prerecorded voice, 28.2% as live voice and 14.8% as text messages, with 21.9% left blank ([FCC, 2026](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e)). For January to August 2026, the shares are 40.9% prerecorded, 27.1% live voice and 15.7% text messages. Text messages are worth noting. In the FCC data, roughly one unwanted-contact complaint in seven in 2025 and 2026 was about a text, not a call. Their share was higher in 2024 (24.8%), so it moves from year to year. ## Which days do unwanted calls happen on? Weekdays. 93.4% of the FTC complaints report a call date from Monday to Friday. Tuesday, Wednesday and Thursday are the busiest days, each close to 20%. Weekends are quiet, especially Sunday. | Day of the reported call | Complaints | Share | |---|---:|---:| | Monday | 59,917 | 16.5% | | Tuesday | 70,737 | 19.5% | | Wednesday | 72,040 | 19.9% | | Thursday | 71,265 | 19.7% | | Friday | 64,215 | 17.7% | | Saturday | 17,665 | 4.9% | | Sunday | 6,277 | 1.7% | This follows business hours. Telemarketing and robocall operations run like call centers, with shifts, dialler campaigns and weekday staffing. Monday is a little lower than mid-week. Part of that may be the Labor Day holiday on 7 September 2026, which falls inside our window. People also complain quickly. The median gap between the reported call time and the complaint's creation time is 4.9 hours. A quarter of complaints are filed within 1.1 hours. Ninety per cent arrive within about five and a half days. For anyone who uses complaint data as a signal, that means a new abusive number can show up in the data within hours of its first calls. ## Where are the consumers who complain? In the biggest states, as you would expect. Florida, California and Texas together account for 28.7% of the complaints. Florida leads even though California has the larger population. The area-code view shows the same Florida weight: five of the fifteen most frequent consumer area codes are Floridian (813, 407, 954, 561 and 904). | Consumer state | Complaints | Share | |---|---:|---:| | Florida | 37,345 | 10.3% | | California | 35,944 | 9.9% | | Texas | 30,800 | 8.5% | | Illinois | 16,321 | 4.5% | | Pennsylvania | 15,122 | 4.2% | | Georgia | 13,750 | 3.8% | | Michigan | 12,300 | 3.4% | | New York | 12,259 | 3.4% | | Ohio | 11,850 | 3.3% | | Virginia | 11,429 | 3.2% | | Consumer area code | Area | Complaints | Share | |---|---|---:|---:| | 813 | Tampa, FL | 4,259 | 1.18% | | 404 | Atlanta, GA | 3,540 | 0.98% | | 407 | Orlando, FL | 3,302 | 0.91% | | 402 | Eastern Nebraska | 3,144 | 0.87% | | 214 | Dallas, TX | 3,139 | 0.87% | Treat this as a map of who complains, not of who gets the most calls. Complaint rates depend on population, age, awareness of the FTC and how many people are on the Do Not Call Registry. We did not adjust for population. ## Can you stop robocalls by blocking reported numbers? Only partly, and the data shows why. Of the 344,714 FTC complaints that include a caller number, there are 297,872 distinct numbers. **92.5% of those numbers appear in exactly one complaint.** The 100 most-reported numbers account for just 2.9% of complaints. The FCC data looks the same. Of the 40,690 FCC complaints dated January to August 2026 that include a caller ID number, 96.6% of the numbers appear only once, and the top 100 cover 1.9%. | Repeat pattern | FTC sample | FCC Jan–Aug 2026 | |---|---:|---:| | Complaints with a caller number | 344,714 | 40,690 | | Distinct caller numbers | 297,872 | 38,624 | | Numbers reported only once | 92.5% | 96.6% | | Share of complaints from the top 100 numbers | 2.9% | 1.9% | Abusive callers rotate through large pools of numbers and often display numbers they don't own. That is exactly the problem caller ID authentication tries to address: the FCC required voice providers to implement STIR/SHAKEN in the IP parts of their networks by 30 June 2021 ([FCC, 2021](https://www.fcc.gov/call-authentication)). A blocklist of reported numbers catches repeat offenders. It cannot catch a number on its first day. Number reputation should therefore be one layer of a screening system, next to call authentication results, line type, call patterns and your own history with the caller. ## How has the volume changed over time? Complaints to the FCC fell from a peak of 230,181 in 2018 to 96,716 in 2023, then rose again to 124,659 in 2025 (counted by the call date the consumer entered). | Year | FCC unwanted-call complaints | |---|---:| | 2018 | 230,181 | | 2019 | 192,141 | | 2020 | 156,304 | | 2021 | 161,735 | | 2022 | 118,071 | | 2023 | 96,716 | | 2024 | 120,329 | | 2025 | 124,659 | Monthly counts for 2025–2026 range mostly between 10,000 and 13,300. July and August 2026 (13,109 and 13,217) are the highest months in the period. October 2025 is an outlier with only 1,113 complaints, and November 2025 has 6,845. We didn't investigate the cause and treat it as a gap in collection, not a drop in calls. The FTC's own yearly totals are much larger, because the FTC is the main place consumers report Do Not Call violations. In fiscal year 2025 it received over 2.6 million Do Not Call complaints, and the Registry held over 258 million active registrations ([FTC, 2025](https://www.ftc.gov/reports/national-do-not-call-registry-data-book-fiscal-year-2025)). Our 30-file sample averages about 12,000 complaints per daily file (362,116 ÷ 30). ## What are the limits of this analysis? Complaint data is useful, but it is not a measurement of all calls. Keep these limits in mind: - **Reports are unverified.** The FCC says it does not verify the facts alleged in complaints ([FCC, 2026](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e)). The FTC data is also consumer-reported. - **Caller numbers can be spoofed.** A reported number may belong to an innocent person whose number was displayed by someone else. - **Categories are self-chosen.** More than half of the FTC complaints use a generic subject. - **Short window.** The FTC sample covers six weeks. Seasonal patterns can't be read from it. - **Dates are entered by consumers.** The FCC data contains impossible dates (for example, years in the future), so we counted only calls dated 2015 to August 2026. In 55% of the 2026 FCC rows the caller ID field is empty. - **No population adjustment.** State and area-code tables reflect where complainants live. ## What does this mean for businesses? Three practical lessons come out of the numbers: 1. **Screen for patterns, not just numbers.** With more than nine in ten reported numbers seen only once, a static blocklist misses most new abuse. Combine number reputation with line type and call behaviour. Our [spam reputation](/services/spam-reputation) check (limited access; US, Canada and Germany) returns reasons, not just a score, and treats `no_reports` as "no negative signals", never as "safe". See [screening inbound calls with spam reputation](/blog/screening-inbound-calls-with-spam-reputation). 2. **Protect your own outbound numbers.** If you call customers, complaints about your numbers, even from spoofing, damage answer rates. Check the reputation of your own caller IDs regularly. 3. **Plan for weekday peaks.** Inbound screening, IVR capacity and fraud review should expect most abusive traffic from Tuesday to Thursday. ## How can you reproduce these numbers? Download the FTC daily files for the dates above, count rows by `Subject`, `Recorded_Message_Or_Robocall`, `Consumer_State`, `Consumer_Area_Code` and the weekday of `Violation_Date`, and count distinct values of `Company_Phone_Number` without storing them. For the FCC, run aggregate queries (`$select=…count(*)&$group=…`) on dataset `vakf-fz8e` filtered by `issue_date`. Data retrieved on 25 September 2026. We plan to refresh this analysis periodically and will state the new date range each time. ## What are the key takeaways? - 69.4% of FTC Do Not Call complaints in our sample are about robocalls. Debt relief (24.1%) and impersonation (11.4%) lead the named subjects. - 93.4% of complaints report a call on a weekday. Complaints arrive fast, with a median of 4.9 hours after the call. - 92.5% of reported caller numbers appear once. Number blocklists alone can't keep up, so reputation should be combined with other signals. - Texts are a steady share of FCC unwanted-contact complaints (14.8% in 2025). - Complaint data is unverified and spoofing-prone. Use it as evidence with reasons and dates, never as proof about a person. ## Sources 1. [Do Not Call (DNC) Reported Calls Data](https://www.ftc.gov/policy-notices/open-government/data-sets/do-not-call-data) — Federal Trade Commission, 2026 2. [DNC_Complaint_Numbers daily files (2026-08-14 to 2026-09-24)](https://www.ftc.gov/sites/default/files/DNC_Complaint_Numbers_2026-09-24.csv) — Federal Trade Commission, 2026 3. [National Do Not Call Registry Data Book for Fiscal Year 2025](https://www.ftc.gov/reports/national-do-not-call-registry-data-book-fiscal-year-2025) — Federal Trade Commission, 2025 4. [Consumer Complaints Data - Unwanted Calls](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e) — Federal Communications Commission, 2026 5. [Combating Spoofed Robocalls with Caller ID Authentication](https://www.fcc.gov/call-authentication) — Federal Communications Commission, 2021 ## Frequently asked questions ### What do most US nuisance-call complaints to the FTC say? In the 30 daily FTC files we analysed (complaints created 13 August to 23 September 2026), 69.4% of the 362,116 complaints were flagged as robocalls. The most common named subject was debt reduction (24.1%), followed by calls pretending to be government, businesses, family or friends (11.4%). ### On which days do unwanted calls happen? Mostly on weekdays. 93.4% of the complaints we analysed report a call date from Monday to Friday. Saturday accounts for 4.9% and Sunday for 1.7%. ### Can you block robocalls by blocking the reported numbers? Only partly. In the FTC sample, 92.5% of the reported caller numbers appeared in just one complaint, and the 100 most-reported numbers covered only 2.9% of complaints. Callers rotate and spoof numbers, so number reputation works best as one layer among several. ### Does this post publish the reported phone numbers? No. We publish aggregates only. Caller numbers were used in memory to count how often numbers repeat and were never written to our outputs. Area codes in this post are those of the consumers who complained, not of the callers. ### How often will this analysis be updated? We plan to refresh it periodically with newer data and will always state the date range. The method below lets anyone reproduce the numbers from the public files. --- # VoIP number detection for sign-ups: what works and what to watch out for > Why VoIP numbers matter at sign-up, fixed vs non-fixed VoIP, which signals detect them, and how to add friction without turning away real customers. Canonical: https://mobilevalidate.com/blog/voip-number-detection-for-signups · Last updated: 2026-09-25 ![Cover: VoIP number detection for sign-ups: what works and what to watch out for](https://mobilevalidate.com/og/blog/voip-number-detection-for-signups.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Fraud prevention · Tags: VoIP, Line type, Sign up fraud, Carrier lookup, Fraud prevention VoIP number detection tells you, at sign-up, whether a phone number is served by an internet-telephony provider rather than a mobile network. It matters because app-based VoIP numbers are cheap to create in bulk. But many genuine customers use VoIP too, so the right response is usually extra verification, not a block. ## Why does a VoIP number matter at sign-up? Phone verification works as an anti-abuse control because a phone number is supposed to be scarce. A mobile number normally comes with a SIM, a contract or prepaid top-up and, in many countries, an identity check. That cost is what stops one person from opening a thousand accounts. Some VoIP numbers remove that cost. An app-based number can often be created in minutes, used to receive one code and released again. That makes it attractive for: - **Promotion and referral abuse**: one person, many "new customer" bonuses. - **Fake account farms**: accounts created for spam, fake reviews or resale. - **Evading bans**: a blocked user returns with a fresh number. - **Account takeover**: an attacker attaches a number they can drop afterwards. The same property makes VoIP useful to honest people: a second number for a small business, a number that works while travelling, a home phone line delivered over broadband. That's why line type should inform your decision, not make it on its own. The [VoIP glossary entry](/glossary/voip-number) covers the basics. ## What is the difference between fixed and non-fixed VoIP? Regulators and the industry separate VoIP services by how they're tied to a place and to the public phone network. In US rules, an **interconnected VoIP service** is one that enables real-time two-way voice, requires a broadband connection and IP-compatible equipment at the user's location, and lets users receive calls from and make calls to the ordinary phone network ([47 CFR § 9.3, 2026](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-A/part-9/subpart-A/section-9.3)). Within that group, the industry commonly distinguishes two kinds: | | Fixed VoIP | Non-fixed VoIP | |---|---|---| | Tied to | One service address | An account; works anywhere online | | Typical examples | Home phone from a cable or fibre provider; office desk phones | App-based numbers; softphones; cloud phone systems | | How it's obtained | Usually with a broadband contract | Often online in minutes | | Behaves like | A landline | A mobile app | | Sign-up risk | Low, similar to a landline | Higher, because numbers can be created and released quickly | Most lookup data doesn't separate the two reliably. A result of `voip` tells you the number is served by an internet-telephony provider. The carrier name, together with your own context, often tells you which kind it is: a large cable operator suggests fixed VoIP, while a provider known for app-based numbers suggests non-fixed. ## Which signals can detect a VoIP number? There is no single perfect signal. These are the ones that exist, from weakest to strongest: | Signal | How it works | Strength | Main limitation | |---|---|---|---| | Numbering plan | Some countries reserve ranges for VoIP or nomadic services | Weak | Most VoIP numbers use ordinary geographic ranges | | Offline library type | Libraries like [libphonenumber](https://github.com/google/libphonenumber) return a type from range metadata | Weak | Describes the range, not today's service; US/CA ranges show `FIXED_LINE_OR_MOBILE` | | Carrier lookup `line_type` | Current data about the specific number | Strong | Coverage varies by country; `unknown` when no data | | Current carrier name | Which provider serves the number now | Strong in context | Brand names and resellers can hide the underlying provider | | Porting history | Number moved from mobile/landline to a VoIP provider | Medium | Needs current data; a port alone is normal | | Spam `voip_range` hint | Range belongs to a VoIP carrier | Context only | US, CA, DE; adds no risk points by design | | Messenger presence | Number has an account on a messaging app | Indirect | VoIP numbers can also register on messengers | The more reliable method is a lookup against current data for the individual number. MobileValidate's [carrier lookup](/services/carrier-lookup) returns `line_type` (with `voip` as one value) and the current `carrier` for numbers worldwide. For US and Canadian numbers, where porting between landline, wireless and VoIP services is common, the [US and Canada carrier lookup](/services/us-carrier-lookup) returns current-carrier data in bulk jobs. ## Why can't you rely on the number range? Because in many countries the range says little about today's service. In the US and Canada, mobile and fixed numbers share the same area codes. That's why offline libraries return `FIXED_LINE_OR_MOBILE` for most North American numbers. We checked this with libphonenumber-js 1.13.13: `+1 415 555 2671` parses as a valid number of type `FIXED_LINE_OR_MOBILE`, with no way to tell VoIP apart. On top of that, a number can be ported from a mobile carrier to a VoIP provider and back while keeping its digits. Our post on [why carrier lookups can be wrong after porting](/blog/mobile-number-portability-why-carrier-lookups-can-be-wrong) goes into this. Elsewhere, ranges help more but still aren't enough. A country may have a dedicated range for nomadic services while also allowing VoIP providers to issue ordinary geographic numbers. Use the range as a free first filter, for example to reject premium-rate numbers, and a lookup for the line type. ## How do you check line type in a sign-up flow? Add the carrier check to the request you already make before sending a code. A test-mode example: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["carrier", "whatsapp"], "wait": 5}' ``` Response (excerpt, test mode): ```json "checks": { "network.carrier": {"status": "completed", "registered": true, "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "confidence": "high", "cached": false, "billed": false, "reason": null}, "whatsapp.registered": {"status": "completed", "registered": true, "confidence": "high", "cached": false, "billed": false, "reason": null} } ``` For a VoIP number, `line_type` is `"voip"` and `carrier` names the provider. When we hold no data, the result is `unknown` with `reason: "NO_DATA"` and isn't charged. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Test keys return fixed test data; the [test mode page](/docs/test-mode) lists every test number. ## What should you do when a sign-up number is VoIP? Match the reaction to what's at stake in the flow, and combine VoIP with other signals before adding friction: | Situation | VoIP alone | VoIP plus another risk signal | |---|---|---| | Free sign-up, no rewards | Allow | Add a CAPTCHA or e-mail confirmation | | Sign-up with a bonus, trial or referral credit | Allow, hold the reward until the account has history | Require another verification method | | B2B account, company number | Allow; cloud telephony is normal for businesses | Check the company domain and e-mail | | New 2FA number on an existing account | Allow and notify the old contact details | Confirm through the old number or e-mail | | Marketplace seller or payout account | Ask for another verification method | Manual review | "Another risk signal" can be a new device, a disposable e-mail domain, a mismatch between the number's country and the IP country, many sign-ups from one provider in a short time, or a missing messenger account. NIST's guidance points the same way: it asks verifiers to consider risk indicators such as device swap, SIM change and number porting before relying on the phone network for codes ([NIST, 2025](https://csrc.nist.gov/pubs/sp/800/63/b/4/final)). A useful habit is to look at **clusters** rather than single numbers. Ten sign-ups from ten mobile carriers are normal. Ten sign-ups in an hour from the same small VoIP provider, with similar e-mail patterns, are not. ## How many false positives should you expect? More than most teams assume, which is why blanket blocks backfire. VoIP isn't a niche. In the FCC's public unwanted-call complaint data, the `method` field records how the *complaining consumer* gets their phone service. From January to August 2026, 14,377 of 90,759 complaints came from consumers on "Internet (VOIP)" service, about 16%, according to our own count of the dataset ([FCC, 2026](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e)). Those are ordinary people with VoIP phone lines, not callers. The number says nothing about VoIP sign-ups in your product, but it shows that real consumers use VoIP. Our analysis of [US nuisance-call complaints](/blog/us-nuisance-call-complaints-what-the-data-shows) describes the dataset and its limits. Common false positives: - **Cable and fibre home phones.** They are fixed VoIP and behave like landlines. They usually can't receive SMS, which is a delivery problem, not fraud. - **Business numbers.** Companies increasingly run on cloud phone systems. Blocking VoIP in a B2B sign-up blocks your customers. - **Travellers and expats** who keep a home-country number through an app. - **Ported numbers** that data sources haven't caught up with yet. Measure it. Log every VoIP decision and check how many stepped-up users go on to complete the extra verification. If most do, the rule is costing you conversions without catching much. ## How does VoIP relate to spam and robocalls? Robocall and spam operations often use VoIP numbers, because they can rotate through many numbers cheaply and spoof caller IDs. That's a calling problem more than a sign-up one, but the data overlaps. MobileValidate's [spam reputation](/services/spam-reputation) check (limited access; US, CA and DE numbers) returns `voip_range: true` when a number's range belongs to a VoIP carrier. It is deliberately a **hint only**: it adds no points to `risk_score`. A number is rated `high`, `medium` or `low` because of reports against it, such as regulator actions, government complaint data or community reports, never because of its line type. If a sign-up number has both `line_type: voip` and a `high` risk level with a `fraud_hacking` or `impersonation` category, that combination deserves a manual review. ## What are the key takeaways? - App-based (non-fixed) VoIP numbers are cheap to create and release, so they appear often in fake sign-ups and promotion abuse. - Fixed VoIP, such as broadband home phones and office systems, behaves like a landline and carries little sign-up risk. - The number range is a weak signal. A lookup of the specific number's `line_type` and current carrier is more reliable, and `unknown` answers are free. - Treat VoIP as a reason for proportionate friction, such as holding rewards, another verification step or a lower send limit, not as an automatic block. - Look for clusters and combinations, and measure how many stepped-up users turn out to be genuine. To put this into a full pre-send pipeline, read [OTP fraud prevention: checks to run before sending a code](/blog/otp-fraud-prevention-checks-before-sending-a-code). The [line type glossary entry](/glossary/line-type) lists every value the carrier lookup can return. ## Sources 1. [47 CFR § 9.3 — Definitions (interconnected VoIP service)](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-A/part-9/subpart-A/section-9.3) — eCFR / Federal Communications Commission, 2026 2. [Consumer Complaints Data — Unwanted Calls](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e) — Federal Communications Commission, 2026 3. [libphonenumber](https://github.com/google/libphonenumber) — Google, 2026 4. [NIST SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management](https://csrc.nist.gov/pubs/sp/800/63/b/4/final) — NIST, 2025 ## Frequently asked questions ### Can I tell a VoIP number from its digits alone? Rarely. A few countries have dedicated VoIP or nomadic ranges, but many VoIP numbers sit in ordinary geographic ranges, and in the US and Canada numbers can be ported between landline, mobile and VoIP services. A lookup against current data for the specific number is needed. ### Should I block all VoIP numbers at sign-up? Usually not. Many real people and businesses use VoIP numbers, including home phone services from cable operators and cloud phone systems. Add a verification step or lower limits for VoIP numbers where the risk justifies it. ### What is the difference between fixed and non-fixed VoIP? Fixed VoIP is tied to one location, such as a home phone service delivered over a broadband line. Non-fixed VoIP can be used from anywhere with an internet connection, typically through an app. Non-fixed numbers are the ones that are easy to create in bulk. ### Does the spam reputation check treat VoIP numbers as risky? No. It reports voip_range as a hint only and adds no points to the score. A VoIP range is not a risk by itself. --- # WhatsApp Business account check: what the business flag tells you (and what it doesn't) > What the WhatsApp Business flag means, how it differs from a personal account, its limits, and how B2B and support teams can use it responsibly. Canonical: https://mobilevalidate.com/blog/whatsapp-business-account-check-what-it-tells-you · Last updated: 2026-09-25 ![Cover: WhatsApp Business account check: what the business flag tells you (and what it doesn't)](https://mobilevalidate.com/og/blog/whatsapp-business-account-check-what-it-tells-you.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Guides · Tags: WhatsApp, WhatsApp business, Lead verification, B2b, Consent A WhatsApp Business account check tells you whether a phone number has a WhatsApp account and, if it does, whether that account was set up as a business account. It doesn't tell you who owns the number, whether the business is real, or anything about its profile. Used with that in mind, the flag is a useful signal for B2B lead qualification and support routing. This post explains what the flag means, where it falls short and how to use it responsibly. ## How is a WhatsApp Business account different from a personal one? Both are WhatsApp accounts tied to one phone number. The difference is how they were set up and what the owner can do with them. WhatsApp launched the WhatsApp Business app on 18 January 2018 as a free Android app for small businesses. The launch post listed business profiles (description, e-mail or store address, website), messaging tools such as quick replies and away messages, and an account type: people would know they were talking to a business because it would be listed as a Business Account ([WhatsApp, 2018](http://web.archive.org/web/20180118174727/https://blog.whatsapp.com/10000637/Introducing-the-WhatsApp-Business-App)). Today WhatsApp for Business has two main products: - **The WhatsApp Business app**, used by small businesses on a phone, with a business profile and messaging tools ([WhatsApp Business App](https://business.whatsapp.com/products/business-app)). - **The WhatsApp Business Platform**, which WhatsApp describes as enterprise-level WhatsApp APIs that businesses can integrate with back-end systems such as a CRM ([WhatsApp Business Platform](https://business.whatsapp.com/products/business-platform)). The check reports on the account registered to the number, so when it says an account exists, the business flag tells you which kind it is. ## What does the business flag mean? It means the account behind the number is configured as a business account. Nothing more. MobileValidate's [WhatsApp Business check](/services/whatsapp-business-check) (`whatsapp.business`) returns two answers in one result: | `registered` | `attributes.business` | Meaning | |---|---|---| | `true` | `true` | A WhatsApp Business account exists | | `true` | `false` | A regular (personal) WhatsApp account exists | | `true` | absent (`null` in the v1 `whatsapp` object) | An account exists; the business flag couldn't be determined this time | | `false` | ignore (may be `false`) | Conclusive: no WhatsApp account | | `null` | — | Unknown: no conclusive answer; `reason` explains why | The business check also answers the plain registration question. If you ask for both `whatsapp` and `whatsapp.business`, the API runs only `whatsapp.business` and you pay for one check per number. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). ## What are the limits of the business flag? The flag describes a configuration choice made by whoever set up the account. Read it with these limits in mind: - **It's not verification.** Anyone can install the Business app. A scammer can create a business account as easily as a bakery can. Official confirmation of a business identity is a separate matter. The 2018 launch post already distinguished plain business accounts from "Confirmed Accounts", where the account phone number had been confirmed to match the business phone number ([WhatsApp, 2018](http://web.archive.org/web/20180118174727/https://blog.whatsapp.com/10000637/Introducing-the-WhatsApp-Business-App)). Our check doesn't report any such confirmation. - **Many businesses use personal accounts.** Sole traders, freelancers and small firms often run their business from a personal WhatsApp account. `business: false` doesn't mean "not a business". - **Some business accounts belong to individuals.** People sometimes choose the Business app for its features, for example away messages, without running a company. - **It's a point-in-time answer.** Accounts are created, switched and deleted, and numbers get reassigned. Every answer carries `checked_at`. - **No profile data.** The check never returns the business name, description, catalog, photo or any other profile field. It can't tell you *which* business it is. So treat the flag as one piece of evidence that's consistent (or inconsistent) with what you already know, never as proof. ## Why can the flag be null in real time? Because the business flag is harder to determine quickly than registration itself. In real-time lookups, a conclusive `registered: true` can come back without the flag. That result is still a conclusive registration answer and is billed as one. Design for it: - If the flag only adds context, for example in a support-routing rule, treat a missing flag as "not known" and fall back to your default route. - If the flag is essential, for example when qualifying a list of B2B leads, run the numbers as a [bulk job](/docs/bulk-jobs), where the flag is filled more reliably. - Never convert a missing flag into `false`. `null` means unknown. ## What does a request look like? With a test key, `+447700900006` is the documented business account and the other test numbers behave as usual. This is a real test-mode request: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900006", "+447700900001", "+447700900002", "+447700900003"], "checks": ["whatsapp.business"], "wait": 5}' ``` The four `whatsapp.business` results, trimmed: ```json {"status": "completed", "registered": true, "attributes": {"business": true}, "confidence": "high", "billed": false, "reason": null} {"status": "completed", "registered": true, "attributes": {"business": false}, "confidence": "high", "billed": false, "reason": null} {"status": "completed", "registered": false, "attributes": {"business": false}, "confidence": "high", "billed": false, "reason": null} {"status": "unknown", "registered": null, "attributes": null, "confidence": null, "billed": false, "reason": "UPSTREAM_TIMEOUT"} ``` And the summary for the request: ```json "by_service": {"whatsapp.business": {"completed": 3, "registered": 2, "not_registered": 1, "unknown": 1, "pending": 0}} ``` Test keys return fixed answers and never bill. Every result also carries `checked_at`. See [test mode](/docs/test-mode) for all test numbers, and our [WhatsApp API guide](/blog/check-if-a-number-is-on-whatsapp-api-guide) for polling, pending answers and code in Node and Python. ## How can B2B teams use it for lead qualification? As one consistency check on inbound leads, next to the checks you already do. A lead that fills in your demo form gives you a company name, a work e-mail and a phone number. The business flag helps you see whether the number fits the story: | Lead claims | Check result | Reading | Suggested action | |---|---|---|---| | A company, gives a company mobile | `business: true` | Consistent | Normal routing | | A company, gives a company mobile | `business: false` | Common for small firms and staff phones | Normal routing; check the e-mail domain | | A company | `registered: false` | Neutral in markets where WhatsApp is rare | Normal routing; see [channel by country](/blog/choosing-a-messaging-channel-by-country) | | A company | `registered: false`, plus a disposable e-mail and a `voip` line | Several weak signals agree | Verify before a salesperson spends time | | An individual consumer | `business: true` | Possibly a business owner or a mis-filed lead | Ask in the first reply, don't assume | Combine the flag with the [carrier lookup](/services/carrier-lookup) and e-mail checks, as described in [lead verification](/use-cases/lead-verification). The value is in spotting leads that don't hang together, not in ranking leads by account type. ## How can support teams use it for routing? When a number contacts you, knowing whether it's run as a business account can help you route the conversation: - **Partner and supplier queues.** An inbound contact from a business account that matches a known partner number can go to the account team rather than consumer support. - **Impersonation reports.** Customers forward messages from numbers claiming to be your company. Recording whether the reported number is a business account helps your fraud team triage. Scammers often use business accounts to look official, so a `true` flag is not reassurance. Combine it with [spam reputation](/services/spam-reputation) where available. - **Channel choice for replies.** If a customer who opted in to WhatsApp updates writes from a business account, they may be contacting you for their company. That can change which team and which templates should answer. Keep these rules explainable. An agent should see "business account: yes, checked 2 hours ago", not an unexplained score. ## What are the responsible-use rules? The business flag is about a number that usually belongs to, or is used by, a person. Use it only where you have a clear reason. 1. **Check numbers you already hold** for a legitimate purpose: leads who contacted you, sellers applying to your platform, numbers reported to your fraud team. Under the [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj), you need a lawful basis (Article 6) and should keep only what you need (Article 5(1)(c)): the flag and its date, not the raw response. 2. **Don't use it to find people to message.** WhatsApp's [Business Messaging Policy](https://business.whatsapp.com/policy) says businesses may only contact people on WhatsApp if they gave you their mobile phone number and you received opt-in permission to message them. A business flag is not consent, and a list of "numbers with business accounts" is not a marketing list. 3. **Respect objections.** People can object through our [opt-out form](/opt-out). Suppressed numbers are skipped and never charged. Our [acceptable use policy](/legal/acceptable-use) forbids unsolicited messaging, profiling and scanning number ranges, and requests with 20 or more consecutive numbers are refused as `suspected_enumeration`. WhatsApp is named here descriptively. MobileValidate is not affiliated with WhatsApp or Meta. ## What are the key takeaways? - The WhatsApp Business check answers two questions: does the number have a WhatsApp account, and is it a business account. - The flag describes how the account is set up. It isn't verification, it doesn't identify the business, and it returns no profile data. - `business: false` doesn't mean "not a business", and `business: true` doesn't mean "trustworthy". - In real time the flag can be missing next to a conclusive `registered` answer. Treat it as unknown, and use bulk jobs when the flag is essential. - Use it as a consistency signal for B2B leads and as context for support and fraud routing, next to carrier and e-mail checks. - It's not consent. Message only people who opted in, and check only numbers you have a reason to process. Details and pricing are on the [WhatsApp Business check](/services/whatsapp-business-check) page. ## Sources 1. [Introducing the WhatsApp Business App (archived copy)](http://web.archive.org/web/20180118174727/https://blog.whatsapp.com/10000637/Introducing-the-WhatsApp-Business-App) — WhatsApp, 2018 2. [WhatsApp Business Messaging Policy](https://business.whatsapp.com/policy) — WhatsApp, 2026 3. [WhatsApp Business App](https://business.whatsapp.com/products/business-app) — WhatsApp, 2026 4. [WhatsApp Business Platform](https://business.whatsapp.com/products/business-platform) — WhatsApp, 2026 5. [General Data Protection Regulation (EU) 2016/679](https://eur-lex.europa.eu/eli/reg/2016/679/oj) — European Union, 2016 ## Frequently asked questions ### Does a WhatsApp Business account prove that a number belongs to a real company? No. Anyone can install the WhatsApp Business app and create a business account. The flag says how the account is set up, not who owns the number or whether the business is genuine. ### Why is the business flag null even though registered is true? The account check was conclusive, but the business flag could not be determined for that answer. This happens more often in real-time lookups. The result still counts as a conclusive registration answer. ### Does the check return the business name or profile? No. The result contains registered and the business flag only. Business names, descriptions, photos, catalogs and other profile details are never returned. ### Can I use the flag to build a list of businesses to message on WhatsApp? No. WhatsApp's business messaging policy requires that people gave you their number and opted in before you contact them, and our acceptable use policy forbids finding recipients for unsolicited messages. ### Should I run the business check in real time or in bulk? Both work. The business flag is filled more reliably in bulk jobs, so if the flag matters more than speed, for example when qualifying a lead list, run it as a job. --- # WhatsApp OTP vs SMS OTP: cost per verified user, with a calculator > WhatsApp authentication rates vs SMS prices by country, the October 2026 changes, and a calculator for cost per verified user including SMS fallback. Canonical: https://mobilevalidate.com/blog/whatsapp-otp-vs-sms-otp-cost · Last updated: 2026-09-25 ![Cover: WhatsApp OTP vs SMS OTP: cost per verified user, with a calculator](https://mobilevalidate.com/og/blog/whatsapp-otp-vs-sms-otp-cost.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Deliverability · Tags: WhatsApp, OTP, SMS, Pricing, Channel selection, SMS cost A WhatsApp one-time passcode is billed as an authentication template message: Meta charges per delivered message at a rate set by the recipient's country, from under a tenth of a cent to about five cents. SMS usually costs more per message in the same country. But users without WhatsApp still need SMS, so compare cost per verified user, not message prices. All prices below are list prices in USD, fetched on 25 September 2026. Your contract, volume tier and currency can change them. WhatsApp is named here descriptively. MobileValidate is not affiliated with WhatsApp or Meta. ## How does WhatsApp charge for OTP messages? Per delivered message. Since **1 July 2025**, Meta charges on a per-message basis on the WhatsApp Business Platform. It replaced the older conversation-based model. You are "only charged when a template message is delivered", and rates depend on the template's category and "the recipient WhatsApp phone number's country calling code" ([Meta, 2026](https://developers.facebook.com/docs/whatsapp/pricing)). Passcodes go out as **authentication templates**, one of three template categories next to marketing and utility. Three details matter for OTP budgets: - **Volume tiers.** Since 1 July 2025, utility and authentication messages get lower rates at higher monthly volumes, per market. - **Delivery-based billing.** A message that isn't delivered isn't charged. That includes numbers with no WhatsApp account. - **Rate changes are scheduled.** Meta updates rate cards up to four times a year (1 January, 1 April, 1 July and 1 October) with at least one month's notice. When we fetched the pricing page on 25 September 2026, the current rate cards were marked "effective July 1, 2026". ## What does a WhatsApp authentication message cost by country? Here are the authentication rates from Meta's USD rate card effective 1 July 2026, next to a public SMS list price for the same country ([Meta rate card, 2026](https://developers.facebook.com/docs/whatsapp/pricing#rate-cards); [Plivo SMS pricing, 2026](https://www.plivo.com/sms/pricing/gb/)): | Market | WhatsApp authentication (USD) | Authentication-international (USD) | Public SMS list price, outbound (USD) | |---|---:|---:|---:| | India | 0.0014 | 0.0304 | 0.0800 (international route) | | Brazil | 0.0068 | n/a | from 0.0484 | | United Kingdom | 0.0220 | n/a | from 0.0372 | | Germany | 0.0550 | n/a | from 0.0950 | | Indonesia | 0.0250 | 0.1360 | from 0.3333 | | North America (US, Canada) | 0.0034 | n/a | 0.0077 (US long code, plus carrier surcharges) | Read the SMS column with care. It is one messaging platform's public pay-as-you-go price per SMS, fetched on 25 September 2026. "From" means the cheapest network: prices differ by the recipient's mobile network, and in Brazil, Indonesia and Germany the most expensive network listed costs roughly 30% to 110% more than the cheapest one. For India, the listed route is the international one, used by senders outside India; the domestic route isn't offered on that page. For the US, carrier surcharges come on top. Long messages are billed per segment. Negotiated SMS rates can be much lower, and other platforms price differently. Use your own invoice. Even with those caveats, the pattern is clear. In most markets on the list, the WhatsApp authentication rate is a fraction of the public SMS price. The gap is widest where SMS is expensive and WhatsApp is widespread. ## What changes on 1 October 2026? Meta published the rate card effective 1 October 2026 on its pricing page. For authentication, the main changes are ([Meta, 2026](https://developers.facebook.com/docs/whatsapp/pricing)): - **Higher utility and authentication rates** in Kazakhstan, Kuwait, Morocco, Oman and Ukraine. - **Lower rates** in Bangladesh, Iraq, Nepal and Sri Lanka. Sri Lanka's authentication rate becomes $0.0020. - **Nine markets become standalone** on the rate card instead of sitting in a "Rest of" region: Bangladesh, Iraq, Kazakhstan, Kuwait, Morocco, Nepal, Oman, Sri Lanka and Ukraine. - **Nine new authentication-international rates** in those same nine markets, for example $0.1440 for Sri Lanka and $0.1600 for Kazakhstan. Rates in India, Brazil, the UK, Germany, Indonesia and North America are unchanged on the October card. ## When does the authentication-international rate apply? Only to large senders based outside the recipient's country. Meta's rules ([Meta, 2026](https://developers.facebook.com/docs/whatsapp/pricing/authentication-international-rates)): - A business becomes eligible if it sends more than **750,000** messages outside customer service windows in a moving 30-day period, across all its WhatsApp Business Accounts, to users in markets that have an authentication-international rate. - Once eligible, it gets **30 days' notice** before the higher rate applies, and eligibility is permanent. - The rate applies only when the business's primary business location is **in another country** than the recipient's. An Indian business sending codes to Indian users pays the normal rate. - Nine markets have the rate today (Indonesia since 1 June 2024, India since 1 July 2024, and Egypt, Malaysia, Nigeria, Pakistan, Saudi Arabia, South Africa and the UAE since 1 February 2025). Nine more are added on 1 October 2026. If you are a global app with heavy traffic to India or Indonesia, model both rates. The international rate for India ($0.0304) is more than 20 times the domestic one. ## How do you calculate cost per verified user? Message price is only half of it. Not every user has WhatsApp, and some WhatsApp codes need an SMS fallback. Use this formula per country: **Cost per verified user = c + s × (W + p × S) + (1 − s) × S** | Symbol | Meaning | Where to get it | |---|---|---| | `s` | Share of your users with a WhatsApp account | Your own data, or a pre-check on each number | | `W` | WhatsApp authentication rate for the country | Meta rate card (your volume tier) | | `S` | Your SMS price per OTP (all segments) | Your SMS invoice | | `p` | Share of WhatsApp sends that need an SMS fallback | Your logs: codes not delivered or not entered in time | | `c` | Pre-check cost per number, if you run one | Our [pricing page](/pricing); unknowns and cached repeats are free | Worked examples, **excluding `c`**, with illustrative assumptions (`s` = 0.9 in India, Brazil and Indonesia, 0.7 in the UK; `p` = 0.05 everywhere) and the list prices from the table above: | Scenario | W | S | Cost per verified user | Per 1,000 users | SMS-only per 1,000 | Difference | |---|---:|---:|---:|---:|---:|---:| | India, domestic sender | 0.0014 | 0.0800 | $0.0129 | $12.86 | $80.00 | −84% | | India, auth-international | 0.0304 | 0.0800 | $0.0390 | $38.96 | $80.00 | −51% | | Brazil | 0.0068 | 0.0484 | $0.0131 | $13.14 | $48.40 | −73% | | United Kingdom | 0.0220 | 0.0372 | $0.0279 | $27.86 | $37.20 | −25% | | Indonesia, domestic sender | 0.0250 | 0.3333 | $0.0708 | $70.83 | $333.30 | −79% | | Indonesia, auth-international | 0.1360 | 0.3333 | $0.1707 | $170.73 | $333.30 | −49% | The assumptions for `s` and `p` are placeholders, not measurements. Replace them with your own numbers. Two effects dominate. A lower `s` pushes the result toward the SMS-only price. A cheap negotiated SMS rate shrinks the gap, sometimes to nothing in markets like North America. ## Where does a WhatsApp pre-check fit? Before the send, to pick the channel. Without a pre-check, the usual pattern is "try WhatsApp, fall back to SMS on failure". Money-wise that works, because an undelivered WhatsApp message isn't charged. The cost is time: users without WhatsApp wait for a failed status before their SMS leaves, and some give up. A pre-check answers the question first. With MobileValidate, the `whatsapp` check on `POST /v1/lookup` returns `registered: true`, `false` or `null` (unknown) with a `checked_at` time. Here is a test-mode request using documented [test numbers](/docs/test-mode): ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900003"], "checks": ["whatsapp"], "wait": 5}' ``` In test mode, `…001` answers `registered: true`, `…002` `registered: false` and `…003` `unknown` (`registered: null`). Your routing rule is short: | `whatsapp.registered` | Route | |---|---| | `true` | Offer WhatsApp as the default, SMS as the alternative | | `false` | Send SMS (or voice) straight away | | `null` (unknown) | Use your default flow; never block on missing data | The pre-check earns its cost in three situations. First, when you show the channel choice in the UI before sending ("We'll send your code on WhatsApp"). Second, when you use a timer-based fallback, where a wrong guess means paying for both messages. Third, when the check doubles as a fraud signal, next to line type, before any paid message leaves. See [OTP fraud prevention checks](/blog/otp-fraud-prevention-checks-before-sending-a-code) and [SMS pumping](/blog/sms-pumping-how-it-works-and-how-to-stop-it). Keep `c` small. Check each number once, not at every login: repeat checks inside the freshness window come from your account's cache and aren't billed. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Our [WhatsApp API guide](/blog/check-if-a-number-is-on-whatsapp-api-guide) covers timeouts and caching in detail. ## When is SMS still the better choice? In a few clear cases: - **Low WhatsApp penetration** among your users. If `s` is low, most users need SMS anyway, and a second channel adds work for little saving. - **Cheap negotiated SMS.** In markets such as the US, where public SMS prices are low per segment, the difference per code can be a fraction of a cent. - **Auth-international exposure.** A global sender over the threshold pays up to $0.16 per code in some markets from 1 October 2026, which can exceed local SMS contracts. - **Users who prefer SMS.** Let people choose. A code on an app they don't open converts worse than an SMS they read. Measure `p` and conversion per channel after rollout, and keep SMS as a protected fallback. Our [channel selection](/use-cases/channel-selection) and [SMS cost reduction](/use-cases/sms-cost-reduction) pages show the full flow. ## What are the key takeaways? - WhatsApp OTP is billed per delivered authentication message, by recipient country, under the per-message model in place since 1 July 2025. - On the rate card effective 1 July 2026, authentication rates run from $0.0008 (Colombia) to $0.0550 (Germany); India is $0.0014 and the UK $0.0220. - From 1 October 2026, nine more markets get authentication-international rates and several rates change. India, Brazil, the UK, Indonesia and North America stay the same. - Compare **cost per verified user**: c + s × (W + p × S) + (1 − s) × S, with your own share of WhatsApp users and fallback rate. - A WhatsApp pre-check picks the channel before sending. It saves waiting time and double sends, and unknown results are free and never a reason to block. ## Sources 1. [Pricing on the WhatsApp Business Platform (fetched 25 September 2026)](https://developers.facebook.com/docs/whatsapp/pricing) — Meta for Developers, 2026 2. [USD rate card: cost per message on the WhatsApp Business Platform, effective July 1, 2026 and October 1, 2026 (CSV/XLSX linked from the pricing page)](https://developers.facebook.com/docs/whatsapp/pricing#rate-cards) — Meta, 2026 3. [Authentication-international rates (fetched 25 September 2026)](https://developers.facebook.com/docs/whatsapp/pricing/authentication-international-rates) — Meta for Developers, 2026 4. [SMS pricing by country (United Kingdom, India, Brazil, Indonesia, Germany, United States pages)](https://www.plivo.com/sms/pricing/gb/) — Plivo, 2026 ## Frequently asked questions ### How much does a WhatsApp OTP cost? Meta charges per delivered authentication template message, at a rate set by the recipient's country calling code. On the USD rate card effective 1 July 2026, rates range from $0.0008 (Colombia) to $0.0550 (Germany), for example $0.0014 for India and $0.0220 for the United Kingdom. Volume tiers can lower these rates. ### What is the WhatsApp authentication-international rate? A higher authentication rate that applies in some markets when an eligible business based in another country sends codes there. Eligibility starts above 750,000 qualifying messages in a moving 30-day period, with 30 days' notice. Nine markets have it today and nine more are added on 1 October 2026. ### Is WhatsApp OTP always cheaper than SMS? No. In many markets the WhatsApp authentication rate is well below public SMS prices, but the gap varies by country, by your SMS contract and by whether authentication-international rates apply to you. Users without WhatsApp still need SMS, so compare cost per verified user, not price per message. ### Do I pay for a WhatsApp OTP sent to a number without WhatsApp? Meta's pricing page says you are only charged when a template message is delivered. A message to a number with no WhatsApp account isn't delivered, so it isn't charged, but the user waits for the failure before your SMS fallback starts. ### What does a WhatsApp pre-check add? It tells you before the send whether the number has a WhatsApp account, so you can offer the right channel at once instead of waiting for a failed delivery. Results are registered, not registered or unknown; unknown results are free and shouldn't block anyone. --- # Why SMS messages aren't delivered: a cause tree with fixes > A cause tree for undelivered SMS: invalid numbers, unreachable phones, wrong line types, carrier filtering and handsets, with a fix for each. Canonical: https://mobilevalidate.com/blog/why-sms-is-not-delivered · Last updated: 2026-09-25 ![Cover: Why SMS messages aren't delivered: a cause tree with fixes](https://mobilevalidate.com/og/blog/why-sms-is-not-delivered.png) By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-25 · Category: Deliverability · Tags: SMS, Deliverability, Delivery receipts, OTP, Carrier filtering SMS fails for six broad reasons: the number is invalid, it isn't assigned any more, the phone is unreachable, the line can't take texts, a carrier filtered the message, or the handset hid it. Your API's "success" usually means only that the message was accepted. Work down the cause tree, and check the number before you send. ## Why does "success" not mean "delivered"? An SMS passes through several hands: your application, your messaging provider, one or more intermediaries, the recipient's operator and finally the handset. The response to your API call comes from the first hop. It confirms that the provider accepted the request, not that a phone received anything. Amazon's documentation for SNS shows the difference clearly. A successful delivery log carries a provider response such as "Message has been accepted by phone carrier", and the docs note that "it can take up to 72 hours for delivery logs to appear" for some carriers ([AWS](https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html)). The same page lists failure reasons that no API response could have told you at send time, including "Blocked as spam by phone carrier", "Phone is currently unreachable/unavailable" and "Invalid phone number". So the first fix for "the API said success but nothing arrived" is visibility: turn on delivery receipts or delivery status logs. The second is to stop sending messages that were never going to arrive. ## What is the cause tree? Work through the causes in order. Each level assumes the one above it is fine. | # | Cause | Typical sign | Check before sending | Fix | |---|---|---|---|---| | 1 | Invalid or badly formatted number | Rejected at once, or "invalid number" | E.164 normalization and numbering-plan validation | Ask the user to correct it | | 2 | Number not assigned, or disconnected | Unknown-subscriber errors | Live network status; list recency | Remove it; re-confirm the contact | | 3 | Phone unreachable (off, no coverage, full memory) | "Absent subscriber", expired | Live network status | Retry later, or use another channel | | 4 | Line can't take SMS (landline, some VoIP) | Failures to one number type | Line type | Offer a voice call or another channel | | 5 | Carrier filtering | Delivered to carrier, never received; spam errors | Sender registration status; content review | Register the sender, fix content, get consent | | 6 | Handset side | Delivery receipt says delivered | None from your side | Ask the user to check blocked senders and spam folders | Levels 1 to 4 are about the **number**. Level 5 is about **you as a sender**. Level 6 is about the **device**. Most wasted spend sits in levels 1 to 4, and that is where a pre-send check helps. ## How do invalid and unassigned numbers fail? A number can be wrong in two ways. It can be impossible (too short, wrong prefix, a typo), or it can be possible but not assigned to anyone right now. Impossible numbers are free to catch. Normalize to [E.164](/glossary/e164) with the right default country and validate against the numbering plan. Our [E.164 guide for developers](/blog/e164-phone-number-format-guide-for-developers) covers the common traps, such as national formats without a country and leading zeros. Unassigned numbers are harder. The digits are valid, but the operator has no subscriber on them. They come from old lists, typos that happen to form a valid number, and fake sign-ups. At network level, delivery to such a number ends with an error such as MAP's `unknownSubscriber`, defined in [3GPP TS 29.002](https://www.3gpp.org/DynaReport/29002.htm) ([3GPP](https://www.3gpp.org/DynaReport/29002.htm)). You only learn it after paying, unless you check first. See [how to check if a phone number is active](/blog/how-to-check-if-a-phone-number-is-active) for what each check can confirm. ## What does "absent subscriber" mean? It means the number exists but the phone couldn't be reached when delivery was attempted. The phone may be switched off, out of coverage, or unable to accept messages at that moment. In MAP the error for text messages is `absentSubscriberSM`. SMS works as store and forward: the message centre can keep the message and retry until its validity period runs out, as described in [3GPP TS 23.040](https://www.3gpp.org/DynaReport/23040.htm) ([3GPP](https://www.3gpp.org/DynaReport/23040.htm)). If the phone never comes back in time, the message expires. What to do depends on the message: - **One-time passcodes** go stale in minutes. Don't wait for retries. Offer another channel straight away. - **Transactional notices** can wait. Let the network retry. - **A number that is absent for weeks** is probably abandoned. Stop sending and re-confirm the contact. For the full list of network errors and what a delivery receipt can and can't tell you, see [SMS delivery receipts vs HLR lookup](/blog/sms-delivery-receipt-vs-hlr-lookup). ## Why does line type matter? Text messages need a line that can receive them. Most fixed lines can't, some VoIP numbers don't, and premium-rate or shared-cost ranges shouldn't receive your codes at all. A number's [line type](/glossary/line-type) comes from carrier data, not from the digits alone, because numbers move between services when they are ported. A test-mode request that normalizes a national-format number and checks its line type: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["07700 900001", "07700 900002", "12345"], "checks": ["carrier"], "default_country": "GB", "wait": 5}' ``` Response (excerpt of `results`, test mode): ```json [ {"input": "07700 900001", "e164": "+447700900001", "number_status": "valid", "checks": {"network.carrier": {"status": "completed", "attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"}, "billed": false}}}, {"input": "07700 900002", "e164": "+447700900002", "number_status": "valid", "checks": {"network.carrier": {"status": "unknown", "registered": null, "reason": "NO_DATA", "billed": false}}}, {"input": "12345", "e164": null, "number_status": "invalid_number"} ] ``` The first number is a mobile line: send. The second has no carrier data: send under your normal rules, since `unknown` means "no evidence", and it isn't charged. The third is impossible: ask for a correction and send nothing. For line types like `fixed_line` or `toll_free`, offer a voice call or another channel instead of a text. ## Why do carriers filter messages? Operators protect their subscribers from unwanted messages. The US wireless industry's guidelines say it plainly: "Service Providers deploy filters and other tools that limit messaging traffic bearing the characteristics of Unwanted Messages" ([CTIA, 2023](https://api.ctia.org/wp-content/uploads/2023/05/230523-CTIA-Messaging-Principles-and-Best-Practices-FINAL.pdf)). Filtering is a common reason for "accepted by the carrier, never received". Things that raise the chance of filtering: - **Unregistered senders.** In the US, application-to-person texts from ordinary long numbers go through [10DLC](/glossary/10dlc) registration. Other countries have their own sender-ID registration rules. - **Missing consent.** The same guidelines expect business senders to get consumers' consent before messaging them and to honour opt-out requests. - **Content patterns** such as URL shorteners, misleading text or wording common in spam. - **Volume spikes** from a sender with little history. - **Sending to stale numbers.** High failure rates make a sender look careless. A number check can't fix sender registration or content. It does help with the last point: fewer failed sends make your traffic look like what it should be, which is messages to people who expect them. ## What happens on the handset? Sometimes the network did its job and the message still wasn't seen. Common reasons: - The user blocked the sender or filtered unknown senders. - The phone moved the message to a spam or junk folder. - The user is on a messaging app setting that hides texts from unknown numbers. - The storage was full when the message arrived. Networks often retry, but not forever. You can't check any of this from your side. Tell users where to look in your "didn't get the code?" screen, and offer a second channel they have chosen. ## How do you troubleshoot a delivery problem? A short routine that works for most support tickets and dashboards: 1. **Find the record.** Look up the message ID and its final status in your provider's delivery log, not the send response. 2. **Check the number.** Is it valid E.164? What is its line type and country? Is it on your allow-list? 3. **Read the error.** Invalid number, unknown subscriber, absent subscriber, blocked or filtered? Each points to a different level of the tree. 4. **Look for a pattern.** One number, one carrier, one country or one message template? Patterns point to filtering or routing, not to the user. 5. **Compare with verification.** For OTP, the ratio of codes entered to codes sent per country is the most honest delivery metric you have. 6. **Fix upstream.** Clean the list, add the pre-send check, register the sender or change the channel. ## Where does a pre-send check fit? Between "we have a number" and "we pay to send". It removes levels 1 and 4 of the tree today, and levels 2 and 3 once a live check is available: | Check | Removes | Status | |---|---|---| | Normalization and validation | Impossible numbers | Free, on every request | | Carrier lookup (`carrier`) | Wrong line types; tells you the current carrier and porting hints | Available | | Messenger presence (`whatsapp` and others) | Offers a second channel the user already has | Available | | Live network status (`hlr`) | Unassigned and currently unreachable numbers | Coming soon | Invalid and duplicate numbers are never checked or charged, and neither are other inconclusive results (unknown, unsupported country, timeout). Current rates are on the [pricing page](/pricing). ## What are the key takeaways? - An API "success" means the message was accepted, not delivered. Turn on delivery receipts or status logs. - Work down the tree: invalid, unassigned, unreachable, wrong line type, filtered, handset. - The first four causes are about the number, and a pre-send check addresses them before you pay. - Carrier filtering is about you as a sender: registration, consent and content. - For OTP, measure codes entered against codes sent per country, and offer another channel when a text can't arrive. The [SMS cost reduction use case](/use-cases/sms-cost-reduction) shows the same checks as a budget exercise. If you are weighing WhatsApp against SMS for codes, read [WhatsApp OTP vs SMS OTP: cost and reach](/blog/whatsapp-otp-vs-sms-otp-cost). ## Sources 1. [Amazon SNS SMS delivery monitoring with Amazon CloudWatch metrics and logs](https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html) — Amazon Web Services 2. [Messaging Principles and Best Practices](https://api.ctia.org/wp-content/uploads/2023/05/230523-CTIA-Messaging-Principles-and-Best-Practices-FINAL.pdf) — CTIA, 2023 3. [3GPP TS 23.040: Technical realization of the Short Message Service (SMS)](https://www.3gpp.org/DynaReport/23040.htm) — 3GPP 4. [3GPP TS 29.002: Mobile Application Part (MAP) specification](https://www.3gpp.org/DynaReport/29002.htm) — 3GPP ## Frequently asked questions ### My SMS API says success, but the message never arrived. Why? A success response usually means your provider accepted the message, not that the phone received it. The message can still fail at the carrier, be filtered, or wait for a phone that is switched off. Enable delivery receipts or delivery status logs to see what happened next. ### What does absent subscriber mean? It is a network error meaning the phone couldn't be reached when delivery was attempted, for example because it was switched off or out of coverage. The network may retry for a while. Repeated absent-subscriber errors over weeks suggest an abandoned number. ### Can a message show as delivered but not be received? Yes, occasionally. Some routes report delivery before the handset confirms, and a phone can hide or block messages after delivery. Treat a delivery receipt as strong evidence, not proof, and use verification rates for OTP. ### Which check should I run before sending? Normalize the number and reject impossible ones (free), then check the line type so you don't text landlines or premium-rate numbers. A live network status check helps skip unreachable numbers; ours is coming soon. --- # Account security and takeover risk > Check the phone number or e-mail address a user adds or changes on an account, and step up verification when the signals suggest takeover risk. Canonical: https://mobilevalidate.com/use-cases/account-security · Last updated: 2026-09-25 ![An account's two-factor phone number is changed to a VoIP number; the change is flagged and an extra verification step is required.](https://mobilevalidate.com/images/account-takeover-contact-change-risk.svg) *When a 2FA number changes to a riskier line, ask for an extra verification step.* Account takeovers often start with a change of contact details. The attacker adds their own phone number for two-factor codes or swaps the recovery e-mail. Checking the new number and address when they change gives your security logic facts to weigh: the line type, whether the mailbox exists and, where enabled, spam reputation. Risky changes can get an extra verification step before they take effect. ## Where does contact-detail risk show up? Attackers who get into an account usually try to keep it. That means changing where codes and reset links go. Warning signs include: - **A new 2FA number that is a VoIP line.** Legitimate users have VoIP numbers too, but attackers often use them because they're cheap and easy to get. The [carrier lookup](/services/carrier-lookup) reports `line_type`. See [VoIP number](/glossary/voip-number). - **A recovery e-mail with no mailbox behind it.** An address at a major webmail provider that doesn't exist can't receive recovery mail. That points to a typo or a throwaway. The [e-mail mailbox check](/services/email-verification) answers this. - **A number with fraud or spam reports.** Where [spam reputation](/services/spam-reputation) is enabled (limited access; US, CA and DE numbers), reasons such as `reason_unassigned` or `reason_regulator` are worth weighing. - **A change of country.** The carrier lookup's `country` can differ from the account's usual country. Each signal is weak on its own. A combination, together with your own session data such as a new device or an unusual location, is what should trigger a step-up. ## How does the workflow look? 1. The user submits a new phone number or e-mail address. 2. Call `POST /v1/lookup` with the new identifiers and `checks: ["carrier", "spam", "email"]`. Leave out `spam` if it isn't enabled for your account. 3. Combine the results with your session risk and apply the table below. 4. For a step-up, confirm through the old number or e-mail, or hold the change for a cooling-off period. 5. Log the decision with `checked_at`. Keep the raw response only as long as you need it. The request is small (one number, one address), so it fits in the synchronous part of a settings change. ## What should you do with each result? | Signals | Suggested action | |---|---| | Mobile line, mailbox exists, no reports | Allow the change | | `line_type: voip` and the session is otherwise normal | Allow, and notify the old contact details | | `line_type: voip` and a new device or location | Step up: confirm through the old number or e-mail | | Mailbox `registered: false` | Ask the user to check the address; don't make it the recovery e-mail | | Spam `risk_level: high` or `reason_unassigned: true` | Step up, and hold the change for review | | Checks `unknown` | Decide on your other signals; unknown is not negative and is not charged | ## How much does it cost? Each check on each identifier is billed only when the answer is conclusive. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Contact-detail changes are rare events, so this use case usually costs very little: a few real-time checks per change. Spam reputation bills every level, including `no_reports`, and is free outside the US, Canada and Germany. See [pricing](/pricing) for current rates. ## Example request Test mode, with a number that has no carrier data and no reports, and an address with an existing mailbox: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900002"], "emails": ["registered@test.mobilevalidate.com"], "checks": ["carrier", "spam", "email"]}' ``` Response (excerpt, test mode: the `checks` of the phone row, then of the e-mail row): ```json [ { "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 }, "number.spam": { "service": "number.spam", "status": "completed", "registered": true, "attributes": { "risk_level": "no_reports", "risk_score": 0, "reason_regulator": false, "reason_government": false, "reason_community": false, "reason_unassigned": false, "voip_range": false, "sources": 0 }, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:26.024Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, { "email.valid": { "service": "email.valid", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:26.024Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } } ] ``` The carrier answer is `unknown` (`NO_DATA`) and free. The rest of the decision rests on the spam level, the mailbox result and your own session signals. ## What limits and rules apply? The usual lookup limits apply: up to 100 identifiers and 20 checks per request, rate limits per key and a daily cap per account. Requests that look like sequential number ranges or generated e-mail lists are rejected. E-mail checks return only yes, no or unknown, never names or profiles. Check only the contact details your users give you for their own accounts, and say in your privacy notice that you do so for security. Results support a step-up decision. They are not identity proof, and on their own they shouldn't lock people out of their accounts. People can object through the [opt-out form](/opt-out). ## Frequently asked questions ### When should I run these checks? At moments when attackers change contact details: a new phone number for two-factor authentication, a changed recovery e-mail, a password reset to a new destination or a first login from a new device. ### Should I lock the account when a signal looks risky? Usually not. Ask for an extra verification step instead, such as confirming on the old number or e-mail, a delay before the change takes effect, or a support review. Locking hurts genuine users who just changed phones. ### Does the mailbox check read or send e-mail? No. It only answers whether the mailbox exists, for major webmail providers. Nothing is sent to the address and no mailbox content is accessed. ### Can I use these checks as identity verification? No. They are risk signals about a number or an address, not proof of who someone is, and must not be used as KYC or for credit, employment, housing or insurance decisions. ### Is spam reputation part of this? It can be, where it is enabled. Spam reputation is in limited access (internal customers only) and covers US, Canadian and German numbers. --- # Call-center and spam screening > Screen inbound callers and outbound dialling lists by line type and carrier and, where enabled, by spam-report reputation for US, CA and DE numbers. Canonical: https://mobilevalidate.com/use-cases/call-center-screening · Last updated: 2026-09-25 ![Incoming calls queued for an agent, each with a risk gauge; one high-risk call is held back.](https://mobilevalidate.com/images/call-center-spam-screening.svg) *Score callers before an agent picks up, and hold back high-risk calls.* Call-center screening checks phone numbers before or during a call, so agents and diallers spend their time on real contacts. The [carrier lookup](/services/carrier-lookup) reports the line type, the carrier and the country. Where it's enabled, [spam reputation](/services/spam-reputation) reports whether a US, Canadian or German number appears in spam and nuisance-call reports, with the reasons. Spam reputation is in limited access for now (internal customers only). ## What problems does screening solve? Contact centers lose time and money in both directions: - **Inbound.** Robocalls and nuisance calls tie up agents and IVR minutes. Scam callers pretend to be customers. A reputation signal lets you send high-risk calls to an IVR or a verification step instead of a live agent. - **Outbound.** Dialling lists contain fixed lines where you expected mobiles, numbers that were recently offered as unassigned, and numbers with fraud reports. Checking them first protects agent time and your caller reputation. - **Lead intake.** Web leads with a spoofed or recently unassigned number are a common sign of fake leads. See [lead verification](/use-cases/lead-verification). Spam reputation works from report classes described in general terms: telecom regulator actions, government nuisance-call complaint data, community reports and a signal for recently unassigned numbers. There's also a VoIP hint, which is not a risk by itself. Every level comes with the reasons behind it. Report texts and reporter details are never returned. ## How does the workflow look? **Inbound calls:** 1. Your telephony platform receives the caller ID. 2. Call `POST /v1/lookup` with `checks: ["spam", "carrier"]` and a short `wait`. 3. Route the call using the table below and log the level, not the raw response. **Outbound lists:** 1. Run a bulk job with `checks: ["carrier"]`. For US and Canadian lists, add `network.carrier_us`, and add `spam` if it's enabled for your account. 2. Download the CSV. Each check has its own columns, such as `number.spam.risk_level` and `network.carrier.line_type`. 3. Remove or reorder rows before they reach the dialler. ## What should you do with each result? | Result | Suggested action | |---|---| | `risk_level: high` (score ≥ 80 and a regulator action or two or more signal classes) | Inbound: IVR or verification step. Outbound: remove from the list | | `risk_level: medium` | Inbound: ask for extra verification. Outbound: review | | `risk_level: low` | Handle normally and watch for patterns | | `risk_level: no_reports` | No negative signal. Not proof of safety | | `reason_unassigned: true` | Treat as a possible spoofed caller ID or fake lead | | `line_type: fixed_line` on a mobile campaign | Move to the voice-only queue | | `unknown` / `unsupported_country` | Handle normally; not charged | ## How much does it cost? You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). For spam reputation, every level, including `no_reports`, is a conclusive answer and is billed. Numbers outside the US, Canada and Germany return `unsupported_country` for free. The carrier lookup is billed when it returns a carrier. When no carrier data exists for a number, the answer is `unknown` and free. See [pricing](/pricing) for current rates. Spam answers come from our own reference data, which is refreshed daily. That keeps real-time answers fast, and repeat checks within 24 hours are served from your account's cache for free. That matters for repeat callers. ## Example request ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "checks": ["spam", "carrier"], "wait": 3}' ``` In test mode the whole `+44 7700 900xxx` test range is allowed for spam reputation, so the documented numbers work. Response (excerpt, test mode: `checks` of the first item): ```json { "number.spam": { "service": "number.spam", "status": "completed", "registered": true, "attributes": { "risk_level": "high", "risk_score": 95, "reason_regulator": true, "reason_government": false, "reason_community": true, "reason_unassigned": false, "voip_range": false, "top_category": "robocall", "first_seen": "2025-11", "last_seen": "2026-08", "sources": 2 }, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:24.778Z", "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-09-25T14:28:24.778Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } } ``` For attributes services, `registered: true` means "data found". The answer is in `attributes`. ## What limits and rules apply? A lookup takes up to 100 numbers and a job up to 50,000. Requests that look like sequential number ranges or generated e-mail lists are rejected, and each account has a daily cap on numbers. Spam scores can change as new reports arrive and old ones age out, so read `checked_at`. A reputation level is a signal about a number, not a judgment about a person. Numbers get spoofed and reassigned. Always give legitimate callers a way through, for example a verification step. Don't use the results for credit, employment, housing or insurance decisions. People who find their number in our data can object or ask for access through the [opt-out form](/opt-out) and the [data-subject notice](/legal/data-subject-notice). ## Frequently asked questions ### Is the spam reputation check available to every customer? Not yet. Spam reputation is in limited access, for internal customers only, until its review is finished. The carrier lookup is available to all customers. ### Which countries does spam reputation cover? The United States, Canada and Germany, where report data is dense. Numbers from other countries return unsupported_country and are not charged. ### Does no_reports mean a caller is safe? No. It means we hold no reports for the number. New, rarely used or spoofed numbers can still be abusive. Treat it as no negative signal and combine it with other checks. ### Is a no_reports answer charged? Yes. Every risk level, including no_reports, is a conclusive answer and is billed. Unknown and unsupported-country results are free. ### Can the carrier lookup tell me who is calling? No. It returns line type, carrier and country for the number. It never returns names or any other details about the person behind the number. --- # Channel selection for consented messaging > See which messaging apps an opted-in customer's number uses, such as WhatsApp, Telegram, Viber, iMessage or RCS, and send on a channel that arrives. Canonical: https://mobilevalidate.com/use-cases/channel-selection · Last updated: 2026-09-25 ![One phone number with three channel options; the messaging app where the number is registered is chosen.](https://mobilevalidate.com/images/messaging-channel-selection.svg) *Send on the channel where the number is registered.* Channel selection means sending each message a customer expects on a channel that will reach them. One request tells you whether an opted-in customer's number has a WhatsApp, Telegram or Viber account. A bulk job also covers iMessage and RCS. You can route order updates, reminders and passcodes accordingly and fall back to SMS when no account exists. This use case is only for customers who have agreed to hear from you. ## Why does channel choice matter? A message sent on a channel the customer doesn't use is wasted. It either fails or waits unread. Messaging habits vary a lot by country. [WhatsApp](/services/whatsapp-number-check) is the default in many markets. [Viber](/services/viber-number-check) is common in parts of Eastern Europe and Southeast Asia. [LINE](/services/line-number-check) dominates in Japan and Taiwan, and [Zalo](/services/zalo-number-check) in Vietnam. iPhone users can receive [iMessage](/services/imessage-number-check). Many Android phones, and iPhones since Apple added RCS support, can receive [RCS](/services/rcs-capability-check) messages. Knowing which channels exist for a number helps you: - send passcodes on a channel that will arrive, with SMS as a fallback - use a channel that supports what the message needs, such as rich media, buttons or read receipts - avoid paying for SMS when the customer asked to be contacted on a cheaper app they use ## How does the workflow look? Channel data changes slowly, so you don't need to check before every message. A typical setup: 1. When a customer opts in and chooses which channels they accept, run a real-time lookup for the real-time channels (`whatsapp`, `telegram`, `viber`). 2. Run a bulk job over your opted-in customer base with `checks: ["whatsapp", "telegram", "viber", "imessage", "rcs"]` to include the bulk-only channels. 3. Store the result and its `checked_at` against each customer in your CRM. 4. When sending, pick from the channels the customer consented to and that show `registered: true`, and fall back to SMS. 5. Refresh on a schedule, for example monthly, or when a delivery fails. ## What should you do with each result? | Result for a consented channel | Suggested routing | |---|---| | `registered: true` | Eligible. Send on this channel if the customer prefers it | | `registered: false` | Don't use this channel for this number | | `registered: null` (unknown) | Keep the previous routing or fall back to SMS; check again later | | RCS `registered: true` with `device_os` | Eligible for RCS. Use `device_os` to preview the message on the right handset platform | | No channel `true` | Use SMS or voice | ## How much does it cost? Each channel is a separate check, billed only when conclusive. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Checking five channels on one number can bill up to five checks, so check only the channels you can actually send on. Bulk jobs cost less per check than real-time lookups and are the natural fit for refreshing a customer base. See [pricing](/pricing) for current rates. Run `POST /v1/jobs/estimate` first to see the maximum cost, and pass it as `max_cost` when you create the job. Numbers checked recently for the same service come free from your account's cache. ## Example request A bulk job with five channels, two of them bulk only: ```bash curl https://api.mobilevalidate.com/v1/jobs \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: channels-2026-09" \ -d '{"numbers": ["+447700900001", "+447700900002"], "checks": ["whatsapp", "telegram", "viber", "imessage", "rcs"]}' ``` The job returns straight away with its `id` and `progress` (`total: 2`, `checks_total: 10`). Fetch the rows with `GET /v1/jobs/{id}/results`. Response (excerpt, test mode: the first row of the results page): ```json { "kind": "phone", "input": "+44770*****01", "e164": "+447700900001", "country": "GB", "number_status": "valid", "checks": { "whatsapp.registered": {"service": "whatsapp.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:27.270Z", "cached": false, "age_seconds": 1, "billed": false, "reason": null, "poll_after_ms": null}, "telegram.registered": {"service": "telegram.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:27.270Z", "cached": false, "age_seconds": 1, "billed": false, "reason": null, "poll_after_ms": null}, "viber.registered": {"service": "viber.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:27.270Z", "cached": false, "age_seconds": 1, "billed": false, "reason": null, "poll_after_ms": null}, "imessage.registered": {"service": "imessage.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:27.270Z", "cached": false, "age_seconds": 1, "billed": false, "reason": null, "poll_after_ms": null}, "rcs.registered": {"service": "rcs.registered", "status": "completed", "registered": true, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:27.270Z", "cached": false, "age_seconds": 1, "billed": false, "reason": null, "poll_after_ms": null} } } ``` Stored results show the input masked (`+44770*****01`). The normalized `e164` is returned so you can match rows to your records. ## What limits and rules apply? A job takes up to 50,000 numbers and e-mails and up to 20 checks. The total of rows × checks can't exceed 100,000. Requests that look like sequential number ranges or generated e-mail lists are rejected. Each account also has a daily cap on numbers. Job data is kept for 30 days by default, and you can delete a finished job's data at any time with `DELETE /v1/jobs/{id}`. This use case is only for messages to people who agreed to receive them. Don't check number lists to find out who uses an app, and don't start conversations on a channel because an account exists. Messaging platforms require opt-in for business messages, and our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging. ## Frequently asked questions ### Does a messenger account mean I may message that person there? No. An account shows the channel can reach the number. Your right to send depends on the person's consent and the platform's business messaging rules, which usually require opt-in. ### Why are iMessage and RCS bulk only? Those checks are only offered in bulk jobs in our API. Run them in a job ahead of time, for example when a customer signs up or once a month, and store the channel preference in your CRM. ### How often should I refresh channel data? People switch phones and apps, and numbers get reassigned. Refreshing active customers every few weeks, or before a major send, keeps routing current. Each result carries checked_at so you can see its age. ### What does the RCS check add? It reports whether the number can receive RCS messages and, when known, the handset platform in device_os (ios, android or unknown). ### Which channel should I pick when several exist? Use the channel the customer chose. When they allowed several, pick by cost, the features the message needs and the customer's past engagement. The check tells you what is possible, not what they prefer. --- # Lead and form verification > Check the phone number and e-mail address on a lead form in one request, so fake, mistyped and low-quality leads are flagged before sales calls them. Canonical: https://mobilevalidate.com/use-cases/lead-verification · Last updated: 2026-09-25 ![A lead list with a verdict per row and a summary bar of registered, not registered and unknown numbers.](https://mobilevalidate.com/images/lead-list-verification.svg) *One verdict per lead, plus a summary of the whole list.* Lead verification checks the contact details on a form before your team spends time on them. One request checks the phone number, including its line type and whether it has a messenger account, and the e-mail address, including whether the mailbox exists. You can then score leads, send the doubtful ones to review and ask for corrections while the person is still on the page. ## Why verify leads at the form? Bad leads cost sales time, dialler minutes and ad budget, and they distort your conversion data. Common problems: - **Typos.** A digit missing from the phone number or a misspelled e-mail domain. Catching these on the form lets the person fix them straight away. - **Made-up details.** Numbers such as `555 0100`-style fillers, or e-mail addresses at real providers that don't exist. - **Numbers that can't be reached the way you plan.** A fixed line when your process relies on SMS, or a VoIP number where you expect a mobile. - **Bots and paid form fillers.** They often reuse patterns. A burst of generated numbers or addresses is exactly what our anti-enumeration rules reject. A useful combination for most forms is the [carrier lookup](/services/carrier-lookup) plus [WhatsApp](/services/whatsapp-number-check) for the number, and the [e-mail mailbox check](/services/email-verification) for the address. For US and Canadian leads handled in batches, the [US/CA carrier lookup](/services/us-carrier-lookup) adds current-carrier data. ## How does the workflow look? 1. When the form is submitted, call `POST /v1/lookup` with `numbers`, `emails` and `checks: ["carrier", "whatsapp", "email"]`. 2. Result rows come numbers first, then e-mails. Each row has a `kind` (`phone` or `email`) and a `checks` map. 3. Score the lead with the table below. Store the score and the `checked_at` time in your CRM, not the whole response. 4. If the score is low, ask for a correction on the form ("please check your number") or send the lead to manual review. 5. For leads that arrive in batches, for example from events or partners you have a lawful basis for, use a bulk job with the same checks. Download the CSV and import it back into your CRM. ## What should you do with each result? | Signals | Suggested action | |---|---| | Mobile line and a mailbox that exists | Accept the lead | | Mobile line, messenger account, mailbox check `unknown` (e.g. a company domain) | Accept. Unknown is not negative | | `number_status: invalid_number` or `email_status: invalid_email` | Ask for a correction on the form | | Mailbox `registered: false` | Ask for a correction or review. The address will probably bounce | | `line_type` is `voip`, no messenger account, mailbox missing | Review before sales spends time on it | | Many signals `unknown` | Accept with normal handling. Missing data is not evidence of fraud | ## How much does it cost? Each check on each identifier is billed separately, and only when the answer is conclusive. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The request below runs three checks and can bill up to three. Company e-mail domains that the mailbox check doesn't cover return `unknown` and cost nothing. See [pricing](/pricing) for current rates. Real-time checks at the form cost more per check than bulk jobs, but they let the person fix a mistake straight away. For batches, the bulk price applies. If the same lead submits twice inside the freshness window, the second check comes free from your account's cache. ## Example request ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001"], "emails": ["not-registered@test.mobilevalidate.com"], "checks": ["carrier", "whatsapp", "email"]}' ``` Response (excerpt, test mode: the `checks` of the phone row, then of the e-mail row): ```json [ { "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-09-25T14:28:23.538Z", "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-09-25T14:28:23.538Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } }, { "email.valid": { "service": "email.valid", "status": "completed", "registered": false, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:23.538Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } } ] ``` The phone looks fine and the mailbox doesn't exist, so the form should ask the person to check their e-mail address. ## What limits and rules apply? A lookup takes up to 100 numbers and e-mails in total and up to 20 checks. You need at least one check for each kind of identifier you send. Addresses are normalized by trimming spaces and lowercasing, and nothing else is changed. Requests that look like sequential number ranges or generated e-mail lists are rejected. That includes 20 or more addresses on one domain that differ only by digits. See [e-mail checks](/docs/emails). Say in your privacy notice that you verify contact details to prevent fraud and keep your records accurate. Verify only leads you have a lawful basis to process. Don't use the checks to enrich people you have no relationship with, and remember that a passed check is not consent to marketing. People can object through the [opt-out form](/opt-out). ## Frequently asked questions ### Can I check a phone number and an e-mail address in the same request? Yes. Send numbers and emails together (up to 100 in total) with at least one phone check and one e-mail check. Phone checks run on the numbers and e-mail checks run on the addresses. ### Does a missing messenger account mean the lead is fake? No. Many real people don't use a given messenger. A missing account is one weak signal. It carries more weight when combined with other checks, such as a non-mobile line type or a mailbox that doesn't exist. ### Which e-mail addresses can the mailbox check answer for? The e-mail mailbox check covers major webmail providers. For other domains it returns unknown with the reason UNSUPPORTED_PROVIDER, and that result is not charged. ### Do you return the lead's name or social profile? No. Checks answer only yes, no or unknown, plus data such as line type or carrier. Names, photos and profiles are never returned. ### Can I verify leads I bought from a third party? Only if you have a lawful basis to process them and the people agreed to be contacted by you. The checks can't make a non-consented list usable for marketing. --- # OTP and sign-up fraud prevention > Check a phone number's line type and messenger presence before sending a one-time passcode, so fake and high-risk sign-ups are reviewed first. Canonical: https://mobilevalidate.com/use-cases/otp-and-signup-fraud · Last updated: 2026-09-25 ![A sign-up form with a one-time code; one number passes the checks while a VoIP number and a risky number are stopped.](https://mobilevalidate.com/images/otp-and-signup-fraud-screening.svg) *Check the number before you send the code, and hold back VoIP and high-risk numbers.* Checking a number before you send a one-time passcode (OTP) tells you whether the number looks like a real, reachable mobile line or a likely source of abuse. A single request can return the line type, the carrier and whether the number has a messenger account. Your sign-up flow can then send, ask for more verification or refuse, before you pay for a message. ## Why check a number before sending a code? Every OTP you send costs money, and fraudsters know it. In SMS pumping, also called artificially inflated traffic, attackers make your sign-up form send large numbers of codes to number ranges they profit from. Fake sign-ups use throwaway numbers to farm promotions or open accounts in bulk. See [SMS pumping](/glossary/sms-pumping). A pre-send check gives your risk engine facts to work with: - **Line type** from the [carrier lookup](/services/carrier-lookup): `mobile`, `fixed_line`, `voip`, `premium_rate`, `toll_free` and other types. A sign-up with a premium-rate number, or an SMS code sent to a fixed line, is worth a second look. - **Messenger presence**, for example a [WhatsApp](/services/whatsapp-number-check) or [Telegram](/services/telegram-number-check) account. A number with an account passed that platform's own sign-up verification at some point, which makes it more likely to be in real use. - **Spam reputation** (optional, limited access, US/CA/DE only): reports and regulator actions against the number. None of these proves identity. Together they help you decide where extra verification is worth its cost. ## How does the workflow look? The check runs between "user entered a number" and "we send a code": 1. Normalize the number to [E.164](/glossary/e164). The API does this for you and uses `default_country` for numbers typed in national format. 2. Call `POST /v1/lookup` with `checks: ["carrier", "whatsapp"]` and a short `wait`, for example 5 seconds. 3. Apply the decision table below to the results. 4. Log the decision and the `checked_at` time, not the raw response. Keep what you store to a minimum. 5. If a check is `pending` or `unknown`, carry on with your default path. Don't block a user because a check timed out. Your per-IP and per-number rate limits on the code-sending endpoint still apply. The check adds to those defences and doesn't replace them. ## What should you do with each result? | Signal | Suggested action | |---|---| | `line_type` is `mobile` and a messenger account exists | Send the code as usual | | `line_type` is `mobile` and no messenger account | Send the code. Consider a lower send limit for this number | | `line_type` is `voip` | Review: ask for a second factor or use a different verification method | | `line_type` is `premium_rate` or `shared_cost` | Block: a consumer is very unlikely to receive a sign-up code on these numbers | | `line_type` is `toll_free` | Review: some toll-free numbers, for example in the US and Canada, can receive texts, but consumers rarely sign up with one | | `line_type` is `fixed_line` | Offer a voice call instead of SMS | | Spam `risk_level` is `high` (if enabled) | Review or block, depending on your risk appetite | | Any check `unknown` or `pending` | Fall back to your normal flow; don't block on missing data | These are starting points. Tune them with your own fraud data and review the results regularly. ## How much does it cost? You pay per check, and only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). A request with two checks on one number can bill two checks. Real-time prices apply because sign-up needs an answer straight away. See [pricing](/pricing) for current rates. Two things help keep the cost down. Repeat checks of the same number inside the freshness window come from your account's cache for free, which helps when a user taps "resend code". And `max_cost` puts a hard ceiling on what any single request can cost. Compare the cost of a check with the cost of an SMS to the destinations you serve. The check pays for itself when it stops messages that would have been wasted or abused. ## Example request This test-mode request checks two numbers. `+447700900001` answers with data and `+447700900003` simulates a timeout, so you can see both paths. ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900003"], "checks": ["carrier", "whatsapp"], "wait": 5}' ``` Response (excerpt, test mode: the `checks` of both items in `results`): ```json [ { "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-09-25T14:28:22.282Z", "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-09-25T14:28:22.282Z", "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": "UPSTREAM_TIMEOUT", "poll_after_ms": null }, "whatsapp.registered": { "service": "whatsapp.registered", "status": "unknown", "registered": null, "attributes": null, "confidence": null, "confidence_score": null, "checked_at": null, "cached": false, "age_seconds": null, "billed": false, "reason": "UPSTREAM_TIMEOUT", "poll_after_ms": null } } ] ``` ## What limits and rules apply? A lookup takes up to 100 numbers and up to 20 checks. The total of numbers × checks can't exceed 2,000. Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more consecutive numbers in one request return `suspected_enumeration`. Each account also has a daily cap on numbers, which `GET /v1/limits` reports. Use the checks only on numbers people give you to sign up or verify. Tell users in your privacy notice that you verify phone numbers to prevent fraud. The results are risk signals, not identity proof, and must not be used for credit, employment, housing or insurance decisions. People whose numbers were checked can object through the [opt-out form](/opt-out). ## Frequently asked questions ### Should I block every VoIP number at sign-up? Usually not. Many genuine users have VoIP numbers. Treat line_type voip as a reason for extra review or a different verification step, not as a block on its own. ### How fast is the check in a sign-up flow? Real-time checks run on POST /v1/lookup with a wait of up to 30 seconds (default 10). If an answer is not back in time the check returns pending, and your flow can continue with its default path. ### What happens when a check returns unknown? Unknown means no conclusive answer, for example a timeout. It has registered set to null, it is not charged, and your flow should fall back to its normal behaviour instead of blocking the user. ### Does this replace rate limiting on my OTP endpoint? No. Number checks add a signal before you send, but you still need per-IP, per-number and per-country rate limits on the endpoint that sends codes. ### Can I use the spam reputation check here? Spam reputation is in limited access for now (internal customers only) and covers US, Canadian and German numbers. Where it is enabled for your account you can add it as an extra signal. --- # SMS cost reduction and deliverability > Remove invalid, duplicate and unreachable numbers, and route messages people expect over the channels they use, before paying for SMS. Canonical: https://mobilevalidate.com/use-cases/sms-cost-reduction · Last updated: 2026-09-25 ![Messages pass through a filter so only reachable numbers are sent to; the cost chart falls after checking.](https://mobilevalidate.com/images/sms-cost-reduction.svg) *Remove unreachable numbers before sending, so fewer messages are wasted.* SMS spend drops when you stop paying for messages that can't arrive and move expected messages to cheaper channels the recipient already uses. A check before sending finds invalid and duplicate numbers for free. It also finds lines that can't take SMS, and it tells you which opted-in contacts can be reached on a messenger instead. ## Where does SMS money get wasted? Most wasted spend falls into four groups: - **Badly formatted or impossible numbers.** Typos, missing country codes and numbers that can't exist. The API parses each one and marks it `invalid_number`, at no charge. - **Duplicates.** The same person entered twice in different formats, for example `07700 900001` and `+447700900001`. After [E.164](/glossary/e164) normalization they are the same number, and the repeat is marked `duplicate`, also free. - **Lines that don't take SMS.** Fixed lines and some special number types. The [carrier lookup](/services/carrier-lookup) reports `line_type`, and for US and Canadian numbers there is a [bulk carrier lookup](/services/us-carrier-lookup). - **Messages that could go elsewhere.** Some opted-in contacts prefer a messenger such as [WhatsApp](/services/whatsapp-number-check), [Viber](/services/viber-number-check) or [RCS](/services/rcs-capability-check), where the per-message price may be lower than your SMS rate. The first two are free to find. The last two cost a check each, so they're worth it where your SMS rates are high. ## How does the workflow look? For list cleaning, use bulk jobs: 1. Call `POST /v1/jobs/estimate` with the list and the checks you plan to run. It's free and shows how many rows are valid, invalid, duplicate, already cached, unsupported or suppressed, plus the maximum cost. 2. Create the job with `POST /v1/jobs` and pass the estimate's amount as `max_cost`. 3. Wait with `GET /v1/jobs/{id}?wait=30`, or subscribe to the `job.completed` webhook. 4. Download the results as CSV or NDJSON (`GET /v1/jobs/{id}/download?format=csv`). The file keeps your original row order and adds one column per check. 5. Remove invalid rows, set fixed lines to voice or e-mail, and route contacts to the channels they opted into. For messages sent one at a time, such as order updates, a real-time lookup before each send does the same job. ## What should you do with each result? | Result | Suggested action | |---|---| | `number_status: invalid_number` | Remove the number or ask the customer to correct it | | `number_status: duplicate` | Keep one row per person | | `line_type: fixed_line` | Don't send SMS; use voice or e-mail | | `line_type: mobile` | Send SMS as usual | | Messenger `registered: true` and the contact opted in to it | Consider sending on that channel | | Messenger `registered: false` | Keep SMS for this contact | | Any check `unknown` | Keep your current routing. Unknown results are free, so you lose nothing but the check | ## How much does it cost? Bulk checks cost less per check than real-time lookups, and invalid, duplicate and suppressed rows are never charged. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). See [pricing](/pricing) for current rates. To work out whether it pays off, multiply the checks you plan to run by the bulk price. Then compare that with the SMS spend you would avoid: messages to invalid and duplicate numbers, messages to fixed lines, and messages you can move to a cheaper channel. Checks of the same number inside the freshness window are served free from your account's cache, so running an updated list again doesn't bill the same numbers twice. ## Example request First, a free estimate. The list below holds two valid test numbers, one duplicate and one invalid entry: ```bash curl https://api.mobilevalidate.com/v1/jobs/estimate \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002", "+447700900002", "12345"], "checks": ["whatsapp", "viber"]}' ``` Response (test mode): ```json { "object": "estimate", "total": 4, "valid": 2, "invalid": 1, "duplicate": 1, "cached": 0, "unsupported": 0, "suppressed": 0, "checks": ["whatsapp.registered", "viber.registered"], "checks_total": 8, "billable_max": 0, "max_cost": {"amount": "0", "currency": "USD"} } ``` Test keys are never charged, so `billable_max` and `max_cost` are zero here. With a live key they show the most the job could cost. Only the two valid numbers would be checked. The same checks for one-off sends run in real time: ```bash curl https://api.mobilevalidate.com/v1/lookup \ -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"numbers": ["+447700900001", "+447700900002"], "checks": ["whatsapp", "viber"]}' ``` Response (excerpt, test mode: the `checks` of the second item, a number with no accounts): ```json { "whatsapp.registered": { "service": "whatsapp.registered", "status": "completed", "registered": false, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:31.050Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null }, "viber.registered": { "service": "viber.registered", "status": "completed", "registered": false, "attributes": null, "confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:31.050Z", "cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null } } ``` ## What limits and rules apply? A job takes up to 50,000 numbers and e-mails and up to 20 checks. The total of rows × checks can't exceed 100,000 per job. Requests that look like sequential number ranges or generated e-mail lists are rejected. Each account also has a daily cap on numbers, which `GET /v1/limits` reports. Clean only lists you collected lawfully, such as your own customers and contacts who opted in. Channel checks help you reach people who want your messages more cheaply. They are not a way to find new people to message, and our [acceptable use policy](/legal/acceptable-use) forbids unsolicited bulk messaging. ## Frequently asked questions ### How much can I save? It depends on your list quality and your SMS rates. Run a free estimate on a sample to see how many numbers are invalid or duplicate, then compare the price of a bulk check with your SMS price for the destinations you send to. ### Is the estimate really free? Yes. POST /v1/jobs/estimate counts valid, invalid, duplicate, cached, unsupported and suppressed rows and returns the maximum cost without checking anything or charging you. ### Can I move marketing messages to WhatsApp because a number has an account? Only for people who opted in to hear from you on that channel. Having an account is not consent, and messaging platforms require opt-in for business messages. ### Should I use real-time or bulk checks for list cleaning? Bulk jobs. They take up to 50,000 numbers and e-mails per job, run every active service including bulk-only ones, and cost less per check than real-time lookups. ### Do duplicate or invalid numbers cost anything? No. They are flagged per row with number_status invalid_number or duplicate and are never checked or charged. --- # 10DLC > 10DLC is US business text messaging sent from ordinary 10-digit phone numbers, registered by brand and campaign. How registration works and why it affects delivery. Canonical: https://mobilevalidate.com/glossary/10dlc · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* 10DLC (10-digit long code) is the US system for sending business text messages from ordinary 10-digit phone numbers, the same format people use for personal lines. It sits between person-to-person texting and high-volume short codes. To use it, a business registers its brand and each messaging use case (campaign) before sending, so carriers can see who is behind the traffic. ## Why does 10DLC exist? For years, 10-digit numbers were meant for people texting each other. Businesses that wanted volume leased short codes, and some sent bulk traffic from long codes anyway, often unidentified. Carriers answered with filtering, which blocked legitimate messages too. The industry's answer was a sanctioned lane for business traffic on 10-digit numbers. CTIA's [Messaging Principles and Best Practices](https://api.ctia.org/wp-content/uploads/2023/05/230523-CTIA-Messaging-Principles-and-Best-Practices-FINAL.pdf) (May 2023) describes this "Non-Consumer" traffic on 10-digit NANP numbers and the consent, opt-out and registration practices expected of it. ## How does 10DLC registration work? Registration runs through [The Campaign Registry](https://www.campaignregistry.com/about/) (TCR), a central hub used by the major US carriers. The parties it describes: - **Brand**: the company the recipient believes is sending the message. - **Campaign Service Provider (CSP)**: the messaging provider that registers brands and campaigns on your behalf. - **Campaign**: one use case, such as account notifications, 2FA codes or opted-in marketing, with sample messages and a description of how people opt in. - **Mobile network operators**: they review campaigns and set limits on throughput and daily volume. Your provider submits the brand and campaign details, links your sending numbers to the campaign, and passes on any carrier fees. Vetting results affect how many messages per second or per day you can send. ## What happens if you don't register? Carriers may filter, throttle or block unregistered business traffic from 10-digit numbers, and some providers refuse to send it at all. Messages that do get through may carry extra carrier fees. Registration doesn't remove filtering. Content that looks like spam, or campaigns that don't match their registration, can still be blocked. ## How does 10DLC compare with other US sender types? | Sender type | Typical use | Throughput | Setup | |---|---|---|---| | 10DLC | Most business messaging, two-way conversations | Low to medium, depends on vetting | Brand and campaign registration | | Toll-free number | Customer care, notifications | Medium | Toll-free verification | | Short code | High-volume programmes | High | Longer approval, higher cost | ## Why check the recipient numbers too? Registration covers the sender. Deliverability also depends on who you send to. Messages to landlines, disconnected numbers or numbers that have been reassigned to someone new count against your sending reputation, and complaint rates can get a campaign suspended. The [US and Canada carrier lookup](/services/us-carrier-lookup) returns the current carrier and [line type](/glossary/line-type) for North American numbers. That lets you remove landlines, flag [VoIP numbers](/glossary/voip-number) and estimate per-carrier fees before a send. The global [carrier lookup](/services/carrier-lookup) does the same for other countries. Inconclusive answers are free. MobileValidate supports messaging to people who asked for it, and must not be used to build lists for unsolicited texting. See [A2P SMS](/glossary/a2p-sms) for the wider picture. ## Frequently asked questions ### What does 10DLC stand for? 10-digit long code: a standard 10-digit North American phone number (area code plus seven digits) used to send business text messages, as opposed to a 5- or 6-digit short code. ### Do I have to register to send 10DLC messages? In practice, yes. US mobile carriers expect business messaging from 10-digit numbers to be registered by brand and campaign, and unregistered traffic may be filtered, throttled or blocked. Your messaging provider handles the registration with you. ### Does 10DLC apply outside the US? The registration system covers US carriers. Canada uses the same numbering plan, but rules and filtering there differ, so check with your provider. --- # A2P SMS > A2P SMS is text messaging sent by software on behalf of a business, such as one-time passcodes, alerts and reminders. How it differs from P2P and how it is regulated. Canonical: https://mobilevalidate.com/glossary/a2p-sms · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* A2P SMS (application-to-person SMS) is text messaging sent by a software application on behalf of a business or organisation to a person's phone. One-time passcodes, delivery updates, appointment reminders, fraud alerts and opted-in marketing are all A2P traffic. The opposite is P2P (person-to-person) messaging, which people type to each other. ## How does A2P messaging work? A business rarely connects to mobile networks directly. Its application calls an SMS API, and the messaging provider passes the message through one or more aggregators to the network that serves the recipient. Each hop takes a fee. The terminating network charges for delivering the message to its subscriber. Senders identify themselves with one of three kinds of sender IDs, depending on the country: - **Long codes**: ordinary phone numbers. In the US, business texting from 10-digit numbers is called [10DLC](/glossary/10dlc) and needs registration. - **Short codes**: 5- or 6-digit numbers leased for high-volume programmes. - **Alphanumeric sender IDs**: a brand name instead of a number, allowed in many countries but generally not in the US or Canada. When the message reaches the handset, or fails, the network can return a [delivery receipt](/glossary/delivery-receipt-dlr). ## How is A2P messaging regulated? Rules come from law, regulators and the operators themselves. In the US, the wireless industry association CTIA publishes [Messaging Principles and Best Practices](https://api.ctia.org/wp-content/uploads/2023/05/230523-CTIA-Messaging-Principles-and-Best-Practices-FINAL.pdf) (May 2023). It calls business traffic "Non-Consumer" messaging and expects senders to get clear consent, honour opt-outs and register how they use their numbers. Many other countries require sender ID registration or ban unregistered alphanumeric senders. Operators enforce these rules with filters. Traffic that looks unsolicited or arrives over unapproved "grey" routes may be blocked without notice. ## What goes wrong with A2P SMS? - **Invalid and disconnected numbers.** Every message to a dead number still costs money and hurts sender reputation. - **Landlines and non-mobile numbers.** Most landlines can't receive SMS. See [line type](/glossary/line-type). - **Artificially inflated traffic.** Bots trigger OTP messages to numbers that earn the attacker a fee. See [SMS pumping](/glossary/sms-pumping) and [IRSF](/glossary/irsf). - **Unclear delivery status.** Receipts vary by route, and some routes report less than others. ## Is SMS still the right channel? Often, because every mobile phone can receive it. But rich channels are growing. [RCS](/glossary/rcs) adds branded, verified business messaging inside the phone's default app, and many users prefer messengers they already use. Choosing per recipient, with their consent, can lower costs. See [choosing a messaging channel by country](/blog/choosing-a-messaging-channel-by-country). ## How does MobileValidate help A2P senders? MobileValidate checks numbers before you pay for a send. The [carrier lookup](/services/carrier-lookup) returns the line type and current carrier, so you can drop landlines and premium-rate numbers and estimate per-network costs. The [RCS check](/services/rcs-capability-check) shows which opted-in recipients can receive RCS. Inconclusive answers are free. MobileValidate is built for messages people asked for. It must not be used to find targets for unsolicited bulk messaging. See [SMS cost reduction](/use-cases/sms-cost-reduction) for a worked workflow. ## Frequently asked questions ### What is the difference between A2P and P2P SMS? P2P (person-to-person) messages are typed by one person to another. A2P messages are generated by an application for a business or organisation, usually in higher volumes and often one way. ### Do I need consent to send A2P SMS? In most countries, yes, at least for marketing. Rules differ by country and message type, and US carriers expect documented opt-in for business messaging. Check local law and your provider's policy before sending. ### Why are A2P messages filtered or blocked? Operators filter traffic that looks unregistered, unsolicited or fraudulent, or that arrives over unapproved routes. Registering the sender, getting consent and cleaning invalid numbers from your list reduce filtering. --- # Caller ID name (CNAM) > CNAM is the caller name shown with incoming calls in North America, looked up by the called party's carrier. How it works, why it is often wrong, and what we don't do. Canonical: https://mobilevalidate.com/glossary/cnam · Last updated: 2026-09-25 ![A shield and a risk gauge pointing into the high range, with an incoming call flagged as risky.](https://mobilevalidate.com/images/spam-reputation-risk-score.svg) *Spam reputation gives a risk level and the reasons behind it, where the service is enabled.* CNAM (Caller ID Name, also written Calling Name) is the name displayed next to an incoming phone number in the United States and Canada, for example "ACME PHARMACY" or a person's name. It is usually not sent by the caller. The terminating carrier looks up the calling number in a CNAM database at the moment the call arrives and shows the stored name, typically limited to 15 characters. ## How does CNAM work? When a call reaches the called party's carrier, it carries the calling number. If the subscriber has caller name display, the carrier sends a query, often called a "CNAM dip", to a database that maps numbers to names. The provider that issued the number registers the name in such a database. The carrier displays the result, or a generic label such as "WIRELESS CALLER" when no name is stored. Several databases exist, and not all are kept in sync, so the same number can show different names on different networks. Mobile numbers often have no name at all. ## Why is CNAM often wrong? - **Stale records.** Numbers get reassigned, businesses rename, and old names can stay in caches. - **No record.** Many mobile and VoIP numbers never had a name registered. - **Spoofing.** A caller who spoofs a number gets that number's stored name, which makes the call look more trustworthy. US rules ([47 CFR 64.1604](https://www.ecfr.gov/current/title-47/section-64.1604)) prohibit transmitting misleading or inaccurate caller identification information with intent to defraud, cause harm or wrongfully obtain anything of value. The definition of caller identification information in [47 CFR 64.1600](https://www.ecfr.gov/current/title-47/section-64.1600) covers the number and "other information regarding the origination" of a call. ## How does CNAM relate to STIR/SHAKEN? [STIR/SHAKEN](/glossary/stir-shaken) signs the calling number, not the name. A separate IETF standard, [RFC 9795](https://www.rfc-editor.org/rfc/rfc9795) (July 2025), extends signed caller information with "rich call data" such as a display name and logo, so that a verified brand name can travel with the call. Adoption of these branded calling services is still developing. ## What should a business do about CNAM? - **For outbound calls:** register an accurate business name for the numbers you call from, and check how it displays on major networks. Combined with [STIR/SHAKEN](/glossary/stir-shaken) attestation, this helps people recognise legitimate calls. - **For inbound calls:** don't treat a displayed name as proof of identity. Screen calls with signals about the number itself. ## Does MobileValidate offer CNAM or name lookups? No. MobileValidate doesn't return names for phone numbers, and it doesn't offer reverse lookups from a number to a name, photo or social profile. That is a deliberate privacy decision, not a missing feature. What MobileValidate does return is information about the number: the current carrier and [line type](/glossary/line-type) through the [US and Canada carrier lookup](/services/us-carrier-lookup), and, where enabled, [spam reputation](/services/spam-reputation) (limited access, US, CA and DE numbers). See [call-center screening](/use-cases/call-center-screening) for how to route inbound calls with these signals, and our [privacy checklist](/blog/privacy-checklist-for-phone-and-email-checks) for handling phone data responsibly. ## Frequently asked questions ### Does the caller send their name with the call? Usually not. In most North American calls only the number travels with the call. The called party's carrier looks up the name in a CNAM database and displays it. ### Can I change the name shown for my business number? Usually, yes. Ask the provider that issued the number to update its CNAM record. Other carriers may cache the old name for a while. ### Does MobileValidate offer CNAM lookups? No. MobileValidate doesn't offer CNAM or any other lookup that turns a phone number into a person's name, photo or profile. --- # Delivery receipt (DLR) > A delivery receipt (DLR) is the status report a network returns after an SMS is delivered or fails. What DLR statuses mean and why a missing receipt isn't proof of failure. Canonical: https://mobilevalidate.com/glossary/delivery-receipt-dlr · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* A delivery receipt (DLR) is a status report that tells the sender of an SMS what happened to it: delivered to the handset, failed, expired or still pending. The recipient's network generates it, and the messaging provider passes it back to the sending application, usually as a webhook. DLRs are how A2P senders measure delivery rates and spot broken routes. ## Where do delivery receipts come from? In mobile networks, the receipt is the **SMS-STATUS-REPORT** defined in 3GPP [TS 23.040](https://www.3gpp.org/DynaReport/23040.htm), *Technical realization of the Short Message Service*. The sender sets a flag asking for a status report when it submits the message. When the message centre delivers the SMS, or gives up, it sends back a report with a status code. Between businesses and messaging providers, receipts usually travel over the SMPP protocol or an HTTP API. The SMPP 3.4 specification suggests a text format with fields such as `stat` and `err`. Common `stat` values: | Status | Meaning | |---|---| | `DELIVRD` | Delivered to the handset | | `UNDELIV` | Could not be delivered (for example, invalid or barred number) | | `EXPIRED` | Validity period ended before delivery, often because the phone was off | | `REJECTD` | Rejected by the network or a filter | | `ACCEPTD` / `ENROUTE` | Accepted or in transit, no final status yet | | `UNKNOWN` | The final status can't be determined | Providers usually map these, plus network-specific error codes, to their own simpler statuses. ## Why are delivery receipts unreliable? DLRs are useful but not exact: - **Coverage varies.** Some networks, countries and routes don't return receipts, or return only intermediate ones. - **Timing varies.** A receipt can arrive seconds or hours later, or never. - **Some routes misreport.** Low-cost routes have been known to report messages as delivered when they weren't, or to strip receipts. A sudden jump to near-perfect delivery on a cheap route deserves a check. - **Delivered isn't read.** A receipt says nothing about whether a person saw the message, or whether the right person owns the number. Treat a missing receipt as unknown, not as failed. Use test messages to handsets you control to check a route. ## What can you learn from failed receipts? Patterns in failures point to list problems: - Many `UNDELIV` results with "unknown subscriber" errors suggest **disconnected or mistyped numbers**. - Failures to landlines mean **non-mobile numbers** are in the list. See [line type](/glossary/line-type). - Many `EXPIRED` results suggest **phones that are off or out of coverage** for a long time, or abandoned SIMs. Cleaning those numbers before the next send lowers cost and protects sender reputation. ## How does MobileValidate relate to delivery receipts? MobileValidate doesn't send messages, so it doesn't produce DLRs. It answers questions you'd otherwise learn from failed receipts, before you pay for the send. The [carrier lookup](/services/carrier-lookup) returns the line type and current carrier. The upcoming [HLR lookup](/services/hlr-lookup) will report whether a mobile number is reachable right now, a direct measure of [number reachability](/glossary/number-reachability). Unknown answers are free. See [SMS cost reduction](/use-cases/sms-cost-reduction) for how teams combine pre-send checks with their own receipt data. ## Frequently asked questions ### Does a delivered DLR mean the person read the message? No. It means the network reports that the message reached the handset. It says nothing about whether the recipient opened or read it. ### Why do some messages never get a DLR? Not every network or route returns receipts, some return them late, and some messages expire before a final status is known. A missing receipt is an unknown, not a failure. ### Can I check whether a number can receive SMS before sending? Partly. A carrier lookup shows whether the number is mobile, and an HLR lookup shows whether a mobile subscriber is currently reachable. Only an actual send produces a delivery receipt. --- # E.164 phone number format > E.164 is the international phone number format: a plus sign, a country code and up to 15 digits in total. Why APIs use it and how to convert to it. Canonical: https://mobilevalidate.com/glossary/e164 · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* E.164 is the international standard format for telephone numbers: a plus sign, the country code, then the national number, with no spaces or punctuation, and at most 15 digits in total. `+447700900001` is a UK mobile number written in E.164. The format is defined in ITU-T Recommendation E.164, published by the International Telecommunication Union. ## What does an E.164 number consist of? It has three parts: | Part | Example (`+447700900001`) | Notes | |---|---|---| | `+` | `+` | Stands for the international dialling prefix (00, 011, etc.) | | Country code | `44` | 1 to 3 digits, assigned by the ITU | | National significant number | `7700900001` | The number without the national trunk prefix (the leading 0 in the UK) | The maximum length is 15 digits, excluding the `+`. Country codes can be shared. `+1` covers the United States, Canada and a number of Caribbean countries (the North American Numbering Plan), and `+7` covers Russia and Kazakhstan. The country code alone doesn't always identify the country. The first digits of the national number are needed too. ## Why do APIs use E.164? A number can be written in dozens of ways: `07700 900001`, `+44 (0)7700 900001`, `0044 7700-900001`. They all mean the same line. Converting everything to one canonical form is the only reliable way to: - **deduplicate** a list, so the same person isn't checked or messaged twice; - **cache** results per number; - **price and route** per country; - **match** numbers across systems (CRM, messaging provider, fraud tools). MobileValidate converts every number to E.164 before deduplication, caching, pricing or any check. The `e164` field in each result shows the converted form, and `number_status` shows whether the input was valid, invalid or a duplicate. ## How do you convert a national number to E.164? 1. Remove spaces, dashes, dots and brackets. 2. Remove the national trunk prefix (usually a leading `0`; in North America a leading `1` dialled domestically). 3. Add `+` and the country code. So `07700 900001` in the UK becomes `+447700900001`, and `(202) 555-0143` in the US becomes `+12025550143`. Because step 3 needs the country, national-format input needs a country hint. In the API that is the `default_country` field (ISO code, for example `"GB"`). Well-maintained libraries such as Google's libphonenumber also check length and number ranges per country, which catches many typos before any check is paid for. ## Is a valid E.164 number a working number? No. E.164 describes the *format* and whether the number fits the country's numbering plan. It says nothing about whether the number is assigned, reachable or in use on a messaging app. The [number reachability](/glossary/number-reachability) entry explains the difference. To learn more, use a [carrier lookup](/services/carrier-lookup) for line type and network, and channel checks such as the [WhatsApp check](/services/whatsapp-number-check). ## Which numbers are safe to use in examples? Use ranges reserved for fiction. In the UK, Ofcom reserves `07700 900000` to `07700 900999` for drama and they are never assigned to subscribers. MobileValidate's [test mode](/docs/test-mode) uses numbers from this range, such as `+447700900001`. ## Frequently asked questions ### How long can an E.164 number be? At most 15 digits, counting the country code but not the plus sign. The ITU-T E.164 recommendation sets that limit. ### Do I have to send numbers in E.164 to the MobileValidate API? No. The API accepts common formats and converts them. National formats such as 07700 900001 need default_country (for example GB) so the country code can be added. Results always include the e164 form. ### Should I keep the leading zero of a national number? No. The national trunk prefix (0 in most of Europe, 1 in North America when dialled domestically) is dropped in E.164. 07700 900001 in the UK becomes +447700900001. --- # HLR lookup > An HLR lookup queries a mobile number's home network to find out whether the number is live, ported or roaming. How it works and what it can't tell you. Canonical: https://mobilevalidate.com/glossary/hlr-lookup · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* An HLR lookup is a query to a mobile number's home network that asks whether the number is assigned to a subscriber, whether that subscriber can be reached right now, and which network currently serves the number. HLR stands for Home Location Register, the database in GSM and 3G networks that holds the subscription record of every customer. ## How does an HLR lookup work? Every mobile operator keeps a central register of its subscribers. In GSM and UMTS (2G/3G) networks this is the HLR. In 4G/LTE its role is taken by the Home Subscriber Server (HSS), and in 5G by the Unified Data Management function (UDM). All three are specified by 3GPP. The register knows whether a number is in service, whether the SIM is currently attached to a network, and which switch is serving it. When an SMS is sent between networks, the sending side first asks the recipient's home register where to deliver it. This uses the MAP protocol over the SS7 signalling network. An HLR lookup service makes the same kind of routing query without sending a message, and turns the reply into a simple status. The subscriber's phone isn't contacted and nothing appears on the handset. ## What can an HLR lookup tell you? A well-formed answer covers four things: - **Status**: reachable (known and attached), unreachable (known but switched off or out of coverage), or invalid (not assigned). - **Porting**: whether the number now belongs to a different network than the one its range was allocated to. See [mobile number portability](/glossary/mobile-number-portability). - **Roaming**: whether the subscriber is currently on a foreign network. - **Current network**: its name and [MCC/MNC code](/glossary/mcc-mnc). That makes it the most direct test of [number reachability](/glossary/number-reachability) for mobile numbers. ## What can't it tell you? An HLR lookup doesn't tell you who owns a number, whether the owner reads SMS, or whether they use apps like WhatsApp. That needs a channel check such as the [WhatsApp check](/services/whatsapp-number-check). It doesn't work for landlines, and most VoIP numbers have no HLR-style record to query. Answers also depend on the home network. Some operators block or rate-limit external queries, or return generic replies to protect their subscribers. A good service reports those cases as unknown instead of guessing. ## Which HLR data is sensitive? Raw HLR replies can contain identifiers that should never leave a telecom environment: the IMSI (the SIM's permanent subscriber identity), the address of the serving switch (MSC/VLR), and in some cases location-related codes. With those, someone could track a person or attack their account. MobileValidate never returns or stores them. Roaming is reported only as true or false, with no location. ## How does MobileValidate offer HLR lookups? MobileValidate's [HLR lookup](/services/hlr-lookup) is **coming soon**. It will return `status`, `ported`, `roaming`, `network`, `mcc_mnc` and `country`. Conclusive answers (`reachable`, `unreachable`, `invalid`) will be billed, and `unknown` answers will be free. Until it launches, the [carrier lookup](/services/carrier-lookup) gives line type and current carrier from reference data. ## Frequently asked questions ### Is an HLR lookup the same as a carrier lookup? No. A carrier lookup reads reference data about a number (line type, carrier). An HLR lookup asks the home network in real time, so it can also show whether the subscriber is currently reachable. ### Does an HLR lookup work for landlines? No. Only mobile networks keep a home subscriber register. Landline and most VoIP numbers can't be queried this way. ### Does an HLR lookup send anything to the phone? No. The query goes to the network, not to the handset. The subscriber sees nothing. --- # International mobile subscriber identity (IMSI) > The IMSI is the permanent identifier of a mobile subscription, stored on the SIM. How it differs from the phone number and why MobileValidate never returns it. Canonical: https://mobilevalidate.com/glossary/imsi · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* The IMSI (international mobile subscriber identity) is the unique, permanent identifier of a mobile subscription. It is stored on the SIM card or eSIM profile and used by mobile networks to recognise the subscriber, authenticate the SIM and route network procedures. Unlike the phone number, it is meant to stay inside the network and is not shown to other people. ## What does an IMSI consist of? The IMSI is defined by ITU-T Recommendation [E.212](https://www.itu.int/rec/T-REC-E.212/en), *The international identification plan for public networks and subscriptions*, and in 3GPP [TS 23.003](https://www.3gpp.org/DynaReport/23003.htm). It has at most 15 digits in three parts: - **MCC** (mobile country code): 3 digits identifying the country. - **MNC** (mobile network code): 2 or 3 digits identifying the operator in that country. - **MSIN** (mobile subscription identification number): the remaining digits, identifying the subscription within that operator. The MCC and MNC together identify the home network. That pair is public reference data. See [MCC/MNC](/glossary/mcc-mnc). The MSIN, and the full IMSI, identify one subscriber and are sensitive. ## How is the IMSI different from the phone number? The phone number, or [MSISDN](/glossary/msisdn), is the public address people dial. The IMSI is the internal identity of the SIM. The home subscriber register maps one to the other. That separation lets an operator move a number to a new SIM, and it is also what attackers abuse in a [SIM swap](/glossary/sim-swap). The IMSI is also different from the IMEI, which identifies the handset rather than the subscription. ## Why is the IMSI sensitive? A phone number is widely shared. The IMSI is not, and networks use it as the key to a subscriber's records. Someone who holds a person's IMSI, together with other signalling data, may be able to link that person to network activity, which can help with tracking or with attacks on their accounts. For that reason, mobile standards have steadily reduced how often the permanent identity is exposed. In 5G, the permanent identifier (SUPI, which is usually an IMSI) is sent over the air only in concealed form, as the SUCI, per 3GPP TS 33.501. Network-level exposure of identifiers is part of a wider signalling risk class. See [SS7](/glossary/ss7). ## Why does MobileValidate never return an IMSI? Raw replies from a subscriber register can contain the IMSI, the address of the serving switch and other routing identifiers. MobileValidate's policy is to discard them: - **We never return or store** the IMSI, the MSIN, switch or register addresses, or cell and location codes. - **Roaming is reported** only as a yes/no flag or a country, never as a location. - **The network is identified** by its name and MCC/MNC code, which is public reference data. A number-intelligence service needs to answer "is this number live, and on which network?". It doesn't need anything that identifies or locates the SIM. Our [trust page](/trust) describes how we handle data. ## How does this affect what you get from an HLR lookup? The upcoming [HLR lookup](/services/hlr-lookup) will return `status`, `ported`, `roaming`, `network`, `mcc_mnc` and `country`. That is enough to decide whether to send, call or route a number, without exposing the subscriber. See [HLR lookup](/glossary/hlr-lookup) for how the query works. The [carrier lookup](/services/carrier-lookup) returns the line type and current carrier from reference data. ## Frequently asked questions ### Is the IMSI the same as my phone number? No. The phone number (MSISDN) is what people dial. The IMSI identifies the subscription on the SIM or eSIM and is used inside the network. The operator links the two. ### Is the IMSI the same as the IMEI? No. The IMEI identifies the handset. The IMSI identifies the subscription. Moving a SIM to another phone keeps the IMSI and changes the IMEI. ### Can MobileValidate tell me a number's IMSI? No. MobileValidate never returns or stores IMSIs, even when a network reply contains one. Results report the network by its MCC/MNC code instead. --- # International revenue share fraud (IRSF) > IRSF is telecom fraud that pushes calls or texts to high-cost international numbers so the fraudster earns a share of the fee. How it works and how to limit it. Canonical: https://mobilevalidate.com/glossary/irsf · Last updated: 2026-09-25 ![A shield and a risk gauge pointing into the high range, with an incoming call flagged as risky.](https://mobilevalidate.com/images/spam-reputation-risk-score.svg) *Spam reputation gives a risk level and the reasons behind it, where the service is enabled.* International revenue share fraud (IRSF) is telecom fraud in which a criminal generates calls or text messages to high-cost international numbers and collects part of the fee that the destination network charges for terminating that traffic. The victim is whoever originates the traffic and pays the bill. ## How does IRSF work? Every international call or SMS generates a termination fee, which is shared between the carriers along the route. Some number ranges carry high fees, such as premium-rate services, satellite networks and destinations with expensive termination. A fraudster obtains numbers in such ranges from a party willing to share the revenue, or abuses ranges that were never assigned at all. Then the fraudster needs someone else to pay for traffic to those numbers. Common ways are: - **Hijacked phone systems**: a compromised business PBX or SIP account is used to place many long calls overnight. - **Abused web forms**: bots enter the fraudster's numbers into "call me back" or "send me a code" forms. The text-message form is known as [SMS pumping](/glossary/sms-pumping). - **Callback lures**: short missed calls tempt people to call back an expensive number. See [wangiri](/glossary/wangiri). - **Stolen SIMs or roaming abuse**, where calls are placed before the operator detects the fraud. ## Why is IRSF hard to stop? Money moves between carriers after the traffic is carried, so an originating operator or business often pays before anyone can intervene. The ITU has recommendation [ITU-T E.156](https://www.itu.int/rec/T-REC-E.156/en), *Guidelines for ITU-T action on reported misuse of E.164 number resources*, which lets regulators and operators report misused number ranges. But fraudsters move between ranges and countries faster than reports are processed. ## What are the warning signs? - Traffic to **countries where you have no customers**, often concentrated on a few number ranges. - **Sequential numbers** or bursts of requests to near-identical numbers. - **Premium-rate, shared-cost or satellite** [line types](/glossary/line-type) among destinations. - **High volumes at night or on weekends**, when staff are not watching. - A sharp **drop in completion rates**: codes sent but never entered, calls answered but no customer on the line. ## How can you reduce IRSF exposure? 1. **Allow only the destinations you serve**, and add friction for everything else. 2. **Check the destination number before you pay for traffic.** Refuse premium-rate and other non-mobile line types for OTP and callback flows. 3. **Rate-limit and cap spend** per number, account, IP address and country, with alerts on spikes. 4. **Protect public forms** with bot defences before any SMS or call is triggered. 5. **Secure phone systems**: strong SIP credentials, no default passwords, and international calling disabled where it isn't needed. ## How does MobileValidate help? The [carrier lookup](/services/carrier-lookup) returns the line type (`premium_rate`, `shared_cost`, `toll_free`, `voip`, `mobile` and others), the country and the current carrier, so your code can refuse risky destinations before an SMS or call is placed. Inconclusive answers are free. The API also rejects requests that look like sequential number ranges. For a full workflow, see [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). ## Frequently asked questions ### Who pays for IRSF? The party that originated the traffic: a business whose platform was abused, or a subscriber whose line or account was hijacked. Part of the fee flows to whoever controls the destination numbers. ### Is SMS pumping a kind of IRSF? It follows the same model. SMS pumping uses text messages, usually one-time passcodes, instead of voice calls to generate revenue-sharing traffic. ### Can blocking countries stop IRSF? It helps a lot if you have no customers there. Fraudsters shift to other destinations, so combine country rules with number checks, rate limits and spend alerts. --- # Phone number line type > Line type is the kind of service a number belongs to: mobile, fixed line, VoIP, toll-free, premium rate and more. What each means for SMS and calls. Canonical: https://mobilevalidate.com/glossary/line-type · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* A phone number's line type is the kind of service the number belongs to: a mobile line, a fixed (landline) line, a VoIP service, a toll-free or premium-rate number, and so on. It decides whether a number can receive SMS, how much a call to it costs, and which regulations apply when you contact it. ## Which line types exist? MobileValidate's [carrier lookup](/services/carrier-lookup) returns `line_type` with the values below. They follow the categories used by common numbering-plan libraries. | Value | Meaning | SMS? | |---|---|---| | `mobile` | A mobile (cellular) number | Yes | | `fixed_line` | A landline | Usually no | | `fixed_line_or_mobile` | The numbering plan doesn't separate the two | Depends | | `voip` | Served by an internet-telephony provider | Sometimes | | `toll_free` | Free for the caller (e.g. 0800, 800) | No | | `premium_rate` | Caller pays a higher rate (e.g. 09xx) | No | | `shared_cost` | Cost is split between caller and receiver | No | | `personal` | A "follow-me" personal number that forwards elsewhere | Depends | | `pager` | A paging service | No | | `uan` | Universal access number, usually a company hotline | No | | `voicemail` | A direct voicemail access number | No | | `unknown` | Not determinable | — | ## How is line type determined? There are two ways, with different reliability. **From the numbering plan.** Every country publishes which ranges are used for which services. In the UK, for example, numbers starting `07` (other than `070` and `076`) are mobile, and `0800` numbers are toll-free. This works offline and costs nothing, but it describes the range, not the current service. It fails in countries where mobile and landline numbers share ranges (the US and Canada, which is why `fixed_line_or_mobile` exists), and it fails when a number has been [ported](/glossary/mobile-number-portability) to another kind of service. **From network or carrier data.** A carrier lookup reads current data about the specific number. It can tell you that a number in a geographic range is actually served by a VoIP provider, or that a former landline now belongs to a mobile network. In the US, where numbers can be ported between landline, wireless and VoIP services, this is the only reliable way. ## Why does line type matter? - **SMS cost and delivery.** Texts to landlines, toll-free and premium-rate numbers usually fail, but many providers still charge for them. Filtering by `line_type` before a send avoids that. See [SMS cost reduction](/use-cases/sms-cost-reduction). - **Fraud signals.** A cluster of sign-ups from [VoIP numbers](/glossary/voip-number), or OTP requests to premium-rate ranges, are classic patterns of fake accounts and [SMS pumping](/glossary/sms-pumping). - **Compliance.** Some calling and texting rules differ between wireless and landline numbers. In the US, for example, the TCPA's consent rules for autodialled calls apply specifically to wireless numbers. - **Data quality.** A "mobile" form field that holds a landline or toll-free number tells you the entry needs another look. ## How do I check line type with MobileValidate? Use the [carrier lookup](/services/carrier-lookup) (all countries, real time or bulk) or the [US and Canada carrier lookup](/services/us-carrier-lookup) (bulk). Both return `line_type` and the current `carrier`. When we hold no data, the answer is `unknown` and you aren't charged. ## Frequently asked questions ### Why do some numbers come back as fixed_line_or_mobile? In some numbering plans, the United States and Canada among them, mobile and landline numbers share the same ranges. The format alone can't separate them. A carrier lookup based on current network data can. ### Can a number's line type change? Yes. In markets with number portability, a number can move from a landline to a mobile or VoIP service while keeping its digits. Recheck lists periodically. ### Can a landline receive SMS? Usually not as a normal text. Some fixed-line operators turn texts into voice calls, but most SMS sent to landlines is lost. Filter them out before a send. --- # MCC and MNC codes > MCC and MNC codes identify a mobile network: a 3-digit country code plus a 2- or 3-digit network code, defined by ITU-T E.212. How to read them. Canonical: https://mobilevalidate.com/glossary/mcc-mnc · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* An MCC/MNC is the code that identifies a single mobile network. The Mobile Country Code (MCC) is three digits and identifies the country. The Mobile Network Code (MNC) is two or three digits and identifies the network within that country. Together they form the network's PLMN identity (Public Land Mobile Network). The scheme is defined in ITU-T Recommendation E.212. ## How do you read an MCC/MNC code? Split the string after the third digit. `23415` is MCC `234` (United Kingdom) plus MNC `15`. `310260` is MCC `310` (United States) plus a 3-digit MNC `260`. A few MCC examples: | MCC | Country | |---|---| | 234, 235 | United Kingdom | | 262 | Germany | | 302 | Canada | | 310–316 | United States | | 001 | Reserved for test networks | A country can have several MCCs. The MNC length (2 or 3 digits) is set by each national regulator, so always store the combined code as a string. Treating it as a number drops leading zeros (`01` and `1` are different MNCs). ## Where do MCC and MNC codes appear? - **On the SIM.** The first digits of the IMSI (the SIM's subscriber identity) are the MCC and MNC of the home network. - **In the network broadcast.** A phone shows which network it is attached to by reading the MCC/MNC the cell broadcasts. That is how a phone knows it is roaming. - **In HLR and routing replies.** An [HLR lookup](/glossary/hlr-lookup) returns the MCC/MNC of the network currently serving a number. After a [port](/glossary/mobile-number-portability) that is the new network, not the original one. - **In SMS pricing.** Wholesale messaging rates are often listed per MCC/MNC, because costs differ between networks in the same country. ## How is an MCC/MNC different from a phone number's country code? They come from two separate numbering systems. Phone numbers follow [E.164](/glossary/e164), where the UK is `+44` and the US and Canada share `+1`. Networks follow E.212, where the UK is `234`/`235`, the US `310`–`316` and Canada `302`. You can't derive one from the other with a simple table, and the network serving a number can't be read from its digits once porting is involved. ## How does MobileValidate return network codes? The upcoming [HLR lookup](/services/hlr-lookup) will return `mcc_mnc` (for example `"23415"`) together with the current `network` name and its `country`. It will never return the IMSI or other SIM identifiers. Until it launches, the [carrier lookup](/services/carrier-lookup) returns the current carrier *name*, and the original carrier when the number was ported. ## Frequently asked questions ### Is the MCC the same as the phone country code? No. The MCC is a 3-digit mobile network country code from ITU-T E.212. The dialling country code (such as 44 or 1) comes from ITU-T E.164. The UK is 44 for dialling but MCC 234 or 235. ### Why is the MNC sometimes 2 digits and sometimes 3? Each country's regulator chooses. Most countries use 2-digit MNCs. The United States and some other countries use 3 digits. Keep the code as a string so leading zeros are not lost. ### Does a phone number's MCC/MNC change? The number doesn't change, but the network serving it can. After a port, the current MCC/MNC is the new network's code, not the code of the network the range was allocated to. --- # Mobile number portability (MNP) > Mobile number portability lets subscribers keep their number when they switch networks. Why it breaks prefix-based carrier detection and how to handle it. Canonical: https://mobilevalidate.com/glossary/mobile-number-portability · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* Mobile number portability (MNP) is the right of a mobile subscriber to keep their phone number when they move to a different mobile network. Once a number has been ported, the network that serves it is no longer the one its number range was originally allocated to. ## Why was number portability introduced? Regulators introduced portability to make switching easier and increase competition. Without it, changing operator meant changing number, and that kept customers with their existing provider. Some milestones from public regulatory sources: - **United States:** the FCC required wireless local number portability from 24 November 2003. Customers could then move a number between wireless carriers, and from a landline to a wireless service. - **Canada:** wireless number portability started on 14 March 2007 (CRTC). - **European Union:** the European Electronic Communications Code (Directive (EU) 2018/1972, Article 106) requires that a number be ported and activated within one working day of the agreed date. Most countries with competitive mobile markets now offer MNP in some form. ## Why does portability matter for messaging and fraud checks? Because the prefix no longer tells you the network. Many systems still guess the carrier from the first digits of a number. That guess goes wrong for every ported number, and it can lead to: - **wrong routing and pricing**, when an SMS provider bills or routes by destination network; - **wrong line-type assumptions**, when a number moves from a landline to a mobile or VoIP service (US rules allow this); - **missed fraud signals**, when a recent port is part of an account-takeover attempt. In a *port-out scam*, a criminal moves a victim's number to a SIM they control so they can receive the victim's one-time passcodes. ## How does a network know where a ported number lives? Countries handle this differently. Many run a central number portability database that lists every ported number and its current network. Others make each network keep a copy. When a call or SMS is routed, the sending network queries that data, or the home register, to find the right destination. An [HLR lookup](/glossary/hlr-lookup) reads the result of this process directly from the network. A carrier lookup reads it from reference data that is refreshed regularly. ## How do I detect ported numbers with MobileValidate? - The [carrier lookup](/services/carrier-lookup) returns the current `carrier` and, when it differs, the `original_carrier`. A difference means the number was ported. - The [US and Canada carrier lookup](/services/us-carrier-lookup) returns the current carrier for North American numbers, where porting is common. - The [HLR lookup](/services/hlr-lookup) (coming soon) will return a `ported` flag together with the current network and its [MCC/MNC](/glossary/mcc-mnc) code. A port by itself is normal. Millions of people switch operators. Treat it as context, and combine it with other signals, such as a very recent port shortly before a password reset. ## Frequently asked questions ### Can I tell a number's carrier from its prefix? Not reliably, in any country with number portability. The prefix shows which network the range was allocated to. A ported number is served by a different network. ### Does porting change the number's country? No. Numbers are ported between networks within the same country. The country code stays the same. ### How do I find out whether a number was ported? Compare the current carrier with the carrier the range was allocated to. MobileValidate's carrier lookup returns original_carrier when the two differ, and the upcoming HLR lookup will return a ported flag. --- # MSISDN > An MSISDN is the full international phone number of a mobile subscription: country code, network code and subscriber number. How it differs from the IMSI. Canonical: https://mobilevalidate.com/glossary/msisdn · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* An MSISDN is the telephone number of a mobile subscription in its full international form: the country code, the national destination code and the subscriber number, written as one string of digits. It is the number people dial to reach a mobile phone. 3GPP defines it in [TS 23.003](https://www.3gpp.org/DynaReport/23003.htm) as the Mobile Station International ISDN Number. The industry often expands it as Mobile Station International Subscriber Directory Number. ## What does an MSISDN look like? An MSISDN follows the [ITU-T E.164](https://www.itu.int/rec/T-REC-E.164/en) numbering plan, so it has at most 15 digits and three parts: - **Country code (CC)**: 1 to 3 digits, such as 44 for the United Kingdom or 1 for the North American Numbering Plan. - **National destination code (NDC)**: identifies a network or a number range inside the country, such as 7700 in the UK example below. - **Subscriber number (SN)**: the rest of the digits. The UK number +44 7700 900001 has the MSISDN `447700900001`. It is the same number as the [E.164](/glossary/e164) form, just without the plus sign. Telecom systems and many SMS APIs write it this way. Web forms and most modern APIs, including ours, use the E.164 form with the plus. ## How is an MSISDN different from an IMSI? They identify different things: | | MSISDN | IMSI | |---|---|---| | Identifies | The phone number (the service people call) | The subscription on the SIM | | Public? | Yes, it is shared with contacts | No, it stays inside the network | | Standard | ITU-T E.164 | ITU-T E.212 | | Changes when | The subscriber takes a new number | The subscriber gets a new SIM or eSIM profile | The home network's subscriber register links the two. That link is why a number can survive a SIM replacement, and why a [SIM swap](/glossary/sim-swap) is dangerous: the number stays the same while the SIM behind it changes. See [IMSI](/glossary/imsi) for why that identifier should never leave the network. ## Does the MSISDN tell you the network? Only partly. The national destination code shows which network the number range was allocated to. After [mobile number portability](/glossary/mobile-number-portability), the number may be served by a different network. The current network is identified by its [MCC/MNC](/glossary/mcc-mnc) code, which has to be looked up. ## Where do you meet the term? - **SMS and A2P APIs**, where the destination field is often called `msisdn` and expects digits only. - **HLR lookups**, which query the home register by MSISDN. See [HLR lookup](/glossary/hlr-lookup). - **Billing and CRM systems** of mobile operators, where the MSISDN is the customer's line identifier. - **Number recycling**. Operators reassign disconnected MSISDNs to new customers after a quarantine period, so a number in your database may now belong to someone else. ## How does MobileValidate handle MSISDNs? MobileValidate accepts numbers in common formats, converts them to E.164 for free and returns the `e164` form in every result. A [carrier lookup](/services/carrier-lookup) adds the line type and the current carrier. The upcoming [HLR lookup](/services/hlr-lookup) will query the home network by MSISDN and report whether the number is reachable. It will never return the IMSI or other network identifiers behind the number. ## Frequently asked questions ### Is an MSISDN the same as a phone number? In practice, yes. The MSISDN is the mobile subscriber's phone number written in full international form, without the plus sign or any national prefix. +44 7700 900001 has the MSISDN 447700900001. ### Is the MSISDN stored on the SIM card? Not necessarily. The SIM holds the IMSI, which identifies the subscription to the network. The home network maps the IMSI to one or more MSISDNs, so a number can move to a new SIM without changing. ### How long can an MSISDN be? At most 15 digits, because an MSISDN follows the ITU-T E.164 numbering plan, which sets that limit including the country code. --- # Number reachability > A valid number has the right format; a reachable number is live on a network right now. Four levels of phone number checks and what each tells you. Canonical: https://mobilevalidate.com/glossary/number-reachability · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* Number reachability is whether a phone number can receive a call or message right now: it is assigned to a subscriber, the subscriber's phone is attached to a network, and the network accepts traffic for it. It is a stronger property than *validity*, which only says that a number is correctly formatted and fits its country's numbering plan. ## What are the levels of checking a number? It helps to think of four levels, each answering a different question: | Level | Question | How it is checked | |---|---|---| | 1. Valid | Is the format right for this country? | Numbering-plan rules, offline ([E.164](/glossary/e164) conversion) | | 2. Line type and network | What kind of line is it, and which carrier? | [Carrier lookup](/services/carrier-lookup) against current data | | 3. Reachable | Is it assigned and live on the network now? | [HLR lookup](/glossary/hlr-lookup): a live query to the home network | | 4. Channel | Does it use a given app or service? | Channel checks such as [WhatsApp](/services/whatsapp-number-check) | Each level catches problems the one before it misses. A number can be perfectly valid and never have been assigned. A mobile number can be assigned but switched off. A reachable number might have no WhatsApp account. ## Why isn't a valid number enough? Validation only checks the digits. It catches typos and impossible numbers for free, but it can't tell you whether a phone at the other end exists. Lists built from old forms, bought data or manual entry are full of numbers that pass validation but have since been disconnected, recycled or never existed. Sending to them wastes SMS fees and harms delivery statistics. Some messaging providers also slow down senders with high failure rates. ## What does "reachable" mean in practice? For mobile numbers, the home network's subscriber register knows whether the number is in service and whether the SIM is currently attached. An HLR lookup reports this as: - **reachable**: in service and attached; messages should be delivered. - **unreachable**: in service, but the phone is off, out of coverage or temporarily not attached. - **invalid**: not assigned to any subscriber. Reachability changes minute by minute. A phone switched off overnight is unreachable at 3 a.m. and reachable at 9 a.m. Treat one unreachable answer as "try later" and repeated ones over weeks as a sign of an abandoned number. ## How does channel reachability differ? Messaging apps have their own account databases. A number with a WhatsApp, Telegram or Viber account passed that app's own sign-up verification at some point. This is useful when you choose a channel for a message the user expects, and it is a sign that someone really used the number. It doesn't say the phone is on right now, and apps keep inactive accounts for a while before deleting them. ## Which checks should I run? It depends on what a failure costs you: - **Before every OTP:** validity plus line type. Add a channel check if you offer app-based delivery. - **Before a bulk campaign people have agreed to receive:** validity, line type and, once available, reachability, run as a bulk job. - **For lead verification:** validity, line type, a channel check and [spam reputation](/services/spam-reputation) where available. MobileValidate's [HLR lookup](/services/hlr-lookup) for live reachability is coming soon. Carrier, channel and spam checks are available now. ## Frequently asked questions ### If a number is valid, will my SMS arrive? Not necessarily. Valid only means the number fits the country's numbering plan. It may be unassigned, switched off, a landline or disconnected. ### Is an unreachable number a bad number? Not always. Unreachable often just means the phone is off or out of coverage. A number that is unreachable over several weeks is more likely to be abandoned. ### Does WhatsApp registration prove a number is reachable? It shows the number was verified on WhatsApp at some point and still has an account. It doesn't show that the phone is on right now, or that SMS to it will arrive. --- # Rich Communication Services (RCS) > RCS is the carrier messaging standard that adds rich media, read receipts and verified business messages to the phone's built-in messaging app. How it differs from SMS. Canonical: https://mobilevalidate.com/glossary/rcs · Last updated: 2026-09-25 ![A phone with chat bubbles; one messaging-app check says registered, another is unknown.](https://mobilevalidate.com/images/messaging-app-registration-check.svg) *Messaging-app checks answer registered, not registered or unknown, with the time we checked.* Rich Communication Services (RCS) is a messaging standard that upgrades the phone's built-in text messaging app with chat features: high-resolution photos and videos, typing indicators, read receipts, group chats and, for businesses, verified branded messages with buttons and cards. It is the carrier industry's successor to SMS and MMS, specified by the GSMA as the **Universal Profile**. ## How does RCS work? RCS messages travel over mobile data or Wi-Fi, not over the signalling channels used by SMS. Before a chat starts, the sending phone runs **capability discovery** to find out whether the other number supports RCS. If it does, the message goes as RCS. If not, the app falls back to SMS or MMS. According to the [GSMA](https://www.gsma.com/solutions-and-impact/technologies/networks/rcs/universal-profile/), the Universal Profile is "a single, industry-agreed set of features and technical enablers". Its core features include capability discovery, chat, group chat, file transfer, audio messaging, and enablers for business messaging such as rich cards, privacy control and spam protection. Operators decide whether to deploy RCS in 4G networks, while 5G standards make it part of 5G networks and devices. On Android, RCS is part of the default messaging app. Apple added RCS support in iOS 18 in 2024, where the carrier supports it. ## What is RCS business messaging? Businesses can send RCS messages as a verified sender that shows a brand name, logo and verification mark. Messages can include carousels, suggested replies and action buttons, such as "track parcel" or "reschedule". Like other [A2P messaging](/glossary/a2p-sms), RCS business messaging needs sender verification and approval, and should only be used for messages people agreed to receive. ## How is RCS different from SMS and messenger apps? | | SMS | RCS | Messenger apps | |---|---|---|---| | Built into the phone | Yes | Yes, where supported | No, needs an app and an account | | Transport | Signalling network | Mobile data or Wi-Fi | Mobile data or Wi-Fi | | Rich media and receipts | No | Yes | Yes | | Verified business sender | Limited (sender IDs vary by country) | Yes | Yes, on business platforms | | Reach | Every mobile phone | Depends on carrier, handset and settings | Depends on who installed the app | ## Why does RCS capability change? Capability depends on the carrier, the handset, the messaging app version and whether the user has RCS turned on. A new phone, a SIM moved to another device or a changed setting can switch it on or off. Treat a capability answer as fresh for a short time only. ## How does MobileValidate check RCS? The [RCS capability check](/services/rcs-capability-check) answers, per number, whether it can currently receive RCS messages: `registered: true`, `false` or `null` for unknown. When reported, it adds the handset platform (`ios` or `android`). Nothing is sent to the number, and inconclusive answers are free. Teams use it to route opted-in customers to RCS and send SMS to the rest, next to checks such as [iMessage](/services/imessage-number-check) and [WhatsApp](/services/whatsapp-number-check). See [channel selection](/use-cases/channel-selection). ## Frequently asked questions ### Is RCS the same as SMS? No. SMS travels over the mobile network's signalling channels and carries short plain text. RCS runs over mobile data or Wi-Fi and supports media, typing indicators, read receipts and business features. When RCS is unavailable, phones fall back to SMS or MMS. ### Do iPhones support RCS? Yes, since iOS 18 in 2024, where the user's carrier supports it. ### Do I need an app to use RCS? No. RCS lives in the phone's default messaging app. It must be supported by the carrier and the handset, and turned on by the user. --- # SIM swap > A SIM swap moves a phone number to a new SIM. In SIM swap fraud, a criminal does it to receive the victim's calls and one-time passcodes. How it works and how to defend. Canonical: https://mobilevalidate.com/glossary/sim-swap · Last updated: 2026-09-25 ![An account's two-factor phone number is changed to a VoIP number; the change is flagged and an extra verification step is required.](https://mobilevalidate.com/images/account-takeover-contact-change-risk.svg) *When a 2FA number changes to a riskier line, ask for an extra verification step.* A SIM swap is the transfer of a mobile phone number from one SIM card or eSIM profile to another. Operators do it every day for customers who lose or replace a phone. In SIM swap fraud, a criminal tricks or bribes someone at the operator into moving the victim's number to a SIM the criminal controls. From that moment, the criminal receives the victim's calls and text messages, including one-time passcodes. ## How does SIM swap fraud work? The number stays the same, but the SIM behind it changes. In network terms, the operator links the victim's [MSISDN](/glossary/msisdn) to a new [IMSI](/glossary/imsi). The victim's phone loses service, and the attacker's phone starts receiving everything sent to the number. The US FBI's Internet Crime Complaint Center describes the usual methods as social engineering, insider threat and phishing aimed at operator staff ([IC3 public service announcement, February 2022](https://www.ic3.gov/PSA/2022/PSA220208)). It reported that in 2021 it received 1,611 SIM swapping complaints with adjusted losses of more than $68 million. Attackers then use SMS codes to reset passwords for e-mail, bank and cryptocurrency accounts. A close relative is the **port-out scam**, where the attacker moves the number to another operator instead. See [mobile number portability](/glossary/mobile-number-portability). ## What are regulators doing? In November 2023 the FCC adopted rules on [SIM swap and port-out fraud](https://www.federalregister.gov/documents/2023/12/08/2023-26338/protecting-consumers-from-sim-swap-and-port-out-fraud). They require US wireless providers to use secure methods to authenticate a customer before moving a number to a new device or provider, and to notify customers immediately when a SIM change or port-out request is made on their account. NIST's digital identity guidelines ([SP 800-63B-4](https://pages.nist.gov/800-63-4/sp800-63b.html)) treat one-time codes sent over the phone network as a *restricted* authenticator. They tell verifiers to consider risk indicators such as a device swap, SIM change or number porting before sending a code that way. ## What are the warning signs for a business? - A **password reset or 2FA change** shortly after the customer reports "no service", or with no prior contact at all. - A login from a **new device and location** right after an SMS code was sent. - A **recent port** of the number to another operator, especially just before a sensitive action. - Support contacts asking to **change the phone number** on an account under time pressure. ## How can you reduce the risk? 1. **Don't rely on SMS alone** for high-value actions. Offer app-based authenticators, passkeys or hardware keys. 2. **Step up verification** when a number was recently ported or swapped, or when other session signals look unusual. 3. **Delay sensitive changes** such as a new payout account or a new 2FA number, and notify the old contact details. 4. **Log the checks** you ran and when, so disputes can be investigated. ## How does MobileValidate help? MobileValidate does not currently offer a SIM-change check. It provides signals that help with related risks: - The [carrier lookup](/services/carrier-lookup) returns the current carrier and, when the number was ported, the `original_carrier`. A port shortly before a password reset is worth a second look. - The upcoming [HLR lookup](/services/hlr-lookup) will report whether the number is reachable and ported. It will never return the IMSI or other network identifiers. See [account security](/use-cases/account-security) for a workflow that combines these signals with your own session data. ## Frequently asked questions ### Is every SIM swap fraud? No. People swap SIMs legitimately when they lose a phone, move to an eSIM or replace a damaged card. A swap is a risk signal only when it is recent and combined with other unusual activity. ### How is a SIM swap different from a port-out scam? A SIM swap moves the number to a new SIM at the same operator. A port-out scam moves it to a different operator. Both hand the victim's number to the attacker. ### Does MobileValidate offer a SIM swap check? Not today. MobileValidate reports porting and line type, which help with related risks. For SIM-change dates you need a service that obtains them from the operator with the subscriber's consent. --- # SMS pumping > SMS pumping is fraud in which bots trigger OTP or sign-up texts to numbers that earn the attacker a share of the fees. How it works and how to reduce it. Canonical: https://mobilevalidate.com/glossary/sms-pumping · Last updated: 2026-09-25 ![A shield and a risk gauge pointing into the high range, with an incoming call flagged as risky.](https://mobilevalidate.com/images/spam-reputation-risk-score.svg) *Spam reputation gives a risk level and the reasons behind it, where the service is enabled.* SMS pumping is a type of fraud in which an attacker makes a business send large numbers of text messages, usually one-time passcodes or sign-up confirmations, to phone numbers that earn the attacker money for every message delivered. The industry also calls it SMS toll fraud or artificially inflated traffic (AIT). ## How does SMS pumping work? Every SMS generates fees along its route. The sender pays its messaging provider, which pays the networks that carry and terminate the message. In an SMS pumping scheme, the attacker controls, or has a revenue-share deal with, part of that chain, typically for a block of numbers in a high-cost destination. The attacker then uses bots to enter those numbers into forms that send an SMS automatically: "send me a code", "sign up", "reset password". Every message costs the business money and pays part of that fee to the attacker. The numbers are often sequential or drawn from a small set of ranges, and nobody ever enters the codes. ## How big is the problem? Large platforms have reacted publicly: in March 2023 Twitter (now X) restricted SMS-based two-factor authentication to paying subscribers, citing abuse of SMS verification. For smaller companies the damage usually shows up as a sudden SMS bill spike from countries where they have few customers. ## What are the warning signs? - A surge of OTP or sign-up requests with **no matching completions**: codes are sent but never entered. - Traffic concentrated on **one country or a few number ranges** outside your normal markets. - **Sequential or near-sequential numbers**, such as +…001, +…002 and so on. - Requests to **non-mobile line types**: premium-rate, shared-cost, or [VoIP](/glossary/voip-number) ranges you normally don't see. - Bursts at odd hours, from rotating IP addresses or unusual user agents. ## How can you reduce SMS pumping? No single control stops it. Several layers together work better: 1. **Check the number before you send.** A [carrier lookup](/services/carrier-lookup) shows the [line type](/glossary/line-type). Don't send OTP texts to premium-rate, toll-free or fixed-line numbers. 2. **Allow only the countries you serve.** Block or add friction for OTP to destinations where you have no customers. 3. **Prefer channels with evidence of real use.** If a number has a [WhatsApp](/services/whatsapp-number-check) or [Telegram](/services/telegram-number-check) account and the user agreed to receive codes there, delivering there doesn't generate an SMS termination fee. 4. **Rate-limit per number, IP address, device and session**, and cap total daily OTP spend. 5. **Use bot protection** on public forms (a challenge or proof-of-work before the SMS is sent). 6. **Watch the conversion rate** of codes sent to codes verified, per country, and alert on drops. ## How does MobileValidate help? MobileValidate checks the number itself before you pay for an SMS: line type and carrier, channel registrations, and (for US, CA and DE) [spam reputation](/services/spam-reputation). Unknown answers are free. The API also refuses requests that look like sequential number ranges, the same pattern pumping bots use. See the [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud) use case for a worked example. ## Frequently asked questions ### Who pays for SMS pumping? The business whose form sends the messages. It pays its SMS provider for every text, and part of that money ends up with whoever controls the destination numbers. ### Why are one-time passcode forms the main target? They send an SMS to any number typed in, often without a login, a payment or a CAPTCHA. A bot can trigger thousands of messages quickly. ### Does checking numbers before sending stop SMS pumping? It reduces it. Filtering out non-mobile line types, unsupported countries and numbers with no sign of real use removes many pumped destinations before any SMS fee is paid. Combine it with rate limits and bot protection. --- # Signalling System No. 7 (SS7) > SS7 is the signalling protocol suite that phone networks use to set up calls, route SMS and support roaming. Why its trust model creates security risks for SMS codes. Canonical: https://mobilevalidate.com/glossary/ss7 · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* SS7 (Signalling System No. 7) is the set of protocols that telephone networks use to exchange control messages with each other: setting up and ending calls, routing SMS, looking up where a mobile subscriber is registered, and supporting roaming and number portability. It carries the conversation *about* a call, not the call itself. It was standardised by the ITU (then CCITT) from the 1980s. [ITU-T Q.700](https://www.itu.int/rec/T-REC-Q.700/en) is the introduction to the series. ## What does SS7 do in a mobile network? In 2G and 3G mobile networks, the **MAP** (Mobile Application Part) protocol runs on top of SS7 and is specified by 3GPP in [TS 29.002](https://www.3gpp.org/DynaReport/29002.htm). MAP messages let networks: - ask a subscriber's home register where to deliver an SMS or call; - update the home network when a subscriber roams onto another network; - check whether a subscriber is reachable. An [HLR lookup](/glossary/hlr-lookup) uses the same kind of routing query to find out whether a number is live, without sending anything to the phone. 4G networks use the Diameter protocol for similar functions, and 5G uses HTTP-based service interfaces with a dedicated security edge between operators. SS7 remains in service for 2G and 3G and for interconnection between many networks. ## Why is SS7 a security risk? SS7 was designed when only a small number of trusted operators were connected. It assumes that anyone who can send signalling messages is entitled to. Today, access to the global signalling network is far wider, through many operators, resellers and service providers. At a high level, abuse of that trust has been publicly associated with three risk classes: - **Location disclosure**: learning which network area a subscriber is in. - **Interception or redirection** of SMS and calls, including one-time passcodes. - **Disruption**, such as making a subscriber unreachable. The EU cybersecurity agency ENISA assessed signalling security in its report [*Signalling Security in Telecom SS7/Diameter/5G*](https://www.enisa.europa.eu/publications/signalling-security-in-telecom-ss7-diameter-5g) (March 2018) and found "a medium to high level of risk". Operators respond with signalling firewalls and monitoring, following industry guidelines such as those of the GSMA. Protection levels vary by operator and country. ## What does this mean for SMS-based authentication? It is one reason SMS codes are considered a weaker factor. NIST's digital identity guidelines ([SP 800-63B-4](https://pages.nist.gov/800-63-4/sp800-63b.html)) classify one-time codes sent over the phone network as a *restricted* authenticator and require verifiers to offer alternatives. Practical steps: 1. **Offer stronger factors**, such as passkeys, authenticator apps or hardware keys, and encourage them for high-value accounts. 2. **Step up** when other signals look unusual, such as a new device, a recent [SIM swap](/glossary/sim-swap) or a number port. 3. **Limit what an SMS code can do**, for example not allowing it alone to change payout details. ## How does MobileValidate handle signalling data? MobileValidate's upcoming [HLR lookup](/services/hlr-lookup) will ask only whether a number is reachable, ported or roaming, and on which network. It will never return or store the [IMSI](/glossary/imsi), switch or register addresses, or cell and location codes, and it will report roaming only as a flag or a country. MobileValidate doesn't offer location tracking or interception of any kind. See [account security](/use-cases/account-security) for how number signals fit into takeover defences. ## Frequently asked questions ### Is SS7 still used? Yes. 4G and 5G networks use newer signalling (Diameter and HTTP-based interfaces), but 2G and 3G networks, and interconnection between many operators, still depend on SS7. ### Can SS7 weaknesses expose SMS one-time passcodes? It has been reported. Security agencies and standards bodies have warned that signalling abuse can redirect SMS, which is one reason SMS codes are treated as a weaker authentication method. ### Does an HLR lookup use SS7? Classic HLR lookups use the MAP protocol, which runs over SS7. A legitimate lookup service asks only for routing status and should discard sensitive identifiers from the reply. --- # STIR/SHAKEN > STIR/SHAKEN is the caller ID authentication framework used in US and Canadian phone networks. How attestation levels A, B and C work and what they don't tell you. Canonical: https://mobilevalidate.com/glossary/stir-shaken · Last updated: 2026-09-25 ![A shield and a risk gauge pointing into the high range, with an incoming call flagged as risky.](https://mobilevalidate.com/images/spam-reputation-risk-score.svg) *Spam reputation gives a risk level and the reasons behind it, where the service is enabled.* STIR/SHAKEN is a framework of standards that lets phone companies digitally sign the caller ID of a call, so that the receiving network can check whether the calling number was asserted by a provider that knows the caller. It is the main defence against spoofed caller IDs in the United States and Canada. STIR stands for Secure Telephone Identity Revisited, and SHAKEN for Signature-based Handling of Asserted information using toKENs. ## How does STIR/SHAKEN work? The IETF's STIR working group defined the building blocks: - **PASSporT** ([RFC 8225](https://www.rfc-editor.org/rfc/rfc8225)): a signed token that carries the calling number, the called number and a timestamp. - **SIP Identity header** ([RFC 8224](https://www.rfc-editor.org/rfc/rfc8224)): how that token travels with a call over IP networks. - **SHAKEN extension** ([RFC 8588](https://www.rfc-editor.org/rfc/rfc8588)): adds the `attest` claim and an origination identifier. The attestation definitions come from the industry standard ATIS-1000074. The originating provider signs each call with a certificate issued under an industry governance system. Each downstream network passes the signature along. The terminating provider verifies it and can show the result to the customer or use it in call-blocking analytics. ## What do the attestation levels mean? RFC 8588 lets the `attest` claim take one of three values: | Level | Name | What the signing provider asserts | |---|---|---| | A | Full attestation | It knows the customer and that they are authorised to use the calling number | | B | Partial attestation | It knows the customer, but not that they are authorised to use this number | | C | Gateway attestation | It only knows where the call entered its network, often an international gateway | ## What does the law require? The FCC [adopted rules in 2020](https://www.fcc.gov/call-authentication) requiring voice service providers to implement STIR/SHAKEN in the IP portions of their networks by 30 June 2021. It later extended the obligation to gateway providers and to intermediate providers that receive unauthenticated calls. Separately, FCC rules ([47 CFR 64.1604](https://www.ecfr.gov/current/title-47/section-64.1604)) prohibit transmitting misleading caller ID information with intent to defraud, cause harm or wrongfully obtain anything of value. ## What doesn't STIR/SHAKEN tell you? - **Intent.** A fully attested number can still run an illegal robocall campaign. Attestation proves who vouched for the number, not that the call is wanted. - **Non-IP calls.** The framework only works on IP networks. Calls that cross legacy TDM links may arrive unsigned. - **International origin.** Calls from abroad usually arrive with gateway attestation or none. - **The caller's name.** Displayed names come from a separate system. See [CNAM](/glossary/cnam). ## How does STIR/SHAKEN fit with number reputation? Treat attestation as one input. Combine it with the number's reputation, its [line type](/glossary/line-type) and your own history with the caller. MobileValidate's [spam reputation](/services/spam-reputation) check (limited access, US, CA and DE numbers) reports whether a number appears in regulator actions, government complaint data or community reports. The [US and Canada carrier lookup](/services/us-carrier-lookup) returns the current carrier and line type. See [screening inbound calls with spam reputation](/blog/screening-inbound-calls-with-spam-reputation) for a call-center workflow that uses all three. ## Frequently asked questions ### What do attestation levels A, B and C mean? A (full): the originating provider knows the customer and that they may use the number. B (partial): it knows the customer but not their right to the number. C (gateway): it only knows where the call entered its network. ### Does STIR/SHAKEN stop robocalls? Not by itself. It makes spoofed caller IDs easier to detect and trace. A robocaller using its own numbers can still get full attestation. ### Can I see the attestation of calls I receive? Often. Many carriers and SIP trunk providers pass the verification result to business customers as a header or an attestation field. Ask your provider. --- # VoIP number > A VoIP number is served over the internet, not a mobile or fixed network. Why it matters for OTP and fraud checks, and why it is not a red flag alone. Canonical: https://mobilevalidate.com/glossary/voip-number · Last updated: 2026-09-25 ![A cell tower sends signal to a SIM card; the line type is identified as mobile rather than landline or VoIP.](https://mobilevalidate.com/images/carrier-and-line-type-lookup.svg) *Carrier lookup returns the line type (mobile, landline or VoIP) and the network behind the number.* A VoIP number is a phone number served by a Voice over Internet Protocol provider instead of a traditional mobile or fixed-line network. Calls and, where supported, messages travel over the internet to an app, a desk phone or a cloud phone system rather than to a SIM card. ## How do VoIP numbers differ from mobile numbers? A mobile number is tied to a SIM issued by a mobile operator. Getting one usually means a contract or a prepaid SIM, and in many countries an identity check. A VoIP number is tied to an account with an internet service. It can often be created in minutes, used from any device with a connection, and released just as quickly. Regulators distinguish some kinds of VoIP. The US FCC, for example, uses the term *interconnected VoIP* for services that can make and receive calls to and from the ordinary phone network. There are two broad kinds: - **Fixed VoIP** is tied to one location, such as a cable operator's home phone service. It often behaves like a landline. - **Non-fixed VoIP** works from anywhere with an internet connection, such as app-based phone numbers and cloud phone systems. ## Why does a VoIP line type matter? **OTP and sign-up checks.** Anyone can create non-fixed VoIP numbers cheaply and in large numbers, so fake-account and promotion-abuse schemes favour them. Many services add a step when a sign-up number is VoIP, for example an e-mail check, a document check or a manual review. See [OTP and sign-up fraud](/use-cases/otp-and-signup-fraud). **Message delivery.** SMS support on VoIP numbers varies by provider. Some accept business texts reliably, some filter them and some can't receive SMS at all. Knowing the line type helps you pick a channel. **Spam and caller-ID abuse.** Robocall operations often use VoIP ranges because numbers are cheap to rotate. Being in a VoIP range is still **not** a risk by itself. MobileValidate's [spam reputation](/services/spam-reputation) check reports it as a hint (`voip_range`) that adds no points to the score. ## How do you detect a VoIP number? The numbering plan helps only a little. Some countries have dedicated VoIP or "nomadic" ranges, but many VoIP numbers sit in ordinary geographic ranges. In the US and Canada, numbers can also be ported from a landline or mobile service to a VoIP provider and back. The reliable method is a lookup against current data for the specific number: - The [carrier lookup](/services/carrier-lookup) returns `line_type: "voip"` together with the current carrier. - The [US and Canada carrier lookup](/services/us-carrier-lookup) does the same for North American numbers, where porting to VoIP is common. ## How should you act on a VoIP result? Decide by context, not by a blanket rule: | Situation | Reasonable reaction | |---|---| | Consumer sign-up with a free trial or bonus | Add a verification step | | Business customer's main line | Accept; many companies run on cloud telephony | | OTP delivery | Prefer a channel the number has proven it supports | | Lead form | Combine with other checks before discarding | Blocking every VoIP number turns away real customers. Adding friction only where the risk is real keeps both fraud and drop-off low. ## Frequently asked questions ### Are VoIP numbers a sign of fraud? Not by themselves. Businesses, remote workers and many private users rely on VoIP numbers. They are easy to obtain in bulk, though, so they are common in fake sign-ups. Treat VoIP as a reason for an extra check, not as a verdict. ### Can a VoIP number receive SMS? Some can and some can't. It depends on the provider and the service. Delivery is less predictable than to mobile numbers. ### Can I tell from the digits that a number is VoIP? Only sometimes. Many VoIP numbers sit in ordinary geographic ranges. A carrier lookup based on the specific number is more reliable than the prefix. --- # Wangiri > Wangiri is a phone scam: a call rings once from a high-cost international number to trick people into calling back. How it works and how businesses avoid paying for it. Canonical: https://mobilevalidate.com/glossary/wangiri · Last updated: 2026-09-25 ![A shield and a risk gauge pointing into the high range, with an incoming call flagged as risky.](https://mobilevalidate.com/images/spam-reputation-risk-score.svg) *Spam reputation gives a risk level and the reasons behind it, where the service is enabled.* Wangiri is a phone scam in which the scammer places a very short call, usually a single ring, from a high-cost international number, hoping the recipient will call back. The return call is charged at a high rate, and the scammer earns a share of the fee. The name is Japanese for "one ring and cut". It is a form of [international revenue share fraud (IRSF)](/glossary/irsf). ## How does wangiri work? The US Federal Communications Commission describes the pattern in its consumer guide on the ["One Ring" phone scam](https://www.fcc.gov/consumers/guides/one-ring-phone-scam). Numbers may look domestic because some international codes resemble US area codes. The FCC gives "232" (Sierra Leone) and "809" (the Dominican Republic) as examples. If you call back, you "may wind up being charged a fee for connecting, along with significant per-minute fees for as long as they can keep you on the phone". Variations use voicemails that urge people to call an unfamiliar number about a delivery or a sick relative. Behind the scenes, the scammer controls or shares revenue from number ranges with high termination fees, then uses automated diallers to ring huge numbers of phones briefly. Spoofing may disguise the calling number further. ## How does wangiri affect businesses? Consumers are the classic target, but businesses can pay more: - **Missed-call callbacks.** Contact centers that automatically return every missed call can dial wangiri numbers many times. - **Callback forms.** "Call me back" forms that accept any number can be filled by bots with high-cost numbers. This is sometimes called wangiri 2.0. - **Employee phones.** Staff returning unknown calls on company lines add to the bill. The loss is the cost of the outbound calls, and it often shows up only on the next invoice. ## How can you reduce wangiri losses? 1. **Don't auto-return missed calls** to countries you don't serve. Keep an allowlist of destination countries for outbound calling. 2. **Check the number before calling back.** Refuse premium-rate, shared-cost and unexpected international destinations. 3. **Protect callback forms** with bot defences and per-number and per-IP rate limits. 4. **Bar international and premium-rate calling** on lines that don't need it, and set spend alerts with your carrier. 5. **Use caller ID signals** for inbound calls: [STIR/SHAKEN](/glossary/stir-shaken) attestation, where your provider passes it, and number reputation. ## How does MobileValidate help? Before your system dials a callback, the [carrier lookup](/services/carrier-lookup) returns the number's country, current carrier and [line type](/glossary/line-type), including `premium_rate` and `shared_cost`, so your code can refuse risky destinations. Inconclusive answers are free. For inbound calls, [spam reputation](/services/spam-reputation) (limited access, US, CA and DE numbers) shows whether a number appears in regulator actions, complaint data or community reports. See [call-center screening](/use-cases/call-center-screening) for a routing workflow. ## Frequently asked questions ### What does wangiri mean? It is Japanese for 'one ring and cut', which describes the scam: the phone rings once and the caller hangs up before anyone can answer. ### What happens if I call back a wangiri number? You may be connected to an international premium or high-cost number and charged a connection fee plus per-minute charges, part of which goes to the scammer. The FCC advises not returning calls from numbers you don't recognise. ### Can a business be hit by wangiri? Yes. Call centers that return missed calls automatically, and web forms that offer a callback to any number, can place expensive calls to wangiri numbers at scale. --- # Phone numbering-plan metadata changes, 2019–2026 > Every region and country calling code named in the metadata change notes of the open-source libphonenumber library, January 2019 to 23 September 2026: 182 releases, one row per release, change type and code. Canonical: https://mobilevalidate.com/datasets/numbering-plan-changes-2019-2026 · Last updated: 2026-09-25 ## Downloads - CSV: https://mobilevalidate.com/datasets/numbering-plan-changes-2019-2026/data.csv (3784 rows, UTF-8, header row) - JSON: https://mobilevalidate.com/datasets/numbering-plan-changes-2019-2026/data.json ## About this dataset - License: Creative Commons Attribution 4.0 International (CC BY 4.0) — https://creativecommons.org/licenses/by/4.0/. Attribution: "Phone numbering-plan metadata changes, 2019–2026, MobileValidate, https://mobilevalidate.com/datasets/numbering-plan-changes-2019-2026". - Temporal coverage: 2019-01-08/2026-09-23 - Spatial coverage: Worldwide - Methodology: https://mobilevalidate.com/blog/numbering-plans-change-research-2026#methodology - Article: https://mobilevalidate.com/blog/numbering-plans-change-research-2026 - One row means the release note lists that code for that change type. It says that the data changed, not how many number ranges changed. - Phone-metadata notes for non-geographic calling codes (such as +800, +870, +881, +882 and +883; 19 release-code pairs) are not rows; phone rows cover two-letter regions only. - Code-only releases and time-zone refreshes are not rows; 80 of the 182 releases also refreshed time-zone data. - Release notes retrieved on 25 September 2026 (latest release v9.0.40, dated 23 September 2026). ## Columns - `date`: Release date as stated in the release notes (YYYY-MM-DD). - `version`: Library version of the release. - `change_type`: phone (numbering-plan metadata), short_number, carrier (original-carrier prefix data), geocoding or alternate_formatting. - `change_kind`: update (existing data changed) or new (data added for the first time). - `code_type`: region (two-letter region code as used by the library, ISO 3166-1 based) or country_calling_code (carrier, geocoding and formatting data are keyed by calling code). - `code`: The region code or country calling code named in the release note. ## Sources - [libphonenumber release_notes.txt (v8.10.3 to v9.0.40)](https://github.com/google/libphonenumber/blob/master/release_notes.txt) — libphonenumber project (Apache License 2.0) - [libphonenumber PhoneNumberMetadata.xml (list of 245 regions)](https://github.com/google/libphonenumber/blob/master/resources/PhoneNumberMetadata.xml) — libphonenumber project (Apache License 2.0) ## Preview (first 20 of 3784 rows) | date | version | change_type | change_kind | code_type | code | |---|---|---|---|---|---| | 2019-01-08 | v8.10.3 | carrier | update | country_calling_code | 220 | | 2019-01-08 | v8.10.3 | carrier | update | country_calling_code | 231 | | 2019-01-08 | v8.10.3 | carrier | update | country_calling_code | 84 | | 2019-01-08 | v8.10.3 | carrier | update | country_calling_code | 852 | | 2019-01-08 | v8.10.3 | carrier | update | country_calling_code | 95 | | 2019-01-08 | v8.10.3 | carrier | update | country_calling_code | 965 | | 2019-01-08 | v8.10.3 | geocoding | update | country_calling_code | 249 | | 2019-01-08 | v8.10.3 | phone | update | region | EG | | 2019-01-08 | v8.10.3 | phone | update | region | GM | | 2019-01-08 | v8.10.3 | phone | update | region | HK | | 2019-01-08 | v8.10.3 | phone | update | region | LR | | 2019-01-08 | v8.10.3 | phone | update | region | MM | | 2019-01-08 | v8.10.3 | phone | update | region | NG | | 2019-01-08 | v8.10.3 | phone | update | region | SD | | 2019-01-08 | v8.10.3 | phone | update | region | UZ | | 2019-01-08 | v8.10.3 | phone | update | region | VN | | 2019-01-08 | v8.10.3 | phone | update | region | VU | | 2019-01-24 | v8.10.4 | carrier | update | country_calling_code | 254 | | 2019-01-24 | v8.10.4 | carrier | update | country_calling_code | 507 | | 2019-01-24 | v8.10.4 | carrier | update | country_calling_code | 599 | --- # US nuisance-call complaints 2026: FTC and FCC aggregates > Aggregated counts and shares from 362,116 FTC Do Not Call complaints (13 August to 23 September 2026) and FCC unwanted-call complaints (2015 to August 2026): subjects, robocall share, weekdays, states, report lag, caller-number repetition and trend. Canonical: https://mobilevalidate.com/datasets/us-nuisance-call-complaints-2026 · Last updated: 2026-09-25 ## Downloads - CSV: https://mobilevalidate.com/datasets/us-nuisance-call-complaints-2026/data.csv (315 rows, UTF-8, header row) - JSON: https://mobilevalidate.com/datasets/us-nuisance-call-complaints-2026/data.json ## About this dataset - License: Creative Commons Attribution 4.0 International (CC BY 4.0) — https://creativecommons.org/licenses/by/4.0/. Attribution: "US nuisance-call complaints 2026: FTC and FCC aggregates, MobileValidate, https://mobilevalidate.com/datasets/us-nuisance-call-complaints-2026". - Temporal coverage: 2015-01-01/2026-09-23 - Spatial coverage: United States - Methodology: https://mobilevalidate.com/blog/us-nuisance-call-complaints-what-the-data-shows#what-data-did-we-use-and-how - Article: https://mobilevalidate.com/blog/us-nuisance-call-complaints-what-the-data-shows - FTC values cover complaints created 13 August to 23 September 2026 (30 weekday files). FCC trend tables are counted by the call date consumers entered; calls dated outside 2015 to August 2026 were excluded. - Consumer area codes and states belong to the people who complained, not to the callers. Caller numbers were counted in memory only and are not included. - Complaints are consumer-reported and unverified; caller numbers can be spoofed. See the article for limitations. - Data retrieved on 25 September 2026. ## Columns - `source`: FTC (Do Not Call reported calls) or FCC (unwanted-call consumer complaints). - `table`: Aggregate group, e.g. subject, robocall_flag, call_weekday, complaint_weekday, consumer_state, consumer_area_code_top25, report_lag_hours, caller_number_repetition, complaints_by_year, complaints_by_month, call_type, consumer_phone_service, summary. - `key`: Category within the table (subject text, weekday, state, consumer area code, year, month, percentile or metric name). - `metric`: count (number of complaints or numbers), share (fraction 0–1 of the table total or of the stated base), robocall_share (fraction of the subject's complaints flagged as robocalls) or hours. - `value`: The value of the metric. - `period`: Date range (ISO 8601 interval), year or month the value covers. ## Sources - [Do Not Call (DNC) Reported Calls Data (daily files 2026-08-14 to 2026-09-24)](https://www.ftc.gov/policy-notices/open-government/data-sets/do-not-call-data) — Federal Trade Commission - [Consumer Complaints Data - Unwanted Calls (dataset vakf-fz8e)](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e) — Federal Communications Commission ## Preview (first 20 of 315 rows) | source | table | key | metric | value | period | |---|---|---|---|---|---| | FTC | summary | daily_files | count | 30 | 2026-08-13/2026-09-23 | | FTC | summary | complaints | count | 362116 | 2026-08-13/2026-09-23 | | FTC | summary | robocall_flagged | share | 0.6935 | 2026-08-13/2026-09-23 | | FTC | summary | robocall_flagged_of_answered | share | 0.7413 | 2026-08-13/2026-09-23 | | FTC | report_lag_hours | p25 | hours | 1.1 | 2026-08-13/2026-09-23 | | FTC | report_lag_hours | p50 | hours | 4.9 | 2026-08-13/2026-09-23 | | FTC | report_lag_hours | p75 | hours | 27.8 | 2026-08-13/2026-09-23 | | FTC | report_lag_hours | p90 | hours | 131.6 | 2026-08-13/2026-09-23 | | FTC | caller_number_repetition | complaints_with_caller_number | count | 344714 | 2026-08-13/2026-09-23 | | FTC | caller_number_repetition | distinct_caller_numbers | count | 297872 | 2026-08-13/2026-09-23 | | FTC | caller_number_repetition | numbers_reported_once | count | 275568 | 2026-08-13/2026-09-23 | | FTC | caller_number_repetition | numbers_reported_once | share | 0.9251 | 2026-08-13/2026-09-23 | | FTC | caller_number_repetition | complaints_from_top_100_numbers | share | 0.0294 | 2026-08-13/2026-09-23 | | FTC | subject | Other | count | 138978 | 2026-08-13/2026-09-23 | | FTC | subject | Other | share | 0.3838 | 2026-08-13/2026-09-23 | | FTC | subject | Other | robocall_share | 0.66 | 2026-08-13/2026-09-23 | | FTC | subject | Reducing your debt (credit cards, mortgage, student loans) | count | 87168 | 2026-08-13/2026-09-23 | | FTC | subject | Reducing your debt (credit cards, mortgage, student loans) | share | 0.2407 | 2026-08-13/2026-09-23 | | FTC | subject | Reducing your debt (credit cards, mortgage, student loans) | robocall_share | 0.895 | 2026-08-13/2026-09-23 | | FTC | subject | Calls pretending to be government, businesses, or family and friends | count | 41429 | 2026-08-13/2026-09-23 |