Skip to content

Gate SMS verification codes with the SMS policy

published · September 24, 2026 · updated · September 27, 2026

One boolean per phone answer, decided by rules you set once: the countries you send to, the line types and signals you refuse. How to use ok_to_send.

An SMS policy is the set of rules an account keeps about which phone numbers it will send a code or a message to. Every phone lookup ends with ok_to_send, the boolean those rules produce for that answer, and blocked_by, the name of the rule that decided a refusal. The score stays arithmetic and the flags stay facts; the policy is where the decision is made, and it is made once, on the server, so a signup form, an import script and a bulk run all branch the same way.

Why a boolean, when the answer has 49 fields

Because a verification step needs a decision, and every code it sends costs money. SMS pumping works on that cost: a script submits numbers in ranges where someone earns a share of every message delivered, usually in countries the business has no customers in, and the bill arrives with the carrier invoice. The defence is not one field. It is the country the number belongs to, the kind of line it is, whether an SMS-receiving website publishes it, what your own delivery reports said about it, and how many numbers your account has tried in the same range lately. A team that reads those fields one by one ends up with an if-chain that drifts between the signup form, the password reset and the marketing sender. A policy puts the chain in one place and leaves blocked_by in the answer, so the reasoning stays visible.

Seven rules make up a policy, all optional.

Rule What it does
allowed_countries Send only to numbers of these regions. Null sends to any.
blocked_countries Never send to numbers of these regions.
blocked_line_types Refuse these line types, such as premium_rate, pager or voip.
block_signals A list of risk-signal names. Any answer whose risk_signals carries one of them is blocked, whatever its score.
max_risk_score A ceiling. An answer whose risk_score is above it is blocked.
require_reachable Pass only a number a live carrier check reached.
block_opted_out Refuse a number your suppression list holds as opted out. On by default.

They are applied in a fixed order: an invalid number is blocked first and always, then an opt-out, then the country rules, then the line types, then the first blocked signal in the published weights' order, then the score ceiling, and last the live-check requirement. blocked_by names the first rule that fired: invalid, opted_out, country, line_type, the signal's own name, risk_score, unreachable or unconfirmed. A batch, bulk or monitored run answers suppressed for a number served from your suppression list.

The defaults

Until an account sets a policy, the published defaults apply. They block the five signals that almost always mean a code is wasted or abused, fictional, disposable, reported_abuse, reported_abuse_widely and premium_rate, and any score over 60, and they refuse a number your suppression list holds as opted out. They send to every country and every line type, and they require no live check. A number in a block held by a virtual-number wholesaler scores 40 and passes; so does a VoIP number at 30.

The test numbers show the defaults at work without spending a credit. +1 202 555 0103 is the disposable scenario and answers ok_to_send: false with blocked_by: "disposable"; +1 202 555 0105 is fictional and answers blocked_by: "fictional"; +1 202 555 0102, the virtual number, answers ok_to_send: true with a score of 40. The guest demo and the sandbox key answer under the defaults, because neither has an account to keep a policy. Every lookup a secret key makes, the test numbers included, answers under the account's stored policy, which makes them a way to try a policy before a customer meets it.

{
    "e164": "+12025550103",
    "valid": true,
    "is_disposable": true,
    "risk_score": 70,
    "ok_to_send": false,
    "blocked_by": "disposable"
}

Countries: the first control against pumping

SMS providers tell their customers to switch off every destination they do not serve, and a country allow-list is the same control one step earlier, before the code is requested. It is a business choice about where you send, not a verdict that a country's numbers are fraudulent: a bank in Ireland that only ever onboards Irish and British customers loses nothing by refusing a code to anywhere else, and a travel app would lose customers doing the same.

The country is the number's own, read from its calling code and prefix, which matters in two places people get wrong. The North American plan puts the United States, Canada and much of the Caribbean under +1, so +1 876 is Jamaica and not the United States; an allow-list of US and CA refuses it. The United Kingdom's +44 also carries Jersey, Guernsey and the Isle of Man, which libphonenumber reports as JE, GG and IM, so a British service that serves the Crown dependencies names them too. A number with no country at all, such as the satellite and international-network codes, is outside every allow-list. Each region's page under phone facts by country names the regions that share its calling code, as the pages for the United States and the United Kingdom show, so an allow-list can be checked against them before it goes live.

curl -X PUT https://spaw.co/api/v1/phone/policy \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"allowed_countries": ["GB", "IE", "JE", "GG", "IM"], "blocked_line_types": ["premium_rate", "shared_cost"]}'

Send only the keys you are changing. A null sends to any country again or clears the ceiling, an empty signal list blocks on no signal, and a body naming none of the seven rules answers 422, because that is the shape a misspelled key takes. An empty allow-list is refused rather than read as "nowhere". The SMS policy tab on the phone verification page edits the same seven rules.

Line types, and the one you cannot block

Blocking a line type is for numbers that cannot receive what you send: a premium-rate or shared-cost number costs the caller extra, a pager cannot show a code, and a toll-free number is rarely anybody's own handset. In the United Kingdom the 084 and 087 ranges answer shared_cost, because Ofcom's numbering plan classes them as service numbers with a capped charge and keeps premium rate for 09; that is why the example above lists shared_cost beside premium_rate, and why the default premium_rate signal alone lets them through. Blocking voip is a stronger choice than it looks, because a great many real people use VoIP numbers from their carrier or their employer; prefer the virtual signal, which names the block holder, and the score.

fixed_line_or_mobile can never be blocked. It is what the numbering plan answers when it cannot tell a mobile from a landline, which is most of North America, so a rule against it would refuse most American customers rather than any kind of line. Where the regulator's block table names a wireless carrier or a virtual-number wholesaler as the holder, the answer settles the type to mobile or voip and a line-type rule applies to it.

Opt-outs, and when to switch the rule off

When a recipient replies STOP, or your SMS provider reports an opt-out code, the report puts the number on your suppression list with reason opted_out. A batch, bulk or monitored run then serves it from the list as suppressed. A single lookup still answers the number in full, with meta.opted_out: true, and under the defaults block_opted_out makes that answer ok_to_send: false with blocked_by: "opted_out", so a marketing send never reaches someone who asked it to stop.

A sender of one-time codes or other transactional messages may want the rule off. An opt-out from marketing does not always forbid a verification code, and the person typing the number into your form may be its owner asking for a code again. Switch it off with {"block_opted_out": false} and the number is decided like any other while meta.opted_out still tells you it opted out; keep it on for the flows that send anything else. Either way the rule reads your own list, not a consent register, and honouring an opt-out stays your obligation.

What the policy does not do

It does not change the verdict, the score or any field; it adds two. It is not consent: a number that passes is one you may send to as far as fraud and deliverability go, and whether the person agreed to hear from you is still yours to record. It is not a do-not-call scrub, and no field in the answer is one. It does not know your product either: a policy is a floor under your judgement, not a replacement for the segment-level decisions the fields support.

The live-check rule deserves care. It passes only a lookup made with hlr: true that reached the handset, answers unreachable when the network could not reach it, and answers unconfirmed when no live answer came back. While the live check is not enabled on the service, that is every lookup, so leave it off until you ask for the check on every lookup you gate.

What to do next

Related

markdown version: /guides/gate-sms-verification-codes-with-the-sms-policy.md

Verify addresses the same way

The Spaw API runs every check described here on each lookup, with an SMTP handshake that never sends mail. 10 free lookups a month, no card required.

Get your API key

More guides