> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracklysms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Validate phones, then target by carrier / line type

> A concrete walkthrough — clean a list with phone validation, then build an audience that excludes landlines, VoIP, and non-mobile numbers.

This guide shows the end-to-end flow most accounts use to maximize SMS deliverability:

1. Run **phone validation** on a list (LRN + DNC)
2. Build an **audience** that excludes landlines, VoIP, and unreachable numbers
3. Send to the cleaned audience

## Why this matters

Carrier networks reject roughly 20–30% of SMS sent to landline / VoIP numbers, and the FTC DNC + state DNC + carrier DNC lists evolve daily. Validating a list once and then segmenting on the result gives you a reusable, deliverable subset — and the cache makes re-validating that same list essentially free forever.

## Step 1: validate the list

Two ways to trigger validation depending on whether you're using the platform UI or the public API.

### From the Contact Lookup UI

1. Open **Contact Lookup** → select your list from the dropdown.
2. Click **Run phone validation**.
3. The modal shows a count + cost estimate (e.g., *"500 of 9,300 contacts are unvalidated. Estimated cost: \$1.50"*) and lets you confirm.
4. Validation runs as a background job. The progress bar updates every few seconds; you can close the dialog and the job continues server-side.
5. When the job completes, the per-contact badges in the list flip to **Validated ✓** / **Validation failed**.

This batches up to 500 unvalidated contacts per click. For larger lists, click again after the first batch finishes — already-validated contacts are free (`cached: true`), so the cost only ever covers the new portion.

<Note>
  **Updated 2026-04-27:** the per-click 500-contact cap was removed. A single click now validates **every** unvalidated contact in the list. Validation runs as one background job per account — re-clicking while a job is in flight returns "a validation job is already running" instead of starting a duplicate. The caching behavior above is unchanged: re-validating a list still costs only the new portion.
</Note>

### From the public API

For programmatic cleans (e.g., before importing a fresh data drop), use [`POST /v1/phone/validate/batch`](/api-reference/phone/batch-validate):

```bash theme={null}
curl -X POST https://api.tracklysms.com/api/v1/phone/validate/batch \
  -H "X-Api-Key: $TRACKLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phones": [
      "+14155551234",
      "415-555-9876",
      "(212) 555-0100"
    ]
  }'
```

Each response entry tells you exactly what the number is:

```json theme={null}
{
  "phone": "+14155551234",
  "valid": true,
  "line_type": "mobile",
  "carrier": "T-Mobile USA",
  "ported": true,
  "country": "US",
  "disposition": "ok",
  "cost": 0.003,
  "cached": false
}
```

Phone numbers are normalized server-side before any provider call:

| You send          | Normalized     |
| ----------------- | -------------- |
| `4155551234`      | `+14155551234` |
| `(415) 555-1234`  | `+14155551234` |
| `415-555-1234`    | `+14155551234` |
| `+1 415 555 1234` | `+14155551234` |
| `14155551234`     | `+14155551234` |

So you can pass numbers in whatever format you have them — the [`normalize_e164`](/api-reference/phone/validate#phone-number-normalization) helper handles the conversion. Our LRN provider only accepts strict E.164, so anything that can't be normalized is rejected at the validation step (HTTP 400, before any provider call).

## Step 2: build an audience for cleaned, deliverable contacts

Once contacts are validated, the `line_type`, `carrier`, and `is_valid` fields populate on each contact. You can filter on them directly in **Audiences**.

### Example: US/CA mobile-only, no DNC, no VoIP

In the audience builder:

| Field               | Operator | Value             |
| ------------------- | -------- | ----------------- |
| `validation_status` | equals   | `validated_valid` |
| `line_type`         | equals   | `mobile`          |
| `country`           | in       | `US`, `CA`        |
| `on_dnc`            | equals   | `false`           |

This produces an audience of only fully-validated, mobile-line, US/Canada numbers that aren't on the federal or per-account DNC list. Every contact in this audience is one your carrier will accept and the recipient can actually receive.

The same audience expressed via the [Audiences API](/api-reference/v2/audiences):

```json theme={null}
{
  "name": "US/CA Mobile — DNC clean",
  "rules": {
    "all": [
      { "field": "validation_status", "op": "eq", "value": "validated_valid" },
      { "field": "line_type", "op": "eq", "value": "mobile" },
      { "field": "country", "op": "in", "value": ["US", "CA"] },
      { "field": "on_dnc", "op": "eq", "value": false }
    ]
  }
}
```

### Variations worth knowing

* **Drop landlines but keep VoIP** (some VoIP can SMS): `line_type in [mobile, voip]`
* **Carrier-specific A/B**: split a campaign by `carrier eq "Verizon Wireless"` vs `carrier eq "AT&T Mobility"` to compare delivery rate / clicks across carriers
* **Re-clean cadence**: the validation cache is permanent per `(account, phone)`. To force a re-validation (e.g., for numbers you suspect have ported), pass `force=true` to the API or click *Re-validate selected* in the UI

## Step 3: send to the audience

Schedule a campaign against the audience as you normally would. Because the audience is dynamic, every new validated contact that matches the rules automatically gets included on the next send — no need to re-run the audience query manually.

## How the cost adds up over time

Validation pricing is **\$0.003 per non-cached lookup**, with a permanent per-account cache (see [Phone Validation: Caching Behavior](/api-reference/phone/validate#caching-behavior)). A typical cycle:

| Pass                                             | Cost                             |
| ------------------------------------------------ | -------------------------------- |
| Day 0: validate a fresh 100k list                | \$300 (100,000 × \$0.003)        |
| Day 30: re-clean the same list (no new contacts) | \$0 (all cache hits)             |
| Day 30: re-clean with 5,000 new opt-ins added    | \$15 (5,000 × \$0.003)           |
| Day 365: re-clean again                          | \$0 — older numbers still cached |

In practice, re-validation costs trend toward "new contacts since last pass × \$0.003" rather than total list size.

## Common pitfalls

* **Trying to send before validation finishes.** If you queue a send on a partially-validated list, the as-yet-unvalidated contacts will be sent to under their pre-validation flags (which means landlines/VoIP are NOT excluded). Wait for the badges to flip before scheduling.
* **Forgetting `country` in the audience.** Without it, the rule still includes UK/AU mobiles where applicable — fine if intentional, but a surprise if you assumed US/CA.
* **Caching surprises after carrier porting.** LRN data is rare-change, but a number ported in the last week may still show its old carrier in our cache. Pass `force=true` if exact current-carrier accuracy matters for your use case.

## Related references

* [Phone Validation API](/api-reference/phone/validate)
* [Batch Phone Validation API](/api-reference/phone/batch-validate)
* [Audience Segmentation overview](/guides/audiences/overview)
* [Audience filters reference](/guides/audiences/segmentation-logic)
