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.
Last updated
View as MarkdownThe 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 with the same methods is below.
How do I install it?
Install from npm (mobilevalidate):
npm install mobilevalidateFor Python, install mobilevalidate-sdk from PyPI (the import name is mobilevalidate):
pip install mobilevalidate-sdknew MobileValidate({ sandbox: true }) uses the public sandbox key, so you can try every example with the documented test values before you have a key of your own.
You can also call the HTTP API directly. Every example on this page maps one-to-one to an endpoint.
How do I check numbers?
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?
const { data } = await mv.lookup({
numbers: ["+447700900001"],
emails: ["[email protected]"],
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.
How do I run bulk jobs?
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():
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 orderWhat options does the client take?
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. 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?
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.
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 from PyPI:
pip install mobilevalidate-sdkfrom 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"])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.

