boundstone
v0.2.0
Published
Official Node client for Boundstone — phone, email & IP validation you can actually verify.
Maintainers
Readme
boundstone (Node.js)
Official Node.js / TypeScript client for Boundstone — phone, email & IP validation you can actually verify.
Zero runtime dependencies (native fetch). Node 18+. Fully typed.
npm install boundstoneQuickstart
import Boundstone from "boundstone";
const bs = new Boundstone("bs_live_YOUR_KEY"); // create a key at https://app.boundstone.io
const r = await bs.verifyEmail("[email protected]");
console.log(r.valid_syntax, r.mx_found, r.disposable, r.role_account);Using CommonJS? That works too — the package ships dual ESM + CJS builds:
const { Boundstone } = require("boundstone");The honesty contract
Every result carries a checks object with two lists — what was checked, and what
was not. That second list is the whole point: you always know exactly what a
valid is based on, and Boundstone never implies a check it didn't run.
const r = await bs.verifyEmail("[email protected]");
console.log(r.checks.performed); // ['syntax', 'mx', 'disposable_list', 'role_list']
console.log(r.checks.not_performed); // ['smtp_mailbox', 'catch_all']smtp_mailbox and catch_all sit in not_performed because confirming a specific
mailbox exists needs an SMTP probe that many servers answer dishonestly. Boundstone
tells you it didn't run that, rather than dressing up a guess as a verdict.
Phone
const p = await bs.verifyPhone("+16504472983");
console.log(p.valid, p.e164, p.country, p.line_type);
console.log(p.checks.not_performed); // ['carrier_lookup', 'ported_status', 'hlr_liveness']
// NANP numbers also carry an allocation verdict. A phone library can tell you the
// digits parse and the area code is real, but not whether a carrier actually holds
// that exchange — so a library alone accepts unallocated blocks and the reserved
// 555-01XX range used in films, numbers that pass a cheap check and then get dialled.
console.log(p.allocation?.allocated, p.allocation?.snapshot); // true 2026-07-30
const dead = await bs.verifyPhone("+14155550184"); // reserved fictional range
console.log(dead.valid, dead.allocation?.reason); // false reserved_fictional
// null for non-NANP numbers: we hold no allocation data for other numbering
// plans, and saying nothing beats implying coverage we don't have.
const gb = await bs.verifyPhone("+442071838750");
console.log(gb.allocation); // nullBy default phone validation is metadata-grade (format, region, line type) and the
carrier/liveness checks are honestly listed as not_performed. A live HLR dip —
real carrier, ported status and reachability — is available on the API as a paid
opt-in (hlr: true, 5 credits, refunded when the network can't answer); see the
docs.
IP
const ip = await bs.verifyIp("8.8.8.8");
console.log(ip.valid, ip.version, ip.classification, ip.is_public);
console.log(ip.checks.not_performed); // geolocation, proxy/VPN etc. — not fakedAccount
const acc = await bs.account();
console.log(acc.plan, acc.balance);Errors
Non-2xx responses throw a typed BoundstoneError with status, code and the
API's plain-language message. The raw response body is always available on each
result as .raw if you need fields ahead of this client.
import { BoundstoneError } from "boundstone";
try {
await bs.verifyEmail("nope");
} catch (e) {
if (e instanceof BoundstoneError) console.log(e.status, e.code);
}Options
const bs = new Boundstone("bs_live_YOUR_KEY", {
baseUrl: "https://api.boundstone.io", // default
timeoutMs: 15000, // default 10000; AbortController-backed
});Links
- Docs: https://boundstone.io/docs
- OpenAPI 3.1: https://api.boundstone.io/openapi.json
- Free tier: 250 credits/month, no card, credits never expire.
