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 Markdown

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 with the same methods is below.

How do I install it?

Install from npm (mobilevalidate):

Shell
npm install mobilevalidate

For Python, install mobilevalidate-sdk from PyPI (the import name is mobilevalidate):

Shell
pip install mobilevalidate-sdk

new 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?

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?

TypeScript
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?

TypeScript
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():

TypeScript
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?

TypeScript
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?

TypeScript
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:

Shell
pip install mobilevalidate-sdk
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"])
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.