← Blog

How to validate phone numbers in Clay

Clay has no Boundstone app, and does not need one: an HTTP enrichment column and one endpoint gets you format, region and line type on every row — with an explicit record of what was not checked.

Contents

If you run outbound out of Clay, your phone numbers arrive from a dozen places — an Apollo export, a ZoomInfo enrichment, a scraped list, a form fill — and they arrive in a dozen shapes. Before those rows reach a dialer or an SMS send, it is worth knowing which ones are structurally real and what kind of line each one is.

There is no Boundstone app in Clay's catalog. There does not need to be one: Clay's HTTP API enrichment column calls any JSON endpoint, and that is the whole integration.

First, the honest part about Clay's own option

Clay ships a native mobile-validation action, and it is powered by Trestle. If you have not set up any phone validation at all, that is the path of least resistance and it works well.

So why add an HTTP column instead? One reason, and it is not "we are more accurate" — we have not published accuracy numbers, and we will not until benchmark № 001 does. The reason is disagreement. Line type and carrier are exactly the fields where vendors diverge, and on a list you are about to spend rep hours dialing, a second opinion on the rows that matter is cheap. What makes a disagreement useful rather than confusing is knowing what each side actually checked — which is the thing this API is built around.

Add the enrichment column

In your Clay table: Add enrichment → HTTP API.

Field Value
Method POST
URL https://api.boundstone.io/v1/verify/phone
Headers Authorization: Bearer bs_live_YOUR_KEY
Content-Type: application/json
Body {"phone": "{{Phone}}", "country": "US"}

Replace {{Phone}} with your table's phone column and bs_live_YOUR_KEY with a key from the dashboard. country is optional — it is the default region used when a number arrives without a + prefix, which in practice is most CRM exports.

A response looks like this — this is a real one, not a paraphrase:

{
  "input": "+16504472983",
  "valid": true,
  "e164": "+16504472983",
  "country": "US",
  "line_type": "fixed_line_or_mobile",
  "national_format": "(650) 447-2983",
  "allocation": { "allocated": true, "reason": null, "snapshot": "2026-07-30" },
  "checks": {
    "performed": ["format", "region", "line_type_metadata", "allocation"],
    "not_performed": ["carrier_lookup", "ported_status", "hlr_liveness"]
  }
}

⚠️ country is worth setting. Without it, a number that arrives without a + prefix — which is most CRM exports — has no default region to parse against, and comes back valid: false with everything null. That is the API declining to guess, not a failure, but it will quietly gut a column if you skip it.

Map the fields you will actually sort on

Three are worth pulling into their own Clay columns:

  • e164 — write this back over your original phone column. E.164 is the one format every dialer, CRM and SMS platform agrees on, and normalizing at enrichment time removes an entire class of downstream import failure. (national_format is there too if you display numbers to reps.)
  • line_type — the field you route on. See below.
  • allocation.allocated — whether the number's block is actually assigned in the North American Numbering Plan at allocation.snapshot. This is how obviously fabricated numbers in a purchased list get caught: a well-formed number in an unassigned block is still well-formed, and still cannot ring. allocation.reason tells you which kind — unallocated_block or reserved_fictional (the 555-01XX range). Outside the NANP, allocation is null rather than implying coverage that does not exist.

Routing by line type

Clay conditional runs make this straightforward — filter the view, then act on each segment:

line_type What it means Reasonable action
mobile Cellular line SMS and voice both available
fixed_line Landline Voice only — do not spend on SMS
voip VoIP-designated range Dial with lower expectation; common for spam traps and throwaways
toll_free 8XX Usually a company main line, not a person
fixed_line_or_mobile The plan cannot tell See below

fixed_line_or_mobile is not a bug or a gap we are working around. In much of the North American plan, mobile and landline numbers are drawn from the same ranges, so the metadata genuinely cannot separate them — and a vendor that returns a confident mobile for those rows is guessing. If that split matters to your send, it needs the live network, not better metadata.

When you need to know a number is live

Everything above is metadata. It tells you a number is well-formed, in an allocated block, and what kind of line it is. It does not tell you the number is assigned to someone, still in service, or that a call will connect.

For that, add "hlr": true to the request body. That runs a live HLR lookup against the carrier network and returns carrier, ported status and reachability. It costs 5 credits instead of 1, it is never run on the free tier, and when the network declines to answer, the row is refunded rather than guessed at.

Use it as a second pass on the segment you care about — the rows that survived the cheap filters — rather than across the whole table.

What this does not do

Worth stating plainly, because a Clay column that quietly implies more than it checked is worse than no column:

  • It is not DNC or TCPA scrubbing. Validation removes numbers that cannot be real; it says nothing about whether you are permitted to call one. That needs a registry vendor — see DNC vs phone validation.
  • It is not identity matching. Nothing here confirms a number belongs to the person on the row.
  • Without hlr:true, it is not liveness — and not carrier either. Every response carries not_performed: ["carrier_lookup", "ported_status", "hlr_liveness"], which is the point: you can read what was skipped instead of inferring it.

The short version

Clay needs no Boundstone app: an HTTP enrichment column pointed at /v1/verify/phone gives you E.164 normalization, allocation-aware validity and line type on every row, for one credit each. Set country so unprefixed numbers parse. Route SMS on mobile, keep fixed_line on voice, treat fixed_line_or_mobile as genuinely unknown, and spend the paid HLR dip only on the segment that earns it. Every response tells you which checks ran and which did not — so what you push back into your table is a fact, not an impression.

Frequently asked questions

Can I validate phone numbers in Clay without a native integration?

Yes. Clay's HTTP API enrichment column can call any JSON endpoint, which is all Boundstone needs — there is no Boundstone app in Clay's catalog and none is required. You add an HTTP enrichment column, point it at https://api.boundstone.io/v1/verify/phone with your API key in the Authorization header, and map the phone column into the request body. Each row returns the E.164 and national formats, a validity verdict, and line-type metadata. The free tier is 250 credits a month with no card required and credits that never expire, so you can test it across a real table before spending anything.

What does Clay's built-in phone validation use, and why would I add another provider?

Clay's own mobile-validation action is powered by Trestle, and for many teams that is a perfectly good default that needs no setup at all. The reason to add a second provider is disagreement: line-type and carrier answers differ between vendors, and on a list you are about to spend money dialing, a second opinion on the rows that matter is cheap. Boundstone's distinguishing feature is not a claim to be more accurate — we have not published accuracy figures yet — it is that every response states which checks ran and which did not, so a disagreement is legible rather than mysterious.

Will phone validation in Clay tell me whether a number is still active?

Not by default, and any tool that says otherwise from metadata alone is overstating it. Format, region and line-type checks tell you a number is well-formed and what kind of line it is — not that it is assigned, in service, or that a call will connect. Boundstone can run a live HLR lookup as a paid opt-in by sending hlr:true, which queries the carrier network and costs 5 credits, and it refunds the row when the network declines to answer rather than guessing. Without that flag the response marks liveness as not_performed, so the Clay column never quietly implies something it did not check.

Thomas Tsui

Founder of Boundstone — building phone, email and IP validation you can actually verify.

One honest API for email, phone and IP — every response lists what it checked and what it didn't claim to. Free tier: 250 credits/month, no card, credits never expire.

More from Boundstone — API documentation · Benchmark methodology · The benchmark series · Buyer's checklist