whatsapp-data-sdk
v1.3.1
Published
TypeScript SDK for the WhatsApp Data API (checkleaked.cc / RapidAPI): profile info, phone breaches, carrier lookup, Telegram, OSINT, bulk checks and a money-saving cache-first lookup.
Downloads
633
Maintainers
Readme
whatsapp-data-sdk
Typed TypeScript/JavaScript client for the WhatsApp Data API — official SDK for whatsapp.checkleaked.cc (also on RapidAPI).
WhatsApp profile lookups, phone breaches, carrier data, Telegram checks, OSINT, geo/business search and bulk verification — plus a cache-first lookup that reads the cheap DB endpoint before spending on a live check.
- 🟦 Fully typed — every endpoint has request options and response interfaces (types bootstrapped from live responses with quicktype and hand-curated).
- 🪶 Zero runtime dependencies — uses the native
fetch(Node 18+, Bun, Deno, browsers). - 📦 Dual ESM + CommonJS build with
.d.tstypes. - 💸 Built-in cache-first strategy to minimize cost.
- 🔁 Timeouts, retries with backoff (Retry-After aware), per-request
AbortSignalcancellation, and a clean error taxonomy.
Install
npm install whatsapp-data-sdkQuick start
import { WhatsAppDataClient } from 'whatsapp-data-sdk'
const client = new WhatsAppDataClient({
apiKey: process.env.WA_API_KEY!, // sent as x-rapidapi-key
})
const profile = await client.getProfile('59898297150')
console.log(profile.exists, profile.about, profile.profilePic)Authentication & hosts
Your API key is always sent in the x-rapidapi-key header. Choose where requests go with transport:
| transport | Live host | Cache / DB-only host | Notes |
| ------------------- | ------------------------------- | ---------------------------------------- | ----- |
| 'proxy' (default) | whatsapp-proxy.checkleaked.cc | same host, /number_cache path | Simple — one base URL, no host header. |
| 'rapidapi' | wp-data.p.rapidapi.com | wp-data-db-only.p.rapidapi.com | Sets x-rapidapi-host automatically. Use your RapidAPI key. |
// RapidAPI marketplace (live + DB-only hosts)
const client = new WhatsAppDataClient({
apiKey: process.env.RAPIDAPI_KEY!,
transport: 'rapidapi',
})You can override any URL/host via baseUrl, cacheBaseUrl, rapidApiHost, rapidApiCacheHost.
💸 Cache-first lookups (save money)
checkCached() reads the cheap cache / DB-only endpoint first and only falls back to a paid live check when the number isn't cached yet.
// cacheFirst (default): DB first → live only on a miss
const r1 = await client.checkCached('59898297150')
console.log(r1._source) // 'cache' | 'live'
// cacheOnly: never spend on a live check
const r2 = await client.checkCached('59898297150', { mode: 'cacheOnly' })
// live: always fresh
const r3 = await client.checkCached('59898297150', { mode: 'live' })On the rapidapi transport this routes the cache read to wp-data-db-only.p.rapidapi.com and the live fallback to wp-data.p.rapidapi.com — exactly the two-host money-saving flow.
API
Every method maps 1:1 to a marketplace endpoint.
Profile & pictures
await client.getProfile(number, options?) // Get Profile Information
await client.getProfileNoPicture(number, options?) // Get Profile Information (No profile pic)
await client.getLastPicture(number) // Get Last Saved Picture (JPEG bytes)
await client.getDeviceCount(number) // Device count
await client.getOsintInfo(number) // OSINT InfogetProfile options include telegram, google, lookup, includeCarrier, base64, geminiFaceAnalysis, reverseImageSearch, fullAiReport, includeDeviceCount, onlyCheck, useCache, forceBypassCache, and more.
const pic = await client.getLastPicture('59898297150')
// pic.bytes: Uint8Array, pic.contentType: string
htmlImg.src = pic.toDataUri()Phone breaches
await client.getPhoneBreaches(number, { limit, offset }) // Get Phone Breaches (no external key)
await client.getPhoneBreachesExternal(number, apiKey) // Get Phone Breaches (your checkleaked.cc key)Search & database
await client.search({ countryCode: 'UY', isBusiness: true, limit: 20 }) // Search
await client.searchGoogleMaps({ latitude: -34.9, longitude: -56.16, radius: 2000 }) // Search in Google Maps
// Bulk downloads return a Google Drive link to the full dataset (MEGA tier only)
const leaks = await client.getLeakedNumbersInfo() // Leaked Numbers (500M database)
console.log(leaks.driveFolder.url)
const backup = await client.downloadEntireDatabase() // Download the Entire DB
console.log(backup.driveFolder.url)Note:
getLeakedNumbersInfo()anddownloadEntireDatabase()are restricted to MEGA-tier keys (500k+ request plans). Lower tiers get a403/AuthError.
Bulk checks
await client.bulkCheck(numbers, { includeBusiness, noBanStatus }) // Bulk Check (live, max 50)
await client.bulkCheckDbBasic(numbers) // Basic Check (DB, max 1000)
await client.bulkCheckDbFull(numbers) // Full Check (DB, max 1000)Telegram (experimental)
await client.getTelegramProfile('59898297150') // Get Profile Information Telegram
await client.getTelegramProfile(['num1', 'num2']) // batchCarrier
await client.carrierLookup(number) // Carrier Lookup
await client.carrierLookupAlt(number) // Carrier Lookup Alternative (spam-report signals)
await client.carrierLookupAlt(number, { page: 2 }) // page through the reports (response has a `pagination` block)"Not found" resolves — it does not throw
A number simply not on WhatsApp is a normal result, not an error, so these resolve (they never reject):
getProfile/getProfileNoPicture/checkCached→ an entry withexists:false(andcode:'NUMBER_NOT_FOUND').searchwith zero matches → an empty page (success:false,data.docs:[]).getDeviceCountfor a non-WhatsApp number →success:false(withdeviceCountabsent).
const p = await client.getProfile('14155552671')
if (!p.exists) console.log('not on WhatsApp')What does throw: an invalid number (400), auth failure (401/403),
rate limit (429), timeout, network failure, and 5xx.
Per-request options
Every method takes a trailing options object that also accepts signal,
timeoutMs, and retries — for per-call cancellation and tuning:
const controller = new AbortController()
const p = client.getProfile('59898297150', {
telegram: true,
signal: controller.signal, // cancel this specific request
timeoutMs: 5000, // override the client timeout for this call
})
controller.abort() // rejects with WhatsAppDataErrorErrors
All rejections are a subclass of WhatsAppDataError, so a single catch
handles everything. The full taxonomy:
| Class | When |
| --- | --- |
| ValidationError | Bad arguments (thrown synchronously, before any request; no .status). |
| AuthError | 401 / 403 — missing, invalid, or unsubscribed key. |
| RateLimitError | 429 — carries retryAfterMs when the server sent Retry-After. |
| HttpError | Any other non-2xx (e.g. 400, 5xx); carries .status and .body. |
| TimeoutError | The request exceeded timeoutMs. |
| NetworkError | Transport failure (DNS, connection reset, mid-body abort). |
| WhatsAppDataError | Base class for all of the above. |
import { AuthError, RateLimitError, TimeoutError, WhatsAppDataError } from 'whatsapp-data-sdk'
try {
await client.getProfile('59898297150')
} catch (err) {
if (err instanceof AuthError) {/* 401/403 — bad/unsubscribed key */}
else if (err instanceof RateLimitError) {/* 429 — err.retryAfterMs */}
else if (err instanceof TimeoutError) {/* request timed out */}
else if (err instanceof WhatsAppDataError) {
console.error(err.status, err.body)
}
}Configuration
new WhatsAppDataClient({
apiKey: '...', // required
transport: 'proxy', // 'proxy' | 'rapidapi'
timeoutMs: 60000, // request timeout (bounds headers AND body read)
retries: 2, // retry 5xx / 429 / network / timeout (GET only) with backoff
retryDelayMs: 500, // exponential base; 429 honors Retry-After when longer
baseUrl: '...', // override live base URL
cacheBaseUrl: '...', // override cache/DB-only base URL
rapidApiHost: '...', // override live x-rapidapi-host
rapidApiCacheHost: '...',// override cache x-rapidapi-host
userAgent: '...', // custom User-Agent
fetch: globalThis.fetch, // inject a custom fetch (tests / polyfills)
})POSTs (
bulkCheck,bulkCheckDb*) are not auto-retried, to avoid double-spending quota on a request the server may have already processed.
Phone number formats
Pass numbers in any format — the SDK normalizes them, and exports the helpers so you can reuse the same normalization:
import { toDigits, toE164 } from 'whatsapp-data-sdk'
toDigits('+598 98 297 150') // '59898297150' (path & live endpoints)
toE164('59898297150') // '+59898297150' (bulk-DB endpoints; their validator requires it)
// both throw ValidationError on input with no digitsNotes
- This SDK covers exactly the marketplace endpoints (profile, breaches, pictures, device count, search, Google-Maps/proximity search, bulk checks, Telegram, database, carrier, OSINT).
- Enrichments run entirely server-side — you never supply Gemini, LeakCheck, or any other keys. Face analysis, breach lookups, etc. are handled by the API. The only user-provided key is your own checkleaked.cc key for
getPhoneBreachesExternal.
License
MIT © Eduardo Airaudo
