Documentation

First verified number in 60 seconds.

Quickstart

1. Get an API key from the dashboard — sign in with an email, keys are shown once. (Self-serve sign-in emails open at launch; pre-launch accounts are founder-minted via the waitlist.)

2. Make the call:

curl -X POST https://api.boundstone.io/v1/verify/phone \
  -H "authorization: Bearer bs_live_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"phone": "+1 415 555 0184"}'

3. Read the response — including what we didn't check:

{
  "input": "+1 415 555 0184",
  "valid": true,
  "e164": "+14155550184",
  "country": "US",
  "line_type": "fixed_line_or_mobile",
  "national_format": "(415) 555-0184",
  "checks": {
    "performed": ["format", "region", "line_type_metadata"],
    "not_performed": ["carrier_lookup", "ported_status", "hlr_liveness"]
  },
  "credits_charged": 1
}

checks.not_performed is load-bearing. Most vendors return a confident-looking verdict whether or not they actually checked. We list both sides so your code — and your audit trail — knows exactly what a result is based on.

Authentication

Bearer key in the authorization header: Bearer bs_live_…. Keys are created and revoked in the dashboard (max 5 active). We store only a hash — lose a key, mint a new one.

POST /v1/verify/phone

FieldTypeNotes
phonestring, requiredAny human format — E.164 comes back normalized
countrystring, optionalTwo-letter default region for national-format inputs, e.g. "US", "AU"

Returns validity, E.164, country, line type (200+ countries, libphonenumber metadata tier). Carrier, ported status and HLR liveness report as not_performed until those data sources ship — they'll appear in checks.performed the day they're real. 1 credit.

POST /v1/verify/email

FieldTypeNotes
emailstring, required

Returns syntax validity, domain, live MX records, A-record fallback, disposable-domain flag, role-account flag (admin@, billing@…), free-provider flag. SMTP mailbox and catch-all probing are not_performed — we won't guess deliverability we didn't test. 1 credit.

Bulk CSV

curl -X POST "https://api.boundstone.io/v1/bulk/phone?country=US" \
  -H "authorization: Bearer bs_live_YOUR_KEY" \
  -H "content-type: text/csv" \
  --data-binary @numbers.csv

First CSV column is read; a header row is auto-detected. Free plan: 250 rows per job. Paid: 10,000. Credits (1/row) are reserved when the job is accepted and refunded for any rows that fail to process. Small jobs usually finish before your first poll.

EndpointDoes
POST /v1/bulk/phone · /v1/bulk/emailSubmit — returns job_id, 202
GET /v1/bulk/:idStatus: queued → running → done
GET /v1/bulk/:id/results.csvResults as CSV once done

Account & status

EndpointDoes
GET /v1/accountEmail, plan, credit balance (authed)
GET /v1/healthLiveness ping, no auth
GET /v1/statusPublic uptime feed — same data as the status page, CORS-open

Rate limits

Per key, sliding 10-second window: free 15 · Starter 60 · Growth/Scale 120. Hitting the limit returns 429 with a plain message; nothing queues silently.

Errors

StatusCodeMeaning
400bad_requestMissing or malformed input — never charged
401unauthorized / key_revokedBad or revoked key
402insufficient_creditsOut of credits — explicit, never silent overage
404not_foundThat endpoint doesn't exist — we won't pretend it does
429rate_limitedSliding-window limit hit
501ip_intelligence_pendingIP endpoint, until licensed data ships

Every error is JSON: {"error": "code", "message": "what happened, in words"}.

The honesty contract

Three rules the API keeps, machine-checkably: (1) every verification response lists checks.performed and checks.not_performed; (2) capabilities we haven't built return explicit errors instead of fabricated verdicts; (3) accuracy claims live in the numbered public benchmarks, not in adjectives. If a response ever violates these, that's a bug — tell us and we'll fix it in public.