Metadata-Version: 2.4
Name: boundstone
Version: 0.2.0
Summary: Official Python client for Boundstone — phone, email & IP validation you can actually verify.
Project-URL: Homepage, https://boundstone.io
Project-URL: Documentation, https://boundstone.io/docs
Project-URL: Source, https://boundstone.io
Author: Boundstone
License: MIT
Keywords: disposable email,e164,email validation,email verification,ip validation,phone validation,signup fraud
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Internet
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# boundstone (Python)

Official Python client for [Boundstone](https://boundstone.io) — phone, email & IP validation you can actually verify.

**Zero dependencies** (standard library only). Python 3.8+.

```bash
pip install boundstone
```

## Quickstart

```python
from boundstone import Boundstone

bs = Boundstone("bs_live_YOUR_KEY")   # create a key at https://app.boundstone.io

r = bs.verify_email("you@example.com")
print(r.valid_syntax, r.mx_found, r.disposable, r.role_account)
```

## 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.

```python
r = bs.verify_email("you@example.com")
print(r.checks.performed)      # ['syntax', 'mx', 'disposable_list', 'role_list']
print(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

```python
p = bs.verify_phone("+16504472983")
print(p.valid, p.country, p.line_type, p.e164)     # True US fixed_line_or_mobile +16504472983
print(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.
print(p.allocation.allocated, p.allocation.snapshot)   # True 2026-07-30

dead = bs.verify_phone("+14155550184")              # reserved fictional range
print(dead.valid, dead.allocation.reason)           # False reserved_fictional

# allocation is None for non-NANP numbers — we hold no allocation data for other
# numbering plans, and saying nothing beats implying coverage we don't have.
print(bs.verify_phone("+442071838750").allocation)  # None

# For a national number without a country code, pass a default region:
bs.verify_phone("07911 123456", country="GB")
```

Carrier, ported-status and live-reachability (HLR) checks are `not_performed` today —
metadata can't see the live network, and Boundstone won't pretend it can.

## IP

```python
ip = bs.verify_ip("10.0.0.1")
print(ip.valid, ip.version, ip.classification, ip.is_public, ip.is_bogon)
print(ip.checks.not_performed)   # ['geolocation', 'asn', 'hosting_datacenter', 'proxy_vpn_tor', 'reputation']
```

IP **validation and classification** (public / private / bogon…) is live and free.
IP **intelligence** (geolocation, proxy/VPN) needs licensed data and is `not_performed`.

## Account

```python
acct = bs.account()
print(acct.plan, acct.balance)
```

## Errors

Any non-2xx response raises `BoundstoneError` with the status and the API's error code:

```python
from boundstone import Boundstone, BoundstoneError

try:
    bs.verify_email("you@example.com")
except BoundstoneError as e:
    print(e.status, e.code, e.message)   # e.g. 402 insufficient_credits ...
```

## Notes

- The free tier is 250 credits/month, no card, and credits never expire.
- Every result also exposes `.raw` — the untouched JSON dict — if you need a field
  the typed result doesn't surface yet.
- Full API reference: <https://boundstone.io/docs> · OpenAPI: <https://api.boundstone.io/openapi.json>

MIT licensed.
